diff --git a/Assets/Editor/Prefabs/Default_Level.prefab b/Assets/Editor/Prefabs/Default_Level.prefab index d02d669f53..0267131fa0 100644 --- a/Assets/Editor/Prefabs/Default_Level.prefab +++ b/Assets/Editor/Prefabs/Default_Level.prefab @@ -185,7 +185,7 @@ { "id": { "materialAssetId": { - "guid": "{935F694A-8639-515B-8133-81CDC7948E5B}", + "guid": "{0CD745C0-6AA8-569A-A68A-73A3270986C4}", "subId": 803645540 } } @@ -197,7 +197,7 @@ "id": { "lodIndex": 0, "materialAssetId": { - "guid": "{935F694A-8639-515B-8133-81CDC7948E5B}", + "guid": "{0CD745C0-6AA8-569A-A68A-73A3270986C4}", "subId": 803645540 } } diff --git a/Assets/Engine/Engine_Dependencies.xml b/Assets/Engine/Engine_Dependencies.xml index 0d6541e18e..b969b3bc97 100644 --- a/Assets/Engine/Engine_Dependencies.xml +++ b/Assets/Engine/Engine_Dependencies.xml @@ -1,16 +1,9 @@ - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + diff --git a/Assets/Engine/SeedAssetList.seed b/Assets/Engine/SeedAssetList.seed index aafbffbe8f..18ccd19dc3 100644 --- a/Assets/Engine/SeedAssetList.seed +++ b/Assets/Engine/SeedAssetList.seed @@ -1,621 +1,260 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - diff --git a/AutomatedTesting/Gem/PythonCoverage/gem.json b/AutomatedTesting/Gem/PythonCoverage/gem.json index 39e327b5e3..b99ce0daad 100644 --- a/AutomatedTesting/Gem/PythonCoverage/gem.json +++ b/AutomatedTesting/Gem/PythonCoverage/gem.json @@ -2,6 +2,7 @@ "gem_name": "PythonCoverage", "display_name": "PythonCoverage", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Tool", "summary": "A tool for generating gem coverage for Python tests.", diff --git a/AutomatedTesting/Gem/PythonTests/Blast/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/Blast/CMakeLists.txt index cb83fe5344..aea2562e0b 100644 --- a/AutomatedTesting/Gem/PythonTests/Blast/CMakeLists.txt +++ b/AutomatedTesting/Gem/PythonTests/Blast/CMakeLists.txt @@ -6,7 +6,11 @@ # # -if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) +ly_get_list_relative_pal_filename(pal_dir ${CMAKE_CURRENT_LIST_DIR}/Platform/${PAL_PLATFORM_NAME}) + +include(${pal_dir}/PAL_${PAL_PLATFORM_NAME_LOWERCASE}.cmake) # for PAL_TRAIT_BLAST Traits + +if(PAL_TRAIT_BLAST_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) ly_add_pytest( NAME AutomatedTesting::BlastTests_Main TEST_SUITE main diff --git a/AutomatedTesting/Gem/PythonTests/Blast/Platform/Android/PAL_android.cmake b/AutomatedTesting/Gem/PythonTests/Blast/Platform/Android/PAL_android.cmake new file mode 100644 index 0000000000..f91b18e9f1 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Blast/Platform/Android/PAL_android.cmake @@ -0,0 +1,9 @@ +# +# 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 +# +# + +set(PAL_TRAIT_BLAST_TESTS_SUPPORTED FALSE) diff --git a/AutomatedTesting/Gem/PythonTests/Blast/Platform/Linux/PAL_linux.cmake b/AutomatedTesting/Gem/PythonTests/Blast/Platform/Linux/PAL_linux.cmake new file mode 100644 index 0000000000..f91b18e9f1 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Blast/Platform/Linux/PAL_linux.cmake @@ -0,0 +1,9 @@ +# +# 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 +# +# + +set(PAL_TRAIT_BLAST_TESTS_SUPPORTED FALSE) diff --git a/AutomatedTesting/Gem/PythonTests/Blast/Platform/Mac/PAL_mac.cmake b/AutomatedTesting/Gem/PythonTests/Blast/Platform/Mac/PAL_mac.cmake new file mode 100644 index 0000000000..f91b18e9f1 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Blast/Platform/Mac/PAL_mac.cmake @@ -0,0 +1,9 @@ +# +# 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 +# +# + +set(PAL_TRAIT_BLAST_TESTS_SUPPORTED FALSE) diff --git a/AutomatedTesting/Gem/PythonTests/Blast/Platform/Windows/PAL_windows.cmake b/AutomatedTesting/Gem/PythonTests/Blast/Platform/Windows/PAL_windows.cmake new file mode 100644 index 0000000000..28767237de --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Blast/Platform/Windows/PAL_windows.cmake @@ -0,0 +1,9 @@ +# +# 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 +# +# + +set(PAL_TRAIT_BLAST_TESTS_SUPPORTED TRUE) diff --git a/AutomatedTesting/Gem/PythonTests/Blast/Platform/iOS/PAL_ios.cmake b/AutomatedTesting/Gem/PythonTests/Blast/Platform/iOS/PAL_ios.cmake new file mode 100644 index 0000000000..f91b18e9f1 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Blast/Platform/iOS/PAL_ios.cmake @@ -0,0 +1,9 @@ +# +# 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 +# +# + +set(PAL_TRAIT_BLAST_TESTS_SUPPORTED FALSE) diff --git a/AutomatedTesting/Gem/PythonTests/WhiteBox/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/WhiteBox/CMakeLists.txt index 10cf949c25..733e8edf29 100644 --- a/AutomatedTesting/Gem/PythonTests/WhiteBox/CMakeLists.txt +++ b/AutomatedTesting/Gem/PythonTests/WhiteBox/CMakeLists.txt @@ -6,7 +6,11 @@ # # -if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) +ly_get_list_relative_pal_filename(pal_dir ${CMAKE_CURRENT_LIST_DIR}/Platform/${PAL_PLATFORM_NAME}) + +include(${pal_dir}/PAL_${PAL_PLATFORM_NAME_LOWERCASE}.cmake) # for PAL_TRAIT_WHITEBOX Traits + +if(PAL_TRAIT_WHITEBOX_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) ly_add_pytest( NAME AutomatedTesting::WhiteBoxTests TEST_SUITE main diff --git a/AutomatedTesting/Gem/PythonTests/WhiteBox/Platform/Android/PAL_android.cmake b/AutomatedTesting/Gem/PythonTests/WhiteBox/Platform/Android/PAL_android.cmake new file mode 100644 index 0000000000..07aa0bb13d --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/WhiteBox/Platform/Android/PAL_android.cmake @@ -0,0 +1,9 @@ +# +# 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 +# +# + +set(PAL_TRAIT_WHITEBOX_TESTS_SUPPORTED FALSE) diff --git a/AutomatedTesting/Gem/PythonTests/WhiteBox/Platform/Linux/PAL_linux.cmake b/AutomatedTesting/Gem/PythonTests/WhiteBox/Platform/Linux/PAL_linux.cmake new file mode 100644 index 0000000000..07aa0bb13d --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/WhiteBox/Platform/Linux/PAL_linux.cmake @@ -0,0 +1,9 @@ +# +# 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 +# +# + +set(PAL_TRAIT_WHITEBOX_TESTS_SUPPORTED FALSE) diff --git a/AutomatedTesting/Gem/PythonTests/WhiteBox/Platform/Mac/PAL_mac.cmake b/AutomatedTesting/Gem/PythonTests/WhiteBox/Platform/Mac/PAL_mac.cmake new file mode 100644 index 0000000000..07aa0bb13d --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/WhiteBox/Platform/Mac/PAL_mac.cmake @@ -0,0 +1,9 @@ +# +# 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 +# +# + +set(PAL_TRAIT_WHITEBOX_TESTS_SUPPORTED FALSE) diff --git a/AutomatedTesting/Gem/PythonTests/WhiteBox/Platform/Windows/PAL_windows.cmake b/AutomatedTesting/Gem/PythonTests/WhiteBox/Platform/Windows/PAL_windows.cmake new file mode 100644 index 0000000000..a83d56788a --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/WhiteBox/Platform/Windows/PAL_windows.cmake @@ -0,0 +1,9 @@ +# +# 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 +# +# + +set(PAL_TRAIT_WHITEBOX_TESTS_SUPPORTED TRUE) diff --git a/AutomatedTesting/Gem/PythonTests/WhiteBox/Platform/iOS/PAL_ios.cmake b/AutomatedTesting/Gem/PythonTests/WhiteBox/Platform/iOS/PAL_ios.cmake new file mode 100644 index 0000000000..07aa0bb13d --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/WhiteBox/Platform/iOS/PAL_ios.cmake @@ -0,0 +1,9 @@ +# +# 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 +# +# + +set(PAL_TRAIT_WHITEBOX_TESTS_SUPPORTED FALSE) diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_processor_batch_dependency_tests.py b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_processor_batch_dependency_tests.py index ee8e177bbb..303a012cda 100755 --- a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_processor_batch_dependency_tests.py +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_processor_batch_dependency_tests.py @@ -47,82 +47,6 @@ class TestsAssetProcessorBatch_DependenycyTests(object): """ AssetProcessorBatch Dependency tests """ - - @pytest.mark.test_case_id("C16877166") - @pytest.mark.BAT - @pytest.mark.assetpipeline - # fmt:off - def test_WindowsMacPlatforms_RunAPBatch_NotMissingDependency(self, ap_setup_fixture, asset_processor, - workspace): - # fmt:on - """ - Engine Schema - This test case has a conditional scenario depending on the existence of surfacetypes.xml in a project. - Some projects have this file and others do not. Run the conditional scenario depending on the existence - of the file in the project - libs/materialeffects/surfacetypes.xml is listed as an entry engine_dependencies.xml - libs/materialeffects/surfacetypes.xml is not listed as a missing dependency - in the 'assetprocessorbatch' console output - - Test Steps: - 1. Assets are pre-processed - 2. Verify that engine_dependencies.xml exists - 3. Verify engine_dependencies.xml has surfacetypes.xml present - 4. Run Missing Dependency scanner against the engine_dependenciese.xml - 5. Verify that Surfacetypes.xml is NOT in the missing depdencies output - 6. Add the schema file which allows our xml parser to understand dependencies for our engine_dependencies file - 7. Process assets - 8. Run Missing Dependency scanner against the engine_dependenciese.xml - 9. Verify that surfacetypes.xml is in the missing dependencies out - """ - - env = ap_setup_fixture - BATCH_LOG_PATH = env["ap_batch_log_file"] - asset_processor.create_temp_asset_root() - asset_processor.add_relative_source_asset(os.path.join("Assets", "Engine", "engine_dependencies.xml")) - asset_processor.add_scan_folder(os.path.join("Assets", "Engine")) - asset_processor.add_relative_source_asset(os.path.join("Assets", "Engine", "Libs", "MaterialEffects", "surfacetypes.xml")) - - # Precondition: Assets are all processed - asset_processor.batch_process() - - DEPENDENCIES_PATH = os.path.join(asset_processor.temp_project_cache(), "engine_dependencies.xml") - assert os.path.exists(DEPENDENCIES_PATH), "The engine_dependencies.xml does not exist." - surfacetypes_in_dependencies = False - surfacetypes_missing_logline = False - - # Read engine_dependencies.xml to see if surfacetypes.xml is present - with open(DEPENDENCIES_PATH, "r") as dependencies_file: - for line in dependencies_file.readlines(): - if "surfacetypes.xml" in line: - surfacetypes_in_dependencies = True - logger.info("Surfacetypes.xml was listed in the engine_dependencies.xml file.") - break - - if not surfacetypes_in_dependencies: - logger.info("Surfacetypes.xml was not listed in the engine_dependencies.xml file.") - - _, output = asset_processor.batch_process(capture_output=True, - extra_params="--dsp=%engine_dependencies.xml") - log = APOutputParser(output) - for _ in log.get_lines(run=-1, contains=["surfacetypes.xml", "Missing"]): - surfacetypes_missing_logline = True - - assert surfacetypes_missing_logline, "Surfacetypes.xml not seen in the batch log as missing." - - # Add the schema file which allows our xml parser to understand dependencies for our engine_dependencies file - asset_processor.add_relative_source_asset(os.path.join("Assets", "Engine", "Schema", "enginedependency.xmlschema")) - asset_processor.batch_process() - - _, output = asset_processor.batch_process(capture_output=True, - extra_params="--dsp=%engine_dependencies.xml") - log = APOutputParser(output) - surfacetypes_missing_logline = False - for _ in log.get_lines(run=-1, contains=["surfacetypes.xml", "Missing"]): - surfacetypes_missing_logline = True - - assert not surfacetypes_missing_logline, "Surfacetypes.xml not seen in the batch log as missing." - schemas = [ ("C16877167", ".ent"), ("C16877168", "Environment.xml"), diff --git a/AutomatedTesting/Gem/gem.json b/AutomatedTesting/Gem/gem.json index 6c8c7829ce..df197df09d 100644 --- a/AutomatedTesting/Gem/gem.json +++ b/AutomatedTesting/Gem/gem.json @@ -2,10 +2,13 @@ "gem_name": "AutomatedTesting", "display_name": "AutomatedTesting", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Amazon Web Services, Inc.", "type": "Code", "summary": "Project Gem for customizing the AutomatedTesting project functionality.", - "canonical_tags": ["Gem"], + "canonical_tags": [ + "Gem" + ], "user_tags": [], "icon_path": "preview.png", "requirements": "" diff --git a/Code/Editor/Core/LevelEditorMenuHandler.cpp b/Code/Editor/Core/LevelEditorMenuHandler.cpp index fdde1ac305..70aff10f87 100644 --- a/Code/Editor/Core/LevelEditorMenuHandler.cpp +++ b/Code/Editor/Core/LevelEditorMenuHandler.cpp @@ -832,7 +832,7 @@ QAction* LevelEditorMenuHandler::CreateViewPaneAction(const QtViewPane* view) if (view->m_options.showOnToolsToolbar) { - action->setIcon(QIcon(view->m_options.toolbarIcon)); + action->setIcon(QIcon(view->m_options.toolbarIcon.c_str())); } m_actionManager->AddAction(view->m_id, action); diff --git a/Code/Editor/CryEdit.cpp b/Code/Editor/CryEdit.cpp index e4138da932..5cffedd774 100644 --- a/Code/Editor/CryEdit.cpp +++ b/Code/Editor/CryEdit.cpp @@ -45,6 +45,7 @@ AZ_POP_DISABLE_WARNING // AzCore #include +#include #include #include #include @@ -1686,6 +1687,11 @@ bool CCryEditApp::InitInstance() return false; } + if (AZ::SettingsRegistryInterface* settingsRegistry = AZ::SettingsRegistry::Get()) + { + AZ::ComponentApplicationLifecycle::SignalEvent(*settingsRegistry, "LegacySystemInterfaceCreated", R"({})"); + } + // Process some queued events come from system init // Such as asset catalog loaded notification. // There are some systems need to load configurations from assets for post initialization but before loading level diff --git a/Code/Editor/EditorViewportWidget.cpp b/Code/Editor/EditorViewportWidget.cpp index c7814cc842..8e6983a9d7 100644 --- a/Code/Editor/EditorViewportWidget.cpp +++ b/Code/Editor/EditorViewportWidget.cpp @@ -43,10 +43,11 @@ // AzToolsFramework #include +#include +#include #include #include #include -#include // AtomToolsFramework #include @@ -1032,6 +1033,7 @@ void EditorViewportWidget::ConnectViewportInteractionRequestBus() AzToolsFramework::ViewportInteraction::MainEditorViewportInteractionRequestBus::Handler::BusConnect(GetViewportId()); AzToolsFramework::ViewportInteraction::EditorEntityViewportInteractionRequestBus::Handler::BusConnect(GetViewportId()); m_viewportUi.ConnectViewportUiBus(GetViewportId()); + AzFramework::ViewportBorderRequestBus::Handler::BusConnect(GetViewportId()); AzFramework::InputSystemCursorConstraintRequestBus::Handler::BusConnect(); } @@ -1040,6 +1042,7 @@ void EditorViewportWidget::DisconnectViewportInteractionRequestBus() { AzFramework::InputSystemCursorConstraintRequestBus::Handler::BusDisconnect(); + AzFramework::ViewportBorderRequestBus::Handler::BusDisconnect(); m_viewportUi.DisconnectViewportUiBus(); AzToolsFramework::ViewportInteraction::EditorEntityViewportInteractionRequestBus::Handler::BusDisconnect(); AzToolsFramework::ViewportInteraction::MainEditorViewportInteractionRequestBus::Handler::BusDisconnect(); @@ -1124,7 +1127,9 @@ void EditorViewportWidget::OnTitleMenu(QMenu* menu) action = menu->addAction(tr("Create camera entity from current view")); connect(action, &QAction::triggered, this, &EditorViewportWidget::OnMenuCreateCameraEntityFromCurrentView); - if (!gameEngine || !gameEngine->IsLevelLoaded()) + const auto prefabEditorEntityOwnershipInterface = AZ::Interface::Get(); + if (!gameEngine || !gameEngine->IsLevelLoaded() || + (prefabEditorEntityOwnershipInterface && !prefabEditorEntityOwnershipInterface->IsRootPrefabAssigned())) { action->setEnabled(false); action->setToolTip(tr(AZ::ViewportHelpers::TextCantCreateCameraNoLevel)); @@ -2636,4 +2641,25 @@ void EditorViewportWidget::StopFullscreenPreview() // Show the main window MainWindow::instance()->show(); } + +AZStd::optional EditorViewportWidget::GetViewportBorderPadding() const +{ + if (auto viewportEditorModeTracker = AZ::Interface::Get()) + { + auto viewportEditorModes = viewportEditorModeTracker->GetViewportEditorModes({ AzToolsFramework::GetEntityContextId() }); + if (viewportEditorModes->IsModeActive(AzToolsFramework::ViewportEditorMode::Focus) || + viewportEditorModes->IsModeActive(AzToolsFramework::ViewportEditorMode::Component)) + { + AzFramework::ViewportBorderPadding viewportBorderPadding = {}; + viewportBorderPadding.m_top = AzToolsFramework::ViewportUi::ViewportUiTopBorderSize; + viewportBorderPadding.m_left = AzToolsFramework::ViewportUi::ViewportUiLeftRightBottomBorderSize; + viewportBorderPadding.m_right = AzToolsFramework::ViewportUi::ViewportUiLeftRightBottomBorderSize; + viewportBorderPadding.m_bottom = AzToolsFramework::ViewportUi::ViewportUiLeftRightBottomBorderSize; + return viewportBorderPadding; + } + } + + return AZStd::nullopt; +} + #include diff --git a/Code/Editor/EditorViewportWidget.h b/Code/Editor/EditorViewportWidget.h index 68ea48c7f5..01f6068d56 100644 --- a/Code/Editor/EditorViewportWidget.h +++ b/Code/Editor/EditorViewportWidget.h @@ -38,6 +38,7 @@ #include #include +#include // forward declarations. class CBaseObject; @@ -86,6 +87,7 @@ AZ_PUSH_DISABLE_DLL_EXPORT_BASECLASS_WARNING AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING class SANDBOX_API EditorViewportWidget final : public QtViewport + , public AzFramework::ViewportBorderRequestBus::Handler , private IEditorNotifyListener , private IUndoManagerListener , private Camera::EditorCameraRequestBus::Handler @@ -120,6 +122,9 @@ public: void SetFOV(float fov) override; float GetFOV() const override; + // AzFramework::ViewportBorderRequestBus overrides ... + AZStd::optional GetViewportBorderPadding() const override; + private: //////////////////////////////////////////////////////////////////////// // Private types ... diff --git a/Code/Editor/Lib/Tests/test_ViewportManipulatorController.cpp b/Code/Editor/Lib/Tests/test_ViewportManipulatorController.cpp index 8c7023634e..63e59ea940 100644 --- a/Code/Editor/Lib/Tests/test_ViewportManipulatorController.cpp +++ b/Code/Editor/Lib/Tests/test_ViewportManipulatorController.cpp @@ -85,6 +85,7 @@ namespace UnitTest m_rootWidget = AZStd::make_unique(); m_rootWidget->setFixedSize(QSize(100, 100)); + QApplication::setActiveWindow(m_rootWidget.get()); m_controllerList = AZStd::make_shared(); m_controllerList->RegisterViewportContext(TestViewportId); @@ -100,6 +101,8 @@ namespace UnitTest m_controllerList.reset(); m_rootWidget.reset(); + QApplication::setActiveWindow(nullptr); + AllocatorsTestFixture::TearDown(); } @@ -110,7 +113,7 @@ namespace UnitTest const AzFramework::ViewportId ViewportManipulatorControllerFixture::TestViewportId = AzFramework::ViewportId(0); - TEST_F(ViewportManipulatorControllerFixture, An_event_is_not_propagated_to_the_viewport_when_a_manipulator_handles_it_first) + TEST_F(ViewportManipulatorControllerFixture, AnEventIsNotPropagatedToTheViewportWhenAManipulatorHandlesItFirst) { // forward input events to our controller list QObject::connect( @@ -151,4 +154,77 @@ namespace UnitTest editorInteractionViewportFake.Disconnect(); } + + TEST_F(ViewportManipulatorControllerFixture, ChangingFocusDoesNotClearInput) + { + bool endedEvent = false; + // detect input events and ensure that the Alt key press does not end before the end of the test + QObject::connect( + m_inputChannelMapper.get(), &AzToolsFramework::QtEventToAzInputMapper::InputChannelUpdated, m_rootWidget.get(), + [&endedEvent](const AzFramework::InputChannel* inputChannel, [[maybe_unused]] QEvent* event) + { + if (inputChannel->GetInputChannelId() == AzFramework::InputDeviceKeyboard::Key::ModifierAltL && + inputChannel->IsStateEnded()) + { + endedEvent = true; + } + }); + + // given + auto* secondaryWidget = new QWidget(m_rootWidget.get()); + + m_rootWidget->show(); + secondaryWidget->show(); + + m_rootWidget->setFocus(); + + // simulate a key press when root widget has focus + QTest::keyPress(m_rootWidget.get(), Qt::Key_Alt, Qt::KeyboardModifier::AltModifier); + + // when + // change focus to secondary widget + secondaryWidget->setFocus(); + + // then + // the alt key was not released (cleared) + EXPECT_FALSE(endedEvent); + } + + // note: Application State Change includes events such as switching to another application or minimizing + // the current application + TEST_F(ViewportManipulatorControllerFixture, ApplicationStateChangeDoesClearInput) + { + bool endedEvent = false; + // detect input events and ensure that the Alt key press does not end before the end of the test + QObject::connect( + m_inputChannelMapper.get(), &AzToolsFramework::QtEventToAzInputMapper::InputChannelUpdated, m_rootWidget.get(), + [&endedEvent](const AzFramework::InputChannel* inputChannel, [[maybe_unused]] QEvent* event) + { + if (inputChannel->GetInputChannelId() == AzFramework::InputDeviceKeyboard::Key::AlphanumericW && + inputChannel->IsStateEnded()) + { + endedEvent = true; + } + }); + + // given + auto* secondaryWidget = new QWidget(m_rootWidget.get()); + + m_rootWidget->show(); + secondaryWidget->show(); + + m_rootWidget->setFocus(); + + // simulate a key press when root widget has focus + QTest::keyPress(m_rootWidget.get(), Qt::Key_W); + + // when + // simulate changing the window state + QApplicationStateChangeEvent applicationStateChangeEvent(Qt::ApplicationState::ApplicationInactive); + QCoreApplication::sendEvent(m_rootWidget.get(), &applicationStateChangeEvent); + + // then + // the key was released (cleared) + EXPECT_TRUE(endedEvent); + } } // namespace UnitTest diff --git a/Code/Editor/Platform/Linux/Editor/Core/QtEditorApplication_linux.cpp b/Code/Editor/Platform/Linux/Editor/Core/QtEditorApplication_linux.cpp index ad5e57479b..8ba152a9f9 100644 --- a/Code/Editor/Platform/Linux/Editor/Core/QtEditorApplication_linux.cpp +++ b/Code/Editor/Platform/Linux/Editor/Core/QtEditorApplication_linux.cpp @@ -10,6 +10,8 @@ #ifdef PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB #include +#include +#include #endif namespace Editor @@ -23,16 +25,34 @@ namespace Editor return nullptr; } + xcb_connection_t* EditorQtApplicationXcb::GetXcbConnectionFromQt() + { + QPlatformNativeInterface* native = platformNativeInterface(); + AZ_Warning("EditorQtApplicationXcb", native, "Unable to retrieve the native platform interface"); + if (!native) + { + return nullptr; + } + return reinterpret_cast(native->nativeResourceForIntegration(QByteArray("connection"))); + } + + void EditorQtApplicationXcb::OnStartPlayInEditor() + { + auto* interface = AzFramework::XcbConnectionManagerInterface::Get(); + interface->SetEnableXInput(GetXcbConnectionFromQt(), true); + } + + void EditorQtApplicationXcb::OnStopPlayInEditor() + { + auto* interface = AzFramework::XcbConnectionManagerInterface::Get(); + interface->SetEnableXInput(GetXcbConnectionFromQt(), false); + } + bool EditorQtApplicationXcb::nativeEventFilter([[maybe_unused]] const QByteArray& eventType, void* message, long*) { if (GetIEditor()->IsInGameMode()) { #ifdef PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB - // We need to handle RAW Input events in a separate loop. This is a workaround to enable XInput2 RAW Inputs using Editor mode. - // TODO To have this call here might be not be perfect. - AzFramework::XcbEventHandlerBus::Broadcast(&AzFramework::XcbEventHandler::PollSpecialEvents); - - // Now handle the rest of the events. AzFramework::XcbEventHandlerBus::Broadcast( &AzFramework::XcbEventHandler::HandleXcbEvent, static_cast(message)); #endif diff --git a/Code/Editor/Platform/Linux/Editor/Core/QtEditorApplication_linux.h b/Code/Editor/Platform/Linux/Editor/Core/QtEditorApplication_linux.h index 8c145c3aa7..109ae1742b 100644 --- a/Code/Editor/Platform/Linux/Editor/Core/QtEditorApplication_linux.h +++ b/Code/Editor/Platform/Linux/Editor/Core/QtEditorApplication_linux.h @@ -6,19 +6,35 @@ * */ +#if !defined(Q_MOC_RUN) #include +#include +#endif + +using xcb_connection_t = struct xcb_connection_t; namespace Editor { - class EditorQtApplicationXcb : public EditorQtApplication + class EditorQtApplicationXcb + : public EditorQtApplication + , public AzToolsFramework::EditorEntityContextNotificationBus::Handler { Q_OBJECT public: EditorQtApplicationXcb(int& argc, char** argv) : EditorQtApplication(argc, argv) { + // Connect bus to listen for OnStart/StopPlayInEditor events + AzToolsFramework::EditorEntityContextNotificationBus::Handler::BusConnect(); } + xcb_connection_t* GetXcbConnectionFromQt(); + + /////////////////////////////////////////////////////////////////////// + // AzToolsFramework::EditorEntityContextNotificationBus overrides + void OnStartPlayInEditor() override; + void OnStopPlayInEditor() override; + // QAbstractNativeEventFilter: bool nativeEventFilter(const QByteArray& eventType, void* message, long* result) override; }; diff --git a/Code/Editor/Plugins/ComponentEntityEditorPlugin/SandboxIntegration.cpp b/Code/Editor/Plugins/ComponentEntityEditorPlugin/SandboxIntegration.cpp index 5b849dcbe7..82d9f9ede2 100644 --- a/Code/Editor/Plugins/ComponentEntityEditorPlugin/SandboxIntegration.cpp +++ b/Code/Editor/Plugins/ComponentEntityEditorPlugin/SandboxIntegration.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -642,6 +643,9 @@ void SandboxIntegrationManager::PopulateEditorGlobalContextMenu(QMenu* menu, con AzToolsFramework::EntityIdList selected; GetSelectedOrHighlightedEntities(selected); + bool prefabSystemEnabled = false; + AzFramework::ApplicationRequests::Bus::BroadcastResult(prefabSystemEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled); + QAction* action = nullptr; // when nothing is selected, entity is created at root level @@ -658,18 +662,20 @@ void SandboxIntegrationManager::PopulateEditorGlobalContextMenu(QMenu* menu, con // when a single entity is selected, entity is created as its child else if (selected.size() == 1) { - action = menu->addAction(QObject::tr("Create entity")); - QObject::connect( - action, &QAction::triggered, action, - [selected] - { - EBUS_EVENT(AzToolsFramework::EditorRequests::Bus, CreateNewEntityAsChild, selected.front()); - }); + auto containerEntityInterface = AZ::Interface::Get(); + if (!prefabSystemEnabled || (containerEntityInterface && containerEntityInterface->IsContainerOpen(selected.front()))) + { + action = menu->addAction(QObject::tr("Create entity")); + QObject::connect( + action, &QAction::triggered, action, + [selected] + { + AzToolsFramework::EditorRequestBus::Broadcast(&AzToolsFramework::EditorRequestBus::Handler::CreateNewEntityAsChild, selected.front()); + } + ); + } } - bool prefabSystemEnabled = false; - AzFramework::ApplicationRequests::Bus::BroadcastResult(prefabSystemEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled); - if (!prefabSystemEnabled) { menu->addSeparator(); diff --git a/Code/Framework/AzCore/AzCore/Asset/AssetManagerBus.h b/Code/Framework/AzCore/AzCore/Asset/AssetManagerBus.h index f8e263d6b0..f76ea19589 100644 --- a/Code/Framework/AzCore/AzCore/Asset/AssetManagerBus.h +++ b/Code/Framework/AzCore/AzCore/Asset/AssetManagerBus.h @@ -65,8 +65,35 @@ namespace AZ ////////////////////////////////////////////////////////////////////////// // EBusTraits overrides - Application is a singleton - static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; - typedef AZStd::recursive_mutex MutexType; + static constexpr AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; + using MutexType = AZStd::recursive_mutex; + + static constexpr bool EnableEventQueue = true; + using EventQueueMutexType = AZStd::mutex; + struct PostThreadDispatchInvoker + { + ~PostThreadDispatchInvoker(); + }; + + template + struct ThreadDispatchLockGuard + { + ThreadDispatchLockGuard(DispatchMutex& contextMutex) + : m_lock{ contextMutex } + {} + ThreadDispatchLockGuard(DispatchMutex& contextMutex, AZStd::adopt_lock_t adopt_lock) + : m_lock{ contextMutex, adopt_lock } + {} + ThreadDispatchLockGuard(const ThreadDispatchLockGuard&) = delete; + ThreadDispatchLockGuard& operator=(const ThreadDispatchLockGuard&) = delete; + private: + PostThreadDispatchInvoker m_threadPolicyInvoker; + using LockType = AZStd::conditional_t, AZStd::scoped_lock>; + LockType m_lock; + }; + + template + using DispatchLockGuard = ThreadDispatchLockGuard; ////////////////////////////////////////////////////////////////////////// virtual ~AssetCatalogRequests() = default; @@ -200,6 +227,17 @@ namespace AZ using AssetCatalogRequestBus = AZ::EBus; + inline AssetCatalogRequests::PostThreadDispatchInvoker::~PostThreadDispatchInvoker() + { + if (!AssetCatalogRequestBus::IsInDispatchThisThread()) + { + if (AssetCatalogRequestBus::QueuedEventCount()) + { + AssetCatalogRequestBus::ExecuteQueuedEvents(); + } + } + } + /* * Events that AssetManager listens for */ diff --git a/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp b/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp index ba66d56379..ad7e44c2ae 100644 --- a/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp +++ b/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp @@ -14,6 +14,7 @@ #include #include +#include #include #include @@ -44,8 +45,6 @@ #include #include -#include -#include #include #include @@ -215,12 +214,6 @@ namespace AZ // Update old Project path before attempting to merge in new Settings Registry values in order to prevent recursive calls m_oldProjectPath = newProjectPath; - // Merge the project.json file into settings registry under ProjectSettingsRootKey path. - AZ::IO::FixedMaxPath projectMetadataFile{ AZ::SettingsRegistryMergeUtils::FindEngineRoot(m_registry) / newProjectPath }; - projectMetadataFile /= "project.json"; - m_registry.MergeSettingsFile(projectMetadataFile.Native(), - AZ::SettingsRegistryInterface::Format::JsonMergePatch, AZ::SettingsRegistryMergeUtils::ProjectSettingsRootKey); - // Update all the runtime file paths based on the new "project_path" value. AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(m_registry); } @@ -506,6 +499,16 @@ namespace AZ SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(*m_settingsRegistry, m_commandLine, executeRegDumpCommands); SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*m_settingsRegistry); + // The /O3DE/Application/LifecycleEvents array contains a valid set of lifecycle events + // Those lifecycle events are normally read from the /Registry + // which isn't merged until ComponentApplication::Create invokes MergeSettingsToRegistry + // So pre-populate the valid lifecycle even entries + ComponentApplicationLifecycle::RegisterEvent(*m_settingsRegistry, "SystemAllocatorCreated"); + ComponentApplicationLifecycle::RegisterEvent(*m_settingsRegistry, "SettingsRegistryAvailable"); + ComponentApplicationLifecycle::RegisterEvent(*m_settingsRegistry, "ConsoleAvailable"); + ComponentApplicationLifecycle::SignalEvent(*m_settingsRegistry, "SystemAllocatorCreated", R"({})"); + ComponentApplicationLifecycle::SignalEvent(*m_settingsRegistry, "SettingsRegistryAvailable", R"({})"); + // Create the Module Manager m_moduleManager = AZStd::make_unique(); @@ -520,6 +523,7 @@ namespace AZ m_ownsConsole = true; m_console->LinkDeferredFunctors(AZ::ConsoleFunctorBase::GetDeferredHead()); m_settingsRegistryConsoleFunctors = AZ::SettingsRegistryConsoleUtils::RegisterAzConsoleCommands(*m_settingsRegistry, *m_console); + ComponentApplicationLifecycle::SignalEvent(*m_settingsRegistry, "ConsoleAvailable", R"({})"); } } @@ -551,6 +555,7 @@ namespace AZ { AZ::Interface::Unregister(m_console); delete m_console; + ComponentApplicationLifecycle::SignalEvent(*m_settingsRegistry, "ConsoleUnavailable", R"({})"); } m_moduleManager.reset(); @@ -558,6 +563,8 @@ namespace AZ if (AZ::SettingsRegistry::Get() == m_settingsRegistry.get()) { SettingsRegistry::Unregister(m_settingsRegistry.get()); + ComponentApplicationLifecycle::SignalEvent(*m_settingsRegistry, "SettingsRegistryUnavailable", R"({})"); + ComponentApplicationLifecycle::SignalEvent(*m_settingsRegistry, "SystemAllocatorPendingDestruction", R"({})"); } m_settingsRegistry.reset(); @@ -672,6 +679,8 @@ namespace AZ ReflectionEnvironment::GetReflectionManager()->Reflect(azrtti_typeid(this), [this](ReflectContext* context) {Reflect(context); }); RegisterCoreComponents(); + ComponentApplicationLifecycle::SignalEvent(*m_settingsRegistry, "ReflectionManagerAvailable", R"({})"); + TickBus::AllowFunctionQueuing(true); SystemTickBus::AllowFunctionQueuing(true); @@ -691,6 +700,7 @@ namespace AZ // Load the actual modules LoadModules(); + ComponentApplicationLifecycle::SignalEvent(*m_settingsRegistry, "GemsLoaded", R"({})"); // Execute user.cfg after modules have been loaded but before processing any command-line overrides AZ::IO::FixedMaxPath platformCachePath; @@ -756,12 +766,14 @@ namespace AZ m_entities.rehash(0); // force free all memory DestroyReflectionManager(); + ComponentApplicationLifecycle::SignalEvent(*m_settingsRegistry, "ReflectionManagerUnavailable", R"({})"); static_cast(m_settingsRegistry.get())->ClearNotifiers(); static_cast(m_settingsRegistry.get())->ClearMergeEvents(); // Uninit and unload any dynamic modules. m_moduleManager->UnloadModules(); + ComponentApplicationLifecycle::SignalEvent(*m_settingsRegistry, "GemsUnloaded", R"({})"); NameDictionary::Destroy(); diff --git a/Code/Framework/AzCore/AzCore/Component/ComponentApplication.h b/Code/Framework/AzCore/AzCore/Component/ComponentApplication.h index f2b1bb8905..6df93aff4e 100644 --- a/Code/Framework/AzCore/AzCore/Component/ComponentApplication.h +++ b/Code/Framework/AzCore/AzCore/Component/ComponentApplication.h @@ -175,6 +175,8 @@ namespace AZ bool m_loadDynamicModules = true; //! Used by test fixtures to ensure reflection occurs to edit context. bool m_createEditContext = false; + //! Indicates whether the AssetCatalog.xml should be loaded by default in Application::StartCommon + bool m_loadAssetCatalog = true; }; ComponentApplication(); @@ -356,7 +358,7 @@ namespace AZ /// Calculates the root directory of the engine. void CalculateEngineRoot(); - /// Calculates the directory where the bootstrap.cfg file resides. + /// Deprecated: The term "AppRoot" has no meaning void CalculateAppRoot(); template diff --git a/Code/Framework/AzCore/AzCore/Component/ComponentApplicationLifecycle.cpp b/Code/Framework/AzCore/AzCore/Component/ComponentApplicationLifecycle.cpp new file mode 100644 index 0000000000..8e15871bc0 --- /dev/null +++ b/Code/Framework/AzCore/AzCore/Component/ComponentApplicationLifecycle.cpp @@ -0,0 +1,93 @@ +/* + * 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 AZ::ComponentApplicationLifecycle +{ + bool ValidateEvent(AZ::SettingsRegistryInterface& settingsRegistry, AZStd::string_view eventName) + { + using FixedValueString = SettingsRegistryInterface::FixedValueString; + using Type = SettingsRegistryInterface::Type; + FixedValueString eventRegistrationKey{ ApplicationLifecycleEventRegistrationKey }; + eventRegistrationKey += '/'; + eventRegistrationKey += eventName; + return settingsRegistry.GetType(eventRegistrationKey) == Type::Object; + } + + bool SignalEvent(AZ::SettingsRegistryInterface& settingsRegistry, AZStd::string_view eventName, AZStd::string_view eventValue) + { + using FixedValueString = AZ::SettingsRegistryInterface::FixedValueString; + using Format = AZ::SettingsRegistryInterface::Format; + + if (!ValidateEvent(settingsRegistry, eventName)) + { + AZ_Warning("ComponentApplicationLifecycle", false, R"(Cannot signal event %.*s. Name does is not a field of object "%.*s".)" + R"( Please make sure the entry exists in the '/Registry/application_lifecycle_events.setreg")" + " or in *.setreg within the project", AZ_STRING_ARG(eventName), AZ_STRING_ARG(ApplicationLifecycleEventRegistrationKey)); + return false; + } + auto eventRegistrationKey = FixedValueString::format("%.*s/%.*s", AZ_STRING_ARG(ApplicationLifecycleEventRegistrationKey), + AZ_STRING_ARG(eventName)); + + return settingsRegistry.MergeSettings(eventValue, Format::JsonMergePatch, eventRegistrationKey); + } + + bool RegisterEvent(AZ::SettingsRegistryInterface& settingsRegistry, AZStd::string_view eventName) + { + using FixedValueString = SettingsRegistryInterface::FixedValueString; + using Format = AZ::SettingsRegistryInterface::Format; + + if (!ValidateEvent(settingsRegistry, eventName)) + { + FixedValueString eventRegistrationKey{ ApplicationLifecycleEventRegistrationKey }; + eventRegistrationKey += '/'; + eventRegistrationKey += eventName; + return settingsRegistry.MergeSettings(R"({})", Format::JsonMergePatch, eventRegistrationKey); + } + + return true; + } + + bool RegisterHandler(AZ::SettingsRegistryInterface& settingsRegistry, AZ::SettingsRegistryInterface::NotifyEventHandler& handler, + AZ::SettingsRegistryInterface::NotifyCallback callback, AZStd::string_view eventName, bool autoRegisterEvent) + { + using FixedValueString = AZ::SettingsRegistryInterface::FixedValueString; + using Type = AZ::SettingsRegistryInterface::Type; + using NotifyEventHandler = AZ::SettingsRegistryInterface::NotifyEventHandler; + + // Some systems may attempt to register a handler before the settings registry has been loaded + // If so, this flag lets them automatically register an event if it hasn't yet been registered. + // RegisterEvent calls validate event. + if ((!autoRegisterEvent && !ValidateEvent(settingsRegistry, eventName)) || + (autoRegisterEvent && !RegisterEvent(settingsRegistry, eventName))) + { + AZ_Warning( + "ComponentApplicationLifecycle", false, + R"(Cannot register event %.*s. Name is not a field of object "%.*s".)" + R"( Please make sure the entry exists in the '/Registry/application_lifecycle_events.setreg")" + " or in *.setreg within the project", AZ_STRING_ARG(eventName), AZ_STRING_ARG(ApplicationLifecycleEventRegistrationKey)); + return false; + } + auto eventNameRegistrationKey = FixedValueString::format("%.*s/%.*s", AZ_STRING_ARG(ApplicationLifecycleEventRegistrationKey), + AZ_STRING_ARG(eventName)); + auto lifecycleCallback = [callback = AZStd::move(callback), eventNameRegistrationKey](AZStd::string_view path, Type type) + { + if (path == eventNameRegistrationKey) + { + callback(path, type); + } + }; + + handler = NotifyEventHandler(AZStd::move(lifecycleCallback)); + settingsRegistry.RegisterNotifier(handler); + + return true; + } +} diff --git a/Code/Framework/AzCore/AzCore/Component/ComponentApplicationLifecycle.h b/Code/Framework/AzCore/AzCore/Component/ComponentApplicationLifecycle.h new file mode 100644 index 0000000000..6f07c0da3f --- /dev/null +++ b/Code/Framework/AzCore/AzCore/Component/ComponentApplicationLifecycle.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 + +namespace AZ::ComponentApplicationLifecycle +{ + //! Root Key where lifecycle events should be registered under + inline constexpr AZStd::string_view ApplicationLifecycleEventRegistrationKey = "/O3DE/Application/LifecycleEvents"; + + + //! Validates that the event @eventName is stored in the array at ApplicationLifecycleEventRegistrationKey + //! @param settingsRegistry registry where @eventName will be searched + //! @param eventName name of key that validated that exists as an element in the ApplicationLifecycleEventRegistrationKey array + //! @return true if the @eventName was found in the ApplicationLifecycleEventRegistrationKey array + bool ValidateEvent(AZ::SettingsRegistryInterface& settingsRegistry, AZStd::string_view eventName); + + //! Wrapper around setting a value underneath the ApplicationLifecycleEventRegistrationKey + //! It validates if the @eventName is is part of the ApplicationLifecycleEventRegistrationKey array + //! It then appends the @eventName to the ApplicationLifecycleEventRegistrationKey merges the @eventValue into + //! the SettingsRegistry at that key + //! NOTE: This function should only be invoked from ComponentApplication and its derived classes + //! @param settingsRegistry registry where eventName should be set + //! @param eventName name of key underneath the ApplicationLifecycleEventRegistrationKey to signal + //! @param eventValue JSON Object that will be merged into the SettingsRegistry at / + //! @return true if the eventValue was successfully merged at the / + bool SignalEvent(AZ::SettingsRegistryInterface& settingsRegistry, AZStd::string_view eventName, AZStd::string_view eventValue); + + //! Register that the event @eventName is stored in the array at ApplicationLifecycleEventRegistrationKey + //! @param settingsRegistry registry where @eventName will be searched + //! @param eventName name of key that will be stored in the ApplicationLifecycleEventRegistrationKey array + //! @return true if the event passed validation or the eventName was stored in the ApplicationLifecycleEventRegistrationKey array + bool RegisterEvent(AZ::SettingsRegistryInterface& settingsRegistry, AZStd::string_view eventName); + + //! Wrapper around registering the NotifyEventHandler with the SettingsRegistry for the specified event + //! It validates if the @eventName is is part of the ApplicationLifecycleEventRegistrationKey array and if + //! so moves the @callback into @handler and then registers the handler with the SettingsRegistry NotifyEvent + //! @param settingsRegistry registry where handler will be registered + //! @param handler handler where callback will be moved into and then registered with the SettingsRegistry + //! if the specified @eventName passes validation + //! @param callback will be moved into the handler if the specified @eventName is valid + //! @param eventName name of key underneath the ApplicationLifecycleEventRegistrationKey to register + //! @param autoRegisterEvent automatically register this event if it hasn't been registered yet. This is useful + //! when registering a handler before the settings registry has been loaded. + //! @return true if the handler was registered with the SettingsRegistry NotifyEvent + bool RegisterHandler(AZ::SettingsRegistryInterface& settingsRegistry, AZ::SettingsRegistryInterface::NotifyEventHandler& handler, + AZ::SettingsRegistryInterface::NotifyCallback callback, AZStd::string_view eventName, bool autoRegisterEvent = false); +} diff --git a/Code/Framework/AzCore/AzCore/Console/IConsole.h b/Code/Framework/AzCore/AzCore/Console/IConsole.h index 73d17ac65a..aef163d22b 100644 --- a/Code/Framework/AzCore/AzCore/Console/IConsole.h +++ b/Code/Framework/AzCore/AzCore/Console/IConsole.h @@ -262,6 +262,6 @@ static constexpr AZ::ThreadSafety ConsoleThreadSafety<_TYPE, std::enable_if_t Functor##_FUNCTION(#_FUNCTION, _DESC, _FLAGS | AZ::ConsoleFunctorFlags::DontDuplicate, AZ::TypeId::CreateNull(), &_FUNCTION) + inline AZ::ConsoleFunctor Functor##_FUNCTION(_NAME, _DESC, _FLAGS | AZ::ConsoleFunctorFlags::DontDuplicate, AZ::TypeId::CreateNull(), &_FUNCTION) #define AZ_CONSOLEFREEFUNC(...) AZ_MACRO_SPECIALIZE(AZ_CONSOLEFREEFUNC_, AZ_VA_NUM_ARGS(__VA_ARGS__), (__VA_ARGS__)) diff --git a/Code/Framework/AzCore/AzCore/Math/Internal/SimdMathCommon_sse.inl b/Code/Framework/AzCore/AzCore/Math/Internal/SimdMathCommon_sse.inl index e228a19d68..8355f9bfe0 100644 --- a/Code/Framework/AzCore/AzCore/Math/Internal/SimdMathCommon_sse.inl +++ b/Code/Framework/AzCore/AzCore/Math/Internal/SimdMathCommon_sse.inl @@ -383,36 +383,36 @@ namespace AZ AZ_MATH_INLINE bool CmpAllEq(__m128 arg1, __m128 arg2, int32_t mask) { - const __m128i compare = CastToInt(CmpNeq(arg1, arg2)); - return (_mm_movemask_epi8(compare) & mask) == 0; + const __m128 compare = CmpEq(arg1, arg2); + return (_mm_movemask_ps(compare) & mask) == mask; } AZ_MATH_INLINE bool CmpAllLt(__m128 arg1, __m128 arg2, int32_t mask) { - const __m128i compare = CastToInt(CmpGtEq(arg1, arg2)); - return (_mm_movemask_epi8(compare) & mask) == 0; + const __m128 compare = CmpLt(arg1, arg2); + return (_mm_movemask_ps(compare) & mask) == mask; } AZ_MATH_INLINE bool CmpAllLtEq(__m128 arg1, __m128 arg2, int32_t mask) { - const __m128i compare = CastToInt(CmpGt(arg1, arg2)); - return (_mm_movemask_epi8(compare) & mask) == 0; + const __m128 compare = CmpLtEq(arg1, arg2); + return (_mm_movemask_ps(compare) & mask) == mask; } AZ_MATH_INLINE bool CmpAllGt(__m128 arg1, __m128 arg2, int32_t mask) { - const __m128i compare = CastToInt(CmpLtEq(arg1, arg2)); - return (_mm_movemask_epi8(compare) & mask) == 0; + const __m128 compare = CmpGt(arg1, arg2); + return (_mm_movemask_ps(compare) & mask) == mask; } AZ_MATH_INLINE bool CmpAllGtEq(__m128 arg1, __m128 arg2, int32_t mask) { - const __m128i compare = CastToInt(CmpLt(arg1, arg2)); - return (_mm_movemask_epi8(compare) & mask) == 0; + const __m128 compare = CmpGtEq(arg1, arg2); + return (_mm_movemask_ps(compare) & mask) == mask; } diff --git a/Code/Framework/AzCore/AzCore/Math/Internal/SimdMathVec1_sse.inl b/Code/Framework/AzCore/AzCore/Math/Internal/SimdMathVec1_sse.inl index ecdcf40743..bb332c03a2 100644 --- a/Code/Framework/AzCore/AzCore/Math/Internal/SimdMathVec1_sse.inl +++ b/Code/Framework/AzCore/AzCore/Math/Internal/SimdMathVec1_sse.inl @@ -331,31 +331,32 @@ namespace AZ AZ_MATH_INLINE bool Vec1::CmpAllEq(FloatArgType arg1, FloatArgType arg2) { - return Sse::CmpAllEq(arg1, arg2, 0x000F); + // Only check the first bit for Vector1 + return Sse::CmpAllEq(arg1, arg2, 0b0001); } AZ_MATH_INLINE bool Vec1::CmpAllLt(FloatArgType arg1, FloatArgType arg2) { - return Sse::CmpAllLt(arg1, arg2, 0x000F); + return Sse::CmpAllLt(arg1, arg2, 0b0001); } AZ_MATH_INLINE bool Vec1::CmpAllLtEq(FloatArgType arg1, FloatArgType arg2) { - return Sse::CmpAllLtEq(arg1, arg2, 0x000F); + return Sse::CmpAllLtEq(arg1, arg2, 0b0001); } AZ_MATH_INLINE bool Vec1::CmpAllGt(FloatArgType arg1, FloatArgType arg2) { - return Sse::CmpAllGt(arg1, arg2, 0x000F); + return Sse::CmpAllGt(arg1, arg2, 0b0001); } AZ_MATH_INLINE bool Vec1::CmpAllGtEq(FloatArgType arg1, FloatArgType arg2) { - return Sse::CmpAllGtEq(arg1, arg2, 0x000F); + return Sse::CmpAllGtEq(arg1, arg2, 0b0001); } @@ -397,7 +398,7 @@ namespace AZ AZ_MATH_INLINE bool Vec1::CmpAllEq(Int32ArgType arg1, Int32ArgType arg2) { - return Sse::CmpAllEq(arg1, arg2, 0x000F); + return Sse::CmpAllEq(arg1, arg2, 0b0001); } diff --git a/Code/Framework/AzCore/AzCore/Math/Internal/SimdMathVec2_sse.inl b/Code/Framework/AzCore/AzCore/Math/Internal/SimdMathVec2_sse.inl index c890aa5eb7..90fb97e694 100644 --- a/Code/Framework/AzCore/AzCore/Math/Internal/SimdMathVec2_sse.inl +++ b/Code/Framework/AzCore/AzCore/Math/Internal/SimdMathVec2_sse.inl @@ -383,31 +383,32 @@ namespace AZ AZ_MATH_INLINE bool Vec2::CmpAllEq(FloatArgType arg1, FloatArgType arg2) { - return Sse::CmpAllEq(arg1, arg2, 0x00FF); + // Only check the first two bits for Vector2 + return Sse::CmpAllEq(arg1, arg2, 0b0011); } AZ_MATH_INLINE bool Vec2::CmpAllLt(FloatArgType arg1, FloatArgType arg2) { - return Sse::CmpAllLt(arg1, arg2, 0x00FF); + return Sse::CmpAllLt(arg1, arg2, 0b0011); } AZ_MATH_INLINE bool Vec2::CmpAllLtEq(FloatArgType arg1, FloatArgType arg2) { - return Sse::CmpAllLtEq(arg1, arg2, 0x00FF); + return Sse::CmpAllLtEq(arg1, arg2, 0b0011); } AZ_MATH_INLINE bool Vec2::CmpAllGt(FloatArgType arg1, FloatArgType arg2) { - return Sse::CmpAllGt(arg1, arg2, 0x00FF); + return Sse::CmpAllGt(arg1, arg2, 0b0011); } AZ_MATH_INLINE bool Vec2::CmpAllGtEq(FloatArgType arg1, FloatArgType arg2) { - return Sse::CmpAllGtEq(arg1, arg2, 0x00FF); + return Sse::CmpAllGtEq(arg1, arg2, 0b0011); } diff --git a/Code/Framework/AzCore/AzCore/Math/Internal/SimdMathVec3_sse.inl b/Code/Framework/AzCore/AzCore/Math/Internal/SimdMathVec3_sse.inl index a8ff962837..31e8d19d65 100644 --- a/Code/Framework/AzCore/AzCore/Math/Internal/SimdMathVec3_sse.inl +++ b/Code/Framework/AzCore/AzCore/Math/Internal/SimdMathVec3_sse.inl @@ -419,31 +419,32 @@ namespace AZ AZ_MATH_INLINE bool Vec3::CmpAllEq(FloatArgType arg1, FloatArgType arg2) { - return Sse::CmpAllEq(arg1, arg2, 0x0FFF); + // Only check the first three bits for Vector3 + return Sse::CmpAllEq(arg1, arg2, 0b0111); } AZ_MATH_INLINE bool Vec3::CmpAllLt(FloatArgType arg1, FloatArgType arg2) { - return Sse::CmpAllLt(arg1, arg2, 0x0FFF); + return Sse::CmpAllLt(arg1, arg2, 0b0111); } AZ_MATH_INLINE bool Vec3::CmpAllLtEq(FloatArgType arg1, FloatArgType arg2) { - return Sse::CmpAllLtEq(arg1, arg2, 0x0FFF); + return Sse::CmpAllLtEq(arg1, arg2, 0b0111); } AZ_MATH_INLINE bool Vec3::CmpAllGt(FloatArgType arg1, FloatArgType arg2) { - return Sse::CmpAllGt(arg1, arg2, 0x0FFF); + return Sse::CmpAllGt(arg1, arg2, 0b0111); } AZ_MATH_INLINE bool Vec3::CmpAllGtEq(FloatArgType arg1, FloatArgType arg2) { - return Sse::CmpAllGtEq(arg1, arg2, 0x0FFF); + return Sse::CmpAllGtEq(arg1, arg2, 0b0111); } @@ -485,7 +486,7 @@ namespace AZ AZ_MATH_INLINE bool Vec3::CmpAllEq(Int32ArgType arg1, Int32ArgType arg2) { - return Sse::CmpAllEq(arg1, arg2, 0x0FFF); + return Sse::CmpAllEq(arg1, arg2, 0b0111); } diff --git a/Code/Framework/AzCore/AzCore/Math/Internal/SimdMathVec4_sse.inl b/Code/Framework/AzCore/AzCore/Math/Internal/SimdMathVec4_sse.inl index 1cf0a35d7f..f3a4feb524 100644 --- a/Code/Framework/AzCore/AzCore/Math/Internal/SimdMathVec4_sse.inl +++ b/Code/Framework/AzCore/AzCore/Math/Internal/SimdMathVec4_sse.inl @@ -455,31 +455,32 @@ namespace AZ AZ_MATH_INLINE bool Vec4::CmpAllEq(FloatArgType arg1, FloatArgType arg2) { - return Sse::CmpAllEq(arg1, arg2, 0xFFFF); + // Check the first four bits for Vector4 + return Sse::CmpAllEq(arg1, arg2, 0b1111); } AZ_MATH_INLINE bool Vec4::CmpAllLt(FloatArgType arg1, FloatArgType arg2) { - return Sse::CmpAllLt(arg1, arg2, 0xFFFF); + return Sse::CmpAllLt(arg1, arg2, 0b1111); } AZ_MATH_INLINE bool Vec4::CmpAllLtEq(FloatArgType arg1, FloatArgType arg2) { - return Sse::CmpAllLtEq(arg1, arg2, 0xFFFF); + return Sse::CmpAllLtEq(arg1, arg2, 0b1111); } AZ_MATH_INLINE bool Vec4::CmpAllGt(FloatArgType arg1, FloatArgType arg2) { - return Sse::CmpAllGt(arg1, arg2, 0xFFFF); + return Sse::CmpAllGt(arg1, arg2, 0b1111); } AZ_MATH_INLINE bool Vec4::CmpAllGtEq(FloatArgType arg1, FloatArgType arg2) { - return Sse::CmpAllGtEq(arg1, arg2, 0xFFFF); + return Sse::CmpAllGtEq(arg1, arg2, 0b1111); } @@ -521,7 +522,7 @@ namespace AZ AZ_MATH_INLINE bool Vec4::CmpAllEq(Int32ArgType arg1, Int32ArgType arg2) { - return Sse::CmpAllEq(arg1, arg2, 0xFFFF); + return Sse::CmpAllEq(arg1, arg2, 0b1111); } diff --git a/Code/Framework/AzCore/AzCore/Module/ModuleManager.cpp b/Code/Framework/AzCore/AzCore/Module/ModuleManager.cpp index 1fdeafa309..cbf3ce5243 100644 --- a/Code/Framework/AzCore/AzCore/Module/ModuleManager.cpp +++ b/Code/Framework/AzCore/AzCore/Module/ModuleManager.cpp @@ -10,16 +10,13 @@ #include #include -#include -#include #include #include #include -#include #include +#include +#include #include -#include -#include #include #include @@ -221,11 +218,16 @@ namespace AZ } } + AZStd::string componentNamesArray = R"({ "SystemComponents":[)"; + const char* comma = ""; // For all system components, deactivate for (auto componentIt = m_systemComponents.rbegin(); componentIt != m_systemComponents.rend(); ++componentIt) { ModuleEntity::DeactivateComponent(**componentIt); + componentNamesArray += AZStd::string::format(R"(%s"%s")", comma, (*componentIt)->RTTI_GetTypeName()); + comma = ", "; } + componentNamesArray += R"(]})"; // For all modules that we created an entity for, set them to "Init" (meaning not Activated) for (auto& moduleData : m_ownedModules) @@ -239,6 +241,13 @@ namespace AZ // Since the system components have been deactivated clear out the vector. m_systemComponents.clear(); + + // Signal that the System Components have deactivated + if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr) + { + AZ::ComponentApplicationLifecycle::SignalEvent(*settingsRegistry, "SystemComponentsDeactivated", componentNamesArray); + } + } //========================================================================= @@ -284,7 +293,11 @@ namespace AZ { // Split the tag list AZStd::vector tagList; - AZStd::tokenize(tags, ",", tagList); + auto TokenizeTags = [&tagList](AZStd::string_view token) + { + tagList.push_back(token); + }; + AZ::StringFunc::TokenizeVisitor(tags, TokenizeTags, ','); m_systemComponentTags.resize(tagList.size()); AZStd::transform(tagList.begin(), tagList.end(), m_systemComponentTags.begin(), [](const AZStd::string_view& tag) @@ -737,11 +750,17 @@ namespace AZ } } + AZStd::string componentNamesArray = R"({ "SystemComponents":[)"; + const char* comma = ""; // Activate the entities in the appropriate order for (Component* component : componentsToActivate) { ModuleEntity::ActivateComponent(*component); + + componentNamesArray += AZStd::string::format(R"(%s"%s")", comma, component->RTTI_GetTypeName()); + comma = ", "; } + componentNamesArray += R"(]})"; // Done activating; set state to active for (auto& moduleData : modulesToInit) @@ -755,5 +774,12 @@ namespace AZ // Save the activated components for deactivation later m_systemComponents.insert(m_systemComponents.end(), componentsToActivate.begin(), componentsToActivate.end()); + + // Signal that the System Components are activated + if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr) + { + AZ::ComponentApplicationLifecycle::SignalEvent(*settingsRegistry, "SystemComponentsActivated", + componentNamesArray); + } } } // namespace AZ diff --git a/Code/Framework/AzCore/AzCore/Script/ScriptSystemComponent.cpp b/Code/Framework/AzCore/AzCore/Script/ScriptSystemComponent.cpp index a83232c8cb..d34f43e433 100644 --- a/Code/Framework/AzCore/AzCore/Script/ScriptSystemComponent.cpp +++ b/Code/Framework/AzCore/AzCore/Script/ScriptSystemComponent.cpp @@ -87,6 +87,8 @@ void ScriptSystemComponent::Activate() AZ::Data::AssetCatalogRequestBus::Broadcast(&AZ::Data::AssetCatalogRequests::AddExtension, "lua"); AZ::Data::AssetCatalogRequestBus::Broadcast(&AZ::Data::AssetCatalogRequests::AddExtension, "luac"); + AZ::Data::AssetCatalogRequestBus::Broadcast( + &AZ::Data::AssetCatalogRequests::EnableCatalogForAsset, AZ::AzTypeInfo::Uuid()); if (Data::AssetManager::Instance().IsReady()) { diff --git a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.cpp b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.cpp index 36f66312d8..3668ab14fd 100644 --- a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.cpp +++ b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.cpp @@ -29,6 +29,8 @@ namespace AZ::Internal { + static constexpr const char* ProductCacheDirectoryName = "Cache"; + AZ::SettingsRegistryInterface::FixedValueString GetEngineMonikerForProject( SettingsRegistryInterface& settingsRegistry, const AZ::IO::FixedMaxPath& projectJsonPath) { @@ -228,19 +230,20 @@ namespace AZ::Internal namespace AZ::SettingsRegistryMergeUtils { - constexpr AZStd::string_view InternalScanUpEngineRootKey{ "/O3DE/Settings/Internal/engine_root_scan_up_path" }; - constexpr AZStd::string_view InternalScanUpProjectRootKey{ "/O3DE/Settings/Internal/project_root_scan_up_path" }; - AZ::IO::FixedMaxPath FindEngineRoot(SettingsRegistryInterface& settingsRegistry) { + static constexpr AZStd::string_view InternalScanUpEngineRootKey{ "/O3DE/Runtime/Internal/engine_root_scan_up_path" }; + using FixedValueString = SettingsRegistryInterface::FixedValueString; + using Type = SettingsRegistryInterface::Type; + AZ::IO::FixedMaxPath engineRoot; // This is the 'external' engine root key, as in passed from command-line or .setreg files. - auto engineRootKey = SettingsRegistryInterface::FixedValueString::format("%s/engine_path", BootstrapSettingsRootKey); + constexpr auto engineRootKey = FixedValueString(BootstrapSettingsRootKey) + "/engine_path"; // Step 1 Run the scan upwards logic once to find the location of the engine.json if it exist // Once this step is run the {InternalScanUpEngineRootKey} is set in the Settings Registry // to have this scan logic only run once InternalScanUpEngineRootKey the supplied registry - if (settingsRegistry.GetType(InternalScanUpEngineRootKey) == SettingsRegistryInterface::Type::NoType) + if (settingsRegistry.GetType(InternalScanUpEngineRootKey) == Type::NoType) { // We can scan up from exe directory to find engine.json, use that for engine root if it exists. engineRoot = Internal::ScanUpRootLocator("engine.json"); @@ -283,14 +286,18 @@ namespace AZ::SettingsRegistryMergeUtils AZ::IO::FixedMaxPath FindProjectRoot(SettingsRegistryInterface& settingsRegistry) { - AZ::IO::FixedMaxPath projectRoot; - const auto projectRootKey = SettingsRegistryInterface::FixedValueString::format("%s/project_path", BootstrapSettingsRootKey); + static constexpr AZStd::string_view InternalScanUpProjectRootKey{ "/O3DE/Runtime/Internal/project_root_scan_up_path" }; + using FixedValueString = SettingsRegistryInterface::FixedValueString; + using Type = SettingsRegistryInterface::Type; - // Step 1 Run the scan upwards logic once to find the location of the project.json if it exist + AZ::IO::FixedMaxPath projectRoot; + constexpr auto projectRootKey = FixedValueString(BootstrapSettingsRootKey) + "/project_path"; + + // Step 1 Run the scan upwards logic once to find the location of the closest ancestor project.json // Once this step is run the {InternalScanUpProjectRootKey} is set in the Settings Registry // to have this scan logic only run once for the supplied registry // SettingsRegistryInterface::GetType is used to check if a key is set - if (settingsRegistry.GetType(InternalScanUpProjectRootKey) == SettingsRegistryInterface::Type::NoType) + if (settingsRegistry.GetType(InternalScanUpProjectRootKey) == Type::NoType) { projectRoot = Internal::ScanUpRootLocator("project.json"); // Set the {InternalScanUpProjectRootKey} to make sure this code path isn't called again for this settings registry @@ -305,19 +312,129 @@ namespace AZ::SettingsRegistryMergeUtils } // Step 2 Check the project-path key - // This is the project path root key, as in passed from command-line or .setreg files. - if (settingsRegistry.Get(projectRoot.Native(), projectRootKey)) + // This is the project path root key, as passed from command-line or *.setreg files. + settingsRegistry.Get(projectRoot.Native(), projectRootKey); + return projectRoot; + } + + //! The algorithm that is used to find the project cache is as follows + //! 1. The "{BootstrapSettingsRootKey}/project_cache_path" is checked for the path + //! 2. Otherwise append the ProductCacheDirectoryName constant to the + static AZ::IO::FixedMaxPath FindProjectCachePath(SettingsRegistryInterface& settingsRegistry, const AZ::IO::FixedMaxPath& projectPath) + { + using FixedValueString = SettingsRegistryInterface::FixedValueString; + + constexpr auto projectCachePathKey = FixedValueString(BootstrapSettingsRootKey) + "/project_cache_path"; + + // Step 1 Check the project-cache-path key + if (AZ::IO::FixedMaxPath projectCachePath; settingsRegistry.Get(projectCachePath.Native(), projectCachePathKey)) { - return projectRoot; + return projectCachePath; } - // Step 3 Check for a "Cache" directory by scanning upwards from the executable directory - if (auto candidateRoot = Internal::ScanUpRootLocator("Cache"); - !candidateRoot.empty() && AZ::IO::SystemFile::IsDirectory(candidateRoot.c_str())) + // Step 2 Append the "Cache" directory to the project-path + return projectPath / Internal::ProductCacheDirectoryName; + } + + //! Set the user directory with the provided path or using /user as default + static AZ::IO::FixedMaxPath FindProjectUserPath(SettingsRegistryInterface& settingsRegistry, + const AZ::IO::FixedMaxPath& projectPath) + { + using FixedValueString = SettingsRegistryInterface::FixedValueString; + + // User: root - same as the @user@ alias, this is the starting path for transient data and log files. + constexpr auto projectUserPathKey = FixedValueString(BootstrapSettingsRootKey) + "/project_user_path"; + + // Step 1 Check the project-user-path key + if (AZ::IO::FixedMaxPath projectUserPath; settingsRegistry.Get(projectUserPath.Native(), projectUserPathKey)) { - projectRoot = AZStd::move(candidateRoot); + return projectUserPath; + } + + // Step 2 Append the "User" directory to the project-path + return projectPath / "user"; + } + + //! Set the log directory using the settings registry path or using /log as default + static AZ::IO::FixedMaxPath FindProjectLogPath(SettingsRegistryInterface& settingsRegistry, + const AZ::IO::FixedMaxPath& projectUserPath) + { + using FixedValueString = SettingsRegistryInterface::FixedValueString; + + // User: root - same as the @log@ alias, this is the starting path for transient data and log files. + constexpr auto projectLogPathKey = FixedValueString(BootstrapSettingsRootKey) + "/project_log_path"; + + // Step 1 Check the project-user-path key + if (AZ::IO::FixedMaxPath projectLogPath; settingsRegistry.Get(projectLogPath.Native(), projectLogPathKey)) + { + return projectLogPath; + } + + // Step 2 Append the "Log" directory to the project-user-path + return projectUserPath / "log"; + } + + // check for a default write storage path, fall back to the if not + static AZ::IO::FixedMaxPath FindDevWriteStoragePath(const AZ::IO::FixedMaxPath& projectUserPath) + { + AZStd::optional devWriteStorage = Utils::GetDevWriteStoragePath(); + return devWriteStorage.has_value() ? *devWriteStorage : projectUserPath; + } + + // check for the project build path, which is a relative path from the project root + // that specifies where the build directory is located + static void SetProjectBuildPath(SettingsRegistryInterface& settingsRegistry, + const AZ::IO::FixedMaxPath& projectPath) + { + if (AZ::IO::FixedMaxPath projectBuildPath; settingsRegistry.Get(projectBuildPath.Native(), ProjectBuildPath)) + { + settingsRegistry.Remove(FilePathKey_ProjectBuildPath); + settingsRegistry.Remove(FilePathKey_ProjectConfigurationBinPath); + AZ::IO::FixedMaxPath buildConfigurationPath = (projectPath / projectBuildPath).LexicallyNormal(); + if (IO::SystemFile::Exists(buildConfigurationPath.c_str())) + { + settingsRegistry.Set(FilePathKey_ProjectBuildPath, buildConfigurationPath.Native()); + } + + // Add the specific build configuration paths to the Settings Registry + // First try /bin/$ and if that path doesn't exist + // try /bin/$/$ + buildConfigurationPath /= "bin"; + if (IO::SystemFile::Exists((buildConfigurationPath / AZ_BUILD_CONFIGURATION_TYPE).c_str())) + { + settingsRegistry.Set(FilePathKey_ProjectConfigurationBinPath, + (buildConfigurationPath / AZ_BUILD_CONFIGURATION_TYPE).Native()); + } + else if (IO::SystemFile::Exists((buildConfigurationPath / AZ_TRAIT_OS_PLATFORM_CODENAME / AZ_BUILD_CONFIGURATION_TYPE).c_str())) + { + settingsRegistry.Set(FilePathKey_ProjectConfigurationBinPath, + (buildConfigurationPath / AZ_TRAIT_OS_PLATFORM_CODENAME / AZ_BUILD_CONFIGURATION_TYPE).Native()); + } + } + } + + // Sets the project name within the Settings Registry by looking up the "project_name" + // within the project.json file + static void SetProjectName(SettingsRegistryInterface& settingsRegistry, + const AZ::IO::FixedMaxPath& projectPath) + { + using FixedValueString = SettingsRegistryInterface::FixedValueString; + // Project name - if it was set via merging project.json use that value, otherwise use the project path's folder name. + constexpr auto projectNameKey = FixedValueString(ProjectSettingsRootKey) + "/project_name"; + + // Read the project name from the project.json file if it exists + if (AZ::IO::FixedMaxPath projectJsonPath = projectPath / "project.json"; + AZ::IO::SystemFile::Exists(projectJsonPath.c_str())) + { + settingsRegistry.MergeSettingsFile(projectJsonPath.Native(), + AZ::SettingsRegistryInterface::Format::JsonMergePatch, AZ::SettingsRegistryMergeUtils::ProjectSettingsRootKey); + } + // If a project name isn't set the default will be set to the final path segment of the project path + if (FixedValueString projectName; !settingsRegistry.Get(projectName, projectNameKey)) + { + projectName = projectPath.Filename().Native(); + settingsRegistry.Set(projectNameKey, projectName); } - return projectRoot; } AZStd::string_view ConfigParserSettings::DefaultCommentPrefixFilter(AZStd::string_view line) @@ -397,7 +514,7 @@ namespace AZ::SettingsRegistryMergeUtils bool MergeSettingsToRegistry_ConfigFile(SettingsRegistryInterface& registry, AZStd::string_view filePath, const ConfigParserSettings& configParserSettings) { - auto configPath = FindEngineRoot(registry) / filePath; + auto configPath = FindProjectRoot(registry) / filePath; IO::FileReader configFile; bool configFileOpened{}; switch (configParserSettings.m_fileReaderClass) @@ -542,19 +659,77 @@ namespace AZ::SettingsRegistryMergeUtils void MergeSettingsToRegistry_AddRuntimeFilePaths(SettingsRegistryInterface& registry) { using FixedValueString = AZ::SettingsRegistryInterface::FixedValueString; - // Binary folder - AZ::IO::FixedMaxPath path = AZ::Utils::GetExecutableDirectory(); - registry.Set(FilePathKey_BinaryFolder, path.LexicallyNormal().Native()); - // Engine root folder - corresponds to the @engroot@ and @engroot@ aliases - AZ::IO::FixedMaxPath engineRoot = FindEngineRoot(registry); - registry.Set(FilePathKey_EngineRootFolder, engineRoot.LexicallyNormal().Native()); + // Binary folder - corresponds to the @exefolder@ alias + AZ::IO::FixedMaxPath exePath = AZ::Utils::GetExecutableDirectory(); + registry.Set(FilePathKey_BinaryFolder, exePath.LexicallyNormal().Native()); - auto projectPathKey = FixedValueString::format("%s/project_path", BootstrapSettingsRootKey); - SettingsRegistryInterface::FixedValueString projectPathValue; - if (registry.Get(projectPathValue, projectPathKey)) + // Project path - corresponds to the @projectroot@ alias + // NOTE: We make the project-path in the BootstrapSettingsRootKey absolute first + + AZ::IO::FixedMaxPath projectPath = FindProjectRoot(registry); + if (constexpr auto projectPathKey = FixedValueString(BootstrapSettingsRootKey) + "/project_path"; + !projectPath.empty()) { - // Cache folder + if (projectPath.IsRelative()) + { + if (auto projectAbsPath = AZ::Utils::ConvertToAbsolutePath(projectPath.Native()); + projectAbsPath.has_value()) + { + projectPath = AZStd::move(*projectAbsPath); + } + } + + projectPath = projectPath.LexicallyNormal(); + AZ_Warning("SettingsRegistryMergeUtils", AZ::IO::SystemFile::Exists(projectPath.c_str()), + R"(Project path "%s" does not exist. Is the "%.*s" registry setting set to a valid absolute path?)" + , projectPath.c_str(), AZ_STRING_ARG(projectPathKey)); + + registry.Set(FilePathKey_ProjectPath, projectPath.Native()); + } + else + { + AZ_TracePrintf("SettingsRegistryMergeUtils", + R"(Project path isn't set in the Settings Registry at "%.*s".)" + " Project-related filepaths will be set relative to the executable directory\n", + AZ_STRING_ARG(projectPathKey)); + registry.Set(FilePathKey_ProjectPath, exePath.Native()); + } + + // Engine root folder - corresponds to the @engroot@ alias + AZ::IO::FixedMaxPath engineRoot = FindEngineRoot(registry); + if (!engineRoot.empty()) + { + if (engineRoot.IsRelative()) + { + if (auto engineRootAbsPath = AZ::Utils::ConvertToAbsolutePath(engineRoot.Native()); + engineRootAbsPath.has_value()) + { + engineRoot = AZStd::move(*engineRootAbsPath); + } + } + + engineRoot = engineRoot.LexicallyNormal(); + registry.Set(FilePathKey_EngineRootFolder, engineRoot.Native()); + } + + // Cache folder + AZ::IO::FixedMaxPath projectCachePath = FindProjectCachePath(registry, projectPath).LexicallyNormal(); + if (!projectCachePath.empty()) + { + if (projectCachePath.IsRelative()) + { + if (auto projectCacheAbsPath = AZ::Utils::ConvertToAbsolutePath(projectCachePath.Native()); + projectCacheAbsPath.has_value()) + { + projectCachePath = AZStd::move(*projectCacheAbsPath); + } + } + + projectCachePath = projectCachePath.LexicallyNormal(); + registry.Set(FilePathKey_CacheProjectRootFolder, projectCachePath.Native()); + + // Cache/ folder // Get the name of the asset platform assigned by the bootstrap. First check for platform version such as "windows_assets" // and if that's missing just get "assets". FixedValueString assetPlatform; @@ -570,118 +745,67 @@ namespace AZ::SettingsRegistryMergeUtils assetPlatform = AZ::OSPlatformToDefaultAssetPlatform(AZ_TRAIT_OS_PLATFORM_CODENAME); } - // Project path - corresponds to the @projectroot@ alias - // NOTE: Here we append to engineRoot, but if projectPathValue is absolute then engineRoot is discarded. - path = engineRoot / projectPathValue; - - AZ_Warning("SettingsRegistryMergeUtils", AZ::IO::SystemFile::Exists(path.c_str()), - R"(Project path "%s" does not exist. Is the "%.*s" registry setting set to valid absolute path?)" - , path.c_str(), aznumeric_cast(projectPathKey.size()), projectPathKey.data()); - - AZ::IO::FixedMaxPath normalizedProjectPath = path.LexicallyNormal(); - registry.Set(FilePathKey_ProjectPath, normalizedProjectPath.Native()); - - // Set the user directory with the provided path or using project/user as default - auto projectUserPathKey = FixedValueString::format("%s/project_user_path", BootstrapSettingsRootKey); - AZ::IO::FixedMaxPath projectUserPath; - if (!registry.Get(projectUserPath.Native(), projectUserPathKey)) - { - projectUserPath = (normalizedProjectPath / "user").LexicallyNormal(); - } - registry.Set(FilePathKey_ProjectUserPath, projectUserPath.Native()); - - // Set the log directory with the provided path or using project/user/log as default - auto projectLogPathKey = FixedValueString::format("%s/project_log_path", BootstrapSettingsRootKey); - AZ::IO::FixedMaxPath projectLogPath; - if (!registry.Get(projectLogPath.Native(), projectLogPathKey)) - { - projectLogPath = (projectUserPath / "log").LexicallyNormal(); - } - registry.Set(FilePathKey_ProjectLogPath, projectLogPath.Native()); - - // check for a default write storage path, fall back to the project's user/ directory if not - AZStd::optional devWriteStorage = Utils::GetDevWriteStoragePath(); - registry.Set(FilePathKey_DevWriteStorage, devWriteStorage.has_value() - ? devWriteStorage.value() - : projectUserPath.Native()); - - // Set the project in-memory build path if the ProjectBuildPath key has been supplied - if (AZ::IO::FixedMaxPath projectBuildPath; registry.Get(projectBuildPath.Native(), ProjectBuildPath)) - { - registry.Remove(FilePathKey_ProjectBuildPath); - registry.Remove(FilePathKey_ProjectConfigurationBinPath); - AZ::IO::FixedMaxPath buildConfigurationPath = normalizedProjectPath / projectBuildPath; - if (IO::SystemFile::Exists(buildConfigurationPath.c_str())) - { - registry.Set(FilePathKey_ProjectBuildPath, buildConfigurationPath.LexicallyNormal().Native()); - } - - // Add the specific build configuration paths to the Settings Registry - // First try /bin/$ and if that path doesn't exist - // try /bin/$/$ - buildConfigurationPath /= "bin"; - if (IO::SystemFile::Exists((buildConfigurationPath / AZ_BUILD_CONFIGURATION_TYPE).c_str())) - { - registry.Set(FilePathKey_ProjectConfigurationBinPath, - (buildConfigurationPath / AZ_BUILD_CONFIGURATION_TYPE).LexicallyNormal().Native()); - } - else if (IO::SystemFile::Exists((buildConfigurationPath / AZ_TRAIT_OS_PLATFORM_CODENAME / AZ_BUILD_CONFIGURATION_TYPE).c_str())) - { - registry.Set(FilePathKey_ProjectConfigurationBinPath, - (buildConfigurationPath / AZ_TRAIT_OS_PLATFORM_CODENAME / AZ_BUILD_CONFIGURATION_TYPE).LexicallyNormal().Native()); - } - - } - - // Project name - if it was set via merging project.json use that value, otherwise use the project path's folder name. - auto projectNameKey = - AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::ProjectSettingsRootKey) - + "/project_name"; - - AZ::SettingsRegistryInterface::FixedValueString projectName; - if (!registry.Get(projectName, projectNameKey)) - { - projectName = path.Filename().Native(); - registry.Set(projectNameKey, projectName); - } - - // Cache folders - sets up various paths in registry for the cache. - // Make sure the asset platform is set before setting these cache paths. + // Make sure the asset platform is set before setting cache path for the asset platform. if (!assetPlatform.empty()) { - // Cache: project root - no corresponding fileIO alias, but this is where the asset database lives. - // A registry override is accepted using the "project_cache_path" key. - auto projectCacheRootOverrideKey = FixedValueString::format("%s/project_cache_path", BootstrapSettingsRootKey); - // Clear path to make sure that the `project_cache_path` value isn't concatenated to the project path - path.clear(); - if (registry.Get(path.Native(), projectCacheRootOverrideKey)) - { - registry.Set(FilePathKey_CacheProjectRootFolder, path.LexicallyNormal().Native()); - path /= assetPlatform; - registry.Set(FilePathKey_CacheRootFolder, path.LexicallyNormal().Native()); - } - else - { - // Cache: root - same as the @products@ alias, this is the starting path for cache files. - path = normalizedProjectPath / "Cache"; - registry.Set(FilePathKey_CacheProjectRootFolder, path.LexicallyNormal().Native()); - path /= assetPlatform; - registry.Set(FilePathKey_CacheRootFolder, path.LexicallyNormal().Native()); - } + registry.Set(FilePathKey_CacheRootFolder, (projectCachePath / assetPlatform).Native()); } } - else + + // User folder + AZ::IO::FixedMaxPath projectUserPath = FindProjectUserPath(registry, projectPath); + if (!projectUserPath.empty()) { - // Set the default ProjectUserPath to the /user directory - registry.Set(FilePathKey_ProjectUserPath, (engineRoot / "user").LexicallyNormal().Native()); - AZ_TracePrintf("SettingsRegistryMergeUtils", - R"(Project path isn't set in the Settings Registry at "%.*s". Project-related filepaths will not be set)" "\n", - aznumeric_cast(projectPathKey.size()), projectPathKey.data()); + if (projectUserPath.IsRelative()) + { + if (auto projectUserAbsPath = AZ::Utils::ConvertToAbsolutePath(projectUserPath.Native()); + projectUserAbsPath.has_value()) + { + projectUserPath = AZStd::move(*projectUserAbsPath); + } + } + + projectUserPath = projectUserPath.LexicallyNormal(); + registry.Set(FilePathKey_ProjectUserPath, projectUserPath.Native()); } + // Log folder + if (AZ::IO::FixedMaxPath projectLogPath = FindProjectLogPath(registry, projectUserPath); !projectLogPath.empty()) + { + if (projectLogPath.IsRelative()) + { + if (auto projectLogAbsPath = AZ::Utils::ConvertToAbsolutePath(projectLogPath.Native())) + { + projectLogPath = AZStd::move(*projectLogAbsPath); + } + } + + projectLogPath = projectLogPath.LexicallyNormal(); + registry.Set(FilePathKey_ProjectLogPath, projectLogPath.Native()); + } + + // Developer Write Storage folder + if (AZ::IO::FixedMaxPath devWriteStoragePath = FindDevWriteStoragePath(projectUserPath); !devWriteStoragePath.empty()) + { + if (devWriteStoragePath.IsRelative()) + { + if (auto devWriteStorageAbsPath = AZ::Utils::ConvertToAbsolutePath(devWriteStoragePath.Native())) + { + devWriteStoragePath = AZStd::move(*devWriteStorageAbsPath); + } + } + + devWriteStoragePath = devWriteStoragePath.LexicallyNormal(); + registry.Set(FilePathKey_DevWriteStorage, devWriteStoragePath.Native()); + } + + // Set the project in-memory build path if the ProjectBuildPath key has been supplied + SetProjectBuildPath(registry, projectPath); + // Set the project name using the "project_name" key + SetProjectName(registry, projectPath); + #if !AZ_TRAIT_OS_IS_HOST_OS_PLATFORM // Setup the cache, user, and log paths to platform specific locations when running on non-host platforms - path = engineRoot; if (AZStd::optional nonHostCacheRoot = Utils::GetDefaultAppRootPath(); nonHostCacheRoot) { @@ -690,25 +814,25 @@ namespace AZ::SettingsRegistryMergeUtils } else { - registry.Set(FilePathKey_CacheProjectRootFolder, path.LexicallyNormal().Native()); - registry.Set(FilePathKey_CacheRootFolder, path.LexicallyNormal().Native()); + registry.Set(FilePathKey_CacheProjectRootFolder, projectPath.Native()); + registry.Set(FilePathKey_CacheRootFolder, projectPath.Native()); } if (AZStd::optional devWriteStorage = Utils::GetDevWriteStoragePath(); devWriteStorage) { - const AZ::IO::FixedMaxPath devWriteStoragePath(*devWriteStorage); - registry.Set(FilePathKey_DevWriteStorage, devWriteStoragePath.LexicallyNormal().Native()); - registry.Set(FilePathKey_ProjectUserPath, (devWriteStoragePath / "user").LexicallyNormal().Native()); - registry.Set(FilePathKey_ProjectLogPath, (devWriteStoragePath / "user/log").LexicallyNormal().Native()); + const auto devWriteStoragePath = AZ::IO::PathView(*devWriteStorage).LexicallyNormal(); + registry.Set(FilePathKey_DevWriteStorage, devWriteStoragePath.Native()); + registry.Set(FilePathKey_ProjectUserPath, (devWriteStoragePath / "user").Native()); + registry.Set(FilePathKey_ProjectLogPath, (devWriteStoragePath / "user" / "log").Native()); } else { - registry.Set(FilePathKey_DevWriteStorage, path.LexicallyNormal().Native()); - registry.Set(FilePathKey_ProjectUserPath, (path / "user").LexicallyNormal().Native()); - registry.Set(FilePathKey_ProjectLogPath, (path / "user/log").LexicallyNormal().Native()); - } -#endif // AZ_TRAIT_OS_IS_HOST_OS_PLATFORM + registry.Set(FilePathKey_DevWriteStorage, projectPath.Native()); + registry.Set(FilePathKey_ProjectUserPath, (projectPath / "user").Native()); + registry.Set(FilePathKey_ProjectLogPath, (projectPath / "user" / "log").Native()); } +#endif // AZ_TRAIT_OS_IS_HOST_OS_PLATFORM +} void MergeSettingsToRegistry_TargetBuildDependencyRegistry(SettingsRegistryInterface& registry, const AZStd::string_view platform, const SettingsRegistryInterface::Specializations& specializations, AZStd::vector* scratchBuffer) diff --git a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.h b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.h index daa64c0343..56eec91813 100644 --- a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.h +++ b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.h @@ -87,9 +87,9 @@ namespace AZ::SettingsRegistryMergeUtils AZ::IO::FixedMaxPath FindEngineRoot(SettingsRegistryInterface& settingsRegistry); //! The algorithm that is used to find the project root is as follows - //! 1. The first time this function is it performs a upward scan for a project.json file from - //! the executable directory and if found stores that path to an internal key. - //! In the same step it injects the path into the front of list of command line parameters + //! 1. The first time this function runs it performs an upward scan for a "project.json" file from + //! the executable directory and stores that path into an internal key. + //! In the same step it injects the path into the back of the command line parameters //! using the --regset="{BootstrapSettingsRootKey}/project_path=" value //! 2. Next the "{BootstrapSettingsRootKey}/project_path" is checked to see if it has a project path set //! diff --git a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryScriptUtils.cpp b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryScriptUtils.cpp index c32591874b..5cd36785dc 100644 --- a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryScriptUtils.cpp +++ b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryScriptUtils.cpp @@ -15,20 +15,20 @@ namespace AZ::SettingsRegistryScriptUtils::Internal { - static void RegisterScriptProxyForNotify(SettingsRegistryScriptProxy& settingsRegistryProxy) + static void RegisterScriptProxyForNotify(SettingsRegistryInterface* settingsRegistry, + SettingsRegistryScriptProxy::NotifyEventProxy* notifyEventProxy) { - if (settingsRegistryProxy.IsValid()) + if (settingsRegistry != nullptr) { - auto ForwardSettingsUpdateToProxyEvent = [&settingsRegistryProxy](AZStd::string_view path, AZ::SettingsRegistryInterface::Type) + auto ForwardSettingsUpdateToProxyEvent = [notifyEventProxy](AZStd::string_view path, AZ::SettingsRegistryInterface::Type) { - if (settingsRegistryProxy.m_notifyEventProxy) + if (notifyEventProxy) { - settingsRegistryProxy.m_notifyEventProxy->m_scriptNotifyEvent.Signal(path); + notifyEventProxy->m_scriptNotifyEvent.Signal(path); } }; // Register the forwarding function with the BehaviorContext - settingsRegistryProxy.m_notifyEventProxy->m_settingsUpdatedHandler = - settingsRegistryProxy.m_settingsRegistry->RegisterNotifier(ForwardSettingsUpdateToProxyEvent); + notifyEventProxy->m_settingsUpdatedHandler = settingsRegistry->RegisterNotifier(ForwardSettingsUpdateToProxyEvent); } } @@ -37,7 +37,7 @@ namespace AZ::SettingsRegistryScriptUtils::Internal : m_settingsRegistry(AZStd::move(settingsRegistry)) , m_notifyEventProxy(AZStd::make_shared()) { - RegisterScriptProxyForNotify(*this); + RegisterScriptProxyForNotify(m_settingsRegistry.get(), m_notifyEventProxy.get()); } // Raw AZ::SettingsRegistryInterface pointer is not owned by the proxy, so it's deleter is a no-op @@ -45,7 +45,7 @@ namespace AZ::SettingsRegistryScriptUtils::Internal : m_settingsRegistry(settingsRegistry, [](AZ::SettingsRegistryInterface*) {}) , m_notifyEventProxy(AZStd::make_shared()) { - RegisterScriptProxyForNotify(*this); + RegisterScriptProxyForNotify(m_settingsRegistry.get(), m_notifyEventProxy.get()); } // SettingsRegistryScriptProxy function that determines if the SettingsRegistry object is valid diff --git a/Code/Framework/AzCore/AzCore/Utils/Utils.cpp b/Code/Framework/AzCore/AzCore/Utils/Utils.cpp index e6bfd78806..12c6473905 100644 --- a/Code/Framework/AzCore/AzCore/Utils/Utils.cpp +++ b/Code/Framework/AzCore/AzCore/Utils/Utils.cpp @@ -59,7 +59,7 @@ namespace AZ::Utils { // Fix the size value of the fixed string by calculating the c-string length using char traits absolutePath.resize_no_construct(AZStd::char_traits::length(absolutePath.data())); - return srcPath; + return absolutePath; } return AZStd::nullopt; diff --git a/Code/Framework/AzCore/AzCore/azcore_files.cmake b/Code/Framework/AzCore/AzCore/azcore_files.cmake index 41229429f2..0c5a360844 100644 --- a/Code/Framework/AzCore/AzCore/azcore_files.cmake +++ b/Code/Framework/AzCore/AzCore/azcore_files.cmake @@ -41,6 +41,8 @@ set(FILES Component/ComponentApplication.cpp Component/ComponentApplication.h Component/ComponentApplicationBus.h + Component/ComponentApplicationLifecycle.cpp + Component/ComponentApplicationLifecycle.h Component/ComponentBus.cpp Component/ComponentBus.h Component/ComponentExport.h diff --git a/Code/Framework/AzFramework/AzFramework/Application/Application.cpp b/Code/Framework/AzFramework/AzFramework/Application/Application.cpp index f8d0ea8bb1..323e834413 100644 --- a/Code/Framework/AzFramework/AzFramework/Application/Application.cpp +++ b/Code/Framework/AzFramework/AzFramework/Application/Application.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -120,6 +121,11 @@ namespace AzFramework m_archiveFileIO = AZStd::make_unique(m_archive.get()); AZ::IO::FileIOBase::SetInstance(m_archiveFileIO.get()); SetFileIOAliases(); + // The FileIOAvailable event needs to be registered here as this event is sent out + // before the settings registry has merged the .setreg files from the + // (That happens in MergeSettingsToRegistry + AZ::ComponentApplicationLifecycle::RegisterEvent(*m_settingsRegistry, "FileIOAvailable"); + AZ::ComponentApplicationLifecycle::SignalEvent(*m_settingsRegistry, "FileIOAvailable", R"({})"); } if (auto nativeUI = AZ::Interface::Get(); nativeUI == nullptr) @@ -172,6 +178,8 @@ namespace AzFramework // Archive classes relies on the FileIOBase DirectInstance to close // files properly m_directFileIO.reset(); + + AZ::ComponentApplicationLifecycle::SignalEvent(*m_settingsRegistry, "FileIOUnavailable", R"({})"); } void Application::Start(const Descriptor& descriptor, const StartupParameters& startupParameters) @@ -196,7 +204,24 @@ namespace AzFramework systemEntity->Activate(); AZ_Assert(systemEntity->GetState() == AZ::Entity::State::Active, "System Entity failed to activate."); - m_isStarted = (systemEntity->GetState() == AZ::Entity::State::Active); + if (m_isStarted = (systemEntity->GetState() == AZ::Entity::State::Active); m_isStarted) + { + if (m_startupParameters.m_loadAssetCatalog) + { + // Start Monitoring Asset changes over the network and load the AssetCatalog + auto StartMonitoringAssetsAndLoadCatalog = [this](AZ::Data::AssetCatalogRequests* assetCatalogRequests) + { + if (AZ::IO::FixedMaxPath assetCatalogPath; + m_settingsRegistry->Get(assetCatalogPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_CacheRootFolder)) + { + assetCatalogPath /= "assetcatalog.xml"; + assetCatalogRequests->LoadCatalog(assetCatalogPath.c_str()); + } + }; + using AssetCatalogBus = AZ::Data::AssetCatalogRequestBus; + AssetCatalogBus::Broadcast(AZStd::move(StartMonitoringAssetsAndLoadCatalog)); + } + } } void Application::PreModuleLoad() @@ -210,6 +235,17 @@ namespace AzFramework { if (m_isStarted) { + if (m_startupParameters.m_loadAssetCatalog) + { + // Stop Monitoring Assets changes + auto StopMonitoringAssets = [](AZ::Data::AssetCatalogRequests* assetCatalogRequests) + { + assetCatalogRequests->StopMonitoringAssets(); + }; + using AssetCatalogBus = AZ::Data::AssetCatalogRequestBus; + AssetCatalogBus::Broadcast(AZStd::move(StopMonitoringAssets)); + } + ApplicationLifecycleEvents::Bus::Broadcast(&ApplicationLifecycleEvents::OnApplicationAboutToStop); m_pimpl.reset(); diff --git a/Code/Framework/AzFramework/AzFramework/Archive/Archive.cpp b/Code/Framework/AzFramework/AzFramework/Archive/Archive.cpp index cd30b753b5..c1c5775958 100644 --- a/Code/Framework/AzFramework/AzFramework/Archive/Archive.cpp +++ b/Code/Framework/AzFramework/AzFramework/Archive/Archive.cpp @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -363,6 +364,23 @@ namespace AZ::IO , m_mainThreadId{ AZStd::this_thread::get_id() } { CompressionBus::Handler::BusConnect(); + + // If the settings registry is not available at this point, + // then something catastrophic has happened in the application startup. + // That should have been caught and messaged out earlier in startup. + if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr) + { + // Automatically register the event if it's not registered, because + // this system is initialized before the settings registry has loaded the event list. + AZ::ComponentApplicationLifecycle::RegisterHandler( + *settingsRegistry, m_componentApplicationLifecycleHandler, + [this](AZStd::string_view /*path*/, AZ::SettingsRegistryInterface::Type /*type*/) + { + OnSystemEntityActivated(); + }, + "SystemComponentsActivated", + /*autoRegisterEvent*/ true); + } } ////////////////////////////////////////////////////////////////////////// @@ -1175,13 +1193,20 @@ namespace AZ::IO } } - auto bundleManifest = GetBundleManifest(desc.pZip); AZStd::shared_ptr bundleCatalog; + auto bundleManifest = GetBundleManifest(desc.pZip); if (bundleManifest) { bundleCatalog = GetBundleCatalog(desc.pZip, bundleManifest->GetCatalogName()); } + // If this archive is loaded before the serialize context is available, then the manifest and catalog will need to be loaded later. + if (!bundleManifest || !bundleCatalog) + { + m_archivesWithCatalogsToLoad.push_back( + ArchivesWithCatalogsToLoad(szFullPath, szBindRoot, flags, nextBundle, desc.m_strFileName)); + } + bool usePrefabSystemForLevels = false; AzFramework::ApplicationRequests::Bus::BroadcastResult( usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemForLevelsEnabled); @@ -1219,12 +1244,17 @@ namespace AZ::IO m_levelOpenEvent.Signal(levelDirs); } - AZ::IO::ArchiveNotificationBus::Broadcast([](AZ::IO::ArchiveNotifications* archiveNotifications, const char* bundleName, - AZStd::shared_ptr bundleManifest, const AZ::IO::FixedMaxPath& nextBundle, AZStd::shared_ptr bundleCatalog) + if (bundleManifest && bundleCatalog) { - archiveNotifications->BundleOpened(bundleName, bundleManifest, nextBundle.c_str(), bundleCatalog); - }, desc.m_strFileName.c_str(), bundleManifest, nextBundle, bundleCatalog); - + AZ::IO::ArchiveNotificationBus::Broadcast( + [](AZ::IO::ArchiveNotifications* archiveNotifications, const char* bundleName, + AZStd::shared_ptr bundleManifest, const AZ::IO::FixedMaxPath& nextBundle, + AZStd::shared_ptr bundleCatalog) + { + archiveNotifications->BundleOpened(bundleName, bundleManifest, nextBundle.c_str(), bundleCatalog); + }, + desc.m_strFileName.c_str(), bundleManifest, nextBundle, bundleCatalog); + } return true; } @@ -2138,7 +2168,7 @@ namespace AZ::IO } currentDirPattern = currentDir + AZ_FILESYSTEM_SEPARATOR_WILDCARD; - currentFilePattern = currentDir + AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING + "levels.pak"; + currentFilePattern = currentDir + AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING + "level.pak"; ZipDir::FileEntry* fileEntry = findFile.FindExact(currentFilePattern.c_str()); if (fileEntry) @@ -2175,4 +2205,36 @@ namespace AZ::IO return catalogInfo; } + + void Archive::OnSystemEntityActivated() + { + for (const auto& archiveInfo : m_archivesWithCatalogsToLoad) + { + AZStd::intrusive_ptr archive = + OpenArchive(archiveInfo.m_fullPath, archiveInfo.m_bindRoot, archiveInfo.m_flags, nullptr); + if (!archive) + { + continue; + } + + ZipDir::CachePtr pZip = static_cast(archive.get())->GetCache(); + + AZStd::shared_ptr bundleCatalog; + auto bundleManifest = GetBundleManifest(pZip); + if (bundleManifest) + { + bundleCatalog = GetBundleCatalog(pZip, bundleManifest->GetCatalogName()); + } + + AZ::IO::ArchiveNotificationBus::Broadcast( + [](AZ::IO::ArchiveNotifications* archiveNotifications, const char* bundleName, + AZStd::shared_ptr bundleManifest, const AZ::IO::FixedMaxPath& nextBundle, + AZStd::shared_ptr bundleCatalog) + { + archiveNotifications->BundleOpened(bundleName, bundleManifest, nextBundle.c_str(), bundleCatalog); + }, + archiveInfo.m_strFileName.c_str(), bundleManifest, archiveInfo.m_nextBundle, bundleCatalog); + } + m_archivesWithCatalogsToLoad.clear(); + } } diff --git a/Code/Framework/AzFramework/AzFramework/Archive/Archive.h b/Code/Framework/AzFramework/AzFramework/Archive/Archive.h index f08d90a66e..279702b433 100644 --- a/Code/Framework/AzFramework/AzFramework/Archive/Archive.h +++ b/Code/Framework/AzFramework/AzFramework/Archive/Archive.h @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -271,6 +272,11 @@ namespace AZ::IO ZipDir::CachePtr* pZip = {}) const; private: + // Archives can't be fully mounted until the system entity has been activated, + // because mounting them requires the BundlingSystemComponent and the serialization system + // to both be available. + void OnSystemEntityActivated(); + bool OpenPackCommon(AZStd::string_view szBindRoot, AZStd::string_view pName, AZStd::intrusive_ptr pData = nullptr, bool addLevels = true); bool OpenPacksCommon(AZStd::string_view szDir, AZStd::string_view pWildcardIn, AZStd::vector* pFullPaths = nullptr, bool addLevels = true); @@ -313,6 +319,8 @@ namespace AZ::IO mutable AZStd::shared_mutex m_csZips; ZipArray m_arrZips; + AZ::SettingsRegistryInterface::NotifyEventHandler m_componentApplicationLifecycleHandler; + ////////////////////////////////////////////////////////////////////////// // Opened files collector. ////////////////////////////////////////////////////////////////////////// @@ -339,5 +347,34 @@ namespace AZ::IO // [LYN-2376] Remove once legacy slice support is removed LevelPackOpenEvent m_levelOpenEvent; LevelPackCloseEvent m_levelCloseEvent; + + // If pak files are loaded before the serialization and bundling system + // are ready to go, their asset catalogs can't be loaded. + // In this case, cache information about those archives, + // and attempt to load the catalogs later, when the required systems are enabled. + struct ArchivesWithCatalogsToLoad + { + ArchivesWithCatalogsToLoad( + AZStd::string_view fullPath, + AZStd::string_view bindRoot, + int flags, + AZ::IO::PathView nextBundle, + AZ::IO::Path strFileName) + : m_fullPath(fullPath) + , m_bindRoot(bindRoot) + , m_flags(flags) + , m_nextBundle(nextBundle) + , m_strFileName(strFileName) + { + } + + AZ::IO::Path m_strFileName; + AZStd::string m_fullPath; + AZStd::string m_bindRoot; + AZ::IO::PathView m_nextBundle; + int m_flags; + }; + + AZStd::vector m_archivesWithCatalogsToLoad; }; } diff --git a/Code/Framework/AzFramework/AzFramework/Asset/AssetCatalog.cpp b/Code/Framework/AzFramework/AzFramework/Asset/AssetCatalog.cpp index e6b8211c28..3d76bcefc4 100644 --- a/Code/Framework/AzFramework/AzFramework/Asset/AssetCatalog.cpp +++ b/Code/Framework/AzFramework/AzFramework/Asset/AssetCatalog.cpp @@ -565,7 +565,7 @@ namespace AzFramework if (!bytes.empty()) { - AZStd::shared_ptr < AzFramework::AssetRegistry> prevRegistry; + AZStd::shared_ptr prevRegistry; if (!m_initialized) { // First time initialization may have updates already processed which we want to apply @@ -589,7 +589,6 @@ namespace AzFramework AZ_TracePrintf("AssetCatalog", "Loaded registry containing %u assets.\n", m_registry->m_assetIdToInfo.size()); // It's currently possible in tools for us to have received updates from AP which were applied before the catalog was ready to load - // due to CryPak and CrySystem coming online later than our components if (!m_initialized) { ApplyDeltaCatalog(prevRegistry); @@ -611,12 +610,13 @@ namespace AzFramework // the mutex. If the listener tries to perform a blocking asset load via GetAsset() / BlockUntilLoadComplete(), the spawned asset // thread will make a call to the AssetCatalogRequestBus and block on the held mutex. This would cause a deadlock, since the listener // won't free the mutex until the load is complete. - // So instead, queue the notification until the next tick, so that it doesn't occur within the AssetCatalogRequestBus mutex, and also + // So instead, queue the notification until after the AssetCatalogRequestBus mutex is unlocked for the current thread, and also // so that the entire AssetCatalog initialization is complete. - AZ::TickBus::QueueFunction([catalogRegistryString = AZStd::string(catalogRegistryFile)]() - { - AssetCatalogEventBus::Broadcast(&AssetCatalogEventBus::Events::OnCatalogLoaded, catalogRegistryString.c_str()); - }); + auto OnCatalogLoaded = [catalogRegistryString = AZStd::string(catalogRegistryFile)]() + { + AssetCatalogEventBus::Broadcast(&AssetCatalogEventBus::Events::OnCatalogLoaded, catalogRegistryString.c_str()); + }; + AZ::Data::AssetCatalogRequestBus::QueueFunction(AZStd::move(OnCatalogLoaded)); } } @@ -978,6 +978,7 @@ namespace AzFramework AZStd::lock_guard lock(m_registryMutex); m_registry->Clear(); + m_initialized = false; } diff --git a/Code/Framework/AzFramework/AzFramework/Asset/AssetRegistry.cpp b/Code/Framework/AzFramework/AzFramework/Asset/AssetRegistry.cpp index f9eefa7639..26a22f4625 100644 --- a/Code/Framework/AzFramework/AzFramework/Asset/AssetRegistry.cpp +++ b/Code/Framework/AzFramework/AzFramework/Asset/AssetRegistry.cpp @@ -61,6 +61,7 @@ namespace AzFramework //========================================================================= void AssetRegistry::Clear() { + m_assetDependencies = {}; m_assetIdToInfo = AssetIdToInfoMap(); m_assetPathToId = AssetPathToIdMap(); } diff --git a/Code/Framework/AzFramework/AzFramework/FileTag/FileTag.cpp b/Code/Framework/AzFramework/AzFramework/FileTag/FileTag.cpp index f820ee56ed..abbb9ec2f1 100644 --- a/Code/Framework/AzFramework/AzFramework/FileTag/FileTag.cpp +++ b/Code/Framework/AzFramework/AzFramework/FileTag/FileTag.cpp @@ -10,11 +10,11 @@ #include #include #include +#include #include #include #include #include -#include #include #include #include @@ -89,19 +89,19 @@ namespace AzFramework bool FileTagManager::Save(FileTagType fileTagType, const AZStd::string& destinationFilePath = AZStd::string()) { AzFramework::FileTag::FileTagAsset* fileTagAsset = GetFileTagAsset(fileTagType); - AZStd::string filePathToSave = destinationFilePath; + AZ::IO::Path filePathToSave = destinationFilePath; if (filePathToSave.empty()) { filePathToSave = FileTagQueryManager::GetDefaultFileTagFilePath(fileTagType); } - if (!AzFramework::StringFunc::EndsWith(filePathToSave, AzFramework::FileTag::FileTagAsset::Extension())) + if (!filePathToSave.Extension().Native().ends_with(AzFramework::FileTag::FileTagAsset::Extension())) { AZ_Error("FileTag", false, "Unable to save tag file (%s). Invalid file extension, file tag can only have (%s) extension.\n", filePathToSave.c_str(), AzFramework::FileTag::FileTagAsset::Extension()); return false; } - return AZ::Utils::SaveObjectToFile(filePathToSave, AZ::DataStream::StreamType::ST_XML, fileTagAsset); + return AZ::Utils::SaveObjectToFile(filePathToSave.Native(), AZ::DataStream::StreamType::ST_XML, fileTagAsset); } AZ::Outcome FileTagManager::AddTagsInternal(AZStd::string filePath, FileTagType fileTagType, AZStd::vector fileTags, AzFramework::FileTag::FilePatternType filePatternType) @@ -239,17 +239,22 @@ namespace AzFramework QueryFileTagsEventBus::Handler::BusDisconnect(); } - AZStd::string FileTagQueryManager::GetDefaultFileTagFilePath(FileTagType fileTagType) + AZ::IO::Path FileTagQueryManager::GetDefaultFileTagFilePath(FileTagType fileTagType) { - auto destinationFilePath = AZ::IO::FixedMaxPath(AZ::Utils::GetEnginePath()) / EngineAssetSourceRelPath; + AZ::IO::Path destinationFilePath; + if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr) + { + settingsRegistry->Get(destinationFilePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder); + } + destinationFilePath /= EngineAssetSourceRelPath; destinationFilePath /= fileTagType == FileTagType::Exclude ? ExcludeFileName : IncludeFileName; destinationFilePath.ReplaceExtension(AzFramework::FileTag::FileTagAsset::Extension()); - return destinationFilePath.String(); + return destinationFilePath; } bool FileTagQueryManager::Load(const AZStd::string& filePath) { - AZStd::string fileToLoad = filePath; + AZ::IO::Path fileToLoad = filePath; if (fileToLoad.empty()) { fileToLoad = GetDefaultFileTagFilePath(m_fileTagType); diff --git a/Code/Framework/AzFramework/AzFramework/FileTag/FileTag.h b/Code/Framework/AzFramework/AzFramework/FileTag/FileTag.h index be3d6e6d08..d2dee3f629 100644 --- a/Code/Framework/AzFramework/AzFramework/FileTag/FileTag.h +++ b/Code/Framework/AzFramework/AzFramework/FileTag/FileTag.h @@ -11,6 +11,7 @@ #include #include #include +#include #include namespace AzFramework @@ -88,7 +89,7 @@ namespace AzFramework ///////////////////////////////////////////////////////////////////////// - static AZStd::string GetDefaultFileTagFilePath(FileTagType fileTagType); + static AZ::IO::Path GetDefaultFileTagFilePath(FileTagType fileTagType); protected: diff --git a/Code/Framework/AzFramework/AzFramework/FileTag/FileTagComponent.cpp b/Code/Framework/AzFramework/AzFramework/FileTag/FileTagComponent.cpp index 1e03a6df0d..1d09500415 100644 --- a/Code/Framework/AzFramework/AzFramework/FileTag/FileTagComponent.cpp +++ b/Code/Framework/AzFramework/AzFramework/FileTag/FileTagComponent.cpp @@ -16,6 +16,7 @@ #include #include +#include #include namespace AzFramework @@ -66,7 +67,8 @@ namespace AzFramework m_excludeFileQueryManager.reset(aznew FileTagQueryManager(FileTagType::Exclude)); if (!m_excludeFileQueryManager.get()->Load()) { - AZ_Error("FileTagQueryComponent", false, "Not able to load default exclude file (%s). Please make sure that it exists on disk.\n", FileTagQueryManager::GetDefaultFileTagFilePath(FileTagType::Exclude).c_str()); + AZ_Error("FileTagQueryComponent", false, "Not able to load default exclude file (%s). Please make sure that it exists on disk.\n", + FileTagQueryManager::GetDefaultFileTagFilePath(FileTagType::Exclude).c_str()); } AzFramework::AssetCatalogEventBus::Handler::BusConnect(); diff --git a/Code/Framework/AzFramework/AzFramework/IO/LocalFileIO.cpp b/Code/Framework/AzFramework/AzFramework/IO/LocalFileIO.cpp index c1b9c941bc..19bffaccbd 100644 --- a/Code/Framework/AzFramework/AzFramework/IO/LocalFileIO.cpp +++ b/Code/Framework/AzFramework/AzFramework/IO/LocalFileIO.cpp @@ -635,6 +635,7 @@ namespace AZ size_t longestMatch = 0; size_t bufStringLength = inBuffer.size(); AZStd::string_view longestAlias; + AZStd::string_view longestResolvedAlias; for (const auto& [alias, resolvedAlias] : m_aliases) { @@ -653,6 +654,7 @@ namespace AZ { longestMatch = resolvedAlias.size(); longestAlias = alias; + longestResolvedAlias = resolvedAlias; } } } @@ -661,7 +663,10 @@ namespace AZ // rearrange the buffer to have // [alias][old path] size_t aliasSize = longestAlias.size(); - size_t charsToAbsorb = longestMatch; + // If the resolved alias ends in a path separator, do not consume it. + const bool resolvedAliasEndsInPathSeparator = (longestResolvedAlias.ends_with(AZ::IO::PosixPathSeparator) || + longestResolvedAlias.ends_with(AZ::IO::WindowsPathSeparator)); + const size_t charsToAbsorb = resolvedAliasEndsInPathSeparator ? longestMatch - 1 : longestMatch; size_t remainingData = bufStringLength - charsToAbsorb; size_t finalStringSize = aliasSize + remainingData; if (finalStringSize >= outBufferLength) diff --git a/Code/Framework/AzFramework/AzFramework/Viewport/ViewportBus.h b/Code/Framework/AzFramework/AzFramework/Viewport/ViewportBus.h index 444173f773..131ecc0c57 100644 --- a/Code/Framework/AzFramework/AzFramework/Viewport/ViewportBus.h +++ b/Code/Framework/AzFramework/AzFramework/Viewport/ViewportBus.h @@ -8,8 +8,9 @@ #pragma once -#include #include +#include +#include namespace AZ { @@ -20,18 +21,15 @@ namespace AZ namespace AzFramework { - class ViewportRequests - : public AZ::EBusTraits + class ViewportRequests : public AZ::EBusTraits { public: static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; - static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById; + static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById; using BusIdType = ViewportId; static void Reflect(AZ::ReflectContext* context); - virtual ~ViewportRequests() {} - //! Gets the current camera's world to view matrix. virtual const AZ::Matrix4x4& GetCameraViewMatrix() const = 0; //! Sets the current camera's world to view matrix. @@ -44,8 +42,36 @@ namespace AzFramework virtual AZ::Transform GetCameraTransform() const = 0; //! Convenience method, sets the camera's world to view matrix from this AZ::Transform. virtual void SetCameraTransform(const AZ::Transform& transform) = 0; + + protected: + ~ViewportRequests() = default; }; using ViewportRequestBus = AZ::EBus; -} //namespace AzFramework + //! The additional padding around the viewport when a viewport border is active. + struct ViewportBorderPadding + { + float m_top; + float m_bottom; + float m_left; + float m_right; + }; + + //! For performing queries about the state of the viewport border. + class ViewportBorderRequests : public AZ::EBusTraits + { + public: + static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; + static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById; + using BusIdType = ViewportId; + + //! Returns if a viewport border is in effect and what the current dimensions (padding) of the border are. + virtual AZStd::optional GetViewportBorderPadding() const = 0; + + protected: + ~ViewportBorderRequests() = default; + }; + + using ViewportBorderRequestBus = AZ::EBus; +} // namespace AzFramework diff --git a/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbApplication.cpp b/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbApplication.cpp index f57f4a89ac..780e1e72fe 100644 --- a/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbApplication.cpp +++ b/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbApplication.cpp @@ -10,6 +10,8 @@ #include #include +#include + namespace AzFramework { //////////////////////////////////////////////////////////////////////////////////////////////// @@ -34,6 +36,31 @@ namespace AzFramework return m_xcbConnection.get(); } + void SetEnableXInput(xcb_connection_t* connection, bool enable) override + { + struct Mask + { + xcb_input_event_mask_t head; + xcb_input_xi_event_mask_t mask; + }; + const Mask mask { + /*.head=*/{ + /*.device_id=*/XCB_INPUT_DEVICE_ALL_MASTER, + /*.mask_len=*/1 + }, + /*.mask=*/ enable ? + (xcb_input_xi_event_mask_t)(XCB_INPUT_XI_EVENT_MASK_RAW_MOTION | XCB_INPUT_XI_EVENT_MASK_RAW_BUTTON_PRESS | XCB_INPUT_XI_EVENT_MASK_RAW_BUTTON_RELEASE) : + (xcb_input_xi_event_mask_t)XCB_NONE + }; + + const xcb_setup_t* xcbSetup = xcb_get_setup(connection); + const xcb_screen_t* xcbScreen = xcb_setup_roots_iterator(xcbSetup).data; + + xcb_input_xi_select_events(connection, xcbScreen->root, 1, &mask.head); + + xcb_flush(connection); + } + private: XcbUniquePtr m_xcbConnection = nullptr; }; diff --git a/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbConnectionManager.h b/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbConnectionManager.h index daa5bf35af..ca7ce06e6c 100644 --- a/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbConnectionManager.h +++ b/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbConnectionManager.h @@ -24,6 +24,9 @@ namespace AzFramework virtual ~XcbConnectionManager() = default; virtual xcb_connection_t* GetXcbConnection() const = 0; + + //! Enables/Disables XInput Raw Input events. + virtual void SetEnableXInput(xcb_connection_t* connection, bool enable) = 0; }; class XcbConnectionManagerBusTraits diff --git a/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbEventHandler.h b/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbEventHandler.h index 251342093a..f32e45ed99 100644 --- a/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbEventHandler.h +++ b/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbEventHandler.h @@ -23,9 +23,6 @@ namespace AzFramework virtual ~XcbEventHandler() = default; virtual void HandleXcbEvent(xcb_generic_event_t* event) = 0; - - // ATTN This is used as a workaround for RAW Input events when using the Editor. - virtual void PollSpecialEvents(){}; }; class XcbEventHandlerBusTraits : public AZ::EBusTraits diff --git a/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbInputDeviceMouse.cpp b/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbInputDeviceMouse.cpp index 56f21e6533..c3b7a97ccf 100644 --- a/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbInputDeviceMouse.cpp +++ b/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbInputDeviceMouse.cpp @@ -13,21 +13,68 @@ namespace AzFramework { - xcb_window_t GetSystemCursorFocusWindow() + xcb_window_t GetSystemCursorFocusWindow(xcb_connection_t* connection) { void* systemCursorFocusWindow = nullptr; AzFramework::InputSystemCursorConstraintRequestBus::BroadcastResult( systemCursorFocusWindow, &AzFramework::InputSystemCursorConstraintRequests::GetSystemCursorConstraintWindow); - if (!systemCursorFocusWindow) + if (systemCursorFocusWindow) { - return XCB_NONE; + return static_cast(reinterpret_cast(systemCursorFocusWindow)); } - // TODO Clang compile error because cast .... loses information. On GNU/Linux HWND is void* and on 64-bit - // machines its obviously 64 bit but we receive the window id from m_renderOverlay.winId() which is xcb_window_t 32-bit. + // EWMH-compliant window managers set the "_NET_ACTIVE_WINDOW" property + // of the X server's root window to the currently active window. This + // retrieves value of that property. - return static_cast(reinterpret_cast(systemCursorFocusWindow)); + // Get the atom for the _NET_ACTIVE_WINDOW property + constexpr int propertyNameLength = 18; + xcb_generic_error_t* error = nullptr; + XcbStdFreePtr activeWindowAtom {xcb_intern_atom_reply( + connection, + xcb_intern_atom(connection, /*only_if_exists=*/ 1, propertyNameLength, "_NET_ACTIVE_WINDOW"), + &error + )}; + if (!activeWindowAtom || error) + { + if (error) + { + AZ_Warning("XcbInput", false, "Retrieving _NET_ACTIVE_WINDOW atom failed : Error code %d", error->error_code); + free(error); + } + return XCB_WINDOW_NONE; + } + + // Get the root window + const xcb_window_t rootWId = xcb_setup_roots_iterator(xcb_get_setup(connection)).data->root; + + // Fetch the value of the root window's _NET_ACTIVE_WINDOW property + XcbStdFreePtr property {xcb_get_property_reply( + connection, + xcb_get_property( + /*c=*/connection, + /*_delete=*/ 0, + /*window=*/rootWId, + /*property=*/activeWindowAtom->atom, + /*type=*/XCB_ATOM_WINDOW, + /*long_offset=*/0, + /*long_length=*/1 + ), + &error + )}; + + if (!property || error) + { + if (error) + { + AZ_Warning("XcbInput", false, "Retrieving _NET_ACTIVE_WINDOW atom failed : Error code %d", error->error_code); + free(error); + } + return XCB_WINDOW_NONE; + } + + return *static_cast(xcb_get_property_value(property.get())); } xcb_connection_t* XcbInputDeviceMouse::s_xcbConnection = nullptr; @@ -39,8 +86,7 @@ namespace AzFramework : InputDeviceMouse::Implementation(inputDevice) , m_systemCursorState(SystemCursorState::Unknown) , m_systemCursorPositionNormalized(0.5f, 0.5f) - , m_prevConstraintWindow(XCB_NONE) - , m_focusWindow(XCB_NONE) + , m_focusWindow(XCB_WINDOW_NONE) , m_cursorShown(true) { XcbEventHandlerBus::Handler::BusConnect(); @@ -57,14 +103,14 @@ namespace AzFramework InputDeviceMouse::Implementation* XcbInputDeviceMouse::Create(InputDeviceMouse& inputDevice) { - auto* interface = AzFramework::XcbConnectionManagerInterface::Get(); + const auto* interface = AzFramework::XcbConnectionManagerInterface::Get(); if (!interface) { AZ_Warning("XcbInput", false, "XCB interface not available"); return nullptr; } - s_xcbConnection = AzFramework::XcbConnectionManagerInterface::Get()->GetXcbConnection(); + s_xcbConnection = interface->GetXcbConnection(); if (!s_xcbConnection) { AZ_Warning("XcbInput", false, "XCB connection not available"); @@ -126,7 +172,7 @@ namespace AzFramework // Get window information. const XcbStdFreePtr xcbGeometryReply{ xcb_get_geometry_reply( - s_xcbConnection, xcb_get_geometry(s_xcbConnection, window), NULL) }; + s_xcbConnection, xcb_get_geometry(s_xcbConnection, window), nullptr) }; if (!xcbGeometryReply) { @@ -137,7 +183,7 @@ namespace AzFramework xcb_translate_coordinates(s_xcbConnection, window, s_xcbScreen->root, 0, 0); const XcbStdFreePtr xkbTranslateCoordReply{ xcb_translate_coordinates_reply( - s_xcbConnection, translate_coord, NULL) }; + s_xcbConnection, translate_coord, nullptr) }; if (!xkbTranslateCoordReply) { @@ -173,11 +219,11 @@ namespace AzFramework for (const auto& barrier : m_activeBarriers) { xcb_void_cookie_t cookie = xcb_xfixes_create_pointer_barrier_checked( - s_xcbConnection, barrier.id, window, barrier.x0, barrier.y0, barrier.x1, barrier.y1, barrier.direction, 0, NULL); - const XcbStdFreePtr xkbError{ xcb_request_check(s_xcbConnection, cookie) }; + s_xcbConnection, barrier.id, window, barrier.x0, barrier.y0, barrier.x1, barrier.y1, barrier.direction, 0, nullptr); + const XcbStdFreePtr xcbError{ xcb_request_check(s_xcbConnection, cookie) }; AZ_Warning( - "XcbInput", !xkbError, "XFixes, failed to create barrier %d at (%d %d %d %d)", barrier.id, barrier.x0, barrier.y0, + "XcbInput", !xcbError, "XFixes, failed to create barrier %d at (%d %d %d %d)", barrier.id, barrier.x0, barrier.y0, barrier.x1, barrier.y1); } } @@ -207,7 +253,7 @@ namespace AzFramework const xcb_xfixes_query_version_cookie_t query_cookie = xcb_xfixes_query_version(s_xcbConnection, 5, 0); - xcb_generic_error_t* error = NULL; + xcb_generic_error_t* error = nullptr; const XcbStdFreePtr xkbQueryRequestReply{ xcb_xfixes_query_version_reply( s_xcbConnection, query_cookie, &error) }; @@ -244,7 +290,7 @@ namespace AzFramework const xcb_input_xi_query_version_cookie_t query_version_cookie = xcb_input_xi_query_version(s_xcbConnection, 2, 2); - xcb_generic_error_t* error = NULL; + xcb_generic_error_t* error = nullptr; const XcbStdFreePtr xkbQueryRequestReply{ xcb_input_xi_query_version_reply( s_xcbConnection, query_version_cookie, &error) }; @@ -268,40 +314,13 @@ namespace AzFramework return m_xInputInitialized; } - void XcbInputDeviceMouse::SetEnableXInput(bool enable) - { - struct - { - xcb_input_event_mask_t head; - int mask; - } mask; - - mask.head.deviceid = XCB_INPUT_DEVICE_ALL; - mask.head.mask_len = 1; - - if (enable) - { - mask.mask = XCB_INPUT_XI_EVENT_MASK_RAW_MOTION | XCB_INPUT_XI_EVENT_MASK_RAW_BUTTON_PRESS | - XCB_INPUT_XI_EVENT_MASK_RAW_BUTTON_RELEASE | XCB_INPUT_XI_EVENT_MASK_MOTION | XCB_INPUT_XI_EVENT_MASK_BUTTON_PRESS | - XCB_INPUT_XI_EVENT_MASK_BUTTON_RELEASE; - } - else - { - mask.mask = XCB_NONE; - } - - xcb_input_xi_select_events(s_xcbConnection, s_xcbScreen->root, 1, &mask.head); - - xcb_flush(s_xcbConnection); - } - void XcbInputDeviceMouse::SetSystemCursorState(SystemCursorState systemCursorState) { if (systemCursorState != m_systemCursorState) { m_systemCursorState = systemCursorState; - m_focusWindow = GetSystemCursorFocusWindow(); + m_focusWindow = GetSystemCursorFocusWindow(s_xcbConnection); HandleCursorState(m_focusWindow, systemCursorState); } @@ -309,52 +328,10 @@ namespace AzFramework void XcbInputDeviceMouse::HandleCursorState(xcb_window_t window, SystemCursorState systemCursorState) { - bool confined = false, cursorShown = true; - switch (systemCursorState) - { - case SystemCursorState::ConstrainedAndHidden: - { - //!< Constrained to the application's main window and hidden - confined = true; - cursorShown = false; - } - break; - case SystemCursorState::ConstrainedAndVisible: - { - //!< Constrained to the application's main window and visible - confined = true; - } - break; - case SystemCursorState::UnconstrainedAndHidden: - { - //!< Free to move outside the main window but hidden while inside - cursorShown = false; - } - break; - case SystemCursorState::UnconstrainedAndVisible: - { - //!< Free to move outside the application's main window and visible - } - case SystemCursorState::Unknown: - default: - break; - } - - // ATTN GetSystemCursorFocusWindow when getting out of the play in editor will return XCB_NONE - // We need however the window id to reset the cursor. - if (XCB_NONE == window && (confined || cursorShown)) - { - // Reuse the previous window to reset states. - window = m_prevConstraintWindow; - m_prevConstraintWindow = XCB_NONE; - } - else - { - // Remember the window we used to modify cursor and barrier states. - m_prevConstraintWindow = window; - } - - SetEnableXInput(!cursorShown); + const bool confined = (systemCursorState == SystemCursorState::ConstrainedAndHidden) || + (systemCursorState == SystemCursorState::ConstrainedAndVisible); + const bool cursorShown = (systemCursorState == SystemCursorState::ConstrainedAndVisible) || + (systemCursorState == SystemCursorState::UnconstrainedAndVisible); CreateBarriers(window, confined); ShowCursor(window, cursorShown); @@ -368,26 +345,26 @@ namespace AzFramework void XcbInputDeviceMouse::SetSystemCursorPositionNormalizedInternal(xcb_window_t window, AZ::Vector2 positionNormalized) { // TODO Basically not done at all. Added only the basic functions needed. - const XcbStdFreePtr xkbGeometryReply{ xcb_get_geometry_reply( - s_xcbConnection, xcb_get_geometry(s_xcbConnection, window), NULL) }; + const XcbStdFreePtr xcbGeometryReply{ xcb_get_geometry_reply( + s_xcbConnection, xcb_get_geometry(s_xcbConnection, window), nullptr) }; - if (!xkbGeometryReply) + if (!xcbGeometryReply) { return; } - const int16_t x = static_cast(positionNormalized.GetX() * xkbGeometryReply->width); - const int16_t y = static_cast(positionNormalized.GetY() * xkbGeometryReply->height); + const int16_t x = static_cast(positionNormalized.GetX() * xcbGeometryReply->width); + const int16_t y = static_cast(positionNormalized.GetY() * xcbGeometryReply->height); - xcb_warp_pointer(s_xcbConnection, XCB_NONE, window, 0, 0, 0, 0, x, y); + xcb_warp_pointer(s_xcbConnection, XCB_WINDOW_NONE, window, 0, 0, 0, 0, x, y); xcb_flush(s_xcbConnection); } void XcbInputDeviceMouse::SetSystemCursorPositionNormalized(AZ::Vector2 positionNormalized) { - const xcb_window_t window = GetSystemCursorFocusWindow(); - if (XCB_NONE == window) + const xcb_window_t window = GetSystemCursorFocusWindow(s_xcbConnection); + if (XCB_WINDOW_NONE == window) { return; } @@ -401,7 +378,7 @@ namespace AzFramework const xcb_query_pointer_cookie_t pointer = xcb_query_pointer(s_xcbConnection, window); - const XcbStdFreePtr xkbQueryPointerReply{ xcb_query_pointer_reply(s_xcbConnection, pointer, NULL) }; + const XcbStdFreePtr xkbQueryPointerReply{ xcb_query_pointer_reply(s_xcbConnection, pointer, nullptr) }; if (!xkbQueryPointerReply) { @@ -409,7 +386,7 @@ namespace AzFramework } const XcbStdFreePtr xkbGeometryReply{ xcb_get_geometry_reply( - s_xcbConnection, xcb_get_geometry(s_xcbConnection, window), NULL) }; + s_xcbConnection, xcb_get_geometry(s_xcbConnection, window), nullptr) }; if (!xkbGeometryReply) { @@ -429,8 +406,8 @@ namespace AzFramework AZ::Vector2 XcbInputDeviceMouse::GetSystemCursorPositionNormalized() const { - const xcb_window_t window = GetSystemCursorFocusWindow(); - if (XCB_NONE == window) + const xcb_window_t window = GetSystemCursorFocusWindow(s_xcbConnection); + if (XCB_WINDOW_NONE == window) { return AZ::Vector2::CreateZero(); } @@ -455,11 +432,11 @@ namespace AzFramework cookie = xcb_xfixes_hide_cursor_checked(s_xcbConnection, window); } - const XcbStdFreePtr xkbError{ xcb_request_check(s_xcbConnection, cookie) }; + const XcbStdFreePtr xcbError{ xcb_request_check(s_xcbConnection, cookie) }; - if (xkbError) + if (xcbError) { - AZ_Warning("XcbInput", false, "ShowCursor failed: %d", xkbError->error_code); + AZ_Warning("XcbInput", false, "ShowCursor failed: %d", xcbError->error_code); return; } @@ -500,14 +477,6 @@ namespace AzFramework } } - void XcbInputDeviceMouse::HandlePointerMotionEvents(const xcb_generic_event_t* event) - { - const xcb_input_motion_event_t* mouseMotionEvent = reinterpret_cast(event); - - m_systemCursorPosition[0] = mouseMotionEvent->event_x; - m_systemCursorPosition[1] = mouseMotionEvent->event_y; - } - void XcbInputDeviceMouse::HandleRawInputEvents(const xcb_ge_generic_event_t* event) { const xcb_ge_generic_event_t* genericEvent = reinterpret_cast(event); @@ -552,78 +521,20 @@ namespace AzFramework } } - void XcbInputDeviceMouse::PollSpecialEvents() - { - while (xcb_generic_event_t* genericEvent = xcb_poll_for_queued_event(s_xcbConnection)) - { - // TODO Is the following correct? If we are showing the cursor, don't poll RAW Input events. - switch (genericEvent->response_type & ~0x80) - { - case XCB_GE_GENERIC: - { - const xcb_ge_generic_event_t* geGenericEvent = reinterpret_cast(genericEvent); - - // Only handle raw inputs if we have focus. - // Handle Raw Input events first. - if ((geGenericEvent->event_type == XCB_INPUT_RAW_BUTTON_PRESS) || - (geGenericEvent->event_type == XCB_INPUT_RAW_BUTTON_RELEASE) || - (geGenericEvent->event_type == XCB_INPUT_RAW_MOTION)) - { - HandleRawInputEvents(geGenericEvent); - - free(genericEvent); - } - } - break; - } - } - } - void XcbInputDeviceMouse::HandleXcbEvent(xcb_generic_event_t* event) { switch (event->response_type & ~0x80) { - // QT5 is using by default XInput which means we do need to check for XCB_GE_GENERIC event to parse all mouse related events. + // XInput raw events are sent from the server as a XCB_GE_GENERIC + // event. A XCB_GE_GENERIC event is typecast to a + // xcb_ge_generic_event_t, which is distinct from a + // xcb_generic_event_t, and exists so that X11 extensions can extend + // the event emission beyond the size that a normal X11 event could + // contain. case XCB_GE_GENERIC: { const xcb_ge_generic_event_t* genericEvent = reinterpret_cast(event); - - // Handling RAW Inputs here works in GameMode but not in Editor mode because QT is - // not handling RAW input events and passing to. - if (!m_cursorShown) - { - // Handle Raw Input events first. - if ((genericEvent->event_type == XCB_INPUT_RAW_BUTTON_PRESS) || - (genericEvent->event_type == XCB_INPUT_RAW_BUTTON_RELEASE) || (genericEvent->event_type == XCB_INPUT_RAW_MOTION)) - { - HandleRawInputEvents(genericEvent); - } - } - else - { - switch (genericEvent->event_type) - { - case XCB_INPUT_BUTTON_PRESS: - { - const xcb_input_button_press_event_t* mouseButtonEvent = - reinterpret_cast(genericEvent); - HandleButtonPressEvents(mouseButtonEvent->detail, true); - } - break; - case XCB_INPUT_BUTTON_RELEASE: - { - const xcb_input_button_release_event_t* mouseButtonEvent = - reinterpret_cast(genericEvent); - HandleButtonPressEvents(mouseButtonEvent->detail, false); - } - break; - case XCB_INPUT_MOTION: - { - HandlePointerMotionEvents(event); - } - break; - } - } + HandleRawInputEvents(genericEvent); } break; case XCB_FOCUS_IN: @@ -634,6 +545,9 @@ namespace AzFramework m_focusWindow = focusInEvent->event; HandleCursorState(m_focusWindow, m_systemCursorState); } + + auto* interface = AzFramework::XcbConnectionManagerInterface::Get(); + interface->SetEnableXInput(interface->GetXcbConnection(), true); } break; case XCB_FOCUS_OUT: @@ -644,7 +558,10 @@ namespace AzFramework ProcessRawEventQueues(); ResetInputChannelStates(); - m_focusWindow = XCB_NONE; + m_focusWindow = XCB_WINDOW_NONE; + + auto* interface = AzFramework::XcbConnectionManagerInterface::Get(); + interface->SetEnableXInput(interface->GetXcbConnection(), false); } break; } diff --git a/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbInputDeviceMouse.h b/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbInputDeviceMouse.h index 106d204ca9..a69a8a9ec5 100644 --- a/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbInputDeviceMouse.h +++ b/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbInputDeviceMouse.h @@ -65,9 +65,6 @@ namespace AzFramework //! \ref AzFramework::InputDeviceMouse::Implementation::TickInputDevice void TickInputDevice() override; - //! This method is called by the Editor to accommodate some events with the Editor. Never called in Game mode. - void PollSpecialEvents() override; - //! Handle X11 events. void HandleXcbEvent(xcb_generic_event_t* event) override; @@ -77,9 +74,6 @@ namespace AzFramework //! Initialize XInput extension. Used for raw input during confinement and showing/hiding the cursor. static bool InitializeXInput(); - //! Enables/Disables XInput Raw Input events. - void SetEnableXInput(bool enable); - //! Create barriers. void CreateBarriers(xcb_window_t window, bool create); @@ -98,9 +92,6 @@ namespace AzFramework //! Handle button press/release events. void HandleButtonPressEvents(uint32_t detail, bool pressed); - //! Handle motion notify events. - void HandlePointerMotionEvents(const xcb_generic_event_t* event); - //! Will set cursor states and confinement modes. void HandleCursorState(xcb_window_t window, SystemCursorState systemCursorState); @@ -160,7 +151,6 @@ namespace AzFramework AZ::Vector2 m_cursorHiddenPosition; AZ::Vector2 m_systemCursorPositionNormalized; - uint32_t m_systemCursorPosition[MAX_XI_RAW_AXIS]; static xcb_connection_t* s_xcbConnection; static xcb_screen_t* s_xcbScreen; @@ -171,9 +161,6 @@ namespace AzFramework //! Will be true if the xinput2 extension could be initialized. static bool m_xInputInitialized; - //! The window that had focus - xcb_window_t m_prevConstraintWindow; - //! The current window that has focus xcb_window_t m_focusWindow; diff --git a/Code/Framework/AzFramework/Platform/Linux/AzFramework/Asset/AssetSystemComponentHelper_Linux.cpp b/Code/Framework/AzFramework/Platform/Linux/AzFramework/Asset/AssetSystemComponentHelper_Linux.cpp index dcdc4a5925..6cb474f4ce 100644 --- a/Code/Framework/AzFramework/Platform/Linux/AzFramework/Asset/AssetSystemComponentHelper_Linux.cpp +++ b/Code/Framework/AzFramework/Platform/Linux/AzFramework/Asset/AssetSystemComponentHelper_Linux.cpp @@ -9,19 +9,71 @@ #include #include #include +#include #include #include -#include +#include #include #include +#include #include +AZ_CVAR(bool, ap_tether_lifetime, true, nullptr, AZ::ConsoleFunctorFlags::Null, + "If enabled, a parent process that launches the AP will terminate the AP on exit"); + namespace AzFramework::AssetSystem::Platform { void AllowAssetProcessorToForeground() {} + [[noreturn]] static void LaunchAssetProcessorDirectly(const AZ::IO::FixedMaxPath& assetProcessorPath, AZStd::string_view engineRoot, AZStd::string_view projectPath) + { + AZStd::fixed_vector args { + assetProcessorPath.c_str(), + "--start-hidden", + }; + + // Add the engine path to the launch command if not empty + AZ::IO::FixedMaxPathString engineRootArg; + if (!engineRoot.empty()) + { + // No need to quote these paths, this code calls exec directly and + // does not go through shell string interpolation + engineRootArg = AZ::IO::FixedMaxPathString{"--engine-path="} + AZ::IO::FixedMaxPathString{engineRoot}; + args.push_back(engineRootArg.data()); + } + + // Add the active project path to the launch command if not empty + AZ::IO::FixedMaxPathString projectPathArg; + if (!projectPath.empty()) + { + projectPathArg = AZ::IO::FixedMaxPathString{"--regset=/Amazon/AzCore/Bootstrap/project_path="} + AZ::IO::FixedMaxPathString{projectPath}; + args.push_back(projectPathArg.data()); + } + + // Make sure this is at the end + args.push_back(nullptr); // argv itself needs to be null-terminated + + execv(args[0], const_cast(args.data())); + + // exec* family of functions only return on error + fprintf(stderr, "Asset Processor failed with error: %s\n", strerror(errno)); + _exit(1); + } + + static pid_t LaunchAssetProcessorDaemonized(const AZ::IO::FixedMaxPath& assetProcessorPath, AZStd::string_view engineRoot, AZStd::string_view projectPath) + { + // detach the child from parent + setsid(); + const pid_t secondChildPid = fork(); + if (secondChildPid == 0) + { + LaunchAssetProcessorDirectly(assetProcessorPath, engineRoot, projectPath); + } + return secondChildPid; + } + bool LaunchAssetProcessor(AZStd::string_view executableDirectory, AZStd::string_view engineRoot, AZStd::string_view projectPath) { @@ -40,7 +92,8 @@ namespace AzFramework::AssetSystem::Platform } } - pid_t firstChildPid = fork(); + const pid_t parentPid = getpid(); + const pid_t firstChildPid = fork(); if (firstChildPid == 0) { // redirect output to dev/null so it doesn't hijack an existing console window @@ -53,51 +106,33 @@ namespace AzFramework::AssetSystem::Platform AZ::IO::FileDescriptorRedirector stderrRedirect(STDERR_FILENO); stderrRedirect.RedirectTo(devNull, mode); - // detach the child from parent - setsid(); - pid_t secondChildPid = fork(); - if (secondChildPid == 0) + if (ap_tether_lifetime) { - AZStd::array args { - assetProcessorPath.c_str(), assetProcessorPath.c_str(), "--start-hidden", - static_cast(nullptr), static_cast(nullptr), static_cast(nullptr) - }; - int optionalArgPos = 3; - - // Add the engine path to the launch command if not empty - AZ::IO::FixedMaxPathString engineRootArg; - if (!engineRoot.empty()) + prctl(PR_SET_PDEATHSIG, SIGTERM); + if (getppid() != parentPid) { - engineRootArg = AZ::IO::FixedMaxPathString::format(R"(--engine-path="%.*s")", - aznumeric_cast(engineRoot.size()), engineRoot.data()); - args[optionalArgPos++] = engineRootArg.data(); + _exit(1); } + LaunchAssetProcessorDirectly(assetProcessorPath, engineRoot, projectPath); + } + else + { + const pid_t secondChildPid = LaunchAssetProcessorDaemonized(assetProcessorPath, engineRoot, projectPath); + stdoutRedirect.Reset(); + stderrRedirect.Reset(); - // Add the active project path to the launch command if not empty - AZ::IO::FixedMaxPathString projectPathArg; - if (!projectPath.empty()) - { - projectPathArg = AZ::IO::FixedMaxPathString::format(R"(--regset="/Amazon/AzCore/Bootstrap/project_path=%.*s")", - aznumeric_cast(projectPath.size()), projectPath.data()); - args[optionalArgPos++] = projectPathArg.data(); - } - - AZStd::apply(execl, args); - - // exec* family of functions only exit on error - AZ_Error("AssetSystemComponent", false, "Asset Processor failed with error: %s", strerror(errno)); - _exit(1); + // exit the transient child with proper return code + int ret = (secondChildPid < 0) ? 1 : 0; + _exit(ret); } - stdoutRedirect.Reset(); - stderrRedirect.Reset(); - - // exit the transient child with proper return code - int ret = (secondChildPid < 0) ? 1 : 0; - _exit(ret); } else if (firstChildPid > 0) { + if (ap_tether_lifetime) + { + return true; + } // wait for first child to exit to ensure the second child was started int status = 0; pid_t ret = waitpid(firstChildPid, &status, 0); @@ -106,4 +141,4 @@ namespace AzFramework::AssetSystem::Platform return false; } -} +} // namespace AzFramework::AssetSystem::Platform diff --git a/Code/Framework/AzFramework/Tests/ArchiveCompressionTests.cpp b/Code/Framework/AzFramework/Tests/ArchiveCompressionTests.cpp index 6cde5b5e84..aa1a27f9b0 100644 --- a/Code/Framework/AzFramework/Tests/ArchiveCompressionTests.cpp +++ b/Code/Framework/AzFramework/Tests/ArchiveCompressionTests.cpp @@ -41,7 +41,9 @@ namespace UnitTest auto projectPathKey = AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path"; - registry->Set(projectPathKey, "AutomatedTesting"); + AZ::IO::FixedMaxPath enginePath; + registry->Get(enginePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder); + registry->Set(projectPathKey, (enginePath / "AutomatedTesting").Native()); AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry); m_application->Start({}); diff --git a/Code/Framework/AzFramework/Tests/ArchiveTests.cpp b/Code/Framework/AzFramework/Tests/ArchiveTests.cpp index 37babb49a8..ff0e3ab724 100644 --- a/Code/Framework/AzFramework/Tests/ArchiveTests.cpp +++ b/Code/Framework/AzFramework/Tests/ArchiveTests.cpp @@ -45,7 +45,9 @@ namespace UnitTest auto projectPathKey = AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path"; - registry->Set(projectPathKey, "AutomatedTesting"); + AZ::IO::FixedMaxPath enginePath; + registry->Get(enginePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder); + registry->Set(projectPathKey, (enginePath / "AutomatedTesting").Native()); AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry); m_application->Start({}); diff --git a/Code/Framework/AzFramework/Tests/AssetCatalog.cpp b/Code/Framework/AzFramework/Tests/AssetCatalog.cpp index 8cdc54f34f..8a24d164cd 100644 --- a/Code/Framework/AzFramework/Tests/AssetCatalog.cpp +++ b/Code/Framework/AzFramework/Tests/AssetCatalog.cpp @@ -305,10 +305,14 @@ namespace UnitTest AZ::SettingsRegistryInterface* registry = AZ::SettingsRegistry::Get(); auto projectPathKey = AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path"; - registry->Set(projectPathKey, "AutomatedTesting"); + AZ::IO::FixedMaxPath enginePath; + registry->Get(enginePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder); + registry->Set(projectPathKey, (enginePath / "AutomatedTesting").Native()); AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry); - m_app->Start(desc); + AZ::ComponentApplication::StartupParameters startupParameters; + startupParameters.m_loadAssetCatalog = false; + m_app->Start(desc, startupParameters); // 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 diff --git a/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/Actions.h b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/Actions.h index 1650ff4f8d..e86904efa3 100644 --- a/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/Actions.h +++ b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/Actions.h @@ -11,6 +11,13 @@ #include #include +ACTION_TEMPLATE(ReturnMalloc, + HAS_1_TEMPLATE_PARAMS(typename, T), + AND_0_VALUE_PARAMS()) { + T* value = static_cast(malloc(sizeof(T))); + *value = T{}; + return value; +} ACTION_TEMPLATE(ReturnMalloc, HAS_1_TEMPLATE_PARAMS(typename, T), AND_1_VALUE_PARAMS(p0)) { @@ -25,3 +32,38 @@ ACTION_TEMPLATE(ReturnMalloc, *value = T{ p0, p1 }; return value; } +ACTION_TEMPLATE(ReturnMalloc, + HAS_1_TEMPLATE_PARAMS(typename, T), + AND_3_VALUE_PARAMS(p0, p1, p2)) { + T* value = static_cast(malloc(sizeof(T))); + *value = T{ p0, p1, p2 }; + return value; +} +ACTION_TEMPLATE(ReturnMalloc, + HAS_1_TEMPLATE_PARAMS(typename, T), + AND_4_VALUE_PARAMS(p0, p1, p2, p3)) { + T* value = static_cast(malloc(sizeof(T))); + *value = T{ p0, p1, p2, p3 }; + return value; +} +ACTION_TEMPLATE(ReturnMalloc, + HAS_1_TEMPLATE_PARAMS(typename, T), + AND_5_VALUE_PARAMS(p0, p1, p2, p3, p4)) { + T* value = static_cast(malloc(sizeof(T))); + *value = T{ p0, p1, p2, p3, p4 }; + return value; +} +ACTION_TEMPLATE(ReturnMalloc, + HAS_1_TEMPLATE_PARAMS(typename, T), + AND_6_VALUE_PARAMS(p0, p1, p2, p3, p4, p5)) { + T* value = static_cast(malloc(sizeof(T))); + *value = T{ p0, p1, p2, p3, p4, p5 }; + return value; +} +ACTION_TEMPLATE(ReturnMalloc, + HAS_1_TEMPLATE_PARAMS(typename, T), + AND_7_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6)) { + T* value = static_cast(malloc(sizeof(T))); + *value = T{ p0, p1, p2, p3, p4, p5, p6 }; + return value; +} diff --git a/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/MockXcbInterface.cpp b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/MockXcbInterface.cpp index b15809a4c6..a19642e388 100644 --- a/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/MockXcbInterface.cpp +++ b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/MockXcbInterface.cpp @@ -32,6 +32,82 @@ xcb_generic_error_t* xcb_request_check(xcb_connection_t* c, xcb_void_cookie_t co { return MockXcbInterface::Instance()->xcb_request_check(c, cookie); } +const xcb_setup_t* xcb_get_setup(xcb_connection_t *c) +{ + return MockXcbInterface::Instance()->xcb_get_setup(c); +} +xcb_screen_iterator_t xcb_setup_roots_iterator(const xcb_setup_t* R) +{ + return MockXcbInterface::Instance()->xcb_setup_roots_iterator(R); +} +const xcb_query_extension_reply_t* xcb_get_extension_data(xcb_connection_t* c, xcb_extension_t* ext) +{ + return MockXcbInterface::Instance()->xcb_get_extension_data(c, ext); +} +int xcb_flush(xcb_connection_t *c) +{ + return MockXcbInterface::Instance()->xcb_flush(c); +} +xcb_query_pointer_cookie_t xcb_query_pointer(xcb_connection_t* c, xcb_window_t window) +{ + return MockXcbInterface::Instance()->xcb_query_pointer(c, window); +} +xcb_query_pointer_reply_t* xcb_query_pointer_reply(xcb_connection_t* c, xcb_query_pointer_cookie_t cookie, xcb_generic_error_t** e) +{ + return MockXcbInterface::Instance()->xcb_query_pointer_reply(c, cookie, e); +} +xcb_get_geometry_cookie_t xcb_get_geometry(xcb_connection_t* c, xcb_drawable_t drawable) +{ + return MockXcbInterface::Instance()->xcb_get_geometry(c, drawable); +} +xcb_get_geometry_reply_t* xcb_get_geometry_reply(xcb_connection_t* c, xcb_get_geometry_cookie_t cookie, xcb_generic_error_t** e) +{ + return MockXcbInterface::Instance()->xcb_get_geometry_reply(c, cookie, e); +} +xcb_void_cookie_t xcb_warp_pointer( + xcb_connection_t* c, + xcb_window_t src_window, + xcb_window_t dst_window, + int16_t src_x, + int16_t src_y, + uint16_t src_width, + uint16_t src_height, + int16_t dst_x, + int16_t dst_y) +{ + return MockXcbInterface::Instance()->xcb_warp_pointer(c, src_window, dst_window, src_x, src_y, src_width, src_height, dst_x, dst_y); +} +xcb_intern_atom_cookie_t xcb_intern_atom(xcb_connection_t* c, uint8_t only_if_exists, uint16_t name_len, const char* name) +{ + return MockXcbInterface::Instance()->xcb_intern_atom(c, only_if_exists, name_len, name); +} +xcb_intern_atom_reply_t* xcb_intern_atom_reply(xcb_connection_t* c, xcb_intern_atom_cookie_t cookie, xcb_generic_error_t** e) +{ + return MockXcbInterface::Instance()->xcb_intern_atom_reply(c, cookie, e); +} +xcb_get_property_cookie_t xcb_get_property( + xcb_connection_t* c, + uint8_t _delete, + xcb_window_t window, + xcb_atom_t property, + xcb_atom_t type, + uint32_t long_offset, + uint32_t long_length) +{ + return MockXcbInterface::Instance()->xcb_get_property(c, _delete, window, property, type, long_offset, long_length); +} +xcb_get_property_reply_t* xcb_get_property_reply(xcb_connection_t* c, xcb_get_property_cookie_t cookie, xcb_generic_error_t** e) +{ + return MockXcbInterface::Instance()->xcb_get_property_reply(c, cookie, e); +} +void* xcb_get_property_value(const xcb_get_property_reply_t* R) +{ + return MockXcbInterface::Instance()->xcb_get_property_value(R); +} +uint32_t xcb_generate_id(xcb_connection_t *c) +{ + return MockXcbInterface::Instance()->xcb_generate_id(c); +} // ---------------------------------------------------------------------------- // xcb-xkb @@ -116,4 +192,76 @@ xkb_state_component xkb_state_update_mask( state, depressed_mods, latched_mods, locked_mods, depressed_layout, latched_layout, locked_layout); } +// ---------------------------------------------------------------------------- +// xcb-xfixes +xcb_xfixes_query_version_cookie_t xcb_xfixes_query_version( + xcb_connection_t* c, uint32_t client_major_version, uint32_t client_minor_version) +{ + return MockXcbInterface::Instance()->xcb_xfixes_query_version(c, client_major_version, client_minor_version); +} +xcb_xfixes_query_version_reply_t* xcb_xfixes_query_version_reply( + xcb_connection_t* c, xcb_xfixes_query_version_cookie_t cookie, xcb_generic_error_t** e) +{ + return MockXcbInterface::Instance()->xcb_xfixes_query_version_reply(c, cookie, e); +} +xcb_void_cookie_t xcb_xfixes_show_cursor_checked(xcb_connection_t* c, xcb_window_t window) +{ + return MockXcbInterface::Instance()->xcb_xfixes_show_cursor_checked(c, window); +} +xcb_void_cookie_t xcb_xfixes_hide_cursor_checked(xcb_connection_t* c, xcb_window_t window) +{ + return MockXcbInterface::Instance()->xcb_xfixes_hide_cursor_checked(c, window); +} +xcb_void_cookie_t xcb_xfixes_delete_pointer_barrier_checked(xcb_connection_t* c, xcb_xfixes_barrier_t barrier) +{ + return MockXcbInterface::Instance()->xcb_xfixes_delete_pointer_barrier_checked(c, barrier); +} +xcb_translate_coordinates_cookie_t xcb_translate_coordinates(xcb_connection_t* c, xcb_window_t src_window, xcb_window_t dst_window, int16_t src_x, int16_t src_y) +{ + return MockXcbInterface::Instance()->xcb_translate_coordinates(c, src_window, dst_window, src_x, src_y); +} +xcb_translate_coordinates_reply_t* xcb_translate_coordinates_reply(xcb_connection_t* c, xcb_translate_coordinates_cookie_t cookie, xcb_generic_error_t** e) +{ + return MockXcbInterface::Instance()->xcb_translate_coordinates_reply(c, cookie, e); +} +xcb_void_cookie_t xcb_xfixes_create_pointer_barrier_checked( + xcb_connection_t* c, + xcb_xfixes_barrier_t barrier, + xcb_window_t window, + uint16_t x1, + uint16_t y1, + uint16_t x2, + uint16_t y2, + uint32_t directions, + uint16_t num_devices, + const uint16_t* devices) +{ + return MockXcbInterface::Instance()->xcb_xfixes_create_pointer_barrier_checked(c, barrier, window, x1, y1, x2, y2, directions, num_devices, devices); +} + +// ---------------------------------------------------------------------------- +// xcb-xinput +xcb_input_xi_query_version_cookie_t xcb_input_xi_query_version(xcb_connection_t* c, uint16_t major_version, uint16_t minor_version) +{ + return MockXcbInterface::Instance()->xcb_input_xi_query_version(c, major_version, minor_version); +} +xcb_input_xi_query_version_reply_t* xcb_input_xi_query_version_reply( + xcb_connection_t* c, xcb_input_xi_query_version_cookie_t cookie, xcb_generic_error_t** e) +{ + return MockXcbInterface::Instance()->xcb_input_xi_query_version_reply(c, cookie, e); +} +xcb_void_cookie_t xcb_input_xi_select_events( + xcb_connection_t* c, xcb_window_t window, uint16_t num_mask, const xcb_input_event_mask_t* masks) +{ + return MockXcbInterface::Instance()->xcb_input_xi_select_events(c, window, num_mask, masks); +} +int xcb_input_raw_button_press_axisvalues_length (const xcb_input_raw_button_press_event_t *R) +{ + return MockXcbInterface::Instance()->xcb_input_raw_button_press_axisvalues_length(R); +} +xcb_input_fp3232_t* xcb_input_raw_button_press_axisvalues_raw(const xcb_input_raw_button_press_event_t* R) +{ + return MockXcbInterface::Instance()->xcb_input_raw_button_press_axisvalues_raw(R); +} + } diff --git a/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/MockXcbInterface.h b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/MockXcbInterface.h index b57751344e..c554993110 100644 --- a/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/MockXcbInterface.h +++ b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/MockXcbInterface.h @@ -18,6 +18,8 @@ #undef explicit #include #include +#include +#include #include "Printers.h" @@ -62,6 +64,37 @@ public: MOCK_CONST_METHOD1(xcb_disconnect, void(xcb_connection_t* c)); MOCK_CONST_METHOD1(xcb_poll_for_event, xcb_generic_event_t*(xcb_connection_t* c)); MOCK_CONST_METHOD2(xcb_request_check, xcb_generic_error_t*(xcb_connection_t* c, xcb_void_cookie_t cookie)); + MOCK_CONST_METHOD1(xcb_get_setup, const xcb_setup_t*(xcb_connection_t *c)); + MOCK_CONST_METHOD1(xcb_setup_roots_iterator, xcb_screen_iterator_t(const xcb_setup_t* R)); + MOCK_CONST_METHOD2(xcb_get_extension_data, const xcb_query_extension_reply_t*(xcb_connection_t* c, xcb_extension_t* ext)); + MOCK_CONST_METHOD1(xcb_flush, int(xcb_connection_t *c)); + MOCK_CONST_METHOD2(xcb_query_pointer, xcb_query_pointer_cookie_t(xcb_connection_t* c, xcb_window_t window)); + MOCK_CONST_METHOD3(xcb_query_pointer_reply, xcb_query_pointer_reply_t*(xcb_connection_t* c, xcb_query_pointer_cookie_t cookie, xcb_generic_error_t** e)); + MOCK_CONST_METHOD2(xcb_get_geometry, xcb_get_geometry_cookie_t(xcb_connection_t* c, xcb_drawable_t drawable)); + MOCK_CONST_METHOD3(xcb_get_geometry_reply, xcb_get_geometry_reply_t*(xcb_connection_t* c, xcb_get_geometry_cookie_t cookie, xcb_generic_error_t** e)); + MOCK_CONST_METHOD9(xcb_warp_pointer, xcb_void_cookie_t( + xcb_connection_t* c, + xcb_window_t src_window, + xcb_window_t dst_window, + int16_t src_x, + int16_t src_y, + uint16_t src_width, + uint16_t src_height, + int16_t dst_x, + int16_t dst_y)); + MOCK_CONST_METHOD4(xcb_intern_atom, xcb_intern_atom_cookie_t(xcb_connection_t* c, uint8_t only_if_exists, uint16_t name_len, const char* name)); + MOCK_CONST_METHOD3(xcb_intern_atom_reply, xcb_intern_atom_reply_t*(xcb_connection_t* c, xcb_intern_atom_cookie_t cookie, xcb_generic_error_t** e)); + MOCK_CONST_METHOD7(xcb_get_property, xcb_get_property_cookie_t( + xcb_connection_t* c, + uint8_t _delete, + xcb_window_t window, + xcb_atom_t property, + xcb_atom_t type, + uint32_t long_offset, + uint32_t long_length)); + MOCK_CONST_METHOD3(xcb_get_property_reply, xcb_get_property_reply_t*(xcb_connection_t* c, xcb_get_property_cookie_t cookie, xcb_generic_error_t** e)); + MOCK_CONST_METHOD1(xcb_get_property_value, void*(const xcb_get_property_reply_t* R)); + MOCK_CONST_METHOD1(xcb_generate_id, uint32_t(xcb_connection_t *c)); // xcb-xkb MOCK_CONST_METHOD3(xcb_xkb_use_extension, xcb_xkb_use_extension_cookie_t(xcb_connection_t* c, uint16_t wantedMajor, uint16_t wantedMinor)); @@ -83,6 +116,33 @@ public: MOCK_CONST_METHOD4(xkb_state_key_get_utf8, int(xkb_state* state, xkb_keycode_t key, char* buffer, size_t size)); MOCK_CONST_METHOD7(xkb_state_update_mask, xkb_state_component(xkb_state* state, xkb_mod_mask_t depressed_mods, xkb_mod_mask_t latched_mods, xkb_mod_mask_t locked_mods, xkb_layout_index_t depressed_layout, xkb_layout_index_t latched_layout, xkb_layout_index_t locked_layout)); + // xcb-xfixes + MOCK_CONST_METHOD3(xcb_xfixes_query_version, xcb_xfixes_query_version_cookie_t(xcb_connection_t* c, uint32_t client_major_version, uint32_t client_minor_version)); + MOCK_CONST_METHOD3(xcb_xfixes_query_version_reply, xcb_xfixes_query_version_reply_t*(xcb_connection_t* c, xcb_xfixes_query_version_cookie_t cookie, xcb_generic_error_t** e)); + MOCK_CONST_METHOD2(xcb_xfixes_show_cursor_checked, xcb_void_cookie_t(xcb_connection_t* c, xcb_window_t window)); + MOCK_CONST_METHOD2(xcb_xfixes_hide_cursor_checked, xcb_void_cookie_t(xcb_connection_t* c, xcb_window_t window)); + MOCK_CONST_METHOD2(xcb_xfixes_delete_pointer_barrier_checked, xcb_void_cookie_t(xcb_connection_t* c, xcb_xfixes_barrier_t barrier)); + MOCK_CONST_METHOD5(xcb_translate_coordinates, xcb_translate_coordinates_cookie_t(xcb_connection_t* c, xcb_window_t src_window, xcb_window_t dst_window, int16_t src_x, int16_t src_y)); + MOCK_CONST_METHOD3(xcb_translate_coordinates_reply, xcb_translate_coordinates_reply_t*(xcb_connection_t* c, xcb_translate_coordinates_cookie_t cookie, xcb_generic_error_t** e)); + MOCK_CONST_METHOD10(xcb_xfixes_create_pointer_barrier_checked, xcb_void_cookie_t( + xcb_connection_t* c, + xcb_xfixes_barrier_t barrier, + xcb_window_t window, + uint16_t x1, + uint16_t y1, + uint16_t x2, + uint16_t y2, + uint32_t directions, + uint16_t num_devices, + const uint16_t* devices)); + + // xcb-xinput + MOCK_CONST_METHOD3(xcb_input_xi_query_version, xcb_input_xi_query_version_cookie_t(xcb_connection_t* c, uint16_t major_version, uint16_t minor_version)); + MOCK_CONST_METHOD3(xcb_input_xi_query_version_reply, xcb_input_xi_query_version_reply_t*(xcb_connection_t* c, xcb_input_xi_query_version_cookie_t cookie, xcb_generic_error_t** e)); + MOCK_CONST_METHOD4(xcb_input_xi_select_events, xcb_void_cookie_t(xcb_connection_t* c, xcb_window_t window, uint16_t num_mask, const xcb_input_event_mask_t* masks)); + MOCK_CONST_METHOD1(xcb_input_raw_button_press_axisvalues_length, int(const xcb_input_raw_button_press_event_t* R)); + MOCK_CONST_METHOD1(xcb_input_raw_button_press_axisvalues_raw, xcb_input_fp3232_t*(const xcb_input_raw_button_press_event_t* R)); + private: static inline MockXcbInterface* self = nullptr; }; diff --git a/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/XcbBaseTestFixture.h b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/XcbBaseTestFixture.h index 8e9b008fc1..2a63a5f158 100644 --- a/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/XcbBaseTestFixture.h +++ b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/XcbBaseTestFixture.h @@ -22,6 +22,12 @@ namespace AzFramework public: void SetUp() override; + template + static xcb_generic_event_t MakeEvent(T event) + { + return *reinterpret_cast(&event); + } + protected: testing::NiceMock m_interface; xcb_connection_t m_connection{}; diff --git a/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/XcbInputDeviceKeyboardTests.cpp b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/XcbInputDeviceKeyboardTests.cpp index 76d00eda50..7209875235 100644 --- a/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/XcbInputDeviceKeyboardTests.cpp +++ b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/XcbInputDeviceKeyboardTests.cpp @@ -21,12 +21,6 @@ #include "XcbBaseTestFixture.h" #include "XcbTestApplication.h" -template -xcb_generic_event_t MakeEvent(T event) -{ - return *reinterpret_cast(&event); -} - namespace AzFramework { // Sets up default behavior for mock keyboard responses to xcb methods diff --git a/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/XcbInputDeviceMouseTests.cpp b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/XcbInputDeviceMouseTests.cpp new file mode 100644 index 0000000000..05784462d7 --- /dev/null +++ b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/XcbInputDeviceMouseTests.cpp @@ -0,0 +1,545 @@ +/* + * 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 + +#include +#include + +#include "XcbBaseTestFixture.h" +#include "XcbTestApplication.h" +#include "Matchers.h" +#include "Actions.h" + +namespace AzFramework +{ + // Sets up default behavior for mock keyboard responses to xcb methods + class XcbInputDeviceMouseTests + : public XcbBaseTestFixture + { + public: + void SetUp() override + { + using testing::Eq; + using testing::Field; + using testing::Return; + using testing::StrEq; + using testing::_; + + XcbBaseTestFixture::SetUp(); + + ON_CALL(m_interface, xcb_get_setup(&m_connection)) + .WillByDefault(Return(&s_xcbSetup)); + ON_CALL(m_interface, xcb_setup_roots_iterator(&s_xcbSetup)) + .WillByDefault(Return(xcb_screen_iterator_t{&s_xcbScreen})); + + ON_CALL(m_interface, xcb_get_extension_data(&m_connection, &xcb_xfixes_id)) + .WillByDefault(Return(&s_xfixesExtensionReply)); + ON_CALL(m_interface, xcb_xfixes_query_version_reply(&m_connection, _, _)) + .WillByDefault(ReturnMalloc( + /*response_type=*/(uint8_t)XCB_XFIXES_QUERY_VERSION, + /*pad0=*/(uint8_t)0, + /*sequence=*/(uint16_t)1, + /*length=*/0u, + /*major_version=*/5u, + /*minor_version=*/0u + )); + + ON_CALL(m_interface, xcb_get_extension_data(&m_connection, &xcb_input_id)) + .WillByDefault(Return(&s_xfixesExtensionReply)); + ON_CALL(m_interface, xcb_input_xi_query_version_reply(&m_connection, _, _)) + .WillByDefault(ReturnMalloc( + /*response_type=*/(uint8_t)XCB_INPUT_XI_QUERY_VERSION, + /*pad0=*/(uint8_t)0, + /*sequence=*/(uint16_t)1, + /*length=*/0u, + /*major_version=*/(uint16_t)2, + /*minor_version=*/(uint16_t)2 + )); + + // Set the default focus window + EXPECT_CALL(m_interface, xcb_intern_atom(&m_connection, 1, 18, StrEq("_NET_ACTIVE_WINDOW"))) + .WillRepeatedly(Return(xcb_intern_atom_cookie_t{/*.sequence=*/ 1})); + ON_CALL(m_interface, xcb_intern_atom_reply(&m_connection, Field(&xcb_intern_atom_cookie_t::sequence, Eq(1)), _)) + .WillByDefault(ReturnMalloc( + /*response_type=*/(uint8_t)XCB_INTERN_ATOM, + /*pad0=*/(uint8_t)0, + /*sequence=*/(uint16_t)1, + /*length=*/0u, + /*xcb_atom_t=*/s_netActiveWindowAtom + )); + ON_CALL(m_interface, xcb_get_property(&m_connection, 0, s_rootWindow, s_netActiveWindowAtom, XCB_ATOM_WINDOW, 0, 1)) + .WillByDefault(Return(xcb_get_property_cookie_t{/*.sequence=*/ s_getActiveWindowPropertySequence})); + ON_CALL(m_interface, xcb_get_property_reply(&m_connection, Field(&xcb_get_property_cookie_t::sequence, Eq(s_getActiveWindowPropertySequence)), _)) + .WillByDefault(ReturnMalloc( + /*response_type=*/(uint8_t)XCB_GET_PROPERTY, + /*format=*/(uint8_t)0, + /*sequence=*/(uint16_t)s_getActiveWindowPropertySequence, + /*length=*/0u, + /*type=*/XCB_ATOM_WINDOW, + /*bytes_after=*/0u, + /*value_len=*/1u + )); + ON_CALL(m_interface, xcb_get_property_value(Field(&xcb_get_property_reply_t::sequence, Eq(s_getActiveWindowPropertySequence)))) + .WillByDefault(Return(const_cast(&s_nullWindow))); + + ON_CALL(m_interface, xcb_get_geometry(&m_connection, _)) + .WillByDefault(Return(xcb_get_geometry_cookie_t{/*.sequence=*/1})); + ON_CALL(m_interface, xcb_get_geometry_reply(&m_connection, Field(&xcb_get_geometry_cookie_t::sequence, Eq(1)), _)) + .WillByDefault(ReturnMalloc(s_defaultWindowGeometry)); + } + + void PumpApplication() + { + m_application.PumpSystemEventLoopUntilEmpty(); + m_application.TickSystem(); + m_application.Tick(); + } + + protected: + static constexpr inline uint8_t s_xinputMajorOpcode = 131; + static constexpr inline xcb_window_t s_rootWindow = 1; + static constexpr inline xcb_window_t s_nullWindow = XCB_WINDOW_NONE; + static constexpr inline xcb_input_device_id_t s_virtualCorePointerId = 2; + static constexpr inline xcb_input_device_id_t s_physicalPointerDeviceId = 3; + static constexpr inline uint16_t s_screenWidthInPixels = 3840; + static constexpr inline uint16_t s_screenHeightInPixels = 2160; + static constexpr inline uint16_t s_getActiveWindowPropertySequence = 2160; + static constexpr inline xcb_atom_t s_netActiveWindowAtom = 1; + static constexpr inline xcb_setup_t s_xcbSetup{ + /*.status=*/1, + /*.pad0=*/0, + /*.protocol_major_version=*/11, + /*.protocol_minor_version=*/0, + }; + static inline xcb_screen_t s_xcbScreen{ + /*.root=*/s_rootWindow, + /*.default_colormap=*/32, + /*.white_pixel=*/16777215, + /*.black_pixel=*/0, + /*.current_input_masks=*/0, + /*.width_in_pixels=*/s_screenWidthInPixels, + /*.height_in_pixels=*/s_screenHeightInPixels, + /*.width_in_millimeters=*/602, + /*.height_in_millimeters=*/341, + }; + static constexpr inline xcb_query_extension_reply_t s_xfixesExtensionReply{ + /*.response_type=*/XCB_QUERY_EXTENSION, + /*.pad0=*/0, + /*.sequence=*/1, + /*.length=*/0, + /*.present=*/1, + }; + static constexpr inline xcb_query_extension_reply_t s_xinputExtensionReply{ + /*.response_type=*/XCB_QUERY_EXTENSION, + /*.pad0=*/0, + /*.sequence=*/1, + /*.length=*/0, + /*.present=*/1, + /*.major_opcode=*/s_xinputMajorOpcode, + }; + static constexpr inline xcb_get_geometry_reply_t s_defaultWindowGeometry{ + /*.response_type=*/XCB_GET_GEOMETRY, + /*.depth=*/0, + /*.sequence=*/1, + /*.length=*/0, + /*.root=*/s_rootWindow, + /*.x=*/100, + /*.y=*/100, + /*.width=*/100, + /*.height=*/100, + /*.border_width=*/3, + /*.pad0[2]=*/{}, + }; + XcbTestApplication m_application{ + /*enabledGamepadsCount=*/0, + /*keyboardEnabled=*/false, + /*motionEnabled=*/false, + /*mouseEnabled=*/true, + /*touchEnabled=*/false, + /*virtualKeyboardEnabled=*/false + }; + }; + + struct MouseButtonTestData + { + xcb_button_index_t m_button; + }; + + class XcbInputDeviceMouseButtonTests + : public XcbInputDeviceMouseTests + , public testing::WithParamInterface + { + public: + static InputChannelId GetInputChannelIdForButton(const xcb_button_index_t button) + { + switch (button) + { + case XCB_BUTTON_INDEX_1: + return InputDeviceMouse::Button::Left; + case XCB_BUTTON_INDEX_2: + return InputDeviceMouse::Button::Right; + case XCB_BUTTON_INDEX_3: + return InputDeviceMouse::Button::Middle; + } + return InputChannelId{}; + } + + AZStd::array GetIdleChannelIdsForButton(const xcb_button_index_t button) + { + switch (button) + { + case XCB_BUTTON_INDEX_1: + return { InputDeviceMouse::Button::Right, InputDeviceMouse::Button::Middle, InputDeviceMouse::Button::Other1, InputDeviceMouse::Button::Other2 }; + case XCB_BUTTON_INDEX_2: + return { InputDeviceMouse::Button::Left, InputDeviceMouse::Button::Middle, InputDeviceMouse::Button::Other1, InputDeviceMouse::Button::Other2 }; + case XCB_BUTTON_INDEX_3: + return { InputDeviceMouse::Button::Left, InputDeviceMouse::Button::Right, InputDeviceMouse::Button::Other1, InputDeviceMouse::Button::Other2 }; + case XCB_BUTTON_INDEX_4: + return { InputDeviceMouse::Button::Left, InputDeviceMouse::Button::Right, InputDeviceMouse::Button::Middle, InputDeviceMouse::Button::Other2 }; + case XCB_BUTTON_INDEX_5: + return { InputDeviceMouse::Button::Left, InputDeviceMouse::Button::Right, InputDeviceMouse::Button::Middle, InputDeviceMouse::Button::Other1 }; + } + return AZStd::array(); + } + }; + + TEST_P(XcbInputDeviceMouseButtonTests, ButtonInputChannelsUpdateStateFromXcbEvents) + { + using testing::Each; + using testing::Eq; + using testing::NotNull; + using testing::Property; + using testing::Return; + + // Set the expectations for the events that will be generated + // nullptr entries represent when the event queue is empty, and will cause + // PumpSystemEventLoopUntilEmpty to return + // + // Event pointers are freed by the calling code, so these actions + // malloc new copies + // + // The xcb mouse does not react to the `XCB_BUTTON_PRESS` / + // `XCB_BUTTON_RELEASE` events, but it will still receive those events + // from the X server. + EXPECT_CALL(m_interface, xcb_poll_for_event(&m_connection)) + .WillOnce(ReturnMalloc(MakeEvent(xcb_input_raw_button_press_event_t{ + /*response_type=*/XCB_GE_GENERIC, + /*extension=*/s_xinputMajorOpcode, + /*sequence=*/4, + /*length=*/2, + /*event_type=*/XCB_INPUT_RAW_BUTTON_PRESS, + /*deviceid=*/s_virtualCorePointerId, + /*time=*/3984920, + /*detail=*/GetParam().m_button, + /*sourceid=*/s_physicalPointerDeviceId, + /*valuators_len=*/2, + /*flags=*/0, + /*pad0[4]=*/{}, + /*full_sequence=*/4 + }))) + .WillOnce(Return(nullptr)) + .WillOnce(ReturnMalloc(MakeEvent(xcb_button_press_event_t{ + /*response_type=*/XCB_BUTTON_PRESS, + /*detail=*/static_cast(GetParam().m_button), + /*sequence=*/4, + /*time=*/3984920, + /*root=*/s_rootWindow, + /*event=*/119537664, + /*child=*/0, + /*root_x=*/55, + /*root_y=*/1099, + /*event_x=*/55, + /*event_y=*/55, + /*state=*/0, + /*same_screen=*/1 + }))) + .WillOnce(Return(nullptr)) + .WillOnce(ReturnMalloc(MakeEvent(xcb_input_raw_button_release_event_t{ + /*response_type=*/XCB_GE_GENERIC, + /*extension=*/s_xinputMajorOpcode, + /*sequence=*/4, + /*length=*/2, + /*event_type=*/XCB_INPUT_RAW_BUTTON_RELEASE, + /*deviceid=*/s_virtualCorePointerId, + /*time=*/3984964, + /*detail=*/GetParam().m_button, + /*sourceid=*/s_physicalPointerDeviceId, + /*valuators_len=*/2, + /*flags=*/0, + /*pad0[4]=*/{}, + /*full_sequence=*/4 + }))) + .WillOnce(Return(nullptr)) + .WillOnce(ReturnMalloc(MakeEvent(xcb_button_release_event_t{ + /*response_type=*/XCB_BUTTON_RELEASE, + /*detail=*/static_cast(GetParam().m_button), + /*sequence=*/4, + /*time=*/3984964, + /*root=*/s_rootWindow, + /*event=*/119537664, + /*child=*/0, + /*root_x=*/55, + /*root_y=*/1099, + /*event_x=*/55, + /*event_y=*/55, + /*state=*/XCB_KEY_BUT_MASK_BUTTON_1, + /*same_screen=*/1 + }))) + .WillOnce(Return(nullptr)) + ; + + m_application.Start(); + InputSystemCursorRequestBus::Event( + InputDeviceMouse::Id, + &InputSystemCursorRequests::SetSystemCursorState, + SystemCursorState::ConstrainedAndHidden); + + const InputChannel* activeButtonChannel = InputChannelRequests::FindInputChannel(GetInputChannelIdForButton(GetParam().m_button)); + const auto inactiveButtonChannels = [this]() + { + const auto inactiveButtonChannelIds = GetIdleChannelIdsForButton(GetParam().m_button); + AZStd::array channels{}; + AZStd::transform(begin(inactiveButtonChannelIds), end(inactiveButtonChannelIds), begin(channels), [](const InputChannelId& id) + { + return InputChannelRequests::FindInputChannel(id); + }); + return channels; + }(); + + ASSERT_TRUE(activeButtonChannel); + ASSERT_THAT(inactiveButtonChannels, Each(NotNull())); + + EXPECT_THAT(activeButtonChannel->GetState(), Eq(InputChannel::State::Idle)); + EXPECT_THAT(inactiveButtonChannels, Each(Property(&InputChannel::GetState, Eq(InputChannel::State::Idle)))); + + PumpApplication(); + + EXPECT_THAT(activeButtonChannel->GetState(), Eq(InputChannel::State::Began)); + EXPECT_THAT(inactiveButtonChannels, Each(Property(&InputChannel::GetState, Eq(InputChannel::State::Idle)))); + + PumpApplication(); + + EXPECT_THAT(activeButtonChannel->GetState(), Eq(InputChannel::State::Updated)); + EXPECT_THAT(inactiveButtonChannels, Each(Property(&InputChannel::GetState, Eq(InputChannel::State::Idle)))); + + PumpApplication(); + + EXPECT_THAT(activeButtonChannel->GetState(), Eq(InputChannel::State::Ended)); + EXPECT_THAT(inactiveButtonChannels, Each(Property(&InputChannel::GetState, Eq(InputChannel::State::Idle)))); + + PumpApplication(); + + EXPECT_THAT(activeButtonChannel->GetState(), Eq(InputChannel::State::Idle)); + EXPECT_THAT(inactiveButtonChannels, Each(Property(&InputChannel::GetState, Eq(InputChannel::State::Idle)))); + } + + INSTANTIATE_TEST_CASE_P( + AllButtons, + XcbInputDeviceMouseButtonTests, + testing::Values( + MouseButtonTestData{ XCB_BUTTON_INDEX_1 }, + MouseButtonTestData{ XCB_BUTTON_INDEX_2 }, + MouseButtonTestData{ XCB_BUTTON_INDEX_3 } + // XCB_BUTTON_INDEX_4 and XCB_BUTTON_INDEX_5 map to positive and + // negative scroll wheel events, which are handled as motion events + ) + ); + + TEST_F(XcbInputDeviceMouseTests, MovementInputChannelsUpdateStateFromXcbEvents) + { + using testing::Each; + using testing::Eq; + using testing::FloatEq; + using testing::NotNull; + using testing::Property; + using testing::Return; + + // Set the expectations for the events that will be generated + // nullptr entries represent when the event queue is empty, and will cause + // PumpSystemEventLoopUntilEmpty to return + // + // Event pointers are freed by the calling code, so these actions + // malloc new copies + // + // The xcb mouse does not react to the `XCB_MOTION_NOTIFY` event, but + // it will still receive it from the X server. + EXPECT_CALL(m_interface, xcb_poll_for_event(&m_connection)) + .WillOnce(ReturnMalloc(MakeEvent(xcb_input_raw_motion_event_t{ + /*response_type=*/XCB_GE_GENERIC, + /*extension=*/s_xinputMajorOpcode, + /*sequence=*/5, + /*length=*/10, + /*event_type=*/XCB_INPUT_RAW_MOTION, + /*deviceid=*/s_virtualCorePointerId, + /*time=*/0, // use the time value to identify each event + /*detail=*/XCB_MOTION_NORMAL, + /*sourceid=*/s_physicalPointerDeviceId, + /*valuators_len=*/2, // number of axes that have values for this event + /*flags=*/0, + /*pad0[4]=*/{}, + /*full_sequence=*/5, + }))) + .WillOnce(Return(nullptr)) + .WillOnce(ReturnMalloc(MakeEvent(xcb_motion_notify_event_t{ + /*response_type=*/XCB_MOTION_NOTIFY, + /*detail=*/XCB_MOTION_NORMAL, + /*sequence=*/5, + /*time=*/1, // use the time value to identify each event + /*root=*/s_rootWindow, + /*event=*/127926272, + /*child=*/0, + /*root_x=*/95, + /*root_y=*/1079, + /*event_x=*/95, + /*event_y=*/20, + /*state=*/0, + /*same_screen=*/1, + }))) + .WillOnce(Return(nullptr)) + ; + + AZStd::array axisValues + { + xcb_input_fp3232_t{ /*.integral=*/ 1, /*.fraction=*/0 }, // x motion + xcb_input_fp3232_t{ /*.integral=*/ 2, /*.fraction=*/0 } // y motion + }; + + EXPECT_CALL(m_interface, xcb_input_raw_button_press_axisvalues_length(testing::Field(&xcb_input_raw_button_press_event_t::time, 0))) + .WillRepeatedly(testing::Return(2)); // x and y axis + EXPECT_CALL(m_interface, xcb_input_raw_button_press_axisvalues_raw(testing::Field(&xcb_input_raw_button_press_event_t::time, 0))) + .WillRepeatedly(testing::Return(axisValues.data())); // x and y axis + + m_application.Start(); + InputSystemCursorRequestBus::Event( + InputDeviceMouse::Id, + &InputSystemCursorRequests::SetSystemCursorState, + SystemCursorState::ConstrainedAndHidden); + + const InputChannel* xMotionChannel = InputChannelRequests::FindInputChannel(InputDeviceMouse::Movement::X); + const InputChannel* yMotionChannel = InputChannelRequests::FindInputChannel(InputDeviceMouse::Movement::Y); + ASSERT_TRUE(xMotionChannel); + ASSERT_TRUE(yMotionChannel); + + EXPECT_THAT(xMotionChannel->GetState(), Eq(InputChannel::State::Idle)); + EXPECT_THAT(yMotionChannel->GetState(), Eq(InputChannel::State::Idle)); + EXPECT_THAT(xMotionChannel->GetValue(), FloatEq(0.0f)); + EXPECT_THAT(yMotionChannel->GetValue(), FloatEq(0.0f)); + + PumpApplication(); + + EXPECT_THAT(xMotionChannel->GetState(), Eq(InputChannel::State::Began)); + EXPECT_THAT(yMotionChannel->GetState(), Eq(InputChannel::State::Began)); + EXPECT_THAT(xMotionChannel->GetValue(), FloatEq(1.0f)); + EXPECT_THAT(yMotionChannel->GetValue(), FloatEq(2.0f)); + + PumpApplication(); + + EXPECT_THAT(xMotionChannel->GetState(), Eq(InputChannel::State::Ended)); + EXPECT_THAT(yMotionChannel->GetState(), Eq(InputChannel::State::Ended)); + EXPECT_THAT(xMotionChannel->GetValue(), FloatEq(0.0f)); + EXPECT_THAT(yMotionChannel->GetValue(), FloatEq(0.0f)); + } + + struct GetCursorPositionParam + { + int16_t m_x; + int16_t m_y; + }; + + class XcbGetSystemCursorPositionTests + : public XcbInputDeviceMouseTests + , public testing::WithParamInterface + { + }; + + TEST_P(XcbGetSystemCursorPositionTests, GetSystemCursorPositionNormalizedReturnsCorrectValue) + { + using testing::Eq; + using testing::Field; + using testing::Return; + using testing::_; + + xcb_window_t focusWindow = 42; + const xcb_query_pointer_reply_t queryPointerReply{ + /*.response_type=*/XCB_QUERY_POINTER, + /*.same_screen=*/1, + /*.sequence=*/0, + /*.length=*/1, + /*.root=*/s_rootWindow, + /*.child=*/focusWindow, + /*.root_x=*/static_cast(GetParam().m_x + s_defaultWindowGeometry.x), + /*.root_y=*/static_cast(GetParam().m_y + s_defaultWindowGeometry.y), + /*.win_x=*/GetParam().m_x, + /*.win_y=*/GetParam().m_y, + /*.mask=*/{}, + /*.pad0[2]=*/{}, + }; + + // Querying the root window's pointer gives its absolute value + const xcb_query_pointer_reply_t rootWindowQueryPointerReply{ + /*.response_type=*/XCB_QUERY_POINTER, + /*.same_screen=*/1, + /*.sequence=*/0, + /*.length=*/1, + /*.root=*/s_rootWindow, + /*.child=*/s_rootWindow, + /*.root_x=*/static_cast(GetParam().m_x + s_defaultWindowGeometry.x), + /*.root_y=*/static_cast(GetParam().m_y + s_defaultWindowGeometry.y), + /*.win_x=*/static_cast(GetParam().m_x + s_defaultWindowGeometry.x), + /*.win_y=*/static_cast(GetParam().m_y + s_defaultWindowGeometry.y), + /*.mask=*/{}, + /*.pad0[2]=*/{}, + }; + + EXPECT_CALL(m_interface, xcb_get_property_value(Field(&xcb_get_property_reply_t::sequence, Eq(s_getActiveWindowPropertySequence)))) + .WillRepeatedly(Return(&focusWindow)); + + EXPECT_CALL(m_interface, xcb_query_pointer(&m_connection, focusWindow)) + .WillRepeatedly(Return(xcb_query_pointer_cookie_t{/*.sequence=*/1})); + EXPECT_CALL(m_interface, xcb_query_pointer_reply(&m_connection, Field(&xcb_query_pointer_cookie_t::sequence, 1), _)) + .WillRepeatedly(ReturnMalloc(queryPointerReply)); + + EXPECT_CALL(m_interface, xcb_query_pointer(&m_connection, s_rootWindow)) + .WillRepeatedly(Return(xcb_query_pointer_cookie_t{/*.sequence=*/2})); + EXPECT_CALL(m_interface, xcb_query_pointer_reply(&m_connection, Field(&xcb_query_pointer_cookie_t::sequence, 2), _)) + .WillRepeatedly(ReturnMalloc(rootWindowQueryPointerReply)); + + m_application.Start(); + InputSystemCursorRequestBus::Event( + InputDeviceMouse::Id, + &InputSystemCursorRequests::SetSystemCursorState, + SystemCursorState::ConstrainedAndHidden); + + AZ::Vector2 systemCursorPositionNormalized = AZ::Vector2::CreateZero(); + InputSystemCursorRequestBus::EventResult( + systemCursorPositionNormalized, + InputDeviceMouse::Id, + &InputSystemCursorRequests::GetSystemCursorPositionNormalized); + + EXPECT_THAT(systemCursorPositionNormalized, ::testing::AllOf( + testing::Property(&AZ::Vector2::GetX, testing::FloatEq(static_cast(GetParam().m_x) / s_defaultWindowGeometry.width)), + testing::Property(&AZ::Vector2::GetY, testing::FloatEq(static_cast(GetParam().m_y) / s_defaultWindowGeometry.height)) + )); + } + + INSTANTIATE_TEST_CASE_P( + AllPointerPositions, + XcbGetSystemCursorPositionTests, + testing::Values( + // Default mocked window geometry sets width and height to 100, all + // parameter values should be within [0, 100) + GetCursorPositionParam{ 50, 50 }, + GetCursorPositionParam{ 25, 25 }, + GetCursorPositionParam{ 0, 100 } + ) + ); +} // namespace AzFramework diff --git a/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/azframework_xcb_tests_files.cmake b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/azframework_xcb_tests_files.cmake index 147fd2bfe1..7da00fa18c 100644 --- a/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/azframework_xcb_tests_files.cmake +++ b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/azframework_xcb_tests_files.cmake @@ -17,5 +17,6 @@ set(FILES XcbBaseTestFixture.cpp XcbBaseTestFixture.h XcbInputDeviceKeyboardTests.cpp + XcbInputDeviceMouseTests.cpp XcbTestApplication.h ) diff --git a/Code/Framework/AzGameFramework/AzGameFramework/Application/GameApplication.cpp b/Code/Framework/AzGameFramework/AzGameFramework/Application/GameApplication.cpp index 0cce93d751..36acf2b063 100644 --- a/Code/Framework/AzGameFramework/AzGameFramework/Application/GameApplication.cpp +++ b/Code/Framework/AzGameFramework/AzGameFramework/Application/GameApplication.cpp @@ -45,6 +45,13 @@ namespace AzGameFramework enginePakPath = AZ::IO::FixedMaxPath(AZ::Utils::GetExecutableDirectory()) / "engine.pak"; m_archive->OpenPack("@products@", enginePakPath.Native()); } + + // By default, load all archives in the products folder. + // If you want to adjust this for your project, make sure that the archive containing + // the bootstrap for the settings registry is still loaded here, and any archives containing + // assets used early in startup, like default shaders, are loaded here. + constexpr AZStd::string_view paksFolder = "@products@/*.pak"; // (@products@ assumed) + m_archive->OpenPacks(paksFolder); } GameApplication::~GameApplication() @@ -80,9 +87,9 @@ namespace AzGameFramework AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_ProjectRegistry(registry, AZ_TRAIT_OS_PLATFORM_CODENAME, specializations, &scratchBuffer); #endif - // Used the lowercase the platform name since the bootstrap.game...setreg is being loaded + // Used the lowercase the platform name since the bootstrap.game..setreg is being loaded // from the asset cache root where all the files are in lowercased from regardless of the filesystem case-sensitivity - static constexpr char filename[] = "bootstrap.game." AZ_BUILD_CONFIGURATION_TYPE "." AZ_TRAIT_OS_PLATFORM_CODENAME_LOWER ".setreg"; + static constexpr char filename[] = "bootstrap.game." AZ_BUILD_CONFIGURATION_TYPE ".setreg"; AZ::IO::FixedMaxPath cacheRootPath; if (registry.Get(cacheRootPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_CacheRootFolder)) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/API/ViewPaneOptions.h b/Code/Framework/AzToolsFramework/AzToolsFramework/API/ViewPaneOptions.h index be93e1af1a..33b60f0f51 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/API/ViewPaneOptions.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/API/ViewPaneOptions.h @@ -40,7 +40,7 @@ namespace AzToolsFramework bool isDisabledInSimMode = false; ///< set to true if the view pane should not be openable from level editor menu when editor is in simulation mode. bool showOnToolsToolbar = false; ///< set to true if the view pane should create a button on the tools toolbar to open/close the pane - QString toolbarIcon; ///< path to the icon to use for the toolbar button - only used if showOnToolsToolbar is set to true + AZStd::string toolbarIcon; ///< path to the icon to use for the toolbar button - only used if showOnToolsToolbar is set to true }; } // namespace AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Application/ToolsApplication.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Application/ToolsApplication.cpp index cdafa63eba..3c7495e836 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Application/ToolsApplication.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Application/ToolsApplication.cpp @@ -424,6 +424,8 @@ namespace AzToolsFramework ->Property("showInMenu", BehaviorValueProperty(&ViewPaneOptions::showInMenu)) ->Property("canHaveMultipleInstances", BehaviorValueProperty(&ViewPaneOptions::canHaveMultipleInstances)) ->Property("isPreview", BehaviorValueProperty(&ViewPaneOptions::isPreview)) + ->Property("showOnToolsToolbar", BehaviorValueProperty(&ViewPaneOptions::showOnToolsToolbar)) + ->Property("toolbarIcon", BehaviorValueProperty(&ViewPaneOptions::toolbarIcon)) ; behaviorContext->EBus("EditorRequestBus") diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/AssetBrowserComponent.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/AssetBrowserComponent.cpp index 483faf03c2..7da6f794d6 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/AssetBrowserComponent.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/AssetBrowserComponent.cpp @@ -102,7 +102,7 @@ namespace AzToolsFramework AzFramework::AssetCatalogEventBus::Handler::BusDisconnect(); AZ::TickBus::Handler::BusDisconnect(); AssetSystemBus::Handler::BusDisconnect(); - m_assetBrowserModel.release(); + m_assetBrowserModel.reset(); EntryCache::DestroyInstance(); } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ContainerEntity/ContainerEntityInterface.h b/Code/Framework/AzToolsFramework/AzToolsFramework/ContainerEntity/ContainerEntityInterface.h index 2d7d9dc511..67bf64d224 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ContainerEntity/ContainerEntityInterface.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ContainerEntity/ContainerEntityInterface.h @@ -58,6 +58,10 @@ namespace AzToolsFramework //! @return The highest closed entity container id if any, or entityId otherwise. virtual AZ::EntityId FindHighestSelectableEntity(AZ::EntityId entityId) const = 0; + //! Triggers the OnContainerEntityStatusChanged notifications for all registered containers, + //! allowing listeners to update correctly. + virtual void RefreshAllContainerEntities(AzFramework::EntityContextId entityContextId) const = 0; + //! Clears all open state information for Container Entities for the EntityContextId provided. //! Used when context is switched, for example in the case of a new root prefab being loaded //! in place of an old one. diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ContainerEntity/ContainerEntitySystemComponent.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ContainerEntity/ContainerEntitySystemComponent.cpp index 61b257a189..78cf84c6a1 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ContainerEntity/ContainerEntitySystemComponent.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ContainerEntity/ContainerEntitySystemComponent.cpp @@ -26,8 +26,12 @@ namespace AzToolsFramework AZ::Interface::Unregister(this); } - void ContainerEntitySystemComponent::Reflect([[maybe_unused]] AZ::ReflectContext* context) + void ContainerEntitySystemComponent::Reflect(AZ::ReflectContext* context) { + if (auto serializeContext = azrtti_cast(context)) + { + serializeContext->Class()->Version(1); + } } void ContainerEntitySystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided) @@ -138,6 +142,15 @@ namespace AzToolsFramework Clear(editorEntityContextId); } + void ContainerEntitySystemComponent::RefreshAllContainerEntities([[maybe_unused]] AzFramework::EntityContextId entityContextId) const + { + for (AZ::EntityId containerEntityId : m_containers) + { + ContainerEntityNotificationBus::Broadcast( + &ContainerEntityNotificationBus::Events::OnContainerEntityStatusChanged, containerEntityId, m_openContainers.contains(containerEntityId)); + } + } + ContainerEntityOperationResult ContainerEntitySystemComponent::Clear(AzFramework::EntityContextId entityContextId) { // We don't yet support multiple entity contexts, so only clear the default. diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ContainerEntity/ContainerEntitySystemComponent.h b/Code/Framework/AzToolsFramework/AzToolsFramework/ContainerEntity/ContainerEntitySystemComponent.h index 7a11e05096..68153a77cb 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ContainerEntity/ContainerEntitySystemComponent.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ContainerEntity/ContainerEntitySystemComponent.h @@ -47,6 +47,7 @@ namespace AzToolsFramework ContainerEntityOperationResult SetContainerOpen(AZ::EntityId entityId, bool open) override; bool IsContainerOpen(AZ::EntityId entityId) const override; AZ::EntityId FindHighestSelectableEntity(AZ::EntityId entityId) const override; + void RefreshAllContainerEntities(AzFramework::EntityContextId entityContextId) const override; ContainerEntityOperationResult Clear(AzFramework::EntityContextId entityContextId) override; bool IsUnderClosedContainerEntity(AZ::EntityId entityId) const override; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeSystemComponent.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeSystemComponent.cpp index 44ff603c0c..16640f4edf 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeSystemComponent.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeSystemComponent.cpp @@ -47,8 +47,12 @@ namespace AzToolsFramework AZ::Interface::Unregister(this); } - void FocusModeSystemComponent::Reflect([[maybe_unused]] AZ::ReflectContext* context) + void FocusModeSystemComponent::Reflect(AZ::ReflectContext* context) { + if (auto serializeContext = azrtti_cast(context)) + { + serializeContext->Class()->Version(1); + } } void FocusModeSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Input/QtEventToAzInputManager.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Input/QtEventToAzInputManager.cpp index 574470ad6e..7ee20997f9 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Input/QtEventToAzInputManager.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Input/QtEventToAzInputManager.cpp @@ -210,8 +210,8 @@ namespace AzToolsFramework m_enabled = enabled; if (!enabled) { - // Send an internal focus change event to reset our input state to fresh if we're disabled. - HandleFocusChange(nullptr); + // Clear input channels to reset our input state if we're disabled. + ClearInputChannels(nullptr); } } @@ -246,7 +246,7 @@ namespace AzToolsFramework if (eventType == QEvent::Type::MouseMove) { - // clear override cursor when moving outside of the viewport + // Clear override cursor when moving outside of the viewport const auto* mouseEvent = static_cast(event); if (m_overrideCursor && !m_sourceWidget->geometry().contains(m_sourceWidget->mapFromGlobal(mouseEvent->globalPos()))) { @@ -255,6 +255,13 @@ namespace AzToolsFramework } } + // If the application state changes (e.g. we have alt-tabbed or minimized the + // main editor window) then ensure all input channels are cleared + if (eventType == QEvent::ApplicationStateChange) + { + ClearInputChannels(event); + } + // Only accept mouse & key release events that originate from an object that is not our target widget, // as we don't want to erroneously intercept user input meant for another component. if (object != m_sourceWidget && eventType != QEvent::Type::KeyRelease && eventType != QEvent::Type::MouseButtonRelease) @@ -264,9 +271,6 @@ namespace AzToolsFramework if (eventType == QEvent::FocusIn || eventType == QEvent::FocusOut) { - // If our focus changes, go ahead and reset all input devices. - HandleFocusChange(event); - // If we focus in on the source widget and the mouse is contained in its // bounds, refresh the cached cursor position to ensure it is up to date (this // ensures cursor positions are refreshed correctly with context menu focus changes) @@ -451,7 +455,7 @@ namespace AzToolsFramework NotifyUpdateChannelIfNotIdle(cursorZChannel, wheelEvent); } - void QtEventToAzInputMapper::HandleFocusChange(QEvent* event) + void QtEventToAzInputMapper::ClearInputChannels(QEvent* event) { for (auto& channelData : m_channels) { diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Input/QtEventToAzInputManager.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Input/QtEventToAzInputManager.h index 4cb391e63b..4c4e09ea05 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Input/QtEventToAzInputManager.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Input/QtEventToAzInputManager.h @@ -138,8 +138,9 @@ namespace AzToolsFramework void HandleKeyEvent(QKeyEvent* keyEvent); // Handles mouse wheel events. void HandleWheelEvent(QWheelEvent* wheelEvent); - // Handles focus change events. - void HandleFocusChange(QEvent* event); + + // Clear all input channels (set all channel states to 'ended'). + void ClearInputChannels(QEvent* event); // Populates m_keyMappings. void InitializeKeyMappings(); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceToTemplateInterface.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceToTemplateInterface.h index a8c717d6b0..b944ef159a 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceToTemplateInterface.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceToTemplateInterface.h @@ -46,11 +46,10 @@ namespace AzToolsFramework //! Updates the template links (updating instances) for the given template and triggers propagation on its instances. //! @param providedPatch The patch to apply to the template. //! @param templateId The id of the template to update. - //! @param immediate An optional flag whether to apply the patch immediately (needed for Undo/Redos) or wait until next system tick. //! @param instanceToExclude An optional reference to an instance of the template being updated that should not be refreshes as part of propagation. //! Defaults to nullopt, which means that all instances will be refreshed. //! @return True if the template was patched correctly, false if the operation failed. - virtual bool PatchTemplate(PrefabDomValue& providedPatch, TemplateId templateId, bool immediate = false, InstanceOptionalReference instanceToExclude = AZStd::nullopt) = 0; + virtual bool PatchTemplate(PrefabDomValue& providedPatch, TemplateId templateId, InstanceOptionalReference instanceToExclude = AZStd::nullopt) = 0; virtual void ApplyPatchesToInstance(const AZ::EntityId& entityId, PrefabDom& patches, const Instance& instanceToAddPatches) = 0; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceToTemplatePropagator.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceToTemplatePropagator.cpp index 73acb9b8a4..6b281bcbae 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceToTemplatePropagator.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceToTemplatePropagator.cpp @@ -156,7 +156,7 @@ namespace AzToolsFramework } } - bool InstanceToTemplatePropagator::PatchTemplate(PrefabDomValue& providedPatch, TemplateId templateId, bool immediate, InstanceOptionalReference instanceToExclude) + bool InstanceToTemplatePropagator::PatchTemplate(PrefabDomValue& providedPatch, TemplateId templateId, InstanceOptionalReference instanceToExclude) { PrefabDom& templateDomReference = m_prefabSystemComponentInterface->FindTemplateDom(templateId); @@ -178,7 +178,7 @@ namespace AzToolsFramework (result.GetOutcome() != AZ::JsonSerializationResult::Outcomes::PartialSkip), "Some of the patches were not successfully applied."); m_prefabSystemComponentInterface->SetTemplateDirtyFlag(templateId, true); - m_prefabSystemComponentInterface->PropagateTemplateChanges(templateId, immediate, instanceToExclude); + m_prefabSystemComponentInterface->PropagateTemplateChanges(templateId, instanceToExclude); return true; } } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceToTemplatePropagator.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceToTemplatePropagator.h index 80fe7de8d5..75acb410c9 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceToTemplatePropagator.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceToTemplatePropagator.h @@ -33,7 +33,7 @@ namespace AzToolsFramework InstanceOptionalReference GetTopMostInstanceInHierarchy(AZ::EntityId entityId) override; - bool PatchTemplate(PrefabDomValue& providedPatch, TemplateId templateId, bool immediate = false, InstanceOptionalReference instanceToExclude = AZStd::nullopt) override; + bool PatchTemplate(PrefabDomValue& providedPatch, TemplateId templateId, InstanceOptionalReference instanceToExclude = AZStd::nullopt) override; void ApplyPatchesToInstance(const AZ::EntityId& entityId, PrefabDom& patches, const Instance& instanceToAddPatches) override; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceUpdateExecutor.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceUpdateExecutor.cpp index feea3ce25b..9ef74167a6 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceUpdateExecutor.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceUpdateExecutor.cpp @@ -52,7 +52,7 @@ namespace AzToolsFramework AZ::Interface::Unregister(this); } - void InstanceUpdateExecutor::AddTemplateInstancesToQueue(TemplateId instanceTemplateId, bool immediate, InstanceOptionalReference instanceToExclude) + void InstanceUpdateExecutor::AddTemplateInstancesToQueue(TemplateId instanceTemplateId, InstanceOptionalReference instanceToExclude) { auto findInstancesResult = m_templateInstanceMapperInterface->FindInstancesOwnedByTemplate(instanceTemplateId); @@ -79,11 +79,6 @@ namespace AzToolsFramework m_instancesUpdateQueue.emplace_back(instance); } } - - if (immediate) - { - UpdateTemplateInstancesInQueue(); - } } void InstanceUpdateExecutor::RemoveTemplateInstanceFromQueue(const Instance* instance) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceUpdateExecutor.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceUpdateExecutor.h index de2b483c4d..ee461eae88 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceUpdateExecutor.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceUpdateExecutor.h @@ -31,7 +31,7 @@ namespace AzToolsFramework explicit InstanceUpdateExecutor(int instanceCountToUpdateInBatch = 0); - void AddTemplateInstancesToQueue(TemplateId instanceTemplateId, bool immediate = false, InstanceOptionalReference instanceToExclude = AZStd::nullopt) override; + void AddTemplateInstancesToQueue(TemplateId instanceTemplateId, InstanceOptionalReference instanceToExclude = AZStd::nullopt) override; bool UpdateTemplateInstancesInQueue() override; virtual void RemoveTemplateInstanceFromQueue(const Instance* instance) override; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceUpdateExecutorInterface.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceUpdateExecutorInterface.h index 3b894efd21..8ad032e1d0 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceUpdateExecutorInterface.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceUpdateExecutorInterface.h @@ -23,7 +23,7 @@ namespace AzToolsFramework virtual ~InstanceUpdateExecutorInterface() = default; // Add all Instances of Template with given Id into a queue for updating them later. - virtual void AddTemplateInstancesToQueue(TemplateId instanceTemplateId, bool immediate = false, InstanceOptionalReference instanceToExclude = AZStd::nullopt) = 0; + virtual void AddTemplateInstancesToQueue(TemplateId instanceTemplateId, InstanceOptionalReference instanceToExclude = AZStd::nullopt) = 0; // Update Instances in the waiting queue. virtual bool UpdateTemplateInstancesInQueue() = 0; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.cpp index 5ba7382831..8098727177 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.cpp @@ -86,6 +86,45 @@ namespace AzToolsFramework::Prefab return AZ::Success(); } + PrefabFocusOperationResult PrefabFocusHandler::FocusOnParentOfFocusedPrefab( + [[maybe_unused]] AzFramework::EntityContextId entityContextId) + { + // If only one instance is in the hierarchy, this operation is invalid + size_t hierarchySize = m_instanceFocusHierarchy.size(); + if (hierarchySize <= 1) + { + return AZ::Failure( + AZStd::string("Prefab Focus Handler: Could not complete FocusOnParentOfFocusedPrefab operation while focusing on the root.")); + } + + // Retrieve parent of currently focused prefab. + InstanceOptionalReference parentInstance = m_instanceFocusHierarchy[hierarchySize - 2]; + + // Use container entity of parent Instance for focus operations. + AZ::EntityId entityId = parentInstance->get().GetContainerEntityId(); + + // Initialize Undo Batch object + ScopedUndoBatch undoBatch("Edit Prefab"); + + // Clear selection + { + const EntityIdList selectedEntities = EntityIdList{}; + auto selectionUndo = aznew SelectionCommand(selectedEntities, "Clear Selection"); + selectionUndo->SetParent(undoBatch.GetUndoBatch()); + ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequestBus::Events::SetSelectedEntities, selectedEntities); + } + + // Edit Prefab + { + auto editUndo = aznew PrefabFocusUndo("Edit Prefab"); + editUndo->Capture(entityId); + editUndo->SetParent(undoBatch.GetUndoBatch()); + FocusOnPrefabInstanceOwningEntityId(entityId); + } + + return AZ::Success(); + } + PrefabFocusOperationResult PrefabFocusHandler::FocusOnPathIndex([[maybe_unused]] AzFramework::EntityContextId entityContextId, int index) { if (index < 0 || index >= m_instanceFocusHierarchy.size()) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.h index 9decaed1ec..75b9666389 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.h @@ -50,6 +50,7 @@ namespace AzToolsFramework::Prefab // PrefabFocusPublicInterface overrides ... PrefabFocusOperationResult FocusOnOwningPrefab(AZ::EntityId entityId) override; + PrefabFocusOperationResult FocusOnParentOfFocusedPrefab(AzFramework::EntityContextId entityContextId) override; PrefabFocusOperationResult FocusOnPathIndex(AzFramework::EntityContextId entityContextId, int index) override; AZ::EntityId GetFocusedPrefabContainerEntityId(AzFramework::EntityContextId entityContextId) const override; bool IsOwningPrefabBeingFocused(AZ::EntityId entityId) const override; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusPublicInterface.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusPublicInterface.h index 5bd4c6b0f6..2fc9ef6b9a 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusPublicInterface.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusPublicInterface.h @@ -30,6 +30,9 @@ namespace AzToolsFramework::Prefab //! @param entityId The entityId of the entity whose owning instance we want the prefab system to focus on. virtual PrefabFocusOperationResult FocusOnOwningPrefab(AZ::EntityId entityId) = 0; + //! Set the focused prefab instance to the parent of the currently focused prefab instance. Supports undo/redo. + virtual PrefabFocusOperationResult FocusOnParentOfFocusedPrefab(AzFramework::EntityContextId entityContextId) = 0; + //! Set the focused prefab instance to the instance at position index of the current path. Supports undo/redo. //! @param index The index of the instance in the current path that we want the prefab system to focus on. virtual PrefabFocusOperationResult FocusOnPathIndex(AzFramework::EntityContextId entityContextId, int index) = 0; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp index d976c91c3e..5ac9f772dc 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp @@ -12,11 +12,12 @@ #include #include +#include #include #include #include -#include #include +#include #include #include #include @@ -565,6 +566,7 @@ namespace AzToolsFramework parentId = m_prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(editorEntityContextId); } + // If the parent entity isn't owned by a prefab instance, bail. InstanceOptionalReference owningInstanceOfParentEntity = GetOwnerInstanceByEntityId(parentId); if (!owningInstanceOfParentEntity) { @@ -572,6 +574,14 @@ namespace AzToolsFramework "Cannot add entity because the owning instance of parent entity with id '%llu' could not be found.", static_cast(parentId))); } + + // If the parent entity is a closed container, bail. + if (auto containerEntityInterface = AZ::Interface::Get(); !containerEntityInterface->IsContainerOpen(parentId)) + { + return AZ::Failure(AZStd::string::format( + "Cannot add entity because the parent entity (id '%llu') is a closed container entity.", + static_cast(parentId))); + } EntityAlias entityAlias = Instance::GenerateEntityAlias(); @@ -1051,7 +1061,7 @@ namespace AzToolsFramework DuplicateNestedEntitiesInInstance(commonOwningInstance->get(), entities, instanceDomAfter, duplicatedEntityAndInstanceIds, duplicateEntityAliasMap); - PrefabUndoInstance* command = aznew PrefabUndoInstance("Entity/Instance duplication", false); + PrefabUndoInstance* command = aznew PrefabUndoInstance("Entity/Instance duplication"); command->SetParent(undoBatch.GetUndoBatch()); command->Capture(instanceDomBefore, instanceDomAfter, commonOwningInstance->get().GetTemplateId()); command->Redo(); @@ -1322,7 +1332,7 @@ namespace AzToolsFramework Prefab::PrefabDom instanceDomAfter; m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomAfter, parentInstance); - PrefabUndoInstance* command = aznew PrefabUndoInstance("Instance detachment", false); + PrefabUndoInstance* command = aznew PrefabUndoInstance("Instance detachment"); command->Capture(instanceDomBefore, instanceDomAfter, parentTemplateId); command->SetParent(undoBatch.GetUndoBatch()); { diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.cpp index c930c66786..1f00b952b1 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.cpp @@ -159,10 +159,10 @@ namespace AzToolsFramework newInstance->SetTemplateId(newTemplateId); } } - - void PrefabSystemComponent::PropagateTemplateChanges(TemplateId templateId, bool immediate, InstanceOptionalReference instanceToExclude) + + void PrefabSystemComponent::PropagateTemplateChanges(TemplateId templateId, InstanceOptionalReference instanceToExclude) { - UpdatePrefabInstances(templateId, immediate, instanceToExclude); + UpdatePrefabInstances(templateId, instanceToExclude); auto templateIdToLinkIdsIterator = m_templateToLinkIdsMap.find(templateId); if (templateIdToLinkIdsIterator != m_templateToLinkIdsMap.end()) @@ -191,9 +191,9 @@ namespace AzToolsFramework } } - void PrefabSystemComponent::UpdatePrefabInstances(TemplateId templateId, bool immediate, InstanceOptionalReference instanceToExclude) + void PrefabSystemComponent::UpdatePrefabInstances(TemplateId templateId, InstanceOptionalReference instanceToExclude) { - m_instanceUpdateExecutor.AddTemplateInstancesToQueue(templateId, immediate, instanceToExclude); + m_instanceUpdateExecutor.AddTemplateInstancesToQueue(templateId, instanceToExclude); } void PrefabSystemComponent::UpdateLinkedInstances(AZStd::queue& linkIdsQueue) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.h index 480bb83121..7b18d64b08 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.h @@ -231,17 +231,16 @@ namespace AzToolsFramework */ void UpdatePrefabTemplate(TemplateId templateId, const PrefabDom& updatedDom) override; - void PropagateTemplateChanges(TemplateId templateId, bool immediate = false, InstanceOptionalReference instanceToExclude = AZStd::nullopt) override; + void PropagateTemplateChanges(TemplateId templateId, InstanceOptionalReference instanceToExclude = AZStd::nullopt) override; /** * Updates all Instances owned by a Template. * * @param templateId The id of the Template owning Instances to update. - * @param immediate An optional flag whether to apply the patch immediately (needed for Undo/Redos) or wait until next system tick. - * @param instanceToExclude An optional reference to an instance of the template being updated that should not be refreshes as part of propagation. - * Defaults to nullopt, which means that all instances will be refreshed. + * @param instanceToExclude An optional reference to an instance of the template being updated that should not be refreshed + * as part of propagation.Defaults to nullopt, which means that all instances will be refreshed. */ - void UpdatePrefabInstances(TemplateId templateId, bool immediate = false, InstanceOptionalReference instanceToExclude = AZStd::nullopt); + void UpdatePrefabInstances(TemplateId templateId, InstanceOptionalReference instanceToExclude = AZStd::nullopt); private: AZ_DISABLE_COPY_MOVE(PrefabSystemComponent); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponentInterface.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponentInterface.h index 66d85ccee9..761d66fd52 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponentInterface.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponentInterface.h @@ -67,7 +67,7 @@ namespace AzToolsFramework virtual PrefabDom& FindTemplateDom(TemplateId templateId) = 0; virtual void UpdatePrefabTemplate(TemplateId templateId, const PrefabDom& updatedDom) = 0; - virtual void PropagateTemplateChanges(TemplateId templateId, bool immediate = false, InstanceOptionalReference instanceToExclude = AZStd::nullopt) = 0; + virtual void PropagateTemplateChanges(TemplateId templateId, InstanceOptionalReference instanceToExclude = AZStd::nullopt) = 0; virtual AZStd::unique_ptr InstantiatePrefab( AZ::IO::PathView filePath, InstanceOptionalReference parent = AZStd::nullopt) = 0; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabUndo.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabUndo.cpp index 1c2230fa83..385e9b149b 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabUndo.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabUndo.cpp @@ -17,16 +17,17 @@ namespace AzToolsFramework { PrefabUndoBase::PrefabUndoBase(const AZStd::string& undoOperationName) : UndoSystem::URSequencePoint(undoOperationName) + , m_changed(true) + , m_templateId(InvalidTemplateId) { m_instanceToTemplateInterface = AZ::Interface::Get(); AZ_Assert(m_instanceToTemplateInterface, "Failed to grab instance to template interface"); } //PrefabInstanceUndo - PrefabUndoInstance::PrefabUndoInstance(const AZStd::string& undoOperationName, bool useImmediatePropagation) + PrefabUndoInstance::PrefabUndoInstance(const AZStd::string& undoOperationName) : PrefabUndoBase(undoOperationName) { - m_useImmediatePropagation = useImmediatePropagation; } void PrefabUndoInstance::Capture( @@ -42,12 +43,12 @@ namespace AzToolsFramework void PrefabUndoInstance::Undo() { - m_instanceToTemplateInterface->PatchTemplate(m_undoPatch, m_templateId, m_useImmediatePropagation); + m_instanceToTemplateInterface->PatchTemplate(m_undoPatch, m_templateId); } void PrefabUndoInstance::Redo() { - m_instanceToTemplateInterface->PatchTemplate(m_redoPatch, m_templateId, m_useImmediatePropagation); + m_instanceToTemplateInterface->PatchTemplate(m_redoPatch, m_templateId); } @@ -90,7 +91,7 @@ namespace AzToolsFramework void PrefabUndoEntityUpdate::Undo() { [[maybe_unused]] bool isPatchApplicationSuccessful = - m_instanceToTemplateInterface->PatchTemplate(m_undoPatch, m_templateId, true); + m_instanceToTemplateInterface->PatchTemplate(m_undoPatch, m_templateId); AZ_Error( "Prefab", isPatchApplicationSuccessful, @@ -101,7 +102,7 @@ namespace AzToolsFramework void PrefabUndoEntityUpdate::Redo() { [[maybe_unused]] bool isPatchApplicationSuccessful = - m_instanceToTemplateInterface->PatchTemplate(m_redoPatch, m_templateId, true); + m_instanceToTemplateInterface->PatchTemplate(m_redoPatch, m_templateId); AZ_Error( "Prefab", isPatchApplicationSuccessful, @@ -112,7 +113,7 @@ namespace AzToolsFramework void PrefabUndoEntityUpdate::Redo(InstanceOptionalReference instanceToExclude) { [[maybe_unused]] bool isPatchApplicationSuccessful = - m_instanceToTemplateInterface->PatchTemplate(m_redoPatch, m_templateId, false, instanceToExclude); + m_instanceToTemplateInterface->PatchTemplate(m_redoPatch, m_templateId, instanceToExclude); AZ_Error( "Prefab", isPatchApplicationSuccessful, @@ -328,7 +329,7 @@ namespace AzToolsFramework //propagate the link changes link->get().UpdateTarget(); - m_prefabSystemComponentInterface->PropagateTemplateChanges(link->get().GetTargetTemplateId(), false, instanceToExclude); + m_prefabSystemComponentInterface->PropagateTemplateChanges(link->get().GetTargetTemplateId(), instanceToExclude); //mark as dirty m_prefabSystemComponentInterface->SetTemplateDirtyFlag(link->get().GetTargetTemplateId(), true); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabUndo.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabUndo.h index bc0b86a8c6..0af94f86cc 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabUndo.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabUndo.h @@ -29,15 +29,14 @@ namespace AzToolsFramework bool Changed() const override { return m_changed; } protected: - TemplateId m_templateId = InvalidTemplateId; + TemplateId m_templateId; PrefabDom m_redoPatch; PrefabDom m_undoPatch; InstanceToTemplateInterface* m_instanceToTemplateInterface = nullptr; - bool m_changed = true; - bool m_useImmediatePropagation = true; + bool m_changed; }; //! handles the addition and removal of entities from instances @@ -45,7 +44,7 @@ namespace AzToolsFramework : public PrefabUndoBase { public: - explicit PrefabUndoInstance(const AZStd::string& undoOperationName, bool useImmediatePropagation = true); + explicit PrefabUndoInstance(const AZStd::string& undoOperationName); void Capture( const PrefabDom& initialState, diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabUndoHelpers.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabUndoHelpers.cpp index 31a0c60bcb..9c44fc7ffd 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabUndoHelpers.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabUndoHelpers.cpp @@ -23,7 +23,7 @@ namespace AzToolsFramework PrefabDom instanceDomAfterUpdate; PrefabDomUtils::StoreInstanceInPrefabDom(instance, instanceDomAfterUpdate); - PrefabUndoInstance* state = aznew Prefab::PrefabUndoInstance(undoMessage, false); + PrefabUndoInstance* state = aznew Prefab::PrefabUndoInstance(undoMessage); state->Capture(instanceDomBeforeUpdate, instanceDomAfterUpdate, instance.GetTemplateId()); state->SetParent(undoBatch); state->Redo(); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.cpp index a5f1e29942..a68a72f00a 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.cpp @@ -43,6 +43,7 @@ #include #include #include +#include #include #include #include @@ -764,10 +765,21 @@ namespace AzToolsFramework return canHandleData; } - bool EntityOutlinerListModel::CanDropMimeDataAssets(const QMimeData* data, Qt::DropAction /*action*/, int /*row*/, int /*column*/, const QModelIndex& /*parent*/) const + bool EntityOutlinerListModel::CanDropMimeDataAssets( + const QMimeData* data, + [[maybe_unused]] Qt::DropAction action, + [[maybe_unused]] int row, + [[maybe_unused]] int column, + const QModelIndex& parent) const { - using namespace AzToolsFramework; - + // Disable dropping assets on closed container entities. + AZ::EntityId parentId = GetEntityFromIndex(parent); + if (auto containerEntityInterface = AZ::Interface::Get(); + !containerEntityInterface->IsContainerOpen(parentId)) + { + return false; + } + if (data->hasFormat(AssetBrowser::AssetBrowserEntry::GetMimeType())) { return DecodeAssetMimeData(data); @@ -788,8 +800,15 @@ namespace AzToolsFramework return false; } + // If the parent entity is a closed container, bail. + if (auto containerEntityInterface = AZ::Interface::Get(); + !containerEntityInterface->IsContainerOpen(assignParentId)) + { + return false; + } + // Source Files - if (sourceFiles.size() > 0) + if (!sourceFiles.empty()) { // Get position (center of viewport). If no viewport is available, (0,0,0) will be used. AZ::Vector3 viewportCenterPosition = AZ::Vector3::CreateZero(); @@ -943,13 +962,15 @@ namespace AzToolsFramework { return false; } - + + const int count = rowCount(parent); AZ::EntityId newParentId = GetEntityFromIndex(parent); - AZ::EntityId beforeEntityId = GetEntityFromIndex(index(row, 0, parent)); + AZ::EntityId beforeEntityId = (row >= 0 && row < count) ? GetEntityFromIndex(index(row, 0, parent)) : AZ::EntityId(); EntityIdList topLevelEntityIds; topLevelEntityIds.reserve(entityIdListContainer.m_entityIds.size()); ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequestBus::Events::FindTopLevelEntityIdsInactive, entityIdListContainer.m_entityIds, topLevelEntityIds); - if (!ReparentEntities(newParentId, topLevelEntityIds, beforeEntityId)) + const auto appendActionForInvalid = newParentId.IsValid() && (row >= count) ? AppendEnd : AppendBeginning; + if (!ReparentEntities(newParentId, topLevelEntityIds, beforeEntityId, appendActionForInvalid)) { return false; } @@ -971,6 +992,12 @@ namespace AzToolsFramework return false; } + // If the new parent is a closed container, bail. + if (auto containerEntityInterface = AZ::Interface::Get(); !containerEntityInterface->IsContainerOpen(newParentId)) + { + return false; + } + // Ignore entities not owned by the editor context. It is assumed that all entities belong // to the same context since multiple selection doesn't span across views. for (const AZ::EntityId& entityId : selectedEntityIds) @@ -1046,7 +1073,7 @@ namespace AzToolsFramework return true; } - bool EntityOutlinerListModel::ReparentEntities(const AZ::EntityId& newParentId, const EntityIdList &selectedEntityIds, const AZ::EntityId& beforeEntityId) + bool EntityOutlinerListModel::ReparentEntities(const AZ::EntityId& newParentId, const EntityIdList &selectedEntityIds, const AZ::EntityId& beforeEntityId, ReparentForInvalid forInvalid) { AZ_PROFILE_FUNCTION(AzToolsFramework); if (!CanReparentEntities(newParentId, selectedEntityIds)) @@ -1056,10 +1083,18 @@ namespace AzToolsFramework m_isFilterDirty = true; - ScopedUndoBatch undo("Reparent Entities"); //capture child entity order before re-parent operation, which will automatically add order info if not present EntityOrderArray entityOrderArray = GetEntityChildOrder(newParentId); + //search for the insertion entity in the order array + const auto beforeEntityItr = AZStd::find(entityOrderArray.begin(), entityOrderArray.end(), beforeEntityId); + const bool hasInvalidIndex = beforeEntityItr == entityOrderArray.end(); + if (hasInvalidIndex && forInvalid == None) + { + return false; + } + + ScopedUndoBatch undo("Reparent Entities"); // The new parent is dirty due to sort change(s) undo.MarkEntityDirty(GetEntityIdForSortInfo(newParentId)); @@ -1088,9 +1123,7 @@ namespace AzToolsFramework } } - //search for the insertion entity in the order array - auto beforeEntityItr = AZStd::find(entityOrderArray.begin(), entityOrderArray.end(), beforeEntityId); - + //replace order info matching selection with bad values rather than remove to preserve layout for (auto& id : entityOrderArray) { @@ -1100,17 +1133,25 @@ namespace AzToolsFramework } } - if (newParentId.IsValid()) + //if adding to a valid parent entity, insert at the found entity location or at the head/tail depending on placeAtTail flag + if (hasInvalidIndex) { - //if adding to a valid parent entity, insert at the found entity location or at the head of the container - auto insertItr = beforeEntityItr != entityOrderArray.end() ? beforeEntityItr : entityOrderArray.begin(); - entityOrderArray.insert(insertItr, processedEntityIds.begin(), processedEntityIds.end()); - } - else + switch(forInvalid) + { + case AppendEnd: + entityOrderArray.insert(entityOrderArray.end(), processedEntityIds.begin(), processedEntityIds.end()); + break; + case AppendBeginning: + entityOrderArray.insert(entityOrderArray.begin(), processedEntityIds.begin(), processedEntityIds.end()); + break; + default: + AZ_Assert(false, "Unexpected type for ReparentForInvalid"); + break; + } + } + else { - //if adding to an invalid parent entity (the root), insert at the found entity location or at the tail of the container - auto insertItr = beforeEntityItr != entityOrderArray.end() ? beforeEntityItr : entityOrderArray.end(); - entityOrderArray.insert(insertItr, processedEntityIds.begin(), processedEntityIds.end()); + entityOrderArray.insert(beforeEntityItr, processedEntityIds.begin(), processedEntityIds.end()); } //remove placeholder entity ids @@ -2139,20 +2180,9 @@ namespace AzToolsFramework void EntityOutlinerItemDelegate::PaintAncestorForegrounds(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { - // Go through ancestors and add them to the stack - AZStd::stack handlerStack; - + // Ancestor foregrounds are painted on top of the childrens'. for (QModelIndex ancestorIndex = index.parent(); ancestorIndex.isValid(); ancestorIndex = ancestorIndex.parent()) { - handlerStack.push(ancestorIndex); - } - - // Apply the ancestor overrides from top to bottom - while (!handlerStack.empty()) - { - QModelIndex ancestorIndex = handlerStack.top(); - handlerStack.pop(); - AZ::EntityId ancestorEntityId(ancestorIndex.data(EntityOutlinerListModel::EntityIdRole).value()); auto ancestorUiHandler = m_editorEntityFrameworkInterface->GetHandler(ancestorEntityId); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.hxx b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.hxx index 8176867038..0a46ee4850 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.hxx +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.hxx @@ -72,6 +72,13 @@ namespace AzToolsFramework ColumnCount //!< Total number of columns }; + enum ReparentForInvalid + { + None, //!< For an invalid location the entity does not change location + AppendEnd, //!< Append Item to end of target parent list + AppendBeginning, //!< Append Item to the beginning of target parent list + }; + // Note: the ColumnSortIndex column isn't shown, hence the -1 and the need for a separate counter. // A wrong column count number causes refresh issues and hover mismatch on model update. static const int VisibleColumnCount = ColumnCount - 1; @@ -162,7 +169,7 @@ namespace AzToolsFramework // Buffer Processing Slots - These are called using single-shot events when the buffers begin to fill. bool CanReparentEntities(const AZ::EntityId& newParentId, const EntityIdList& selectedEntityIds) const; - bool ReparentEntities(const AZ::EntityId& newParentId, const EntityIdList& selectedEntityIds, const AZ::EntityId& beforeEntityId = AZ::EntityId()); + bool ReparentEntities(const AZ::EntityId& newParentId, const EntityIdList& selectedEntityIds, const AZ::EntityId& beforeEntityId = AZ::EntityId(), ReparentForInvalid forInvalid = None); //! Use the current filter setting and re-evaluate the filter. void InvalidateFilter(); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/LevelRootUiHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/LevelRootUiHandler.cpp index bb6bdf0ebd..b34bbff298 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/LevelRootUiHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/LevelRootUiHandler.cpp @@ -8,6 +8,7 @@ #include +#include #include #include @@ -92,4 +93,16 @@ namespace AzToolsFramework painter->drawLine(rect.bottomLeft(), rect.bottomRight()); painter->restore(); } + + bool LevelRootUiHandler::OnEntityDoubleClick(AZ::EntityId entityId) const + { + if (auto prefabFocusPublicInterface = AZ::Interface::Get(); + !prefabFocusPublicInterface->IsOwningPrefabBeingFocused(entityId)) + { + prefabFocusPublicInterface->FocusOnOwningPrefab(entityId); + } + + // Don't propagate event. + return true; + } } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/LevelRootUiHandler.h b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/LevelRootUiHandler.h index 1e485572b8..3f7f56670e 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/LevelRootUiHandler.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/LevelRootUiHandler.h @@ -33,6 +33,7 @@ namespace AzToolsFramework bool CanToggleLockVisibility(AZ::EntityId entityId) const override; bool CanRename(AZ::EntityId entityId) const override; void PaintItemBackground(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override; + bool OnEntityDoubleClick(AZ::EntityId entityId) const override; private: Prefab::PrefabPublicInterface* m_prefabPublicInterface = nullptr; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp index ae0d18b077..aa6d82e634 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include #include @@ -54,6 +55,7 @@ #include #include #include +#include #include #include @@ -61,6 +63,8 @@ namespace AzToolsFramework { namespace Prefab { + AzFramework::EntityContextId PrefabIntegrationManager::s_editorEntityContextId = AzFramework::EntityContextId::CreateNull(); + ContainerEntityInterface* PrefabIntegrationManager::s_containerEntityInterface = nullptr; EditorEntityUiInterface* PrefabIntegrationManager::s_editorEntityUiInterface = nullptr; PrefabFocusPublicInterface* PrefabIntegrationManager::s_prefabFocusPublicInterface = nullptr; @@ -136,6 +140,9 @@ namespace AzToolsFramework return; } + // Get EditorEntityContextId + EditorEntityContextRequestBus::BroadcastResult(s_editorEntityContextId, &EditorEntityContextRequests::GetEditorEntityContextId); + // Initialize Editor functionality for the Prefab Focus Handler auto prefabFocusInterface = AZ::Interface::Get(); prefabFocusInterface->InitializeEditorInterfaces(); @@ -145,10 +152,16 @@ namespace AzToolsFramework PrefabInstanceContainerNotificationBus::Handler::BusConnect(); AZ::Interface::Register(this); AssetBrowser::AssetBrowserSourceDropBus::Handler::BusConnect(s_prefabFileExtension); + EditorEntityContextNotificationBus::Handler::BusConnect(); + + InitializeShortcuts(); } PrefabIntegrationManager::~PrefabIntegrationManager() { + UninitializeShortcuts(); + + EditorEntityContextNotificationBus::Handler::BusDisconnect(); AssetBrowser::AssetBrowserSourceDropBus::Handler::BusDisconnect(); AZ::Interface::Unregister(this); PrefabInstanceContainerNotificationBus::Handler::BusDisconnect(); @@ -161,6 +174,74 @@ namespace AzToolsFramework PrefabUserSettings::Reflect(context); } + void PrefabIntegrationManager::InitializeShortcuts() + { + // Open/Edit Prefab (+) + // We also support = to enable easier editing on compact US keyboards. + { + m_actions.emplace_back(AZStd::make_unique(nullptr)); + + m_actions.back()->setShortcuts({ QKeySequence(Qt::Key_Plus), QKeySequence(Qt::Key_Equal) }); + m_actions.back()->setText("Open/Edit Prefab"); + m_actions.back()->setStatusTip("Edit the prefab in focus mode."); + + QObject::connect( + m_actions.back().get(), &QAction::triggered, m_actions.back().get(), + [] + { + AzToolsFramework::EntityIdList selectedEntities; + AzToolsFramework::ToolsApplicationRequestBus::BroadcastResult( + selectedEntities, &AzToolsFramework::ToolsApplicationRequests::GetSelectedEntities); + + if (selectedEntities.size() != 1) + { + return; + } + + AZ::EntityId selectedEntity = selectedEntities[0]; + + if (!s_prefabPublicInterface->IsInstanceContainerEntity(selectedEntity)) + { + return; + } + + if (!s_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(selectedEntity)) + { + ContextMenu_EditPrefab(selectedEntity); + } + }); + + EditorActionRequestBus::Broadcast( + &EditorActionRequests::AddActionViaBusCrc, AZ_CRC_CE("com.o3de.action.editortransform.prefabopen"), + m_actions.back().get()); + } + + // Close Prefab (-) + { + m_actions.emplace_back(AZStd::make_unique(nullptr)); + + m_actions.back()->setShortcuts({ QKeySequence(Qt::Key_Minus) }); + m_actions.back()->setText("Close Prefab"); + m_actions.back()->setStatusTip("Close focus mode for this prefab and move one level up."); + + QObject::connect( + m_actions.back().get(), &QAction::triggered, m_actions.back().get(), + [] + { + ContextMenu_ClosePrefab(); + }); + + EditorActionRequestBus::Broadcast( + &EditorActionRequests::AddActionViaBusCrc, AZ_CRC_CE("com.o3de.action.editortransform.prefabclose"), + m_actions.back().get()); + } + } + + void PrefabIntegrationManager::UninitializeShortcuts() + { + m_actions.clear(); + } + int PrefabIntegrationManager::GetMenuPosition() const { return aznumeric_cast(EditorContextMenuOrdering::MIDDLE); @@ -181,16 +262,13 @@ namespace AzToolsFramework AzFramework::ApplicationRequests::Bus::BroadcastResult( prefabWipFeaturesEnabled, &AzFramework::ApplicationRequests::ArePrefabWipFeaturesEnabled); - auto editorEntityContextId = AzFramework::EntityContextId::CreateNull(); - EditorEntityContextRequestBus::BroadcastResult(editorEntityContextId, &EditorEntityContextRequests::GetEditorEntityContextId); - // Create Prefab { if (!selectedEntities.empty()) { // Hide if the only selected entity is the Focused Instance Container if (selectedEntities.size() > 1 || - selectedEntities[0] != s_prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(editorEntityContextId)) + selectedEntities[0] != s_prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(s_editorEntityContextId)) { bool layerInSelection = false; @@ -254,17 +332,30 @@ namespace AzToolsFramework if (s_prefabPublicInterface->IsInstanceContainerEntity(selectedEntity)) { - // Edit Prefab if (!s_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(selectedEntity)) { - QAction* editAction = menu->addAction(QObject::tr("Edit Prefab")); + // Edit Prefab + QAction* editAction = menu->addAction(QObject::tr("Open/Edit Prefab")); + editAction->setShortcut(QKeySequence(Qt::Key_Plus)); editAction->setToolTip(QObject::tr("Edit the prefab in focus mode.")); QObject::connect(editAction, &QAction::triggered, editAction, [selectedEntity] { ContextMenu_EditPrefab(selectedEntity); }); + } + else + { + // Close Prefab + QAction* closeAction = menu->addAction(QObject::tr("Close Prefab")); + closeAction->setShortcut(QKeySequence(Qt::Key_Minus)); + closeAction->setToolTip(QObject::tr("Close focus mode for this prefab and move one level up.")); - itemWasShown = true; + QObject::connect( + closeAction, &QAction::triggered, closeAction, + [] + { + ContextMenu_ClosePrefab(); + }); } // Save Prefab @@ -279,9 +370,9 @@ namespace AzToolsFramework QObject::connect(saveAction, &QAction::triggered, saveAction, [selectedEntity] { ContextMenu_SavePrefab(selectedEntity); }); - - itemWasShown = true; } + + itemWasShown = true; } } } @@ -295,7 +386,8 @@ namespace AzToolsFramework QObject::connect(deleteAction, &QAction::triggered, deleteAction, [] { ContextMenu_DeleteSelected(); }); if (selectedEntities.empty() || - (selectedEntities.size() == 1 && selectedEntities[0] == s_prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(editorEntityContextId))) + (selectedEntities.size() == 1 && + selectedEntities[0] == s_prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(s_editorEntityContextId))) { deleteAction->setDisabled(true); } @@ -306,7 +398,7 @@ namespace AzToolsFramework AZ::EntityId selectedEntityId = selectedEntities[0]; if (s_prefabPublicInterface->IsInstanceContainerEntity(selectedEntityId) && - selectedEntityId != s_prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(editorEntityContextId)) + selectedEntityId != s_prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(s_editorEntityContextId)) { QAction* detachPrefabAction = menu->addAction(QObject::tr("Detach Prefab...")); QObject::connect( @@ -334,6 +426,24 @@ namespace AzToolsFramework } } + void PrefabIntegrationManager::OnStartPlayInEditorBegin() + { + // Focus on the root prefab (AZ::EntityId() will default to it) + s_prefabFocusPublicInterface->FocusOnOwningPrefab(AZ::EntityId()); + } + + void PrefabIntegrationManager::OnStopPlayInEditor() + { + // Refresh all containers when leaving Game Mode to ensure everything is synced. + QTimer::singleShot( + 0, + [&]() + { + s_containerEntityInterface->RefreshAllContainerEntities(s_editorEntityContextId); + } + ); + } + void PrefabIntegrationManager::ContextMenu_CreatePrefab(AzToolsFramework::EntityIdList selectedEntities) { // Save a reference to our currently active window since it will be @@ -343,12 +453,9 @@ namespace AzToolsFramework const AZStd::string prefabFilesPath = "@projectroot@/Prefabs"; // Remove focused instance container entity if it's part of the list - auto editorEntityContextId = AzFramework::EntityContextId::CreateNull(); - EditorEntityContextRequestBus::BroadcastResult(editorEntityContextId, &EditorEntityContextRequests::GetEditorEntityContextId); - auto focusedContainerIter = AZStd::find( selectedEntities.begin(), selectedEntities.end(), - s_prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(editorEntityContextId)); + s_prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(s_editorEntityContextId)); if (focusedContainerIter != selectedEntities.end()) { selectedEntities.erase(focusedContainerIter); @@ -500,6 +607,11 @@ namespace AzToolsFramework } } + void PrefabIntegrationManager::ContextMenu_ClosePrefab() + { + s_prefabFocusPublicInterface->FocusOnParentOfFocusedPrefab(s_editorEntityContextId); + } + void PrefabIntegrationManager::ContextMenu_EditPrefab(AZ::EntityId containerEntity) { s_prefabFocusPublicInterface->FocusOnOwningPrefab(containerEntity); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.h b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.h index e8c10c150a..808a0c2408 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.h @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -56,6 +57,7 @@ namespace AzToolsFramework , public PrefabInstanceContainerNotificationBus::Handler , public PrefabIntegrationInterface , public QObject + , private EditorEntityContextNotificationBus::Handler { public: AZ_CLASS_ALLOCATOR(PrefabIntegrationManager, AZ::SystemAllocator, 0); @@ -76,6 +78,10 @@ namespace AzToolsFramework // EntityOutlinerSourceDropHandlingBus overrides ... void HandleSourceFileType(AZStd::string_view sourceFilePath, AZ::EntityId parentId, AZ::Vector3 position) const override; + // EditorEntityContextNotificationBus overrides ... + void OnStartPlayInEditorBegin() override; + void OnStopPlayInEditor() override; + // PrefabInstanceContainerNotificationBus overrides ... void OnPrefabComponentActivate(AZ::EntityId entityId) override; void OnPrefabComponentDeactivate(AZ::EntityId entityId) override; @@ -96,11 +102,16 @@ namespace AzToolsFramework static void ContextMenu_CreatePrefab(AzToolsFramework::EntityIdList selectedEntities); static void ContextMenu_InstantiatePrefab(); static void ContextMenu_InstantiateProceduralPrefab(); + static void ContextMenu_ClosePrefab(); static void ContextMenu_EditPrefab(AZ::EntityId containerEntity); static void ContextMenu_SavePrefab(AZ::EntityId containerEntity); static void ContextMenu_DeleteSelected(); static void ContextMenu_DetachPrefab(AZ::EntityId containerEntity); + // Shortcut setup handlers + void InitializeShortcuts(); + void UninitializeShortcuts(); + // Prompt and resolve dialogs static bool QueryUserForPrefabSaveLocation( const AZStd::string& suggestedName, const char* initialTargetDirectory, AZ::u32 prefabUserSettingsId, QWidget* activeWindow, @@ -140,7 +151,10 @@ namespace AzToolsFramework AZStd::unique_ptr ConstructSavePrefabDialog(TemplateId templateId, bool useSaveAllPrefabsPreference); void SavePrefabsInDialog(QDialog* unsavedPrefabsDialog); + AZStd::vector> m_actions; + static const AZStd::string s_prefabFileExtension; + static AzFramework::EntityContextId s_editorEntityContextId; static ContainerEntityInterface* s_containerEntityInterface; static EditorEntityUiInterface* s_editorEntityUiInterface; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.cpp index 447f94fc15..8b56b26508 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.cpp @@ -21,6 +21,8 @@ namespace AzToolsFramework { + AzFramework::EntityContextId PrefabUiHandler::s_editorEntityContextId = AzFramework::EntityContextId::CreateNull(); + const QColor PrefabUiHandler::m_backgroundColor = QColor("#444444"); const QColor PrefabUiHandler::m_backgroundHoverColor = QColor("#5A5A5A"); const QColor PrefabUiHandler::m_backgroundSelectedColor = QColor("#656565"); @@ -47,6 +49,9 @@ namespace AzToolsFramework AZ_Assert(false, "PrefabUiHandler - could not get PrefabFocusPublicInterface on PrefabUiHandler construction."); return; } + + // Get EditorEntityContextId + EditorEntityContextRequestBus::BroadcastResult(s_editorEntityContextId, &EditorEntityContextRequests::GetEditorEntityContextId); } QString PrefabUiHandler::GenerateItemInfoString(AZ::EntityId entityId) const @@ -180,7 +185,7 @@ namespace AzToolsFramework painter->restore(); } - void PrefabUiHandler::PaintDescendantBackground(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index, + void PrefabUiHandler::PaintDescendantForeground(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index, const QModelIndex& descendantIndex) const { if (!painter) @@ -425,19 +430,23 @@ namespace AzToolsFramework if (m_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(entityId)) { - auto editorEntityContextId = AzFramework::EntityContextId::CreateNull(); - EditorEntityContextRequestBus::BroadcastResult(editorEntityContextId, &EditorEntityContextRequests::GetEditorEntityContextId); - - // Go one level up. - int length = m_prefabFocusPublicInterface->GetPrefabFocusPathLength(editorEntityContextId); - m_prefabFocusPublicInterface->FocusOnPathIndex(editorEntityContextId, length - 2); + // Close this prefab and focus on the parent + m_prefabFocusPublicInterface->FocusOnParentOfFocusedPrefab(s_editorEntityContextId); } } bool PrefabUiHandler::OnEntityDoubleClick(AZ::EntityId entityId) const { - // Focus on this prefab - m_prefabFocusPublicInterface->FocusOnOwningPrefab(entityId); + if (!m_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(entityId)) + { + // Focus on this prefab + m_prefabFocusPublicInterface->FocusOnOwningPrefab(entityId); + } + else + { + // Close this prefab and focus on the parent + m_prefabFocusPublicInterface->FocusOnParentOfFocusedPrefab(s_editorEntityContextId); + } // Don't propagate event. return true; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.h b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.h index 6c78afc5b7..bb1c646dbe 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.h @@ -10,6 +10,8 @@ #include +#include + namespace AzToolsFramework { @@ -34,9 +36,12 @@ namespace AzToolsFramework QString GenerateItemTooltip(AZ::EntityId entityId) const override; QIcon GenerateItemIcon(AZ::EntityId entityId) const override; void PaintItemBackground(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override; - void PaintDescendantBackground(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index, - const QModelIndex& descendantIndex) const override; void PaintItemForeground(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override; + void PaintDescendantForeground( + QPainter* painter, + const QStyleOptionViewItem& option, + const QModelIndex& index, + const QModelIndex& descendantIndex) const override; bool OnOutlinerItemClick(const QPoint& position, const QStyleOptionViewItem& option, const QModelIndex& index) const override; void OnOutlinerItemCollapse(const QModelIndex& index) const override; bool OnEntityDoubleClick(AZ::EntityId entityId) const override; @@ -49,6 +54,8 @@ namespace AzToolsFramework static QModelIndex GetLastVisibleChild(const QModelIndex& parent); static QModelIndex Internal_GetLastVisibleChild(const QAbstractItemModel* model, const QModelIndex& index); + static AzFramework::EntityContextId s_editorEntityContextId; + static constexpr int m_prefabCapsuleRadius = 6; static constexpr int m_prefabBorderThickness = 2; static const QColor m_backgroundColor; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabViewportFocusPathHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabViewportFocusPathHandler.cpp index 52cf3279a4..920e99665d 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabViewportFocusPathHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabViewportFocusPathHandler.cpp @@ -59,12 +59,11 @@ namespace AzToolsFramework::Prefab connect(m_backButton, &QToolButton::clicked, this, [&]() { - if (int length = m_prefabFocusPublicInterface->GetPrefabFocusPathLength(m_editorEntityContextId); length > 1) - { - m_prefabFocusPublicInterface->FocusOnPathIndex(m_editorEntityContextId, length - 2); - } + m_prefabFocusPublicInterface->FocusOnParentOfFocusedPrefab(m_editorEntityContextId); } ); + + m_backButton->setToolTip("Up one level (-)"); } void PrefabViewportFocusPathHandler::OnPrefabFocusChanged() diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyAssetCtrl.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyAssetCtrl.cpp index 7f3b9e61fd..24b2c7466e 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyAssetCtrl.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyAssetCtrl.cpp @@ -1387,7 +1387,10 @@ namespace AzToolsFramework QDataStream stream(&pixmapBytes, QIODevice::ReadOnly); QPixmap pixmap; stream >> pixmap; - GUI->SetBrowseButtonIcon(pixmap); + if (!pixmap.isNull()) + { + GUI->SetBrowseButtonIcon(pixmap); + } } } } @@ -1417,6 +1420,8 @@ namespace AzToolsFramework } else if (attrib == AZ_CRC_CE("ThumbnailIcon")) { + GUI->SetCustomThumbnailEnabled(false); + AZStd::string iconPath; if (attrValue->Read(iconPath) && !iconPath.empty()) { @@ -1434,8 +1439,11 @@ namespace AzToolsFramework QDataStream stream(&pixmapBytes, QIODevice::ReadOnly); QPixmap pixmap; stream >> pixmap; - GUI->SetCustomThumbnailEnabled(true); - GUI->SetCustomThumbnailPixmap(pixmap); + if (!pixmap.isNull()) + { + GUI->SetCustomThumbnailEnabled(true); + GUI->SetCustomThumbnailPixmap(pixmap); + } } } } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/ThumbnailPropertyCtrl.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/ThumbnailPropertyCtrl.cpp index d8ddee6b76..0bbb898196 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/ThumbnailPropertyCtrl.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/ThumbnailPropertyCtrl.cpp @@ -67,16 +67,9 @@ namespace AzToolsFramework void ThumbnailPropertyCtrl::SetThumbnailKey(Thumbnailer::SharedThumbnailKey key, const char* contextName) { - if (m_customThumbnailEnabled) - { - ClearThumbnail(); - } - else - { - m_key = key; - m_thumbnail->SetThumbnailKey(m_key, contextName); - m_thumbnailEnlarged->SetThumbnailKey(m_key, contextName); - } + m_key = key; + m_thumbnail->SetThumbnailKey(m_key, contextName); + m_thumbnailEnlarged->SetThumbnailKey(m_key, contextName); UpdateVisibility(); } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/InvalidClicks.h b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/InvalidClicks.h index 55a6d614ea..fe54b5379b 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/InvalidClicks.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/InvalidClicks.h @@ -80,8 +80,9 @@ namespace AzToolsFramework private: AZStd::string m_message; //!< Message to display for fading text. - float m_opacity = 1.0f; //!< The opacity of the invalid click message. - AzFramework::ScreenPoint m_invalidClickPosition; //!< The position to display the invalid click message. + float m_opacity = 0.0f; //!< The opacity of the invalid click message. + //! The position to display the invalid click message. + AzFramework::ScreenPoint m_invalidClickPosition = AzFramework::ScreenPoint(0, 0); }; //! Interface to begin invalid click feedback (will run all added InvalidClick behaviors). diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportUi/ViewportUiDisplay.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportUi/ViewportUiDisplay.cpp index 43e2a6793f..c53aa66d81 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportUi/ViewportUiDisplay.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportUi/ViewportUiDisplay.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -19,7 +20,6 @@ namespace AzToolsFramework::ViewportUi::Internal { const static int HighlightBorderSize = 5; - const static int TopHighlightBorderSize = 25; const static char* HighlightBorderColor = "#4A90E2"; static void UnparentWidgets(ViewportUiElementIdInfoLookup& viewportUiElementIdInfoLookup) @@ -61,7 +61,7 @@ namespace AzToolsFramework::ViewportUi::Internal , m_uiOverlay(parent) , m_fullScreenLayout(&m_uiOverlay) , m_uiOverlayLayout() - , m_componentModeBorderText(&m_uiOverlay) + , m_viewportBorderText(&m_uiOverlay) { } @@ -221,11 +221,11 @@ namespace AzToolsFramework::ViewportUi::Internal AZStd::shared_ptr ViewportUiDisplay::GetViewportUiElement(ViewportUiElementId elementId) { - auto element = m_viewportUiElements.find(elementId); - if (element != m_viewportUiElements.end()) + if (auto element = m_viewportUiElements.find(elementId); element != m_viewportUiElements.end()) { return element->second.m_widget; } + return nullptr; } @@ -287,27 +287,30 @@ namespace AzToolsFramework::ViewportUi::Internal { return element.IsValid() && element.m_widget->isVisible(); } + return false; } void ViewportUiDisplay::CreateViewportBorder(const AZStd::string& borderTitle) { const AZStd::string styleSheet = AZStd::string::format( - "border: %dpx solid %s; border-top: %dpx solid %s;", HighlightBorderSize, HighlightBorderColor, TopHighlightBorderSize, + "border: %dpx solid %s; border-top: %dpx solid %s;", HighlightBorderSize, HighlightBorderColor, ViewportUiTopBorderSize, HighlightBorderColor); m_uiOverlay.setStyleSheet(styleSheet.c_str()); m_uiOverlayLayout.setContentsMargins( - HighlightBorderSize + ViewportUiOverlayMargin, TopHighlightBorderSize + ViewportUiOverlayMargin, + HighlightBorderSize + ViewportUiOverlayMargin, ViewportUiTopBorderSize + ViewportUiOverlayMargin, HighlightBorderSize + ViewportUiOverlayMargin, HighlightBorderSize + ViewportUiOverlayMargin); - m_componentModeBorderText.setVisible(true); - m_componentModeBorderText.setText(borderTitle.c_str()); + m_viewportBorderText.setVisible(true); + m_viewportBorderText.setText(borderTitle.c_str()); } void ViewportUiDisplay::RemoveViewportBorder() { - m_componentModeBorderText.setVisible(false); + m_viewportBorderText.setVisible(false); m_uiOverlay.setStyleSheet("border: none;"); - m_uiOverlayLayout.setMargin(ViewportUiOverlayMargin); + m_uiOverlayLayout.setContentsMargins( + ViewportUiOverlayMargin, ViewportUiOverlayMargin + ViewportUiOverlayTopMarginPadding, ViewportUiOverlayMargin, + ViewportUiOverlayMargin); } void ViewportUiDisplay::PositionViewportUiElementFromWorldSpace(ViewportUiElementId elementId, const AZ::Vector3& pos) @@ -359,10 +362,10 @@ namespace AzToolsFramework::ViewportUi::Internal // format the label which will appear on top of the highlight border AZStd::string styleSheet = AZStd::string::format("background-color: %s; border: none;", HighlightBorderColor); - m_componentModeBorderText.setStyleSheet(styleSheet.c_str()); - m_componentModeBorderText.setFixedHeight(TopHighlightBorderSize); - m_componentModeBorderText.setVisible(false); - m_fullScreenLayout.addWidget(&m_componentModeBorderText, 0, 0, Qt::AlignTop | Qt::AlignHCenter); + m_viewportBorderText.setStyleSheet(styleSheet.c_str()); + m_viewportBorderText.setFixedHeight(ViewportUiTopBorderSize); + m_viewportBorderText.setVisible(false); + m_fullScreenLayout.addWidget(&m_viewportBorderText, 0, 0, Qt::AlignTop | Qt::AlignHCenter); } void ViewportUiDisplay::PrepareWidgetForViewportUi(QPointer widget) @@ -395,14 +398,14 @@ namespace AzToolsFramework::ViewportUi::Internal void ViewportUiDisplay::UpdateUiOverlayGeometry() { - // add the component mode border region if visible + // add the viewport border region if visible QRegion region; - if (m_componentModeBorderText.isVisible()) + if (m_viewportBorderText.isVisible()) { // get the border region by taking the entire region and subtracting the non-border area region += m_uiOverlay.rect(); region -= QRect( - QPoint(m_uiOverlay.rect().left() + HighlightBorderSize, m_uiOverlay.rect().top() + TopHighlightBorderSize), + QPoint(m_uiOverlay.rect().left() + HighlightBorderSize, m_uiOverlay.rect().top() + ViewportUiTopBorderSize), QPoint(m_uiOverlay.rect().right() - HighlightBorderSize, m_uiOverlay.rect().bottom() - HighlightBorderSize)); } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportUi/ViewportUiDisplay.h b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportUi/ViewportUiDisplay.h index 5020241815..32b746a1ac 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportUi/ViewportUiDisplay.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportUi/ViewportUiDisplay.h @@ -113,7 +113,7 @@ namespace AzToolsFramework::ViewportUi::Internal QWidget m_uiOverlay; //!< The UI Overlay which displays Viewport UI Elements. QGridLayout m_fullScreenLayout; //!< The layout which extends across the full screen. ViewportUiDisplayLayout m_uiOverlayLayout; //!< The layout used for optionally anchoring Viewport UI Elements. - QLabel m_componentModeBorderText; //!< The text used for the Component Mode border. + QLabel m_viewportBorderText; //!< The text used for the viewport border. QWidget* m_renderOverlay; QPointer m_fullScreenWidget; //!< Reference to the widget attached to m_fullScreenLayout if any. diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportUi/ViewportUiDisplayLayout.h b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportUi/ViewportUiDisplayLayout.h index 4a44f07491..335f664094 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportUi/ViewportUiDisplayLayout.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportUi/ViewportUiDisplayLayout.h @@ -14,13 +14,20 @@ #include #include +namespace AzToolsFramework::ViewportUi +{ + //! Margin for the Viewport UI Overlay (in pixels) + constexpr int ViewportUiOverlayMargin = 5; + //! Padding to make space for ImGui (in pixels) + constexpr int ViewportUiOverlayTopMarginPadding = 20; + //! Size of the top viewport border (in pixels) + constexpr int ViewportUiTopBorderSize = 25; + //! Size of the left, right and bottom viewport border (in pixels) + constexpr int ViewportUiLeftRightBottomBorderSize = 5; +} // namespace AzToolsFramework::ViewportUi + namespace AzToolsFramework::ViewportUi::Internal { - // margin for the Viewport UI Overlay in pixels - constexpr int ViewportUiOverlayMargin = 5; - // padding to make space for ImGui - constexpr int ViewportUiOverlayTopMarginPadding = 20; - //! QGridLayout implementation that uses a grid of QVBox/QHBoxLayouts internally to stack widgets. class ViewportUiDisplayLayout : public QGridLayout { diff --git a/Code/Framework/AzToolsFramework/Tests/AssetFileInfoListComparison.cpp b/Code/Framework/AzToolsFramework/Tests/AssetFileInfoListComparison.cpp index ad80b5c39d..af0ae9addb 100644 --- a/Code/Framework/AzToolsFramework/Tests/AssetFileInfoListComparison.cpp +++ b/Code/Framework/AzToolsFramework/Tests/AssetFileInfoListComparison.cpp @@ -57,9 +57,7 @@ namespace UnitTest ArgumentContainer argContainer{ {} }; // Append Command Line override for the Project Cache Path - auto projectCachePathOverride = FixedValueString::format(R"(--project-cache-path="%s")", m_tempDir.GetDirectory()); - auto projectPathOverride = FixedValueString{ R"(--project-path=AutomatedTesting)" }; - argContainer.push_back(projectCachePathOverride.data()); + auto projectPathOverride = FixedValueString::format(R"(--project-path="%s")", m_tempDir.GetDirectory()); argContainer.push_back(projectPathOverride.data()); m_application = new ToolsTestApplication("AssetFileInfoListComparisonTest", aznumeric_caster(argContainer.size()), argContainer.data()); AzToolsFramework::AssetSeedManager assetSeedManager; @@ -100,7 +98,7 @@ namespace UnitTest m_application->Start(AzFramework::Application::Descriptor()); // Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is - // shared across the whole engine, if multiple tests are run in parallel, the saving could cause a crash + // 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); @@ -223,7 +221,7 @@ namespace UnitTest // AssetFileInfo should contain {2*, 4*, 5} AzToolsFramework::AssetFileInfoList assetFileInfoList; - + ASSERT_TRUE(AZ::Utils::LoadObjectFromFileInPlace(TempFiles[FileIndex::ResultAssetFileInfoList], assetFileInfoList)) << "Unable to read the asset file info list.\n"; EXPECT_EQ(assetFileInfoList.m_fileInfoList.size(), 3); @@ -256,7 +254,7 @@ namespace UnitTest } } - // Verifying that correct assetId are present in the assetFileInfo list + // Verifying that correct assetId are present in the assetFileInfo list AZStd::unordered_set expectedAssetIds{ m_assets[2], m_assets[4], m_assets[5] }; for (const AzToolsFramework::AssetFileInfo& assetFileInfo : assetFileInfoList.m_fileInfoList) @@ -298,7 +296,7 @@ namespace UnitTest { firstAssetIdToAssetFileInfoMap[assetFileInfo.m_assetId] = AZStd::move(assetFileInfo); } - + AzToolsFramework::AssetFileInfoList secondAssetFileInfoList; ASSERT_TRUE(AZ::Utils::LoadObjectFromFileInPlace(TempFiles[FileIndex::SecondAssetFileInfoList], secondAssetFileInfoList)) << "Unable to read the asset file info list.\n"; @@ -315,7 +313,7 @@ namespace UnitTest auto foundSecond = secondAssetIdToAssetFileInfoMap.find(assetFileInfo.m_assetId); if (foundSecond != secondAssetIdToAssetFileInfoMap.end()) { - // Even if the asset Id is present in both the AssetFileInfo List, it should match the file hash from the second AssetFileInfo list + // Even if the asset Id is present in both the AssetFileInfo List, it should match the file hash from the second AssetFileInfo list for (int idx = 0; idx < AzToolsFramework::AssetFileInfo::s_arraySize; idx++) { if (foundSecond->second.m_hash[idx] != assetFileInfo.m_hash[idx]) @@ -343,7 +341,7 @@ namespace UnitTest } } - // Verifying that correct assetId are present in the assetFileInfo list + // Verifying that correct assetId are present in the assetFileInfo list AZStd::unordered_set expectedAssetIds{ m_assets[0], m_assets[1], m_assets[2], m_assets[3], m_assets[4], m_assets[5] }; for (const AzToolsFramework::AssetFileInfo& assetFileInfo : assetFileInfoList.m_fileInfoList) @@ -403,7 +401,7 @@ namespace UnitTest } } - // Verifying that correct assetId are present in the assetFileInfo list + // Verifying that correct assetId are present in the assetFileInfo list AZStd::unordered_set expectedAssetIds{ m_assets[1], m_assets[2], m_assets[3], m_assets[4] }; for (const AzToolsFramework::AssetFileInfo& assetFileInfo : assetFileInfoList.m_fileInfoList) @@ -462,7 +460,7 @@ namespace UnitTest } } - // Verifying that correct assetId are present in the assetFileInfo list + // Verifying that correct assetId are present in the assetFileInfo list AZStd::unordered_set expectedAssetIds{ m_assets[5] }; for (const AzToolsFramework::AssetFileInfo& assetFileInfo : assetFileInfoList.m_fileInfoList) @@ -493,7 +491,7 @@ namespace UnitTest EXPECT_EQ(assetFileInfoList.m_fileInfoList.size(), 5); - // Verifying that correct assetId are present in the assetFileInfo list + // Verifying that correct assetId are present in the assetFileInfo list AZStd::unordered_set expectedAssetIds{ m_assets[0], m_assets[1], m_assets[2], m_assets[3], m_assets[4] }; for (const AzToolsFramework::AssetFileInfo& assetFileInfo : assetFileInfoList.m_fileInfoList) @@ -601,7 +599,7 @@ namespace UnitTest } } - // Verifying that correct assetId are present in the assetFileInfo list + // Verifying that correct assetId are present in the assetFileInfo list AZStd::unordered_set expectedAssetIds{ m_assets[2] }; for (const AzToolsFramework::AssetFileInfo& assetFileInfo : assetFileInfoList.m_fileInfoList) @@ -625,12 +623,12 @@ namespace UnitTest AssetFileInfoListComparison::ComparisonData filePatternComparisonData(AssetFileInfoListComparison::ComparisonType::FilePattern,"$1", "Asset[0-3].txt", AssetFileInfoListComparison::FilePatternType::Regex); filePatternComparisonData.m_firstInput = TempFiles[FileIndex::FirstAssetFileInfoList]; assetFileInfoListComparison.AddComparisonStep(filePatternComparisonData); - + AzToolsFramework::AssetFileInfoListComparison::ComparisonData deltaComparisonData(AzToolsFramework::AssetFileInfoListComparison::ComparisonType::Delta, TempFiles[FileIndex::ResultAssetFileInfoList]); deltaComparisonData.m_firstInput = "$1"; deltaComparisonData.m_secondInput = TempFiles[FileIndex::SecondAssetFileInfoList]; assetFileInfoListComparison.AddComparisonStep(deltaComparisonData); - + ASSERT_TRUE(assetFileInfoListComparison.CompareAndSaveResults().IsSuccess()) << "Multiple Comparison Operation( FilePattern + Delta ) failed.\n"; // Output of the FilePattern Operation should be {0,1,2,3} // Output of the Delta Operation should be {2*,4*,5} @@ -666,7 +664,7 @@ namespace UnitTest } } - // Verifying that correct assetId are present in the assetFileInfo list + // Verifying that correct assetId are present in the assetFileInfo list AZStd::unordered_set expectedAssetIds{ m_assets[2], m_assets[4], m_assets[5] }; for (const AzToolsFramework::AssetFileInfo& assetFileInfo : assetFileInfoList.m_fileInfoList) @@ -738,7 +736,7 @@ namespace UnitTest } } - // Verifying that correct assetId are present in the assetFileInfo list + // Verifying that correct assetId are present in the assetFileInfo list AZStd::unordered_set expectedAssetIds{ m_assets[4], m_assets[5] }; for (const AzToolsFramework::AssetFileInfo& assetFileInfo : assetFileInfoList.m_fileInfoList) diff --git a/Code/Framework/AzToolsFramework/Tests/AssetSeedManager.cpp b/Code/Framework/AzToolsFramework/Tests/AssetSeedManager.cpp index 827737f561..f04a0642d1 100644 --- a/Code/Framework/AzToolsFramework/Tests/AssetSeedManager.cpp +++ b/Code/Framework/AzToolsFramework/Tests/AssetSeedManager.cpp @@ -63,10 +63,8 @@ namespace UnitTest ArgumentContainer argContainer{ {} }; // Append Command Line override for the Project Cache Path - AZ::IO::Path cacheProjectRootFolder{ m_tempDir.GetDirectory() }; - auto projectCachePathOverride = FixedValueString::format(R"(--project-cache-path="%s")", cacheProjectRootFolder.c_str()); - auto projectPathOverride = FixedValueString{ R"(--project-path=AutomatedTesting)" }; - argContainer.push_back(projectCachePathOverride.data()); + auto cacheProjectRootFolder = AZ::IO::Path{ m_tempDir.GetDirectory() } / "Cache"; + auto projectPathOverride = FixedValueString::format(R"(--project-path="%s")", m_tempDir.GetDirectory()); argContainer.push_back(projectPathOverride.data()); m_application = new ToolsTestApplication("AssetSeedManagerTest", aznumeric_caster(argContainer.size()), argContainer.data()); m_assetSeedManager = new AzToolsFramework::AssetSeedManager(); diff --git a/Code/Framework/AzToolsFramework/Tests/BoundsTestComponent.cpp b/Code/Framework/AzToolsFramework/Tests/BoundsTestComponent.cpp index 11a077f53c..1aa7b39593 100644 --- a/Code/Framework/AzToolsFramework/Tests/BoundsTestComponent.cpp +++ b/Code/Framework/AzToolsFramework/Tests/BoundsTestComponent.cpp @@ -28,9 +28,12 @@ namespace UnitTest return true; } - void BoundsTestComponent::Reflect([[maybe_unused]] AZ::ReflectContext* context) + void BoundsTestComponent::Reflect(AZ::ReflectContext* context) { - // noop + if (auto serializeContext = azrtti_cast(context)) + { + serializeContext->Class()->Version(1); + } } void BoundsTestComponent::Activate() diff --git a/Code/Framework/AzToolsFramework/Tests/BoundsTestComponent.h b/Code/Framework/AzToolsFramework/Tests/BoundsTestComponent.h index 1aabcfcd64..036f9c8798 100644 --- a/Code/Framework/AzToolsFramework/Tests/BoundsTestComponent.h +++ b/Code/Framework/AzToolsFramework/Tests/BoundsTestComponent.h @@ -42,5 +42,4 @@ namespace UnitTest AZ::Aabb GetWorldBounds() override; AZ::Aabb GetLocalBounds() override; }; - } // namespace UnitTest diff --git a/Code/Framework/AzToolsFramework/Tests/ComponentAddRemove.cpp b/Code/Framework/AzToolsFramework/Tests/ComponentAddRemove.cpp index e416efc5c6..9d6c8a4bff 100644 --- a/Code/Framework/AzToolsFramework/Tests/ComponentAddRemove.cpp +++ b/Code/Framework/AzToolsFramework/Tests/ComponentAddRemove.cpp @@ -572,7 +572,9 @@ namespace UnitTest AZ::SettingsRegistryInterface* registry = AZ::SettingsRegistry::Get(); auto projectPathKey = AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path"; - registry->Set(projectPathKey, "AutomatedTesting"); + AZ::IO::FixedMaxPath enginePath; + registry->Get(enginePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder); + registry->Set(projectPathKey, (enginePath / "AutomatedTesting").Native()); AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry); AzFramework::Application::Descriptor descriptor; diff --git a/Code/Framework/AzToolsFramework/Tests/GenericComponentWrapperTest.cpp b/Code/Framework/AzToolsFramework/Tests/GenericComponentWrapperTest.cpp index 54132b974e..dbe3963069 100644 --- a/Code/Framework/AzToolsFramework/Tests/GenericComponentWrapperTest.cpp +++ b/Code/Framework/AzToolsFramework/Tests/GenericComponentWrapperTest.cpp @@ -59,7 +59,9 @@ protected: AZ::SettingsRegistryInterface* registry = AZ::SettingsRegistry::Get(); auto projectPathKey = AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path"; - registry->Set(projectPathKey, "AutomatedTesting"); + AZ::IO::FixedMaxPath enginePath; + registry->Get(enginePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder); + registry->Set(projectPathKey, (enginePath / "AutomatedTesting").Native()); AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry); m_app.Start(AZ::ComponentApplication::Descriptor()); @@ -184,7 +186,9 @@ public: AZ::SettingsRegistryInterface* registry = AZ::SettingsRegistry::Get(); auto projectPathKey = AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path"; - registry->Set(projectPathKey, "AutomatedTesting"); + AZ::IO::FixedMaxPath enginePath; + registry->Get(enginePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder); + registry->Set(projectPathKey, (enginePath / "AutomatedTesting").Native()); AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry); m_app.Start(AzFramework::Application::Descriptor()); diff --git a/Code/Framework/AzToolsFramework/Tests/PlatformAddressedAssetCatalogTests.cpp b/Code/Framework/AzToolsFramework/Tests/PlatformAddressedAssetCatalogTests.cpp index 9c226cbb1b..e58e347b2a 100644 --- a/Code/Framework/AzToolsFramework/Tests/PlatformAddressedAssetCatalogTests.cpp +++ b/Code/Framework/AzToolsFramework/Tests/PlatformAddressedAssetCatalogTests.cpp @@ -44,10 +44,8 @@ namespace UnitTest ArgumentContainer argContainer{ {} }; // Append Command Line override for the Project Cache Path - AZ::IO::Path cacheProjectRootFolder{ m_tempDir.GetDirectory() }; - auto projectCachePathOverride = FixedValueString::format(R"(--project-cache-path="%s")", cacheProjectRootFolder.c_str()); - auto projectPathOverride = FixedValueString{ R"(--project-path=AutomatedTesting)" }; - argContainer.push_back(projectCachePathOverride.data()); + auto cacheProjectRootFolder = AZ::IO::Path{ m_tempDir.GetDirectory() } / "Cache"; + auto projectPathOverride = FixedValueString::format(R"(--project-path="%s")", m_tempDir.GetDirectory()); argContainer.push_back(projectPathOverride.data()); m_application = new ToolsTestApplication("AddressedAssetCatalogManager", aznumeric_caster(argContainer.size()), argContainer.data()); @@ -195,10 +193,7 @@ namespace UnitTest ArgumentContainer argContainer{ {} }; // Append Command Line override for the Project Cache Path - AZ::IO::Path cacheProjectRootFolder{ m_tempDir.GetDirectory() }; - auto projectCachePathOverride = FixedValueString::format(R"(--project-cache-path="%s")", cacheProjectRootFolder.c_str()); - auto projectPathOverride = FixedValueString{ R"(--project-path=AutomatedTesting)" }; - argContainer.push_back(projectCachePathOverride.data()); + auto projectPathOverride = FixedValueString::format(R"(--project-path="%s")", m_tempDir.GetDirectory()); argContainer.push_back(projectPathOverride.data()); m_application = new ToolsTestApplication("MessageTest", aznumeric_caster(argContainer.size()), argContainer.data()); diff --git a/Code/Framework/AzToolsFramework/Tests/Slices.cpp b/Code/Framework/AzToolsFramework/Tests/Slices.cpp index 1e849de620..9b546357cd 100644 --- a/Code/Framework/AzToolsFramework/Tests/Slices.cpp +++ b/Code/Framework/AzToolsFramework/Tests/Slices.cpp @@ -1059,7 +1059,9 @@ namespace UnitTest AZ::SettingsRegistryInterface* registry = AZ::SettingsRegistry::Get(); auto projectPathKey = AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path"; - registry->Set(projectPathKey, "AutomatedTesting"); + AZ::IO::FixedMaxPath enginePath; + registry->Get(enginePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder); + registry->Set(projectPathKey, (enginePath / "AutomatedTesting").Native()); AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry); m_app.Start(AzFramework::Application::Descriptor()); diff --git a/Code/LauncherUnified/Launcher.cpp b/Code/LauncherUnified/Launcher.cpp index 0ef9cdfc3b..0f832ff1a5 100644 --- a/Code/LauncherUnified/Launcher.cpp +++ b/Code/LauncherUnified/Launcher.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -664,6 +665,8 @@ namespace O3DELauncher systemInitParams.pSystem = CreateSystemInterface(systemInitParams); #endif // !defined(AZ_MONOLITHIC_BUILD) + AZ::ComponentApplicationLifecycle::SignalEvent(*settingsRegistry, "LegacySystemInterfaceCreated", R"({})"); + ReturnCode status = ReturnCode::Success; if (systemInitParams.pSystem) diff --git a/Code/Legacy/CrySystem/IDebugCallStack.cpp b/Code/Legacy/CrySystem/IDebugCallStack.cpp index deb9c11581..a02658d34a 100644 --- a/Code/Legacy/CrySystem/IDebugCallStack.cpp +++ b/Code/Legacy/CrySystem/IDebugCallStack.cpp @@ -242,7 +242,7 @@ void IDebugCallStack::WriteLineToLog(const char* format, ...) va_end(ArgList); AZ::IO::HandleType fileHandle = AZ::IO::InvalidHandle; - AZ::IO::FileIOBase::GetDirectInstance()->Open("@Log@\\error.log", AZ::IO::GetOpenModeFromStringMode("a+t"), fileHandle); + AZ::IO::FileIOBase::GetDirectInstance()->Open("@log@\\error.log", AZ::IO::GetOpenModeFromStringMode("a+t"), fileHandle); if (fileHandle != AZ::IO::InvalidHandle) { AZ::IO::FileIOBase::GetDirectInstance()->Write(fileHandle, szBuffer, strlen(szBuffer)); @@ -254,7 +254,7 @@ void IDebugCallStack::WriteLineToLog(const char* format, ...) ////////////////////////////////////////////////////////////////////////// void IDebugCallStack::StartMemLog() { - AZ::IO::FileIOBase::GetDirectInstance()->Open("@Log@\\memallocfile.log", AZ::IO::OpenMode::ModeWrite, m_memAllocFileHandle); + AZ::IO::FileIOBase::GetDirectInstance()->Open("@log@\\memallocfile.log", AZ::IO::OpenMode::ModeWrite, m_memAllocFileHandle); assert(m_memAllocFileHandle != AZ::IO::InvalidHandle); } diff --git a/Code/Legacy/CrySystem/System.h b/Code/Legacy/CrySystem/System.h index 015b09a69b..a3a0f12278 100644 --- a/Code/Legacy/CrySystem/System.h +++ b/Code/Legacy/CrySystem/System.h @@ -590,7 +590,7 @@ public: bool InitVTuneProfiler(); - void OpenBasicPaks(); + void OpenPlatformPaks(); void OpenLanguagePak(const char* sLanguage); void OpenLanguageAudioPak(const char* sLanguage); void GetLocalizedPath(const char* sLanguage, AZStd::string& sLocalizedPath); diff --git a/Code/Legacy/CrySystem/SystemInit.cpp b/Code/Legacy/CrySystem/SystemInit.cpp index ee1c498a9a..ac0683cf1b 100644 --- a/Code/Legacy/CrySystem/SystemInit.cpp +++ b/Code/Legacy/CrySystem/SystemInit.cpp @@ -649,7 +649,7 @@ bool CSystem::InitFileSystem_LoadEngineFolders(const SSystemInitParams&) auto projectName = AZ::Utils::GetProjectName(); AZ_Printf(AZ_TRACE_SYSTEM_WINDOW, "Project Name: %s\n", projectName.empty() ? "None specified" : projectName.c_str()); - OpenBasicPaks(); + OpenPlatformPaks(); // Load game-specific folder. LoadConfiguration("game.cfg"); @@ -786,29 +786,19 @@ void CSystem::InitLocalization() OpenLanguageAudioPak(language.c_str()); } -void CSystem::OpenBasicPaks() +void CSystem::OpenPlatformPaks() { - static bool bBasicPaksLoaded = false; - if (bBasicPaksLoaded) + static bool bPlatformPaksLoaded = false; + if (bPlatformPaksLoaded) { return; } - bBasicPaksLoaded = true; - - // open pak files - constexpr AZStd::string_view paksFolder = "@products@/*.pak"; // (@products@ assumed) - m_env.pCryPak->OpenPacks(paksFolder); - - InlineInitializationProcessing("CSystem::OpenBasicPaks OpenPacks( paksFolder.c_str() )"); + bPlatformPaksLoaded = true; ////////////////////////////////////////////////////////////////////////// // Open engine packs ////////////////////////////////////////////////////////////////////////// - const char* const assetsDir = "@products@"; - - // After game paks to have same search order as with files on disk - m_env.pCryPak->OpenPack(assetsDir, "engine.pak"); #if defined(AZ_RESTRICTED_PLATFORM) #define AZ_RESTRICTED_SECTION SYSTEMINIT_CPP_SECTION_15 @@ -816,6 +806,7 @@ void CSystem::OpenBasicPaks() #endif #ifdef AZ_PLATFORM_ANDROID + const char* const assetsDir = "@products@"; // Load Android Obb files if available const char* obbStorage = AZ::Android::Utils::GetObbStoragePath(); AZStd::string mainObbPath = AZStd::move(AZStd::string::format("%s/%s", obbStorage, AZ::Android::Utils::GetObbFileName(true))); @@ -824,7 +815,7 @@ void CSystem::OpenBasicPaks() m_env.pCryPak->OpenPack(assetsDir, patchObbPath.c_str()); #endif //AZ_PLATFORM_ANDROID - InlineInitializationProcessing("CSystem::OpenBasicPaks OpenPacks( Engine... )"); + InlineInitializationProcessing("CSystem::OpenPlatformPaks OpenPacks( Engine... )"); } ////////////////////////////////////////////////////////////////////////// @@ -1328,7 +1319,7 @@ AZ_POP_DISABLE_WARNING ////////////////////////////////////////////////////////////////////////// // Open basic pak files after intro movie playback started ////////////////////////////////////////////////////////////////////////// - OpenBasicPaks(); + OpenPlatformPaks(); ////////////////////////////////////////////////////////////////////////// // AUDIO diff --git a/Code/Tools/AssetBundler/CMakeLists.txt b/Code/Tools/AssetBundler/CMakeLists.txt index d613b35ce5..dcc62595c9 100644 --- a/Code/Tools/AssetBundler/CMakeLists.txt +++ b/Code/Tools/AssetBundler/CMakeLists.txt @@ -47,6 +47,10 @@ ly_add_target( AZ::AssetBundlerBatch.Static ) +# Adds a specialized .setreg to identify gems enabled in the active project. +# This associates the AssetBundlerBatch target with the .Builders gem variants. +ly_set_gem_variant_to_load(TARGETS AssetBundlerBatch VARIANTS Builders) + # AssetBundler - Qt GUI Application ly_add_target( NAME AssetBundler ${PAL_TRAIT_BUILD_ASSETBUNDLER_APPLICATION_TYPE} @@ -73,6 +77,10 @@ ly_add_target( ${additional_dependencies} ) +# Adds a specialized .setreg to identify gems enabled in the active project. +# This associates the AssetBundler target with the .Builders gem variants. +ly_set_gem_variant_to_load(TARGETS AssetBundler VARIANTS Builders) + if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) ly_add_target( diff --git a/Code/Tools/AssetBundler/source/utils/applicationManager.cpp b/Code/Tools/AssetBundler/source/utils/applicationManager.cpp index 0742035c1a..0388570fdd 100644 --- a/Code/Tools/AssetBundler/source/utils/applicationManager.cpp +++ b/Code/Tools/AssetBundler/source/utils/applicationManager.cpp @@ -54,7 +54,12 @@ namespace AssetBundler bool ApplicationManager::Init() { AZ::Debug::TraceMessageBus::Handler::BusConnect(); - Start(AzFramework::Application::Descriptor()); + + ComponentApplication::StartupParameters startupParameters; + // The AssetBundler does not need to load gems + startupParameters.m_loadDynamicModules = false; + Start(AzFramework::Application::Descriptor(), startupParameters); + AZ::SerializeContext* context; EBUS_EVENT_RESULT(context, AZ::ComponentApplicationBus, GetSerializeContext); AZ_Assert(context, "No serialize context"); diff --git a/Code/Tools/AssetBundler/tests/applicationManagerTests.cpp b/Code/Tools/AssetBundler/tests/applicationManagerTests.cpp index 35d3a5da1f..07eae67a81 100644 --- a/Code/Tools/AssetBundler/tests/applicationManagerTests.cpp +++ b/Code/Tools/AssetBundler/tests/applicationManagerTests.cpp @@ -66,12 +66,18 @@ namespace AssetBundler } auto projectPathKey = AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path"; - registry->Set(projectPathKey, "AutomatedTesting"); + AZ::IO::FixedMaxPath enginePath; + registry->Get(enginePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder); + registry->Set(projectPathKey, (enginePath / "AutomatedTesting").Native()); AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry); m_data->m_applicationManager.reset(aznew MockApplicationManagerTest(0, 0)); - m_data->m_applicationManager->Start(AzFramework::Application::Descriptor()); + + AZ::ComponentApplication::StartupParameters startupParameters; + // The AssetBundler does not need to load gems + startupParameters.m_loadDynamicModules = false; + m_data->m_applicationManager->Start(AzFramework::Application::Descriptor(), startupParameters); // 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 diff --git a/Code/Tools/AssetBundler/tests/tests_main.cpp b/Code/Tools/AssetBundler/tests/tests_main.cpp index 21a6bf8a6f..86432d730e 100644 --- a/Code/Tools/AssetBundler/tests/tests_main.cpp +++ b/Code/Tools/AssetBundler/tests/tests_main.cpp @@ -106,7 +106,9 @@ namespace AssetBundler } auto projectPathKey = AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path"; - registry->Set(projectPathKey, "AutomatedTesting"); + AZ::IO::FixedMaxPath enginePath; + registry->Get(enginePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder); + registry->Set(projectPathKey, (enginePath / "AutomatedTesting").Native()); AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry); AZ::IO::FixedMaxPath engineRoot = AZ::Utils::GetEnginePath(); diff --git a/Code/Tools/AssetProcessor/native/InternalBuilders/SettingsRegistryBuilder.cpp b/Code/Tools/AssetProcessor/native/InternalBuilders/SettingsRegistryBuilder.cpp index c65ab24aed..305578219f 100644 --- a/Code/Tools/AssetProcessor/native/InternalBuilders/SettingsRegistryBuilder.cpp +++ b/Code/Tools/AssetProcessor/native/InternalBuilders/SettingsRegistryBuilder.cpp @@ -159,6 +159,7 @@ namespace AssetProcessor builderDesc.m_busId = m_builderId; builderDesc.m_createJobFunction = AZStd::bind(&SettingsRegistryBuilder::CreateJobs, this, AZStd::placeholders::_1, AZStd::placeholders::_2); builderDesc.m_processJobFunction = AZStd::bind(&SettingsRegistryBuilder::ProcessJob, this, AZStd::placeholders::_1, AZStd::placeholders::_2); + builderDesc.m_version = 1; AssetBuilderSDK::AssetBuilderBus::Broadcast(&AssetBuilderSDK::AssetBuilderBusTraits::RegisterBuilderInformation, builderDesc); @@ -259,6 +260,11 @@ namespace AssetProcessor scratchBuffer.reserve(512 * 1024); // Reserve 512kb to avoid repeatedly resizing the buffer; AZStd::fixed_vector platformCodes; AzFramework::PlatformHelper::AppendPlatformCodeNames(platformCodes, request.m_platformInfo.m_identifier); + AZ_Assert(platformCodes.size() <= 1, "A one-to-one mapping of asset type platform identifier" + " to platform codename is required in the SettingsRegistryBuilder." + " The bootstrap.game is now only produced per build configuration and doesn't take into account" + " different platforms names"); + const AZStd::string& assetPlatformIdentifier = request.m_jobDescription.GetPlatformIdentifier(); // Determines the suffix that will be used for the launcher based on processing server vs non-server assets const char* launcherType = assetPlatformIdentifier != AzFramework::PlatformHelper::GetPlatformName(AzFramework::PlatformId::SERVER) @@ -293,9 +299,9 @@ namespace AssetProcessor outputBuffer.Reserve(512 * 1024); // Reserve 512kb to avoid repeatedly resizing the buffer; SettingsExporter exporter(outputBuffer, excludes); - for (AZStd::string_view platform : platformCodes) + if (!platformCodes.empty()) { - AZ::u32 productSubID = static_cast(AZStd::hash{}(platform)); // Deliberately ignoring half the bits. + AZStd::string_view platform = platformCodes.front(); for (size_t i = 0; i < AZStd::size(specializations); ++i) { const AZ::SettingsRegistryInterface::Specializations& specialization = specializations[i]; @@ -337,7 +343,7 @@ namespace AssetProcessor // The purpose of this section is to copy the Gem's SourcePaths from the Global Settings Registry // the local SettingsRegistry. The reason this is needed is so that the call to // `MergeSettingsToRegistry_GemRegistries` below is able to locate each gem's "/Registry" folder - // that will be merged into the bootstrap.game...setreg file + // that will be merged into the bootstrap.game..setreg file // This is used by the GameLauncher applications to read from a single merged .setreg file // containing the settings needed to run a game/simulation without have access to the source code base registry AZStd::vector gemInfos; @@ -407,9 +413,8 @@ namespace AssetProcessor return; } - outputPath += specialization.GetSpecialization(0); // Append configuration - outputPath += '.'; - outputPath += platform; + AZStd::string_view specializationString(specialization.GetSpecialization(0)); + outputPath += specializationString; // Append configuration outputPath += ".setreg"; AZ::IO::SystemFile file; @@ -426,7 +431,11 @@ namespace AssetProcessor } file.Close(); - response.m_outputProducts.emplace_back(outputPath, m_assetType, productSubID + aznumeric_cast(i)); + const AZ::u32 hashedSpecialization = static_cast(AZStd::hash{}(specializationString)); + AZ_Assert(hashedSpecialization != 0, "Product ID generation failed for specialization %.*s." + " This can result in a product ID collision with other builders for this asset.", + AZ_STRING_ARG(specializationString)); + response.m_outputProducts.emplace_back(outputPath, m_assetType, hashedSpecialization); response.m_outputProducts.back().m_dependenciesHandled = true; outputPath.erase(extensionOffset); diff --git a/Code/Tools/AssetProcessor/native/tests/AssetCatalog/AssetCatalogUnitTests.cpp b/Code/Tools/AssetProcessor/native/tests/AssetCatalog/AssetCatalogUnitTests.cpp index 2a1c4e4755..5c449e2764 100644 --- a/Code/Tools/AssetProcessor/native/tests/AssetCatalog/AssetCatalogUnitTests.cpp +++ b/Code/Tools/AssetProcessor/native/tests/AssetCatalog/AssetCatalogUnitTests.cpp @@ -128,7 +128,9 @@ namespace AssetProcessor settingsRegistry->Set(cacheRootKey, m_data->m_temporarySourceDir.absoluteFilePath("Cache").toUtf8().constData()); auto projectPathKey = AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path"; - settingsRegistry->Set(projectPathKey, "AutomatedTesting"); + AZ::IO::FixedMaxPath enginePath; + settingsRegistry->Get(enginePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder); + settingsRegistry->Set(projectPathKey, (enginePath / "AutomatedTesting").Native()); AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*settingsRegistry); AssetUtilities::ComputeProjectCacheRoot(m_data->m_cacheRootDir); QString normalizedCacheRoot = AssetUtilities::NormalizeDirectoryPath(m_data->m_cacheRootDir.absolutePath()); diff --git a/Code/Tools/AssetProcessor/native/tests/AssetProcessorMessagesTests.cpp b/Code/Tools/AssetProcessor/native/tests/AssetProcessorMessagesTests.cpp index 516f6beb3c..6a0da96151 100644 --- a/Code/Tools/AssetProcessor/native/tests/AssetProcessorMessagesTests.cpp +++ b/Code/Tools/AssetProcessor/native/tests/AssetProcessorMessagesTests.cpp @@ -107,12 +107,13 @@ namespace AssetProcessorMessagesTests AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey }; constexpr AZ::SettingsRegistryInterface::FixedValueString projectPathKey{ bootstrapKey + "/project_path" }; - registry->Set(projectPathKey, "AutomatedTesting"); + AZ::IO::FixedMaxPath enginePath; + registry->Get(enginePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder); + registry->Set(projectPathKey, (enginePath / "AutomatedTesting").Native()); 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); diff --git a/Code/Tools/AssetProcessor/native/tests/AssetProcessorTest.cpp b/Code/Tools/AssetProcessor/native/tests/AssetProcessorTest.cpp index 3249b7e396..2629f7b956 100644 --- a/Code/Tools/AssetProcessor/native/tests/AssetProcessorTest.cpp +++ b/Code/Tools/AssetProcessor/native/tests/AssetProcessorTest.cpp @@ -71,12 +71,13 @@ namespace AssetProcessor auto registry = AZ::SettingsRegistry::Get(); auto bootstrapKey = AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey); auto projectPathKey = bootstrapKey + "/project_path"; - registry->Set(projectPathKey, "AutomatedTesting"); + AZ::IO::FixedMaxPath enginePath; + registry->Get(enginePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder); + registry->Set(projectPathKey, (enginePath / "AutomatedTesting").Native()); AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry); // Forcing the branch token into settings registry before starting the application manager. // This avoids writing the asset_processor.setreg file which can cause fileIO errors. - AZ::IO::FixedMaxPathString enginePath = AZ::Utils::GetEnginePath(); auto branchTokenKey = bootstrapKey + "/assetProcessor_branch_token"; AZStd::string token; AzFramework::StringFunc::AssetPath::CalculateBranchToken(enginePath.c_str(), token); diff --git a/Code/Tools/AssetProcessor/native/tests/AssetProcessorTest.h b/Code/Tools/AssetProcessor/native/tests/AssetProcessorTest.h index 258268c182..719b04610c 100644 --- a/Code/Tools/AssetProcessor/native/tests/AssetProcessorTest.h +++ b/Code/Tools/AssetProcessor/native/tests/AssetProcessorTest.h @@ -50,7 +50,9 @@ namespace AssetProcessor + "/project_path"; if(auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr) { - settingsRegistry->Set(projectPathKey, "AutomatedTesting"); + AZ::IO::FixedMaxPath enginePath; + settingsRegistry->Get(enginePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder); + settingsRegistry->Set(projectPathKey, (enginePath / "AutomatedTesting").Native()); AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*settingsRegistry); } } diff --git a/Code/Tools/AssetProcessor/native/tests/assetmanager/AssetProcessorManagerTest.cpp b/Code/Tools/AssetProcessor/native/tests/assetmanager/AssetProcessorManagerTest.cpp index da6e995c97..f8d758e092 100644 --- a/Code/Tools/AssetProcessor/native/tests/assetmanager/AssetProcessorManagerTest.cpp +++ b/Code/Tools/AssetProcessor/native/tests/assetmanager/AssetProcessorManagerTest.cpp @@ -192,7 +192,9 @@ void AssetProcessorManagerTest::SetUp() registry->Set(cacheRootKey, tempPath.absoluteFilePath("Cache").toUtf8().constData()); auto projectPathKey = AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path"; - registry->Set(projectPathKey, "AutomatedTesting"); + AZ::IO::FixedMaxPath enginePath; + registry->Get(enginePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder); + registry->Set(projectPathKey, (enginePath / "AutomatedTesting").Native()); AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry); m_data->m_databaseLocationListener.BusConnect(); @@ -4137,11 +4139,19 @@ struct LockedFileTest switch (message.GetMessageType()) { case SourceFileNotificationMessage::MessageType: - if (const auto sourceFileMessage = azrtti_cast(&message); - sourceFileMessage != nullptr && sourceFileMessage->m_type == SourceFileNotificationMessage::NotificationType::FileRemoved - && m_callback) + if (const auto sourceFileMessage = azrtti_cast(&message); sourceFileMessage != nullptr && + sourceFileMessage->m_type == SourceFileNotificationMessage::NotificationType::FileRemoved) { - m_callback(); + // The File Remove message will occur before an attempt to delete the file + // Wait for more than 1 File Remove message. + // This indicates the AP has attempted to delete the file once, failed to do so and is now retrying + ++m_deleteCounter; + + if(m_deleteCounter > 1 && m_callback) + { + m_callback(); + m_callback = {}; // Unset it to be safe, we only intend to run the callback once + } } break; default: @@ -4165,6 +4175,7 @@ struct LockedFileTest ModtimeScanningTest::TearDown(); } + AZStd::atomic_int m_deleteCounter{ 0 }; AZStd::function m_callback; }; @@ -4204,6 +4215,10 @@ TEST_F(LockedFileTest, DeleteFile_LockedProduct_DeleteFails) TEST_F(LockedFileTest, DeleteFile_LockedProduct_DeletesWhenReleased) { + // This test is intended to verify the AP will successfully retry deleting a source asset + // when one of its product assets is locked temporarily + // We'll lock the file by holding it open + auto theFile = m_data->m_absolutePath[1].toUtf8(); const char* theFileString = theFile.constData(); auto [sourcePath, productPath] = *m_data->m_productPaths.find(theFileString); @@ -4216,19 +4231,22 @@ TEST_F(LockedFileTest, DeleteFile_LockedProduct_DeletesWhenReleased) ASSERT_GT(m_data->m_productPaths.size(), 0); QFile product(productPath); + // Open the file and keep it open to lock it + // We'll start a thread later to unlock the file + // This will allow us to test how AP handles trying to delete a locked file ASSERT_TRUE(product.open(QIODevice::ReadOnly)); // Check if we can delete the file now, if we can't, proceed with the test // If we can, it means the OS running this test doesn't lock open files so there's nothing to test if (!AZ::IO::SystemFile::Delete(productPath.toUtf8().constData())) { - AZStd::thread workerThread; + m_deleteCounter = 0; - m_callback = [&product, &workerThread]() { - workerThread = AZStd::thread([&product]() { - AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(60)); - product.close(); - }); + // Set up a callback which will fire after at least 1 retry + // Unlock the file at that point so AP can successfully delete it + m_callback = [&product]() + { + product.close(); }; QMetaObject::invokeMethod( @@ -4238,8 +4256,9 @@ TEST_F(LockedFileTest, DeleteFile_LockedProduct_DeletesWhenReleased) EXPECT_FALSE(QFile::exists(productPath)); EXPECT_EQ(m_data->m_deletedSources.size(), 1); - - workerThread.join(); + + EXPECT_GT(m_deleteCounter, 1); // Make sure the AP tried more than once to delete the file + m_errorAbsorber->ExpectAsserts(0); } else { diff --git a/Code/Tools/DeltaCataloger/Tests/tests_main.cpp b/Code/Tools/DeltaCataloger/Tests/tests_main.cpp index 1a3b86d34b..d5a344b686 100644 --- a/Code/Tools/DeltaCataloger/Tests/tests_main.cpp +++ b/Code/Tools/DeltaCataloger/Tests/tests_main.cpp @@ -45,7 +45,9 @@ protected: AZ::SettingsRegistryInterface* registry = AZ::SettingsRegistry::Get(); auto projectPathKey = AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path"; - registry->Set(projectPathKey, "AutomatedTesting"); + AZ::IO::FixedMaxPath enginePath; + registry->Get(enginePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder); + registry->Set(projectPathKey, (enginePath / "AutomatedTesting").Native()); AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry); AZ::ComponentApplication::Descriptor desc; diff --git a/Code/Tools/ProjectManager/CMakeLists.txt b/Code/Tools/ProjectManager/CMakeLists.txt index d34abcbc6c..a47ccb62c9 100644 --- a/Code/Tools/ProjectManager/CMakeLists.txt +++ b/Code/Tools/ProjectManager/CMakeLists.txt @@ -34,6 +34,7 @@ ly_add_target( INCLUDE_DIRECTORIES PRIVATE Source + Platform/${PAL_PLATFORM_NAME} BUILD_DEPENDENCIES PRIVATE 3rdParty::Qt::Core diff --git a/Code/Tools/ProjectManager/Platform/Linux/PAL_linux_files.cmake b/Code/Tools/ProjectManager/Platform/Linux/PAL_linux_files.cmake index 11222602d5..c3acd44f9b 100644 --- a/Code/Tools/ProjectManager/Platform/Linux/PAL_linux_files.cmake +++ b/Code/Tools/ProjectManager/Platform/Linux/PAL_linux_files.cmake @@ -11,4 +11,6 @@ set(FILES ProjectBuilderWorker_linux.cpp ProjectUtils_linux.cpp ProjectManagerDefs_linux.cpp + ProjectManager_Traits_Platform.h + ProjectManager_Traits_Linux.h ) diff --git a/Code/Tools/ProjectManager/Platform/Linux/ProjectManager_Traits_Linux.h b/Code/Tools/ProjectManager/Platform/Linux/ProjectManager_Traits_Linux.h new file mode 100644 index 0000000000..7c0543361f --- /dev/null +++ b/Code/Tools/ProjectManager/Platform/Linux/ProjectManager_Traits_Linux.h @@ -0,0 +1,11 @@ +/* + * 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 + +#define AZ_TRAIT_PROJECT_MANAGER_CUSTOM_TITLEBAR false diff --git a/Code/Tools/ProjectManager/Platform/Linux/ProjectManager_Traits_Platform.h b/Code/Tools/ProjectManager/Platform/Linux/ProjectManager_Traits_Platform.h new file mode 100644 index 0000000000..97aee25507 --- /dev/null +++ b/Code/Tools/ProjectManager/Platform/Linux/ProjectManager_Traits_Platform.h @@ -0,0 +1,11 @@ +/* + * 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 diff --git a/Code/Tools/ProjectManager/Platform/Mac/PAL_mac_files.cmake b/Code/Tools/ProjectManager/Platform/Mac/PAL_mac_files.cmake index 54b35f0d3c..6d4d453f21 100644 --- a/Code/Tools/ProjectManager/Platform/Mac/PAL_mac_files.cmake +++ b/Code/Tools/ProjectManager/Platform/Mac/PAL_mac_files.cmake @@ -11,4 +11,6 @@ set(FILES ProjectBuilderWorker_mac.cpp ProjectUtils_mac.cpp ProjectManagerDefs_mac.cpp + ProjectManager_Traits_Platform.h + ProjectManager_Traits_Mac.h ) diff --git a/Code/Tools/ProjectManager/Platform/Mac/ProjectManager_Traits_Mac.h b/Code/Tools/ProjectManager/Platform/Mac/ProjectManager_Traits_Mac.h new file mode 100644 index 0000000000..7c0543361f --- /dev/null +++ b/Code/Tools/ProjectManager/Platform/Mac/ProjectManager_Traits_Mac.h @@ -0,0 +1,11 @@ +/* + * 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 + +#define AZ_TRAIT_PROJECT_MANAGER_CUSTOM_TITLEBAR false diff --git a/Code/Tools/ProjectManager/Platform/Mac/ProjectManager_Traits_Platform.h b/Code/Tools/ProjectManager/Platform/Mac/ProjectManager_Traits_Platform.h new file mode 100644 index 0000000000..dc77e77fd0 --- /dev/null +++ b/Code/Tools/ProjectManager/Platform/Mac/ProjectManager_Traits_Platform.h @@ -0,0 +1,11 @@ +/* + * 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 diff --git a/Code/Tools/ProjectManager/Platform/Windows/PAL_windows_files.cmake b/Code/Tools/ProjectManager/Platform/Windows/PAL_windows_files.cmake index d95b0d2502..22b4614ddf 100644 --- a/Code/Tools/ProjectManager/Platform/Windows/PAL_windows_files.cmake +++ b/Code/Tools/ProjectManager/Platform/Windows/PAL_windows_files.cmake @@ -11,4 +11,6 @@ set(FILES ProjectBuilderWorker_windows.cpp ProjectUtils_windows.cpp ProjectManagerDefs_windows.cpp + ProjectManager_Traits_Platform.h + ProjectManager_Traits_Windows.h ) diff --git a/Code/Tools/ProjectManager/Platform/Windows/ProjectManager_Traits_Platform.h b/Code/Tools/ProjectManager/Platform/Windows/ProjectManager_Traits_Platform.h new file mode 100644 index 0000000000..f5eac50dbc --- /dev/null +++ b/Code/Tools/ProjectManager/Platform/Windows/ProjectManager_Traits_Platform.h @@ -0,0 +1,11 @@ +/* + * 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 diff --git a/Code/Tools/ProjectManager/Platform/Windows/ProjectManager_Traits_Windows.h b/Code/Tools/ProjectManager/Platform/Windows/ProjectManager_Traits_Windows.h new file mode 100644 index 0000000000..e6422b5a77 --- /dev/null +++ b/Code/Tools/ProjectManager/Platform/Windows/ProjectManager_Traits_Windows.h @@ -0,0 +1,11 @@ +/* + * 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 + +#define AZ_TRAIT_PROJECT_MANAGER_CUSTOM_TITLEBAR true diff --git a/Code/Tools/ProjectManager/Resources/ProjectManager.qss b/Code/Tools/ProjectManager/Resources/ProjectManager.qss index e755964a7f..d3ec066be7 100644 --- a/Code/Tools/ProjectManager/Resources/ProjectManager.qss +++ b/Code/Tools/ProjectManager/Resources/ProjectManager.qss @@ -9,7 +9,7 @@ QMainWindow { #ScreensCtrl { min-width:1200px; - min-height:800px; + min-height:700px; } QPushButton:focus { @@ -242,11 +242,15 @@ QTabBar::tab:focus { /************** Project Settings **************/ #projectSettings { - margin-top:42px; + margin-top:30px; +} + +#projectPreviewLabel { + margin: 10px 0 5px 0; } #projectTemplate { - margin: 55px 0 0 50px; + margin: 25px 0 0 50px; } #projectTemplateLabel { font-size:16px; diff --git a/Code/Tools/ProjectManager/Source/Application.cpp b/Code/Tools/ProjectManager/Source/Application.cpp index a7e4805ce9..29e0df3c3a 100644 --- a/Code/Tools/ProjectManager/Source/Application.cpp +++ b/Code/Tools/ProjectManager/Source/Application.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -194,8 +195,12 @@ namespace O3DE::ProjectManager // set stylesheet after creating the main window or their styles won't get updated AzQtComponents::StyleManager::setStyleSheet(m_mainWindow.data(), QStringLiteral("style:ProjectManager.qss")); - // the decoration wrapper is intended to remember window positioning and sizing + // the decoration wrapper is intended to remember window positioning and sizing +#if AZ_TRAIT_PROJECT_MANAGER_CUSTOM_TITLEBAR auto wrapper = new AzQtComponents::WindowDecorationWrapper(); +#else + auto wrapper = new AzQtComponents::WindowDecorationWrapper(AzQtComponents::WindowDecorationWrapper::OptionDisabled); +#endif wrapper->setGuest(m_mainWindow.data()); // show the main window here to apply the stylesheet before restoring geometry or we diff --git a/Code/Tools/ProjectManager/Source/CreateProjectCtrl.cpp b/Code/Tools/ProjectManager/Source/CreateProjectCtrl.cpp index 65e01803aa..51e1163713 100644 --- a/Code/Tools/ProjectManager/Source/CreateProjectCtrl.cpp +++ b/Code/Tools/ProjectManager/Source/CreateProjectCtrl.cpp @@ -240,7 +240,7 @@ namespace O3DE::ProjectManager PythonBindingsInterface::Get()->AddProject(projectInfo.m_path); #ifdef TEMPLATE_GEM_CONFIGURATION_ENABLED - const GemCatalogScreen::EnableDisableGemsResult gemResult = m_gemCatalogScreen->EnableDisableGemsForProject(m_projectInfo.m_path); + const GemCatalogScreen::EnableDisableGemsResult gemResult = m_gemCatalogScreen->EnableDisableGemsForProject(projectInfo.m_path); if (gemResult == GemCatalogScreen::EnableDisableGemsResult::Failed) { QMessageBox::critical(this, tr("Failed to configure gems"), tr("Failed to configure gems for template.")); diff --git a/Code/Tools/ProjectManager/Source/CreateProjectCtrl.h b/Code/Tools/ProjectManager/Source/CreateProjectCtrl.h index 40ddb14b83..58f4758edd 100644 --- a/Code/Tools/ProjectManager/Source/CreateProjectCtrl.h +++ b/Code/Tools/ProjectManager/Source/CreateProjectCtrl.h @@ -62,9 +62,6 @@ namespace O3DE::ProjectManager QPushButton* m_secondaryButton = nullptr; #endif // TEMPLATE_GEM_CONFIGURATION_ENABLED - QString m_projectTemplatePath; - ProjectInfo m_projectInfo; - NewProjectSettingsScreen* m_newProjectSettingsScreen = nullptr; GemCatalogScreen* m_gemCatalogScreen = nullptr; }; diff --git a/Code/Tools/ProjectManager/Source/EngineSettingsScreen.cpp b/Code/Tools/ProjectManager/Source/EngineSettingsScreen.cpp index dec1c9a257..c7df00f423 100644 --- a/Code/Tools/ProjectManager/Source/EngineSettingsScreen.cpp +++ b/Code/Tools/ProjectManager/Source/EngineSettingsScreen.cpp @@ -11,19 +11,28 @@ #include #include #include +#include #include #include #include #include +#include namespace O3DE::ProjectManager { EngineSettingsScreen::EngineSettingsScreen(QWidget* parent) : ScreenWidget(parent) { - auto* layout = new QVBoxLayout(); + QScrollArea* scrollArea = new QScrollArea(this); + scrollArea->setWidgetResizable(true); + + QWidget* scrollWidget = new QWidget(this); + scrollArea->setWidget(scrollWidget); + + QVBoxLayout* layout = new QVBoxLayout(scrollWidget); layout->setAlignment(Qt::AlignTop); + scrollWidget->setLayout(layout); setObjectName("engineSettingsScreen"); @@ -39,9 +48,18 @@ namespace O3DE::ProjectManager formTitleLabel->setObjectName("formTitleLabel"); layout->addWidget(formTitleLabel); - m_engineVersion = new FormLineEditWidget(tr("Engine Version"), engineInfo.m_version, this); - m_engineVersion->lineEdit()->setReadOnly(true); - layout->addWidget(m_engineVersion); + FormLineEditWidget* engineName = new FormLineEditWidget(tr("Engine Name"), engineInfo.m_name, this); + engineName->lineEdit()->setReadOnly(true); + layout->addWidget(engineName); + + FormLineEditWidget* engineVersion = new FormLineEditWidget(tr("Engine Version"), engineInfo.m_version, this); + engineVersion->lineEdit()->setReadOnly(true); + layout->addWidget(engineVersion); + + FormBrowseEditWidget* engineFolder = new FormBrowseEditWidget(tr("Engine Folder"), engineInfo.m_path, this); + engineFolder->lineEdit()->setReadOnly(true); + connect( engineFolder, &FormBrowseEditWidget::OnBrowse, [engineInfo]{ AzQtComponents::ShowFileOnDesktop(engineInfo.m_path); }); + layout->addWidget(engineFolder); m_thirdParty = new FormFolderBrowseEditWidget(tr("3rd Party Software Folder"), engineInfo.m_thirdPartyPath, this); m_thirdParty->lineEdit()->setValidator(new PathValidator(PathValidator::PathMode::ExistingFolder, this)); @@ -71,7 +89,11 @@ namespace O3DE::ProjectManager connect(m_defaultProjectTemplates->lineEdit(), &QLineEdit::textChanged, this, &EngineSettingsScreen::OnTextChanged); layout->addWidget(m_defaultProjectTemplates); - setLayout(layout); + QVBoxLayout* mainLayout = new QVBoxLayout(); + mainLayout->setAlignment(Qt::AlignTop); + mainLayout->setMargin(0); + mainLayout->addWidget(scrollArea); + setLayout(mainLayout); } ProjectManagerScreen EngineSettingsScreen::GetScreenEnum() diff --git a/Code/Tools/ProjectManager/Source/EngineSettingsScreen.h b/Code/Tools/ProjectManager/Source/EngineSettingsScreen.h index 2f16400405..1efabd4b5e 100644 --- a/Code/Tools/ProjectManager/Source/EngineSettingsScreen.h +++ b/Code/Tools/ProjectManager/Source/EngineSettingsScreen.h @@ -29,7 +29,6 @@ namespace O3DE::ProjectManager void OnTextChanged(); private: - FormLineEditWidget* m_engineVersion; FormBrowseEditWidget* m_thirdParty; FormBrowseEditWidget* m_defaultProjects; FormBrowseEditWidget* m_defaultGems; diff --git a/Code/Tools/ProjectManager/Source/FormBrowseEditWidget.cpp b/Code/Tools/ProjectManager/Source/FormBrowseEditWidget.cpp index 8cd28fcbb4..fe101cf37a 100644 --- a/Code/Tools/ProjectManager/Source/FormBrowseEditWidget.cpp +++ b/Code/Tools/ProjectManager/Source/FormBrowseEditWidget.cpp @@ -20,7 +20,8 @@ namespace O3DE::ProjectManager setObjectName("formBrowseEditWidget"); QPushButton* browseButton = new QPushButton(this); - connect(browseButton, &QPushButton::pressed, this, &FormBrowseEditWidget::HandleBrowseButton); + connect( browseButton, &QPushButton::pressed, [this]{ emit OnBrowse(); }); + connect( this, &FormBrowseEditWidget::OnBrowse, this, &FormBrowseEditWidget::HandleBrowseButton); m_frameLayout->addWidget(browseButton); } @@ -34,7 +35,7 @@ namespace O3DE::ProjectManager int key = event->key(); if (key == Qt::Key_Return || key == Qt::Key_Enter) { - HandleBrowseButton(); + emit OnBrowse(); } } diff --git a/Code/Tools/ProjectManager/Source/FormBrowseEditWidget.h b/Code/Tools/ProjectManager/Source/FormBrowseEditWidget.h index a1f6948ce9..179fe03253 100644 --- a/Code/Tools/ProjectManager/Source/FormBrowseEditWidget.h +++ b/Code/Tools/ProjectManager/Source/FormBrowseEditWidget.h @@ -24,10 +24,13 @@ namespace O3DE::ProjectManager explicit FormBrowseEditWidget(const QString& labelText = "", QWidget* parent = nullptr); ~FormBrowseEditWidget() = default; + signals: + void OnBrowse(); + protected: void keyPressEvent(QKeyEvent* event) override; protected slots: - virtual void HandleBrowseButton() = 0; + virtual void HandleBrowseButton() {}; }; } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp index db34375db5..9f5d4a70f2 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp @@ -30,7 +30,7 @@ namespace O3DE::ProjectManager : ScreenWidget(parent) { m_gemModel = new GemModel(this); - m_proxModel = new GemSortFilterProxyModel(m_gemModel, this); + m_proxyModel = new GemSortFilterProxyModel(m_gemModel, this); QVBoxLayout* vLayout = new QVBoxLayout(); vLayout->setMargin(0); @@ -39,7 +39,7 @@ namespace O3DE::ProjectManager m_downloadController = new DownloadController(); - m_headerWidget = new GemCatalogHeaderWidget(m_gemModel, m_proxModel, m_downloadController); + m_headerWidget = new GemCatalogHeaderWidget(m_gemModel, m_proxyModel, m_downloadController); vLayout->addWidget(m_headerWidget); connect(m_gemModel, &GemModel::gemStatusChanged, this, &GemCatalogScreen::OnGemStatusChanged); @@ -50,10 +50,12 @@ namespace O3DE::ProjectManager hLayout->setMargin(0); vLayout->addLayout(hLayout); - m_gemListView = new GemListView(m_proxModel, m_proxModel->GetSelectionModel(), this); + m_gemListView = new GemListView(m_proxyModel, m_proxyModel->GetSelectionModel(), this); m_gemInspector = new GemInspector(m_gemModel, this); m_gemInspector->setFixedWidth(240); + connect(m_gemInspector, &GemInspector::TagClicked, this, &GemCatalogScreen::SelectGem); + QWidget* filterWidget = new QWidget(this); filterWidget->setFixedWidth(240); m_filterWidgetLayout = new QVBoxLayout(); @@ -61,7 +63,7 @@ namespace O3DE::ProjectManager m_filterWidgetLayout->setSpacing(0); filterWidget->setLayout(m_filterWidgetLayout); - GemListHeaderWidget* listHeaderWidget = new GemListHeaderWidget(m_proxModel); + GemListHeaderWidget* listHeaderWidget = new GemListHeaderWidget(m_proxyModel); QVBoxLayout* middleVLayout = new QVBoxLayout(); middleVLayout->setMargin(0); @@ -80,19 +82,21 @@ namespace O3DE::ProjectManager void GemCatalogScreen::ReinitForProject(const QString& projectPath) { - m_gemModel->clear(); + m_gemModel->Clear(); m_gemsToRegisterWithProject.clear(); FillModel(projectPath); + m_proxyModel->ResetFilters(); + if (m_filterWidget) { - m_filterWidget->hide(); - m_filterWidget->deleteLater(); + m_filterWidget->ResetAllFilters(); + } + else + { + m_filterWidget = new GemFilterWidget(m_proxyModel); + m_filterWidgetLayout->addWidget(m_filterWidget); } - - m_proxModel->ResetFilters(); - m_filterWidget = new GemFilterWidget(m_proxModel); - m_filterWidgetLayout->addWidget(m_filterWidget); m_headerWidget->ReinitForProject(); @@ -145,10 +149,11 @@ namespace O3DE::ProjectManager } } - void GemCatalogScreen::OnGemStatusChanged(const QModelIndex& modelIndex, uint32_t numChangedDependencies) + void GemCatalogScreen::OnGemStatusChanged(const QString& gemName, uint32_t numChangedDependencies) { if (m_notificationsEnabled) { + QModelIndex modelIndex = m_gemModel->FindIndexByNameString(gemName); bool added = GemModel::IsAdded(modelIndex); bool dependency = GemModel::IsAddedDependency(modelIndex); @@ -170,7 +175,7 @@ namespace O3DE::ProjectManager if (added && GemModel::GetDownloadStatus(modelIndex) == GemInfo::DownloadStatus::NotDownloaded) { m_downloadController->AddGemDownload(GemModel::GetName(modelIndex)); - GemModel::SetDownloadStatus(*m_proxModel, modelIndex, GemInfo::DownloadStatus::Downloading); + GemModel::SetDownloadStatus(*m_proxyModel, modelIndex, GemInfo::DownloadStatus::Downloading); } } @@ -192,6 +197,20 @@ namespace O3DE::ProjectManager } } + void GemCatalogScreen::SelectGem(const QString& gemName) + { + QModelIndex modelIndex = m_gemModel->FindIndexByNameString(gemName); + if (!m_proxyModel->filterAcceptsRow(modelIndex.row(), QModelIndex())) + { + m_proxyModel->ResetFilters(); + m_filterWidget->ResetAllFilters(); + } + + QModelIndex proxyIndex = m_proxyModel->mapFromSource(modelIndex); + m_proxyModel->GetSelectionModel()->select(proxyIndex, QItemSelectionModel::ClearAndSelect); + m_gemListView->scrollTo(proxyIndex); + } + void GemCatalogScreen::hideEvent(QHideEvent* event) { ScreenWidget::hideEvent(event); @@ -234,7 +253,11 @@ namespace O3DE::ProjectManager const QVector allRepoGemInfos = allRepoGemInfosResult.GetValue(); for (const GemInfo& gemInfo : allRepoGemInfos) { - m_gemModel->AddGem(gemInfo); + // do not add gems that have already been downloaded + if (!m_gemModel->FindIndexByNameString(gemInfo.m_name).isValid()) + { + m_gemModel->AddGem(gemInfo); + } } } else @@ -258,7 +281,8 @@ namespace O3DE::ProjectManager GemModel::SetWasPreviouslyAdded(*m_gemModel, modelIndex, true); GemModel::SetIsAdded(*m_gemModel, modelIndex, true); } - else + // ${Name} is a special name used in templates and is not really an error + else if (enabledGemName != "${Name}") { AZ_Warning("ProjectManager::GemCatalog", false, "Cannot find entry for gem with name '%s'. The CMake target name probably does not match the specified name in the gem.json.", diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h index 1ade87af0c..55fbb1befc 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h @@ -46,8 +46,9 @@ namespace O3DE::ProjectManager DownloadController* GetDownloadController() const { return m_downloadController; } public slots: - void OnGemStatusChanged(const QModelIndex& modelIndex, uint32_t numChangedDependencies); + void OnGemStatusChanged(const QString& gemName, uint32_t numChangedDependencies); void OnAddGemClicked(); + void SelectGem(const QString& gemName); protected: void hideEvent(QHideEvent* event) override; @@ -68,7 +69,7 @@ namespace O3DE::ProjectManager GemInspector* m_gemInspector = nullptr; GemModel* m_gemModel = nullptr; GemCatalogHeaderWidget* m_headerWidget = nullptr; - GemSortFilterProxyModel* m_proxModel = nullptr; + GemSortFilterProxyModel* m_proxyModel = nullptr; QVBoxLayout* m_filterWidgetLayout = nullptr; GemFilterWidget* m_filterWidget = nullptr; DownloadController* m_downloadController = nullptr; diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemFilterWidget.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemFilterWidget.cpp index 4f737d8629..b608445d0f 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemFilterWidget.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemFilterWidget.cpp @@ -213,11 +213,99 @@ namespace O3DE::ProjectManager m_filterLayout->setContentsMargins(0, 0, 0, 0); filterSection->setLayout(m_filterLayout); + ResetAllFilters(); + } + + void GemFilterWidget::ResetAllFilters() + { ResetGemStatusFilter(); - AddGemOriginFilter(); - AddTypeFilter(); - AddPlatformFilter(); - AddFeatureFilter(); + ResetGemOriginFilter(); + ResetTypeFilter(); + ResetPlatformFilter(); + ResetFeatureFilter(); + } + + void GemFilterWidget::ResetFilterWidget( + FilterCategoryWidget*& filterPtr, + const QString& filterName, + const QVector& elementNames, + const QVector& elementCounts, + int defaultShowCount) + { + bool wasCollapsed = false; + if (filterPtr) + { + wasCollapsed = filterPtr->IsCollapsed(); + } + + FilterCategoryWidget* filterWidget = new FilterCategoryWidget( + filterName, elementNames, elementCounts, /*showAllLessButton=*/defaultShowCount != 4, /*collapsed*/ wasCollapsed, + /*defaultShowCount=*/defaultShowCount); + if (filterPtr) + { + m_filterLayout->replaceWidget(filterPtr, filterWidget); + } + else + { + m_filterLayout->addWidget(filterWidget); + } + + filterPtr->deleteLater(); + filterPtr = filterWidget; + } + + template + void GemFilterWidget::ResetSimpleOrFilter( + FilterCategoryWidget*& filterPtr, + const QString& filterName, + int numFilterElements, + bool (*filterMatcher)(GemModel*, filterType, int), + QString (*typeStringGetter)(filterType), + filterFlagsType (GemSortFilterProxyModel::*filterFlagsGetter)() const, + void (GemSortFilterProxyModel::*filterFlagsSetter)(const filterFlagsType&)) + { + QVector elementNames; + QVector elementCounts; + const int numGems = m_gemModel->rowCount(); + for (int filterIndex = 0; filterIndex < numFilterElements; ++filterIndex) + { + const filterType gemFilterToBeCounted = static_cast(1 << filterIndex); + + int gemFilterCount = 0; + for (int gemIndex = 0; gemIndex < numGems; ++gemIndex) + { + // If filter matches increment filter count + gemFilterCount += filterMatcher(m_gemModel, gemFilterToBeCounted, gemIndex); + } + elementNames.push_back(typeStringGetter(gemFilterToBeCounted)); + elementCounts.push_back(gemFilterCount); + } + + // Replace existing filter and delete old one + ResetFilterWidget(filterPtr, filterName, elementNames, elementCounts); + + const QList buttons = filterPtr->GetButtonGroup()->buttons(); + for (int i = 0; i < buttons.size(); ++i) + { + const filterType gemFilter = static_cast(1 << i); + QAbstractButton* button = buttons[i]; + + connect( + button, &QAbstractButton::toggled, this, + [=](bool checked) + { + filterFlagsType gemFilters = (m_filterProxyModel->*filterFlagsGetter)(); + if (checked) + { + gemFilters |= gemFilter; + } + else + { + gemFilters &= ~gemFilter; + } + (m_filterProxyModel->*filterFlagsSetter)(gemFilters); + }); + } } void GemFilterWidget::ResetGemStatusFilter() @@ -241,25 +329,7 @@ namespace O3DE::ProjectManager elementNames.push_back(GemSortFilterProxyModel::GetGemActiveString(GemSortFilterProxyModel::GemActive::Inactive)); elementCounts.push_back(totalGems - enabledGemTotal); - bool wasCollapsed = false; - if (m_statusFilter) - { - wasCollapsed = m_statusFilter->IsCollapsed(); - } - - FilterCategoryWidget* filterWidget = - new FilterCategoryWidget("Status", elementNames, elementCounts, /*showAllLessButton=*/false, /*collapsed*/wasCollapsed); - if (m_statusFilter) - { - m_filterLayout->replaceWidget(m_statusFilter, filterWidget); - } - else - { - m_filterLayout->addWidget(filterWidget); - } - - m_statusFilter->deleteLater(); - m_statusFilter = filterWidget; + ResetFilterWidget(m_statusFilter, "Status", elementNames, elementCounts); const QList buttons = m_statusFilter->GetButtonGroup()->buttons(); @@ -317,157 +387,42 @@ namespace O3DE::ProjectManager connect(activeButton, &QAbstractButton::toggled, this, updateGemActive); } - void GemFilterWidget::AddGemOriginFilter() + void GemFilterWidget::ResetGemOriginFilter() { - QVector elementNames; - QVector elementCounts; - const int numGems = m_gemModel->rowCount(); - for (int originIndex = 0; originIndex < GemInfo::NumGemOrigins; ++originIndex) - { - const GemInfo::GemOrigin gemOriginToBeCounted = static_cast(1 << originIndex); - - int gemOriginCount = 0; - for (int gemIndex = 0; gemIndex < numGems; ++gemIndex) + ResetSimpleOrFilter + ( + m_originFilter, "Provider", GemInfo::NumGemOrigins, + [](GemModel* gemModel, GemInfo::GemOrigin origin, int gemIndex) { - const GemInfo::GemOrigin gemOrigin = m_gemModel->GetGemOrigin(m_gemModel->index(gemIndex, 0)); - - // Is the gem of the given origin? - if (gemOriginToBeCounted == gemOrigin) - { - gemOriginCount++; - } - } - - elementNames.push_back(GemInfo::GetGemOriginString(gemOriginToBeCounted)); - elementCounts.push_back(gemOriginCount); - } - - FilterCategoryWidget* filterWidget = new FilterCategoryWidget("Provider", elementNames, elementCounts, /*showAllLessButton=*/false); - m_filterLayout->addWidget(filterWidget); - - const QList buttons = filterWidget->GetButtonGroup()->buttons(); - for (int i = 0; i < buttons.size(); ++i) - { - const GemInfo::GemOrigin gemOrigin = static_cast(1 << i); - QAbstractButton* button = buttons[i]; - - connect(button, &QAbstractButton::toggled, this, [=](bool checked) - { - GemInfo::GemOrigins gemOrigins = m_filterProxyModel->GetGemOrigins(); - if (checked) - { - gemOrigins |= gemOrigin; - } - else - { - gemOrigins &= ~gemOrigin; - } - m_filterProxyModel->SetGemOrigins(gemOrigins); - }); - } + return origin == gemModel->GetGemOrigin(gemModel->index(gemIndex, 0)); + }, + &GemInfo::GetGemOriginString, &GemSortFilterProxyModel::GetGemOrigins, &GemSortFilterProxyModel::SetGemOrigins + ); } - void GemFilterWidget::AddTypeFilter() + void GemFilterWidget::ResetTypeFilter() { - QVector elementNames; - QVector elementCounts; - const int numGems = m_gemModel->rowCount(); - for (int typeIndex = 0; typeIndex < GemInfo::NumTypes; ++typeIndex) - { - const GemInfo::Type type = static_cast(1 << typeIndex); - - int typeGemCount = 0; - for (int gemIndex = 0; gemIndex < numGems; ++gemIndex) + ResetSimpleOrFilter( + m_typeFilter, "Type", GemInfo::NumTypes, + [](GemModel* gemModel, GemInfo::Type type, int gemIndex) { - const GemInfo::Types types = m_gemModel->GetTypes(m_gemModel->index(gemIndex, 0)); - - // Is type (Asset, Code, Tool) part of the gem? - if (types & type) - { - typeGemCount++; - } - } - - elementNames.push_back(GemInfo::GetTypeString(type)); - elementCounts.push_back(typeGemCount); - } - - FilterCategoryWidget* filterWidget = new FilterCategoryWidget("Type", elementNames, elementCounts, /*showAllLessButton=*/false); - m_filterLayout->addWidget(filterWidget); - - const QList buttons = filterWidget->GetButtonGroup()->buttons(); - for (int i = 0; i < buttons.size(); ++i) - { - const GemInfo::Type type = static_cast(1 << i); - QAbstractButton* button = buttons[i]; - - connect(button, &QAbstractButton::toggled, this, [=](bool checked) - { - GemInfo::Types types = m_filterProxyModel->GetTypes(); - if (checked) - { - types |= type; - } - else - { - types &= ~type; - } - m_filterProxyModel->SetTypes(types); - }); - } + return static_cast(type & gemModel->GetTypes(gemModel->index(gemIndex, 0))); + }, + &GemInfo::GetTypeString, &GemSortFilterProxyModel::GetTypes, &GemSortFilterProxyModel::SetTypes); } - void GemFilterWidget::AddPlatformFilter() + void GemFilterWidget::ResetPlatformFilter() { - QVector elementNames; - QVector elementCounts; - const int numGems = m_gemModel->rowCount(); - for (int platformIndex = 0; platformIndex < GemInfo::NumPlatforms; ++platformIndex) - { - const GemInfo::Platform platform = static_cast(1 << platformIndex); - - int platformGemCount = 0; - for (int gemIndex = 0; gemIndex < numGems; ++gemIndex) + ResetSimpleOrFilter( + m_platformFilter, "Supported Platforms", GemInfo::NumPlatforms, + [](GemModel* gemModel, GemInfo::Platform platform, int gemIndex) { - const GemInfo::Platforms platforms = m_gemModel->GetPlatforms(m_gemModel->index(gemIndex, 0)); - - // Is platform supported? - if (platforms & platform) - { - platformGemCount++; - } - } - - elementNames.push_back(GemInfo::GetPlatformString(platform)); - elementCounts.push_back(platformGemCount); - } - - FilterCategoryWidget* filterWidget = new FilterCategoryWidget("Supported Platforms", elementNames, elementCounts, /*showAllLessButton=*/false); - m_filterLayout->addWidget(filterWidget); - - const QList buttons = filterWidget->GetButtonGroup()->buttons(); - for (int i = 0; i < buttons.size(); ++i) - { - const GemInfo::Platform platform = static_cast(1 << i); - QAbstractButton* button = buttons[i]; - - connect(button, &QAbstractButton::toggled, this, [=](bool checked) - { - GemInfo::Platforms platforms = m_filterProxyModel->GetPlatforms(); - if (checked) - { - platforms |= platform; - } - else - { - platforms &= ~platform; - } - m_filterProxyModel->SetPlatforms(platforms); - }); - } + return static_cast(platform & gemModel->GetPlatforms(gemModel->index(gemIndex, 0))); + }, + &GemInfo::GetPlatformString, &GemSortFilterProxyModel::GetPlatforms, &GemSortFilterProxyModel::SetPlatforms); } - void GemFilterWidget::AddFeatureFilter() + void GemFilterWidget::ResetFeatureFilter() { // Alphabetically sorted, unique features and their number of occurrences in the gem database. QMap uniqueFeatureCounts; @@ -497,11 +452,15 @@ namespace O3DE::ProjectManager elementCounts.push_back(iterator.value()); } - FilterCategoryWidget* filterWidget = new FilterCategoryWidget("Features", elementNames, elementCounts, - /*showAllLessButton=*/true, false, /*defaultShowCount=*/5); - m_filterLayout->addWidget(filterWidget); + ResetFilterWidget(m_featureFilter, "Features", elementNames, elementCounts, /*defaultShowCount=*/5); - const QList buttons = filterWidget->GetButtonGroup()->buttons(); + for (QMetaObject::Connection& connection : m_featureTagConnections) + { + disconnect(connection); + } + m_featureTagConnections.clear(); + + const QList buttons = m_featureFilter->GetButtonGroup()->buttons(); for (int i = 0; i < buttons.size(); ++i) { const QString& feature = elementNames[i]; @@ -523,13 +482,13 @@ namespace O3DE::ProjectManager }); // Sync the UI state with the proxy model filtering. - connect(m_filterProxyModel, &GemSortFilterProxyModel::OnInvalidated, this, [=] + m_featureTagConnections.push_back(connect(m_filterProxyModel, &GemSortFilterProxyModel::OnInvalidated, this, [=] { const QSet& filteredFeatureTags = m_filterProxyModel->GetFeatures(); const bool isChecked = filteredFeatureTags.contains(button->text()); QSignalBlocker signalsBlocker(button); button->setChecked(isChecked); - }); + })); } } } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemFilterWidget.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemFilterWidget.h index 6340f8309b..e422178d08 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemFilterWidget.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemFilterWidget.h @@ -66,17 +66,41 @@ namespace O3DE::ProjectManager ~GemFilterWidget() = default; public slots: + void ResetAllFilters(); void ResetGemStatusFilter(); private: - void AddGemOriginFilter(); - void AddTypeFilter(); - void AddPlatformFilter(); - void AddFeatureFilter(); + void ResetGemOriginFilter(); + void ResetTypeFilter(); + void ResetPlatformFilter(); + void ResetFeatureFilter(); + + void ResetFilterWidget( + FilterCategoryWidget*& filterPtr, + const QString& filterName, + const QVector& elementNames, + const QVector& elementCounts, + int defaultShowCount = 4); + + template + void ResetSimpleOrFilter( + FilterCategoryWidget*& filterPtr, + const QString& filterName, + int numFilterElements, + bool (*filterMatcher)(GemModel*, filterType, int), + QString (*typeStringGetter)(filterType), + filterFlagsType (GemSortFilterProxyModel::*filterFlagsGetter)() const, + void (GemSortFilterProxyModel::*filterFlagsSetter)(const filterFlagsType&)); QVBoxLayout* m_filterLayout = nullptr; GemModel* m_gemModel = nullptr; GemSortFilterProxyModel* m_filterProxyModel = nullptr; FilterCategoryWidget* m_statusFilter = nullptr; + FilterCategoryWidget* m_originFilter = nullptr; + FilterCategoryWidget* m_typeFilter = nullptr; + FilterCategoryWidget* m_platformFilter = nullptr; + FilterCategoryWidget* m_featureFilter = nullptr; + + QVector m_featureTagConnections; }; } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h index 8c6d40505a..12cce5a4ea 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h @@ -81,6 +81,8 @@ namespace O3DE::ProjectManager DownloadStatus m_downloadStatus = UnknownDownloadStatus; QStringList m_features; QString m_requirement; + QString m_licenseText; + QString m_licenseLink; QString m_directoryLink; QString m_documentationLink; QString m_version = "Unknown Version"; diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.cpp index 7630e92e88..b0b8cca29a 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.cpp @@ -52,6 +52,22 @@ namespace O3DE::ProjectManager Update(selectedIndices[0]); } + void SetLabelElidedText(QLabel* label, QString text) + { + QFontMetrics nameFontMetrics(label->font()); + int labelWidth = label->width(); + + // Don't elide if the widgets are sized too small (sometimes occurs when loading gem catalog) + if (labelWidth > 100) + { + label->setText(nameFontMetrics.elidedText(text, Qt::ElideRight, labelWidth)); + } + else + { + label->setText(text); + } + } + void GemInspector::Update(const QModelIndex& modelIndex) { if (!modelIndex.isValid()) @@ -59,38 +75,52 @@ namespace O3DE::ProjectManager m_mainWidget->hide(); } - m_nameLabel->setText(m_model->GetDisplayName(modelIndex)); - m_creatorLabel->setText(m_model->GetCreator(modelIndex)); + SetLabelElidedText(m_nameLabel, m_model->GetDisplayName(modelIndex)); + SetLabelElidedText(m_creatorLabel, m_model->GetCreator(modelIndex)); m_summaryLabel->setText(m_model->GetSummary(modelIndex)); m_summaryLabel->adjustSize(); + m_licenseLinkLabel->setText(m_model->GetLicenseText(modelIndex)); + m_licenseLinkLabel->SetUrl(m_model->GetLicenseLink(modelIndex)); + m_directoryLinkLabel->SetUrl(m_model->GetDirectoryLink(modelIndex)); m_documentationLinkLabel->SetUrl(m_model->GetDocLink(modelIndex)); if (m_model->HasRequirement(modelIndex)) { - m_reqirementsIconLabel->show(); - m_reqirementsTitleLabel->show(); - m_reqirementsTextLabel->show(); + m_requirementsIconLabel->show(); + m_requirementsTitleLabel->show(); + m_requirementsTextLabel->show(); + m_requirementsMainSpacer->changeSize(0, 20, QSizePolicy::Fixed, QSizePolicy::Fixed); - m_reqirementsTitleLabel->setText("Requirement"); - m_reqirementsTextLabel->setText(m_model->GetRequirement(modelIndex)); + m_requirementsTitleLabel->setText(tr("Requirement")); + m_requirementsTextLabel->setText(m_model->GetRequirement(modelIndex)); } else { - m_reqirementsIconLabel->hide(); - m_reqirementsTitleLabel->hide(); - m_reqirementsTextLabel->hide(); + m_requirementsIconLabel->hide(); + m_requirementsTitleLabel->hide(); + m_requirementsTextLabel->hide(); + m_requirementsMainSpacer->changeSize(0, 0, QSizePolicy::Fixed, QSizePolicy::Fixed); } // Depending gems - m_dependingGems->Update("Depending Gems", "The following Gems will be automatically enabled with this Gem.", m_model->GetDependingGemNames(modelIndex)); + QStringList dependingGems = m_model->GetDependingGemNames(modelIndex); + if (!dependingGems.isEmpty()) + { + m_dependingGems->Update(tr("Depending Gems"), tr("The following Gems will be automatically enabled with this Gem."), dependingGems); + m_dependingGems->show(); + } + else + { + m_dependingGems->hide(); + } // Additional information - m_versionLabel->setText(QString("Gem Version: %1").arg(m_model->GetVersion(modelIndex))); - m_lastUpdatedLabel->setText(QString("Last Updated: %1").arg(m_model->GetLastUpdated(modelIndex))); - m_binarySizeLabel->setText(QString("Binary Size: %1 KB").arg(m_model->GetBinarySizeInKB(modelIndex))); + m_versionLabel->setText(tr("Gem Version: %1").arg(m_model->GetVersion(modelIndex))); + m_lastUpdatedLabel->setText(tr("Last Updated: %1").arg(m_model->GetLastUpdated(modelIndex))); + m_binarySizeLabel->setText(tr("Binary Size: %1 KB").arg(m_model->GetBinarySizeInKB(modelIndex))); m_mainWidget->adjustSize(); m_mainWidget->show(); @@ -108,35 +138,51 @@ namespace O3DE::ProjectManager { // Gem name, creator and summary m_nameLabel = CreateStyledLabel(m_mainLayout, 18, s_headerColor); - m_creatorLabel = CreateStyledLabel(m_mainLayout, 12, s_headerColor); + m_creatorLabel = CreateStyledLabel(m_mainLayout, s_baseFontSize, s_headerColor); m_mainLayout->addSpacing(5); // TODO: QLabel seems to have issues determining the right sizeHint() for our font with the given font size. // This results into squeezed elements in the layout in case the text is a little longer than a sentence. - m_summaryLabel = CreateStyledLabel(m_mainLayout, 12, s_textColor); + m_summaryLabel = CreateStyledLabel(m_mainLayout, s_baseFontSize, s_headerColor); m_mainLayout->addWidget(m_summaryLabel); m_summaryLabel->setWordWrap(true); m_summaryLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); m_summaryLabel->setOpenExternalLinks(true); m_mainLayout->addSpacing(5); + // License + { + QHBoxLayout* licenseHLayout = new QHBoxLayout(); + licenseHLayout->setMargin(0); + licenseHLayout->setAlignment(Qt::AlignLeft); + m_mainLayout->addLayout(licenseHLayout); + + QLabel* licenseLabel = CreateStyledLabel(licenseHLayout, s_baseFontSize, s_headerColor); + licenseLabel->setText(tr("License: ")); + + m_licenseLinkLabel = new LinkLabel("", QUrl(), s_baseFontSize); + licenseHLayout->addWidget(m_licenseLinkLabel); + + licenseHLayout->addStretch(); + + m_mainLayout->addSpacing(5); + } + // Directory and documentation links { QHBoxLayout* linksHLayout = new QHBoxLayout(); linksHLayout->setMargin(0); m_mainLayout->addLayout(linksHLayout); - QSpacerItem* spacerLeft = new QSpacerItem(0, 0, QSizePolicy::Expanding); - linksHLayout->addSpacerItem(spacerLeft); + linksHLayout->addStretch(); - m_directoryLinkLabel = new LinkLabel("View in Directory"); + m_directoryLinkLabel = new LinkLabel(tr("View in Directory")); linksHLayout->addWidget(m_directoryLinkLabel); linksHLayout->addWidget(new QLabel("|")); - m_documentationLinkLabel = new LinkLabel("Read Documentation"); + m_documentationLinkLabel = new LinkLabel(tr("Read Documentation")); linksHLayout->addWidget(m_documentationLinkLabel); - QSpacerItem* spacerRight = new QSpacerItem(0, 0, QSizePolicy::Expanding); - linksHLayout->addSpacerItem(spacerRight); + linksHLayout->addStretch(); m_mainLayout->addSpacing(8); } @@ -144,46 +190,48 @@ namespace O3DE::ProjectManager // Separating line QFrame* hLine = new QFrame(); hLine->setFrameShape(QFrame::HLine); - hLine->setStyleSheet("color: #666666;"); + hLine->setObjectName("horizontalSeparatingLine"); m_mainLayout->addWidget(hLine); m_mainLayout->addSpacing(10); // Requirements - m_reqirementsTitleLabel = GemInspector::CreateStyledLabel(m_mainLayout, 16, s_headerColor); + m_requirementsTitleLabel = GemInspector::CreateStyledLabel(m_mainLayout, 16, s_headerColor); - QHBoxLayout* requrementsLayout = new QHBoxLayout(); - requrementsLayout->setAlignment(Qt::AlignTop); - requrementsLayout->setMargin(0); - requrementsLayout->setSpacing(0); + QHBoxLayout* requirementsLayout = new QHBoxLayout(); + requirementsLayout->setAlignment(Qt::AlignTop); + requirementsLayout->setMargin(0); + requirementsLayout->setSpacing(0); - m_reqirementsIconLabel = new QLabel(); - m_reqirementsIconLabel->setPixmap(QIcon(":/Warning.svg").pixmap(24, 24)); - requrementsLayout->addWidget(m_reqirementsIconLabel); + m_requirementsIconLabel = new QLabel(); + m_requirementsIconLabel->setPixmap(QIcon(":/Warning.svg").pixmap(24, 24)); + requirementsLayout->addWidget(m_requirementsIconLabel); - m_reqirementsTextLabel = GemInspector::CreateStyledLabel(requrementsLayout, 10, s_textColor); - m_reqirementsTextLabel->setWordWrap(true); - m_reqirementsTextLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); - m_reqirementsTextLabel->setOpenExternalLinks(true); + m_requirementsTextLabel = GemInspector::CreateStyledLabel(requirementsLayout, 10, s_textColor); + m_requirementsTextLabel->setWordWrap(true); + m_requirementsTextLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); + m_requirementsTextLabel->setOpenExternalLinks(true); - QSpacerItem* reqirementsSpacer = new QSpacerItem(0, 0, QSizePolicy::Expanding); - requrementsLayout->addSpacerItem(reqirementsSpacer); + QSpacerItem* requirementsSpacer = new QSpacerItem(0, 0, QSizePolicy::MinimumExpanding); + requirementsLayout->addSpacerItem(requirementsSpacer); - m_mainLayout->addLayout(requrementsLayout); + m_mainLayout->addLayout(requirementsLayout); - m_mainLayout->addSpacing(20); + m_requirementsMainSpacer = new QSpacerItem(0, 20, QSizePolicy::Fixed, QSizePolicy::Fixed); + m_mainLayout->addSpacerItem(m_requirementsMainSpacer); // Depending gems m_dependingGems = new GemsSubWidget(); + connect(m_dependingGems, &GemsSubWidget::TagClicked, this, [=](const QString& tag){ emit TagClicked(tag); }); m_mainLayout->addWidget(m_dependingGems); m_mainLayout->addSpacing(20); // Additional information QLabel* additionalInfoLabel = CreateStyledLabel(m_mainLayout, 14, s_headerColor); - additionalInfoLabel->setText("Additional Information"); + additionalInfoLabel->setText(tr("Additional Information")); - m_versionLabel = CreateStyledLabel(m_mainLayout, 12, s_textColor); - m_lastUpdatedLabel = CreateStyledLabel(m_mainLayout, 12, s_textColor); - m_binarySizeLabel = CreateStyledLabel(m_mainLayout, 12, s_textColor); + m_versionLabel = CreateStyledLabel(m_mainLayout, s_baseFontSize, s_textColor); + m_lastUpdatedLabel = CreateStyledLabel(m_mainLayout, s_baseFontSize, s_textColor); + m_binarySizeLabel = CreateStyledLabel(m_mainLayout, s_baseFontSize, s_textColor); } } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.h index ca36cef240..c6548527ab 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.h @@ -16,7 +16,7 @@ #include #include -#include +#include #endif QT_FORWARD_DECLARE_CLASS(QVBoxLayout) @@ -36,10 +36,16 @@ namespace O3DE::ProjectManager void Update(const QModelIndex& modelIndex); static QLabel* CreateStyledLabel(QLayout* layout, int fontSize, const QString& colorCodeString); + // Fonts + inline constexpr static int s_baseFontSize = 12; + // Colors inline constexpr static const char* s_headerColor = "#FFFFFF"; inline constexpr static const char* s_textColor = "#DDDDDD"; + signals: + void TagClicked(const QString& tag); + private slots: void OnSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected); @@ -54,13 +60,15 @@ namespace O3DE::ProjectManager QLabel* m_nameLabel = nullptr; QLabel* m_creatorLabel = nullptr; QLabel* m_summaryLabel = nullptr; + LinkLabel* m_licenseLinkLabel = nullptr; LinkLabel* m_directoryLinkLabel = nullptr; LinkLabel* m_documentationLinkLabel = nullptr; // Requirements - QLabel* m_reqirementsTitleLabel = nullptr; - QLabel* m_reqirementsIconLabel = nullptr; - QLabel* m_reqirementsTextLabel = nullptr; + QLabel* m_requirementsTitleLabel = nullptr; + QLabel* m_requirementsIconLabel = nullptr; + QLabel* m_requirementsTextLabel = nullptr; + QSpacerItem* m_requirementsMainSpacer = nullptr; // Depending and conflicting gems GemsSubWidget* m_dependingGems = nullptr; diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.cpp index acdef483ae..3f57aebf71 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.cpp @@ -27,6 +27,14 @@ namespace O3DE::ProjectManager void GemModel::AddGem(const GemInfo& gemInfo) { + if (FindIndexByNameString(gemInfo.m_name).isValid()) + { + // do not add gems with duplicate names + // this can happen by mistake or when a gem repo has a gem with the same name as a local gem + AZ_TracePrintf("GemModel", "Ignoring duplicate gem: %s", gemInfo.m_name.toUtf8().constData()); + return; + } + QStandardItem* item = new QStandardItem(); item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); @@ -50,16 +58,20 @@ namespace O3DE::ProjectManager item->setData(gemInfo.m_path, RolePath); item->setData(gemInfo.m_requirement, RoleRequirement); item->setData(gemInfo.m_downloadStatus, RoleDownloadStatus); + item->setData(gemInfo.m_licenseText, RoleLicenseText); + item->setData(gemInfo.m_licenseLink, RoleLicenseLink); appendRow(item); const QModelIndex modelIndex = index(rowCount()-1, 0); + m_nameToIndexMap[gemInfo.m_displayName] = modelIndex; m_nameToIndexMap[gemInfo.m_name] = modelIndex; } void GemModel::Clear() { clear(); + m_nameToIndexMap.clear(); } void GemModel::UpdateGemDependencies() @@ -238,6 +250,16 @@ namespace O3DE::ProjectManager return modelIndex.data(RoleRequirement).toString(); } + QString GemModel::GetLicenseText(const QModelIndex& modelIndex) + { + return modelIndex.data(RoleLicenseText).toString(); + } + + QString GemModel::GetLicenseLink(const QModelIndex& modelIndex) + { + return modelIndex.data(RoleLicenseLink).toString(); + } + GemModel* GemModel::GetSourceModel(QAbstractItemModel* model) { GemSortFilterProxyModel* proxyModel = qobject_cast(model); @@ -276,9 +298,11 @@ namespace O3DE::ProjectManager void GemModel::SetIsAdded(QAbstractItemModel& model, const QModelIndex& modelIndex, bool isAdded) { + // get the gemName first, because the modelIndex data change after adding because of filters + QString gemName = modelIndex.data(RoleName).toString(); model.setData(modelIndex, isAdded, RoleIsAdded); - UpdateDependencies(model, modelIndex); + UpdateDependencies(model, gemName, isAdded); } bool GemModel::HasDependentGems(const QModelIndex& modelIndex) const @@ -294,15 +318,17 @@ namespace O3DE::ProjectManager return false; } - void GemModel::UpdateDependencies(QAbstractItemModel& model, const QModelIndex& modelIndex) + void GemModel::UpdateDependencies(QAbstractItemModel& model, const QString& gemName, bool isAdded) { GemModel* gemModel = GetSourceModel(&model); AZ_Assert(gemModel, "Failed to obtain GemModel"); + QModelIndex modelIndex = gemModel->FindIndexByNameString(gemName); + QVector dependencies = gemModel->GatherGemDependencies(modelIndex); uint32_t numChangedDependencies = 0; - if (IsAdded(modelIndex)) + if (isAdded) { for (const QModelIndex& dependency : dependencies) { @@ -324,7 +350,7 @@ namespace O3DE::ProjectManager bool hasDependentGems = gemModel->HasDependentGems(modelIndex); if (IsAddedDependency(modelIndex) != hasDependentGems) { - SetIsAddedDependency(model, modelIndex, hasDependentGems); + SetIsAddedDependency(*gemModel, modelIndex, hasDependentGems); } for (const QModelIndex& dependency : dependencies) @@ -343,7 +369,7 @@ namespace O3DE::ProjectManager } } - gemModel->emit gemStatusChanged(modelIndex, numChangedDependencies); + gemModel->emit gemStatusChanged(gemName, numChangedDependencies); } void GemModel::SetIsAddedDependency(QAbstractItemModel& model, const QModelIndex& modelIndex, bool isAdded) diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h index 938543eb39..56594ce794 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h @@ -50,6 +50,8 @@ namespace O3DE::ProjectManager static QStringList GetFeatures(const QModelIndex& modelIndex); static QString GetPath(const QModelIndex& modelIndex); static QString GetRequirement(const QModelIndex& modelIndex); + static QString GetLicenseText(const QModelIndex& modelIndex); + static QString GetLicenseLink(const QModelIndex& modelIndex); static GemModel* GetSourceModel(QAbstractItemModel* model); static const GemModel* GetSourceModel(const QAbstractItemModel* model); @@ -64,7 +66,7 @@ namespace O3DE::ProjectManager static bool NeedsToBeAdded(const QModelIndex& modelIndex, bool includeDependencies = false); static bool NeedsToBeRemoved(const QModelIndex& modelIndex, bool includeDependencies = false); static bool HasRequirement(const QModelIndex& modelIndex); - static void UpdateDependencies(QAbstractItemModel& model, const QModelIndex& modelIndex); + static void UpdateDependencies(QAbstractItemModel& model, const QString& gemName, bool isAdded); static void SetDownloadStatus(QAbstractItemModel& model, const QModelIndex& modelIndex, GemInfo::DownloadStatus status); bool DoGemsToBeAddedHaveRequirements() const; @@ -78,7 +80,7 @@ namespace O3DE::ProjectManager int TotalAddedGems(bool includeDependencies = false) const; signals: - void gemStatusChanged(const QModelIndex& modelIndex, uint32_t numChangedDependencies); + void gemStatusChanged(const QString& gemName, uint32_t numChangedDependencies); private: void FindGemDisplayNamesByNameStrings(QStringList& inOutGemNames); @@ -107,7 +109,9 @@ namespace O3DE::ProjectManager RoleTypes, RolePath, RoleRequirement, - RoleDownloadStatus + RoleDownloadStatus, + RoleLicenseText, + RoleLicenseLink }; QHash m_nameToIndexMap; diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemSortFilterProxyModel.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemSortFilterProxyModel.cpp index 7ec45ac721..32d0e2fee9 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemSortFilterProxyModel.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemSortFilterProxyModel.cpp @@ -207,6 +207,8 @@ namespace O3DE::ProjectManager void GemSortFilterProxyModel::ResetFilters() { m_searchString.clear(); + m_gemSelectedFilter = GemSelected::NoFilter; + m_gemActiveFilter = GemActive::NoFilter; m_gemOriginFilter = {}; m_platformFilter = {}; m_typeFilter = {}; diff --git a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoInspector.cpp b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoInspector.cpp index d065ab59f8..6655aef86d 100644 --- a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoInspector.cpp +++ b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoInspector.cpp @@ -99,7 +99,7 @@ namespace O3DE::ProjectManager m_nameLabel->setObjectName("gemRepoInspectorNameLabel"); m_mainLayout->addWidget(m_nameLabel); - m_repoLinkLabel = new LinkLabel(tr("Repo Url"), QUrl(""), 12, this); + m_repoLinkLabel = new LinkLabel(tr("Repo Url"), QUrl(), 12, this); m_mainLayout->addWidget(m_repoLinkLabel); m_mainLayout->addSpacing(5); diff --git a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoItemDelegate.cpp b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoItemDelegate.cpp index 576a4aff6e..fdfcf02155 100644 --- a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoItemDelegate.cpp +++ b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoItemDelegate.cpp @@ -39,7 +39,6 @@ namespace O3DE::ProjectManager QRect fullRect, itemRect, contentRect; CalcRects(options, fullRect, itemRect, contentRect); - QRect buttonRect = CalcButtonRect(contentRect); QFont standardFont(options.font); standardFont.setPixelSize(static_cast(s_fontSize)); @@ -70,15 +69,12 @@ namespace O3DE::ProjectManager painter->restore(); } - // Repo enabled - DrawButton(painter, buttonRect, modelIndex); - // Repo name QString repoName = GemRepoModel::GetName(modelIndex); repoName = QFontMetrics(standardFont).elidedText(repoName, Qt::TextElideMode::ElideRight, s_nameMaxWidth); QRect repoNameRect = GetTextRect(standardFont, repoName, s_fontSize); - int currentHorizontalOffset = buttonRect.left() + s_buttonWidth + s_buttonSpacing; + int currentHorizontalOffset = contentRect.left(); repoNameRect.moveTo(currentHorizontalOffset, contentRect.center().y() - repoNameRect.height() / 2); repoNameRect = painter->boundingRect(repoNameRect, Qt::TextSingleLine, repoName); @@ -126,7 +122,7 @@ namespace O3DE::ProjectManager initStyleOption(&options, modelIndex); int marginsHorizontal = s_itemMargins.left() + s_itemMargins.right() + s_contentMargins.left() + s_contentMargins.right(); - return QSize(marginsHorizontal + s_buttonWidth + s_buttonSpacing + s_nameMaxWidth + s_creatorMaxWidth + s_updatedMaxWidth + s_contentSpacing * 3, s_height); + return QSize(marginsHorizontal + s_nameMaxWidth + s_creatorMaxWidth + s_updatedMaxWidth + s_contentSpacing * 3, s_height); } bool GemRepoItemDelegate::editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& modelIndex) @@ -139,13 +135,8 @@ namespace O3DE::ProjectManager if (event->type() == QEvent::KeyPress) { auto keyEvent = static_cast(event); - if (keyEvent->key() == Qt::Key_Space) - { - const bool isAdded = GemRepoModel::IsEnabled(modelIndex); - GemRepoModel::SetEnabled(*model, modelIndex, !isAdded); - return true; - } - else if (keyEvent->key() == Qt::Key_X) + + if (keyEvent->key() == Qt::Key_X) { emit RemoveRepo(modelIndex); return true; @@ -163,17 +154,10 @@ namespace O3DE::ProjectManager QRect fullRect, itemRect, contentRect; CalcRects(option, fullRect, itemRect, contentRect); - const QRect buttonRect = CalcButtonRect(contentRect); const QRect deleteButtonRect = CalcDeleteButtonRect(contentRect); - const QRect refreshButtonRect = CalcRefreshButtonRect(contentRect, buttonRect); + const QRect refreshButtonRect = CalcRefreshButtonRect(contentRect); - if (buttonRect.contains(mouseEvent->pos())) - { - const bool isAdded = GemRepoModel::IsEnabled(modelIndex); - GemRepoModel::SetEnabled(*model, modelIndex, !isAdded); - return true; - } - else if (deleteButtonRect.contains(mouseEvent->pos())) + if (deleteButtonRect.contains(mouseEvent->pos())) { emit RemoveRepo(modelIndex); return true; @@ -201,50 +185,15 @@ namespace O3DE::ProjectManager return QFontMetrics(font).boundingRect(text); } - QRect GemRepoItemDelegate::CalcButtonRect(const QRect& contentRect) const - { - const QPoint topLeft = QPoint(contentRect.left(), contentRect.top() + contentRect.height() / 2 - s_buttonHeight / 2); - const QSize size = QSize(s_buttonWidth, s_buttonHeight); - return QRect(topLeft, size); - } - - void GemRepoItemDelegate::DrawButton(QPainter* painter, const QRect& buttonRect, const QModelIndex& modelIndex) const - { - painter->save(); - QPoint circleCenter; - - const bool isEnabled = GemRepoModel::IsEnabled(modelIndex); - if (isEnabled) - { - painter->setBrush(m_buttonEnabledColor); - painter->setPen(m_buttonEnabledColor); - - circleCenter = buttonRect.center() + QPoint(buttonRect.width() / 2 - s_buttonBorderRadius + 1, 1); - } - else - { - circleCenter = buttonRect.center() + QPoint(-buttonRect.width() / 2 + s_buttonBorderRadius + 1, 1); - } - - // Rounded rect - painter->drawRoundedRect(buttonRect, s_buttonBorderRadius, s_buttonBorderRadius); - - // Circle - painter->setBrush(m_textColor); - painter->drawEllipse(circleCenter, s_buttonCircleRadius, s_buttonCircleRadius); - - painter->restore(); - } - QRect GemRepoItemDelegate::CalcDeleteButtonRect(const QRect& contentRect) const { const QPoint topLeft = QPoint(contentRect.right() - s_iconSize, contentRect.center().y() - s_iconSize / 2); return QRect(topLeft, QSize(s_iconSize, s_iconSize)); } - QRect GemRepoItemDelegate::CalcRefreshButtonRect(const QRect& contentRect, const QRect& buttonRect) const + QRect GemRepoItemDelegate::CalcRefreshButtonRect(const QRect& contentRect) const { - const int topLeftX = buttonRect.left() + s_buttonWidth + s_buttonSpacing + s_nameMaxWidth + s_creatorMaxWidth + s_updatedMaxWidth + s_contentSpacing * 2 + s_refreshIconSpacing; + const int topLeftX = contentRect.left() + s_nameMaxWidth + s_creatorMaxWidth + s_updatedMaxWidth + s_contentSpacing * 2 + s_refreshIconSpacing; const QPoint topLeft = QPoint(topLeftX, contentRect.center().y() - s_refreshIconSize / 3); return QRect(topLeft, QSize(s_refreshIconSize, s_refreshIconSize)); } diff --git a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoItemDelegate.h b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoItemDelegate.h index 69f2eb582d..69d943001d 100644 --- a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoItemDelegate.h +++ b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoItemDelegate.h @@ -36,7 +36,6 @@ namespace O3DE::ProjectManager const QColor m_backgroundColor = QColor("#333333"); // Outside of the actual repo item const QColor m_itemBackgroundColor = QColor("#404040"); // Background color of the repo item const QColor m_borderColor = QColor("#1E70EB"); - const QColor m_buttonEnabledColor = QColor("#1E70EB"); // Item inline constexpr static int s_height = 72; // Repo item total height @@ -53,13 +52,6 @@ namespace O3DE::ProjectManager inline constexpr static int s_creatorMaxWidth = 115; inline constexpr static int s_updatedMaxWidth = 125; - // Button - inline constexpr static int s_buttonWidth = 32; - inline constexpr static int s_buttonHeight = 16; - inline constexpr static int s_buttonBorderRadius = 8; - inline constexpr static int s_buttonCircleRadius = s_buttonBorderRadius - 2; - inline constexpr static int s_buttonSpacing = 20; - // Icon inline constexpr static int s_iconSize = 24; inline constexpr static int s_iconSpacing = 16; @@ -75,8 +67,7 @@ namespace O3DE::ProjectManager QRect GetTextRect(QFont& font, const QString& text, qreal fontSize) const; QRect CalcButtonRect(const QRect& contentRect) const; QRect CalcDeleteButtonRect(const QRect& contentRect) const; - QRect CalcRefreshButtonRect(const QRect& contentRect, const QRect& buttonRect) const; - void DrawButton(QPainter* painter, const QRect& contentRect, const QModelIndex& modelIndex) const; + QRect CalcRefreshButtonRect(const QRect& contentRect) const; void DrawEditButtons(QPainter* painter, const QRect& contentRect) const; QAbstractItemModel* m_model = nullptr; diff --git a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.cpp b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.cpp index 490b509474..0ddfe41434 100644 --- a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.cpp +++ b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.cpp @@ -289,23 +289,22 @@ namespace O3DE::ProjectManager m_gemRepoHeaderTable->setObjectName("gemRepoHeaderTable"); m_gemRepoListHeader = m_gemRepoHeaderTable->horizontalHeader(); m_gemRepoListHeader->setObjectName("gemRepoListHeader"); + m_gemRepoListHeader->setDefaultAlignment(Qt::AlignLeft); m_gemRepoListHeader->setSectionResizeMode(QHeaderView::ResizeMode::Fixed); // Insert columns so the header labels will show up m_gemRepoHeaderTable->insertColumn(0); m_gemRepoHeaderTable->insertColumn(1); m_gemRepoHeaderTable->insertColumn(2); - m_gemRepoHeaderTable->insertColumn(3); - m_gemRepoHeaderTable->setHorizontalHeaderLabels({ tr("Enabled"), tr("Repository Name"), tr("Creator"), tr("Updated") }); + m_gemRepoHeaderTable->setHorizontalHeaderLabels({ tr("Repository Name"), tr("Creator"), tr("Updated") }); - const int headerExtraMargin = 10; - m_gemRepoListHeader->resizeSection(0, GemRepoItemDelegate::s_buttonWidth + GemRepoItemDelegate::s_buttonSpacing - 3); - m_gemRepoListHeader->resizeSection(1, GemRepoItemDelegate::s_nameMaxWidth + GemRepoItemDelegate::s_contentSpacing - headerExtraMargin); - m_gemRepoListHeader->resizeSection(2, GemRepoItemDelegate::s_creatorMaxWidth + GemRepoItemDelegate::s_contentSpacing - headerExtraMargin); - m_gemRepoListHeader->resizeSection(3, GemRepoItemDelegate::s_updatedMaxWidth + GemRepoItemDelegate::s_contentSpacing - headerExtraMargin); + const int headerExtraMargin = 18; + m_gemRepoListHeader->resizeSection(0, GemRepoItemDelegate::s_nameMaxWidth + GemRepoItemDelegate::s_contentSpacing + headerExtraMargin); + m_gemRepoListHeader->resizeSection(1, GemRepoItemDelegate::s_creatorMaxWidth + GemRepoItemDelegate::s_contentSpacing); + m_gemRepoListHeader->resizeSection(2, GemRepoItemDelegate::s_updatedMaxWidth + GemRepoItemDelegate::s_contentSpacing); // Required to set stylesheet in code as it will not be respected if set in qss - m_gemRepoHeaderTable->horizontalHeader()->setStyleSheet("QHeaderView::section { background-color:transparent; color:white; font-size:12px; text-align:left; border-style:none; }"); + m_gemRepoHeaderTable->horizontalHeader()->setStyleSheet("QHeaderView::section { background-color:transparent; color:white; font-size:12px; border-style:none; }"); middleVLayout->addWidget(m_gemRepoHeaderTable); m_gemRepoListView = new GemRepoListView(m_gemRepoModel, m_gemRepoModel->GetSelectionModel(), this); diff --git a/Code/Tools/ProjectManager/Source/GemsSubWidget.cpp b/Code/Tools/ProjectManager/Source/GemsSubWidget.cpp index eb24008eb1..8b7b183008 100644 --- a/Code/Tools/ProjectManager/Source/GemsSubWidget.cpp +++ b/Code/Tools/ProjectManager/Source/GemsSubWidget.cpp @@ -33,6 +33,7 @@ namespace O3DE::ProjectManager m_layout->addWidget(m_textLabel); m_tagWidget = new TagContainerWidget(); + connect(m_tagWidget, &TagContainerWidget::TagClicked, this, [=](const QString& tag){ emit TagClicked(tag); }); m_layout->addWidget(m_tagWidget); } diff --git a/Code/Tools/ProjectManager/Source/GemsSubWidget.h b/Code/Tools/ProjectManager/Source/GemsSubWidget.h index 1b10ec8861..a9fabf5e92 100644 --- a/Code/Tools/ProjectManager/Source/GemsSubWidget.h +++ b/Code/Tools/ProjectManager/Source/GemsSubWidget.h @@ -22,10 +22,15 @@ namespace O3DE::ProjectManager class GemsSubWidget : public QWidget { + Q_OBJECT // AUTOMOC + public: GemsSubWidget(QWidget* parent = nullptr); void Update(const QString& title, const QString& text, const QStringList& gemNames); + signals: + void TagClicked(const QString& tag); + private: QLabel* m_titleLabel = nullptr; QLabel* m_textLabel = nullptr; diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.cpp b/Code/Tools/ProjectManager/Source/PythonBindings.cpp index 740393fec0..905139a4f2 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.cpp +++ b/Code/Tools/ProjectManager/Source/PythonBindings.cpp @@ -709,6 +709,8 @@ namespace O3DE::ProjectManager gemInfo.m_requirement = Py_To_String_Optional(data, "requirements", ""); gemInfo.m_creator = Py_To_String_Optional(data, "origin", ""); gemInfo.m_documentationLink = Py_To_String_Optional(data, "documentation_url", ""); + gemInfo.m_licenseText = Py_To_String_Optional(data, "license", "Unspecified License"); + gemInfo.m_licenseLink = Py_To_String_Optional(data, "license_url", ""); if (gemInfo.m_creator.contains("Open 3D Engine")) { diff --git a/Code/Tools/ProjectManager/Source/TagWidget.cpp b/Code/Tools/ProjectManager/Source/TagWidget.cpp index ace9d72d8f..39231ace4b 100644 --- a/Code/Tools/ProjectManager/Source/TagWidget.cpp +++ b/Code/Tools/ProjectManager/Source/TagWidget.cpp @@ -18,6 +18,11 @@ namespace O3DE::ProjectManager setObjectName("TagWidget"); } + void TagWidget::mousePressEvent([[maybe_unused]] QMouseEvent* event) + { + emit(TagClicked(text())); + } + TagContainerWidget::TagContainerWidget(QWidget* parent) : QWidget(parent) { @@ -45,7 +50,9 @@ namespace O3DE::ProjectManager foreach (const QString& tag, tags) { - flowLayout->addWidget(new TagWidget(tag)); + TagWidget* tagWidget = new TagWidget(tag); + connect(tagWidget, &TagWidget::TagClicked, this, [=](const QString& tag){ emit TagClicked(tag); }); + flowLayout->addWidget(tagWidget); } } } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/TagWidget.h b/Code/Tools/ProjectManager/Source/TagWidget.h index 0dad7468eb..7b4a5b1aaa 100644 --- a/Code/Tools/ProjectManager/Source/TagWidget.h +++ b/Code/Tools/ProjectManager/Source/TagWidget.h @@ -25,6 +25,12 @@ namespace O3DE::ProjectManager public: explicit TagWidget(const QString& text, QWidget* parent = nullptr); ~TagWidget() = default; + + signals: + void TagClicked(const QString& tag); + + protected: + void mousePressEvent(QMouseEvent* event) override; }; // Widget containing multiple tags, automatically wrapping based on the size @@ -38,5 +44,8 @@ namespace O3DE::ProjectManager ~TagContainerWidget() = default; void Update(const QStringList& tags); + + signals: + void TagClicked(const QString& tag); }; } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/UpdateProjectSettingsScreen.cpp b/Code/Tools/ProjectManager/Source/UpdateProjectSettingsScreen.cpp index 6f7f7e1bed..3bfc07c5b0 100644 --- a/Code/Tools/ProjectManager/Source/UpdateProjectSettingsScreen.cpp +++ b/Code/Tools/ProjectManager/Source/UpdateProjectSettingsScreen.cpp @@ -36,6 +36,7 @@ namespace O3DE::ProjectManager QLabel* projectPreviewLabel = new QLabel(tr("Select an image (PNG). Minimum %1 x %2 pixels.") .arg(QString::number(ProjectPreviewImageWidth), QString::number(ProjectPreviewImageHeight))); + projectPreviewLabel->setObjectName("projectPreviewLabel"); previewExtrasLayout->addWidget(projectPreviewLabel); m_projectPreviewImage = new QLabel(this); diff --git a/Code/Tools/SerializeContextTools/SliceConverter.cpp b/Code/Tools/SerializeContextTools/SliceConverter.cpp index 6cfe072f80..d0528a9cad 100644 --- a/Code/Tools/SerializeContextTools/SliceConverter.cpp +++ b/Code/Tools/SerializeContextTools/SliceConverter.cpp @@ -81,8 +81,6 @@ namespace AZ // Load the asset catalog so that we can find any nested assets successfully. We also need to tick the tick bus // so that the OnCatalogLoaded event gets processed now, instead of during application shutdown. - AZ::Data::AssetCatalogRequestBus::Broadcast( - &AZ::Data::AssetCatalogRequestBus::Events::LoadCatalog, "@products@/assetcatalog.xml"); application.Tick(); AZStd::string logggingScratchBuffer; diff --git a/Gems/AWSClientAuth/gem.json b/Gems/AWSClientAuth/gem.json index 75c07d025b..d03b86fb45 100644 --- a/Gems/AWSClientAuth/gem.json +++ b/Gems/AWSClientAuth/gem.json @@ -2,6 +2,7 @@ "gem_name": "AWSClientAuth", "display_name": "AWS Client Authorization", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Amazon Web Services, Inc.", "type": "Code", "summary": "AWS Client Auth provides client authentication and AWS authorization solution.", diff --git a/Gems/AWSCore/gem.json b/Gems/AWSCore/gem.json index 1bb9da9192..9fb974ccf4 100644 --- a/Gems/AWSCore/gem.json +++ b/Gems/AWSCore/gem.json @@ -2,6 +2,7 @@ "gem_name": "AWSCore", "display_name": "AWS Core", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Amazon Web Services, Inc.", "type": "Code", "summary": "The AWS Core Gem provides basic shared AWS functionality such as AWS SDK initialization and client configuration, and is automatically added when selecting any AWS feature Gem.", diff --git a/Gems/AWSGameLift/cdk/aws_gamelift/fleet_configurations.py b/Gems/AWSGameLift/cdk/aws_gamelift/fleet_configurations.py index 59f763498f..72c9d8ea43 100644 --- a/Gems/AWSGameLift/cdk/aws_gamelift/fleet_configurations.py +++ b/Gems/AWSGameLift/cdk/aws_gamelift/fleet_configurations.py @@ -44,7 +44,7 @@ FLEET_CONFIGURATIONS = [ 'build_path': '', # (Conditional) The operating system that the game server binaries are built to run on. # This parameter is required if the parameter build_path is defined. - # Choose from AMAZON_LINUX, AMAZON_LINUX or WINDOWS_2012. + # Choose from AMAZON_LINUX or WINDOWS_2012. 'operating_system': 'WINDOWS_2012' }, # (Optional) Information about the use of a TLS/SSL certificate for a fleet. diff --git a/Gems/AWSGameLift/gem.json b/Gems/AWSGameLift/gem.json index a0d7f62cd1..3fe1f15c3d 100644 --- a/Gems/AWSGameLift/gem.json +++ b/Gems/AWSGameLift/gem.json @@ -2,6 +2,7 @@ "gem_name": "AWSGameLift", "display_name": "AWS GameLift", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Amazon Web Services, Inc.", "type": "Code", "summary": "The AWS GameLift Gem provides a framework to extend O3DE networking layer to work with GameLift resources via GameLift server and client SDK.", diff --git a/Gems/AWSMetrics/gem.json b/Gems/AWSMetrics/gem.json index df16890012..59eae8f3c2 100644 --- a/Gems/AWSMetrics/gem.json +++ b/Gems/AWSMetrics/gem.json @@ -2,6 +2,7 @@ "gem_name": "AWSMetrics", "display_name": "AWS Metrics", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Amazon Web Services, Inc.", "type": "Code", "summary": "The AWS Metrics Gem provides a solution for AWS metrics submission and analytics.", diff --git a/Gems/Achievements/gem.json b/Gems/Achievements/gem.json index bd643f471a..8180584500 100644 --- a/Gems/Achievements/gem.json +++ b/Gems/Achievements/gem.json @@ -2,6 +2,7 @@ "gem_name": "Achievements", "display_name": "Achievements", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Achievements Gem provides a target platform agnostic interface for retrieving achievement details and unlocking achievements.", diff --git a/Gems/AssetMemoryAnalyzer/gem.json b/Gems/AssetMemoryAnalyzer/gem.json index 902102fa27..443eeced18 100644 --- a/Gems/AssetMemoryAnalyzer/gem.json +++ b/Gems/AssetMemoryAnalyzer/gem.json @@ -2,6 +2,7 @@ "gem_name": "AssetMemoryAnalyzer", "display_name": "Asset Memory Analyzer", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Asset Memory Analyzer Gem provides tools to profile asset memory usage in Open 3D Engine through ImGUI (Immediate Mode Graphical User Interface).", diff --git a/Gems/AssetValidation/Code/Tests/AssetValidationTestShared.h b/Gems/AssetValidation/Code/Tests/AssetValidationTestShared.h index 456afd0006..c4872ab425 100644 --- a/Gems/AssetValidation/Code/Tests/AssetValidationTestShared.h +++ b/Gems/AssetValidation/Code/Tests/AssetValidationTestShared.h @@ -150,7 +150,7 @@ struct AssetValidationTest auto projectPathKey = AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path"; - m_registry.Set(projectPathKey, "AutomatedTesting"); + m_registry.Set(projectPathKey, (AZ::IO::FixedMaxPath(GetEngineRoot()) / "AutomatedTesting").Native()); AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(m_registry); // Set the engine root to the temporary directory and re-update the runtime file paths diff --git a/Gems/AssetValidation/gem.json b/Gems/AssetValidation/gem.json index 1e57f60dc4..55cdffb9f3 100644 --- a/Gems/AssetValidation/gem.json +++ b/Gems/AssetValidation/gem.json @@ -2,6 +2,7 @@ "gem_name": "AssetValidation", "display_name": "Asset Validation", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Asset Validation Gem provides seed-related commands to ensure assets have valid seeds for asset bundling.", diff --git a/Gems/Atom/Asset/ImageProcessingAtom/gem.json b/Gems/Atom/Asset/ImageProcessingAtom/gem.json index 1424841256..4fd437d298 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/gem.json +++ b/Gems/Atom/Asset/ImageProcessingAtom/gem.json @@ -2,6 +2,7 @@ "gem_name": "ImageProcessingAtom", "display_name": "Atom Image Processing", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "", diff --git a/Gems/Atom/Asset/Shader/gem.json b/Gems/Atom/Asset/Shader/gem.json index 6d5e9f4dbe..9f59c65f78 100644 --- a/Gems/Atom/Asset/Shader/gem.json +++ b/Gems/Atom/Asset/Shader/gem.json @@ -2,6 +2,7 @@ "gem_name": "AtomShader", "display_name": "Atom Shader Builder", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "", diff --git a/Gems/Atom/Bootstrap/Assets/seedList.seed b/Gems/Atom/Bootstrap/Assets/seedList.seed new file mode 100644 index 0000000000..0f42b7790a --- /dev/null +++ b/Gems/Atom/Bootstrap/Assets/seedList.seed @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/Gems/Atom/Bootstrap/Code/Source/BootstrapSystemComponent.cpp b/Gems/Atom/Bootstrap/Code/Source/BootstrapSystemComponent.cpp index 3f5761270a..c232d4bba7 100644 --- a/Gems/Atom/Bootstrap/Code/Source/BootstrapSystemComponent.cpp +++ b/Gems/Atom/Bootstrap/Code/Source/BootstrapSystemComponent.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -132,7 +133,6 @@ namespace AZ m_createDefaultScene = false; } - AzFramework::AssetCatalogEventBus::Handler::BusConnect(); TickBus::Handler::BusConnect(); // Listen for window system requests (e.g. requests for default window handle) @@ -143,6 +143,20 @@ namespace AZ Render::Bootstrap::DefaultWindowBus::Handler::BusConnect(); Render::Bootstrap::RequestBus::Handler::BusConnect(); + + // If the settings registry isn't available, something earlier in startup will report that failure. + if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr) + { + // Automatically register the event if it's not registered, because + // this system is initialized before the settings registry has loaded the event list. + AZ::ComponentApplicationLifecycle::RegisterHandler( + *settingsRegistry, m_componentApplicationLifecycleHandler, + [this](AZStd::string_view /*path*/, AZ::SettingsRegistryInterface::Type /*type*/) + { + Initialize(); + }, + "LegacySystemInterfaceCreated"); + } } void BootstrapSystemComponent::Deactivate() @@ -153,7 +167,6 @@ namespace AZ AzFramework::WindowSystemRequestBus::Handler::BusDisconnect(); AzFramework::WindowSystemNotificationBus::Handler::BusDisconnect(); TickBus::Handler::BusDisconnect(); - AzFramework::AssetCatalogEventBus::Handler::BusDisconnect(); m_brdfTexture = nullptr; RemoveRenderPipeline(); @@ -164,14 +177,14 @@ namespace AZ m_windowHandle = nullptr; } - void BootstrapSystemComponent::OnCatalogLoaded(const char* /*catalogFile*/) + void BootstrapSystemComponent::Initialize() { - if (m_isAssetCatalogLoaded) + if (m_isInitialized) { return; } - m_isAssetCatalogLoaded = true; + m_isInitialized = true; if (!RPI::RPISystemInterface::Get()->IsInitialized()) { @@ -216,7 +229,7 @@ namespace AZ { m_windowHandle = windowHandle; - if (m_isAssetCatalogLoaded) + if (m_isInitialized) { CreateWindowContext(); if (m_createDefaultScene) diff --git a/Gems/Atom/Bootstrap/Code/Source/BootstrapSystemComponent.h b/Gems/Atom/Bootstrap/Code/Source/BootstrapSystemComponent.h index 566d19b1a4..74679f5753 100644 --- a/Gems/Atom/Bootstrap/Code/Source/BootstrapSystemComponent.h +++ b/Gems/Atom/Bootstrap/Code/Source/BootstrapSystemComponent.h @@ -8,10 +8,10 @@ #pragma once #include -#include #include +#include +#include -#include #include #include #include @@ -29,7 +29,6 @@ #include #include - namespace AZ { namespace Render @@ -40,7 +39,6 @@ namespace AZ : public Component , public TickBus::Handler , public AzFramework::WindowNotificationBus::Handler - , public AzFramework::AssetCatalogEventBus::Handler , public AzFramework::WindowSystemNotificationBus::Handler , public AzFramework::WindowSystemRequestBus::Handler , public Render::Bootstrap::DefaultWindowBus::Handler @@ -82,13 +80,12 @@ namespace AZ void OnTick(float deltaTime, AZ::ScriptTimePoint time) override; int GetTickOrder() override; - // AzFramework::AssetCatalogEventBus::Handler overrides ... - void OnCatalogLoaded(const char* catalogFile) override; - // AzFramework::WindowSystemNotificationBus::Handler overrides ... void OnWindowCreated(AzFramework::NativeWindowHandle windowHandle) override; private: + void Initialize(); + void CreateDefaultRenderPipeline(); void CreateDefaultScene(); void DestroyDefaultScene(); @@ -105,7 +102,7 @@ namespace AZ RPI::ScenePtr m_defaultScene = nullptr; AZStd::shared_ptr m_defaultFrameworkScene = nullptr; - bool m_isAssetCatalogLoaded = false; + bool m_isInitialized = false; // The id of the render pipeline created by this component RPI::RenderPipelineId m_renderPipelineId; @@ -119,6 +116,8 @@ namespace AZ // Maps AZ scenes to RPI scene weak pointers to allow looking up a ScenePtr instead of a raw Scene* AZStd::unordered_map> m_azSceneToAtomSceneMap; + + AZ::SettingsRegistryInterface::NotifyEventHandler m_componentApplicationLifecycleHandler; }; } // namespace Bootstrap } // namespace Render diff --git a/Gems/Atom/Bootstrap/gem.json b/Gems/Atom/Bootstrap/gem.json index 5e98a1887d..df615366da 100644 --- a/Gems/Atom/Bootstrap/gem.json +++ b/Gems/Atom/Bootstrap/gem.json @@ -2,6 +2,7 @@ "gem_name": "Atom_Bootstrap", "display_name": "Atom Bootstrap", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "", diff --git a/Gems/Atom/Component/DebugCamera/gem.json b/Gems/Atom/Component/DebugCamera/gem.json index 586cb37058..8eab74f41d 100644 --- a/Gems/Atom/Component/DebugCamera/gem.json +++ b/Gems/Atom/Component/DebugCamera/gem.json @@ -2,6 +2,7 @@ "gem_name": "Atom_Component_DebugCamera", "display_name": "Atom Debug Camera Component", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "", diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR.materialtype b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR.materialtype index d36213694b..5de756067a 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR.materialtype @@ -1574,15 +1574,6 @@ "shaderOption": "o_baseColor_useTexture" } }, - { - "type": "UseTexture", - "args": { - "textureProperty": "metallic.textureMap", - "useTextureProperty": "metallic.useTexture", - "dependentProperties": ["metallic.textureMapUv"], - "shaderOption": "o_metallic_useTexture" - } - }, { "type": "UseTexture", "args": { @@ -1649,6 +1640,12 @@ "file": "StandardPBR_Roughness.lua" } }, + { + "type": "Lua", + "args": { + "file": "StandardPBR_Metallic.lua" + } + }, { "type": "Lua", "args": { diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR.materialtype b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR.materialtype index 107b525ae4..52befce2b2 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR.materialtype @@ -2698,16 +2698,12 @@ } }, { - "type": "UseTexture", + "type": "Lua", "args": { - "textureProperty": "layer1_metallic.textureMap", - "useTextureProperty": "layer1_metallic.useTexture", - "dependentProperties": ["layer1_metallic.textureMapUv"], - "shaderTags": [ - "ForwardPass", - "ForwardPass_EDS" - ], - "shaderOption": "o_layer1_o_metallic_useTexture" + "file": "StandardPBR_Metallic.lua", + "propertyNamePrefix": "layer1_", + "srgNamePrefix": "m_layer1_", + "optionsNamePrefix": "o_layer1_" } }, { @@ -2835,16 +2831,12 @@ } }, { - "type": "UseTexture", + "type": "Lua", "args": { - "textureProperty": "layer2_metallic.textureMap", - "useTextureProperty": "layer2_metallic.useTexture", - "dependentProperties": ["layer2_metallic.textureMapUv"], - "shaderTags": [ - "ForwardPass", - "ForwardPass_EDS" - ], - "shaderOption": "o_layer2_o_metallic_useTexture" + "file": "StandardPBR_Metallic.lua", + "propertyNamePrefix": "layer2_", + "srgNamePrefix": "m_layer2_", + "optionsNamePrefix": "o_layer2_" } }, { @@ -2963,8 +2955,8 @@ "args": { "textureProperty": "layer3_baseColor.textureMap", "useTextureProperty": "layer3_baseColor.useTexture", - "dependentProperties": ["layer3_baseColor.textureMapUv", "layer3_baseColor.textureBlendMode"], - "shaderTags": [ + "dependentProperties": [ "layer3_baseColor.textureMapUv", "layer3_baseColor.textureBlendMode" ], + "shaderTags": [ "ForwardPass", "ForwardPass_EDS" ], @@ -2972,16 +2964,12 @@ } }, { - "type": "UseTexture", + "type": "Lua", "args": { - "textureProperty": "layer3_metallic.textureMap", - "useTextureProperty": "layer3_metallic.useTexture", - "dependentProperties": ["layer3_metallic.textureMapUv"], - "shaderTags": [ - "ForwardPass", - "ForwardPass_EDS" - ], - "shaderOption": "o_layer3_o_metallic_useTexture" + "file": "StandardPBR_Metallic.lua", + "propertyNamePrefix": "layer3_", + "srgNamePrefix": "m_layer3_", + "optionsNamePrefix": "o_layer3_" } }, { diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype index e0b1949058..f324394309 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype @@ -1104,15 +1104,6 @@ "shaderOption": "o_baseColor_useTexture" } }, - { - "type": "UseTexture", - "args": { - "textureProperty": "metallic.textureMap", - "useTextureProperty": "metallic.useTexture", - "dependentProperties": ["metallic.textureMapUv"], - "shaderOption": "o_metallic_useTexture" - } - }, { "type": "UseTexture", "args": { @@ -1179,6 +1170,12 @@ "file": "StandardPBR_Roughness.lua" } }, + { + "type": "Lua", + "args": { + "file": "StandardPBR_Metallic.lua" + } + }, { "type": "Lua", "args": { diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_Metallic.lua b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_Metallic.lua new file mode 100644 index 0000000000..69ddbf9c57 --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_Metallic.lua @@ -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 +-- +-- +-- +---------------------------------------------------------------------------------------------------- + +function GetMaterialPropertyDependencies() + return {"metallic.textureMap", "metallic.useTexture"} +end + +function GetShaderOptionDependencies() + return {"o_metallic_useTexture"} +end + +function Process(context) + local textureMap = context:GetMaterialPropertyValue_Image("metallic.textureMap") + local useTexture = context:GetMaterialPropertyValue_bool("metallic.useTexture") + context:SetShaderOptionValue_bool("o_metallic_useTexture", useTexture and textureMap ~= nil) +end + +function ProcessEditor(context) + local textureMap = context:GetMaterialPropertyValue_Image("metallic.textureMap") + local useTexture = context:GetMaterialPropertyValue_bool("metallic.useTexture") + + if(nil == textureMap) then + context:SetMaterialPropertyVisibility("metallic.useTexture", MaterialPropertyVisibility_Hidden) + context:SetMaterialPropertyVisibility("metallic.textureMapUv", MaterialPropertyVisibility_Hidden) + context:SetMaterialPropertyVisibility("metallic.factor", MaterialPropertyVisibility_Enabled) + elseif(not useTexture) then + context:SetMaterialPropertyVisibility("metallic.useTexture", MaterialPropertyVisibility_Enabled) + context:SetMaterialPropertyVisibility("metallic.textureMapUv", MaterialPropertyVisibility_Disabled) + context:SetMaterialPropertyVisibility("metallic.factor", MaterialPropertyVisibility_Enabled) + else + context:SetMaterialPropertyVisibility("metallic.useTexture", MaterialPropertyVisibility_Enabled) + context:SetMaterialPropertyVisibility("metallic.textureMapUv", MaterialPropertyVisibility_Enabled) + context:SetMaterialPropertyVisibility("metallic.factor", MaterialPropertyVisibility_Hidden) + end +end diff --git a/Gems/Atom/Feature/Common/Assets/Passes/EnvironmentCubeMapForwardMSAA.pass b/Gems/Atom/Feature/Common/Assets/Passes/EnvironmentCubeMapForwardMSAA.pass index 877ae489c0..683346c291 100644 --- a/Gems/Atom/Feature/Common/Assets/Passes/EnvironmentCubeMapForwardMSAA.pass +++ b/Gems/Atom/Feature/Common/Assets/Passes/EnvironmentCubeMapForwardMSAA.pass @@ -91,10 +91,10 @@ "LoadStoreAction": { "ClearValue": { "Value": [ - 0.4000000059604645, - 0.4000000059604645, - 0.4000000059604645, - {} + 0.0, + 0.0, + 0.0, + 0.0 ] }, "LoadAction": "Clear" @@ -107,10 +107,10 @@ "LoadStoreAction": { "ClearValue": { "Value": [ - 0.4000000059604645, - 0.4000000059604645, - 0.4000000059604645, - {} + 0.0, + 0.0, + 0.0, + 0.0 ] }, "LoadAction": "Clear" diff --git a/Gems/Atom/Feature/Common/Assets/Passes/EnvironmentCubeMapForwardSubsurfaceMSAA.pass b/Gems/Atom/Feature/Common/Assets/Passes/EnvironmentCubeMapForwardSubsurfaceMSAA.pass new file mode 100644 index 0000000000..f6f7dd1e2d --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Passes/EnvironmentCubeMapForwardSubsurfaceMSAA.pass @@ -0,0 +1,158 @@ +{ + "Type": "JsonSerialization", + "Version": 1, + "ClassName": "PassAsset", + "ClassData": { + "PassTemplate": { + "Name": "EnvironmentCubeMapForwardSubsurfaceMSAAPassTemplate", + "PassClass": "RasterPass", + "Slots": [ + // Inputs... + { + "Name": "BRDFTextureInput", + "ShaderInputName": "m_brdfMap", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader" + }, + { + "Name": "DirectionalLightShadowmap", + "ShaderInputName": "m_directionalLightShadowmap", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader", + "ImageViewDesc": { + "IsArray": 1 + } + }, + { + "Name": "ExponentialShadowmapDirectional", + "ShaderInputName": "m_directionalLightExponentialShadowmap", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader", + "ImageViewDesc": { + "IsArray": 1 + } + }, + { + "Name": "ProjectedShadowmap", + "ShaderInputName": "m_projectedShadowmaps", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader", + "ImageViewDesc": { + "IsArray": 1 + } + }, + { + "Name": "ExponentialShadowmapProjected", + "ShaderInputName": "m_projectedExponentialShadowmap", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader", + "ImageViewDesc": { + "IsArray": 1 + } + }, + { + "Name": "TileLightData", + "SlotType": "Input", + "ShaderInputName": "m_tileLightData", + "ScopeAttachmentUsage": "Shader" + }, + { + "Name": "LightListRemapped", + "SlotType": "Input", + "ShaderInputName": "m_lightListRemapped", + "ScopeAttachmentUsage": "Shader" + }, + // Input/Outputs... + { + "Name": "DepthStencilInputOutput", + "SlotType": "InputOutput", + "ScopeAttachmentUsage": "DepthStencil" + }, + { + "Name": "DiffuseOutput", + "SlotType": "InputOutput", + "ScopeAttachmentUsage": "RenderTarget" + }, + { + "Name": "SpecularOutput", + "SlotType": "InputOutput", + "ScopeAttachmentUsage": "RenderTarget" + }, + { + "Name": "AlbedoOutput", + "SlotType": "InputOutput", + "ScopeAttachmentUsage": "RenderTarget" + }, + { + "Name": "SpecularF0Output", + "SlotType": "InputOutput", + "ScopeAttachmentUsage": "RenderTarget" + }, + { + "Name": "NormalOutput", + "SlotType": "InputOutput", + "ScopeAttachmentUsage": "RenderTarget" + }, + // Outputs... + { + "Name": "ScatterDistanceOutput", + "SlotType": "Output", + "ScopeAttachmentUsage": "RenderTarget", + "LoadStoreAction": { + "ClearValue": { + "Value": [ + 0.0, + 0.0, + 0.0, + 0.0 + ] + }, + "LoadAction": "Clear" + } + } + ], + "ImageAttachments": [ + { + "Name": "BRDFTexture", + "Lifetime": "Imported", + "AssetRef": { + "FilePath": "Textures/BRDFTexture.attimage" + } + }, + { + "Name": "ScatterDistanceImage", + "SizeSource": { + "Source": { + "Pass": "Parent", + "Attachment": "Output" + } + }, + "MultisampleSource": { + "Pass": "This", + "Attachment": "DepthStencilInputOutput" + }, + "ImageDescriptor": { + "Format": "R11G11B10_FLOAT", + "SharedQueueMask": "Graphics" + } + } + ], + "Connections": [ + { + "LocalSlot": "BRDFTextureInput", + "AttachmentRef": { + "Pass": "This", + "Attachment": "BRDFTexture" + } + }, + { + "LocalSlot": "ScatterDistanceOutput", + "AttachmentRef": { + "Pass": "This", + "Attachment": "ScatterDistanceImage" + } + } + ] + } + } +} \ No newline at end of file diff --git a/Gems/Atom/Feature/Common/Assets/Passes/EnvironmentCubeMapPipeline.pass b/Gems/Atom/Feature/Common/Assets/Passes/EnvironmentCubeMapPipeline.pass index 70f1999d8c..3bd0401011 100644 --- a/Gems/Atom/Feature/Common/Assets/Passes/EnvironmentCubeMapPipeline.pass +++ b/Gems/Atom/Feature/Common/Assets/Passes/EnvironmentCubeMapPipeline.pass @@ -211,6 +211,105 @@ } } }, + { + "Name": "ForwardSubsurfaceMSAAPass", + "TemplateName": "EnvironmentCubeMapForwardSubsurfaceMSAAPassTemplate", + "Connections": [ + { + "LocalSlot": "DirectionalLightShadowmap", + "AttachmentRef": { + "Pass": "CascadedShadowmapsPass", + "Attachment": "Shadowmap" + } + }, + { + "LocalSlot": "ExponentialShadowmapDirectional", + "AttachmentRef": { + "Pass": "EsmShadowmapsPassDirectional", + "Attachment": "EsmShadowmaps" + } + }, + { + "LocalSlot": "ProjectedShadowmap", + "AttachmentRef": { + "Pass": "ProjectedShadowmapsPass", + "Attachment": "Shadowmap" + } + }, + { + "LocalSlot": "ExponentialShadowmapProjected", + "AttachmentRef": { + "Pass": "EsmShadowmapsPassProjected", + "Attachment": "EsmShadowmaps" + } + }, + { + "LocalSlot": "TileLightData", + "AttachmentRef": { + "Pass": "LightCullingPass", + "Attachment": "TileLightData" + } + }, + { + "LocalSlot": "LightListRemapped", + "AttachmentRef": { + "Pass": "LightCullingPass", + "Attachment": "LightListRemapped" + } + }, + // Input/Outputs... + { + "LocalSlot": "DepthStencilInputOutput", + "AttachmentRef": { + "Pass": "DepthPrePass", + "Attachment": "DepthMSAA" + } + }, + { + "LocalSlot": "DiffuseOutput", + "AttachmentRef": { + "Pass": "ForwardMSAAPass", + "Attachment": "DiffuseOutput" + } + }, + { + "LocalSlot": "SpecularOutput", + "AttachmentRef": { + "Pass": "ForwardMSAAPass", + "Attachment": "SpecularOutput" + } + }, + { + "LocalSlot": "AlbedoOutput", + "AttachmentRef": { + "Pass": "ForwardMSAAPass", + "Attachment": "AlbedoOutput" + } + }, + { + "LocalSlot": "SpecularF0Output", + "AttachmentRef": { + "Pass": "ForwardMSAAPass", + "Attachment": "SpecularF0Output" + } + }, + { + "LocalSlot": "NormalOutput", + "AttachmentRef": { + "Pass": "ForwardMSAAPass", + "Attachment": "NormalOutput" + } + } + ], + "PassData": { + "$type": "RasterPassData", + "DrawListTag": "forwardWithSubsurfaceOutput", + "PipelineViewTag": "MainCamera", + "PassSrgShaderAsset": { + "FilePath": "Shaders/ForwardPassSrg.shader" + } + } + }, { "Name": "SkyBoxPass", "TemplateName": "EnvironmentCubeMapSkyBoxPassTemplate", @@ -325,6 +424,75 @@ } ] }, + { + "Name": "MSAAResolveScatterDistancePass", + "TemplateName": "MSAAResolveColorTemplate", + "Connections": [ + { + "LocalSlot": "Input", + "AttachmentRef": { + "Pass": "ForwardSubsurfaceMSAAPass", + "Attachment": "ScatterDistanceOutput" + } + } + ] + }, + { + "Name": "SubsurfaceScatteringPass", + "TemplateName": "SubsurfaceScatteringPassTemplate", + "Enabled": true, + "Connections": [ + { + "LocalSlot": "InputDiffuse", + "AttachmentRef": { + "Pass": "MSAAResolveDiffusePass", + "Attachment": "Output" + } + }, + { + "LocalSlot": "InputLinearDepth", + "AttachmentRef": { + "Pass": "DepthPrePass", + "Attachment": "DepthLinear" + } + }, + { + "LocalSlot": "InputScatterDistance", + "AttachmentRef": { + "Pass": "MSAAResolveScatterDistancePass", + "Attachment": "Output" + } + } + ], + "PassData": { + "$type": "ComputePassData", + "ShaderAsset": { + "FilePath": "Shaders/PostProcessing/ScreenSpaceSubsurfaceScatteringCS.shader" + }, + "Make Fullscreen Pass": true, + "PipelineViewTag": "MainCamera" + } + }, + { + "Name": "Ssao", + "TemplateName": "SsaoParentTemplate", + "Connections": [ + { + "LocalSlot": "LinearDepth", + "AttachmentRef": { + "Pass": "DepthPrePass", + "Attachment": "DepthLinear" + } + }, + { + "LocalSlot": "Modulate", + "AttachmentRef": { + "Pass": "SubsurfaceScatteringPass", + "Attachment": "Output" + } + } + ] + }, { "Name": "DiffuseSpecularMergePass", "TemplateName": "DiffuseSpecularMergeTemplate", @@ -332,7 +500,7 @@ { "LocalSlot": "InputDiffuse", "AttachmentRef": { - "Pass": "MSAAResolveDiffusePass", + "Pass": "Ssao", "Attachment": "Output" } }, diff --git a/Gems/Atom/Feature/Common/Assets/Passes/PassTemplates.azasset b/Gems/Atom/Feature/Common/Assets/Passes/PassTemplates.azasset index f2df085228..eba745fb3c 100644 --- a/Gems/Atom/Feature/Common/Assets/Passes/PassTemplates.azasset +++ b/Gems/Atom/Feature/Common/Assets/Passes/PassTemplates.azasset @@ -252,6 +252,10 @@ "Name": "EnvironmentCubeMapForwardMSAAPassTemplate", "Path": "Passes/EnvironmentCubeMapForwardMSAA.pass" }, + { + "Name": "EnvironmentCubeMapForwardSubsurfaceMSAAPassTemplate", + "Path": "Passes/EnvironmentCubeMapForwardSubsurfaceMSAA.pass" + }, { "Name": "EnvironmentCubeMapDepthMSAAPassTemplate", "Path": "Passes/EnvironmentCubeMapDepthMSAA.pass" diff --git a/Gems/Atom/Feature/Common/Assets/Passes/SsaoHalfRes.pass b/Gems/Atom/Feature/Common/Assets/Passes/SsaoHalfRes.pass deleted file mode 100644 index d34ae4161d..0000000000 --- a/Gems/Atom/Feature/Common/Assets/Passes/SsaoHalfRes.pass +++ /dev/null @@ -1,88 +0,0 @@ -{ - "Type": "JsonSerialization", - "Version": 1, - "ClassName": "PassAsset", - "ClassData": { - "PassTemplate": { - "Name": "SsaoHalfResTemplate", - "PassClass": "ParentPass", - "Slots": [ - { - "Name": "LinearDepth", - "SlotType": "Input", - "ScopeAttachmentUsage": "Shader" - }, - { - "Name": "Output", - "SlotType": "Output", - "ScopeAttachmentUsage": "Shader" - } - ], - "Connections": [ - { - "LocalSlot": "Output", - "AttachmentRef": { - "Pass": "Upsample", - "Attachment": "Output" - } - } - ], - "PassRequests": [ - { - "Name": "DepthDownsample", - "TemplateName": "DepthDownsampleTemplate", - "Connections": [ - { - "LocalSlot": "FullResDepth", - "AttachmentRef": { - "Pass": "Parent", - "Attachment": "LinearDepth" - } - } - ] - }, - { - "Name": "DownsampledSsao", - "TemplateName": "SsaoParentTemplate", - "Connections": [ - { - "LocalSlot": "LinearDepth", - "AttachmentRef": { - "Pass": "DepthDownsample", - "Attachment": "HalfResDepth" - } - } - ] - }, - { - "Name": "Upsample", - "TemplateName": "DepthUpsampleTemplate", - "Enabled": true, - "Connections": [ - { - "LocalSlot": "FullResDepth", - "AttachmentRef": { - "Pass": "Parent", - "Attachment": "LinearDepth" - } - }, - { - "LocalSlot": "HalfResDepth", - "AttachmentRef": { - "Pass": "DepthDownsample", - "Attachment": "HalfResDepth" - } - }, - { - "LocalSlot": "HalfResSource", - "AttachmentRef": { - "Pass": "DownsampledSsao", - "Attachment": "Output" - } - } - ] - } - ] - } - } -} diff --git a/Gems/Atom/Feature/Common/Assets/atom_feature_common_asset_files.cmake b/Gems/Atom/Feature/Common/Assets/atom_feature_common_asset_files.cmake index 94b711e86c..c4d198fef9 100644 --- a/Gems/Atom/Feature/Common/Assets/atom_feature_common_asset_files.cmake +++ b/Gems/Atom/Feature/Common/Assets/atom_feature_common_asset_files.cmake @@ -205,7 +205,6 @@ set(FILES Passes/SMAAEdgeDetection.pass Passes/SMAANeighborhoodBlending.pass Passes/SsaoCompute.pass - Passes/SsaoHalfRes.pass Passes/SsaoParent.pass Passes/SubsurfaceScattering.pass Passes/Taa.pass diff --git a/Gems/Atom/Feature/Common/Assets/seedList.seed b/Gems/Atom/Feature/Common/Assets/seedList.seed new file mode 100644 index 0000000000..9881686940 --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/seedList.seed @@ -0,0 +1,317 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Material/MaterialAssignment.h b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Material/MaterialAssignment.h index 40555bae00..21d9ec1bba 100644 --- a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Material/MaterialAssignment.h +++ b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Material/MaterialAssignment.h @@ -78,5 +78,10 @@ namespace AZ //! Find an assignment id corresponding to the lod and label substring filters MaterialAssignmentId FindMaterialAssignmentIdInModel( const Data::Instance& model, const MaterialAssignmentLodIndex lodFilter, const AZStd::string& labelFilter); + + //! Special case handling to convert script values to supported types + AZ::RPI::MaterialPropertyValue ConvertMaterialPropertyValueFromScript( + const AZ::RPI::MaterialPropertyDescriptor* propertyDescriptor, const AZStd::any& value); + } // namespace Render } // namespace AZ diff --git a/Gems/Atom/Feature/Common/Code/Source/Decals/DecalTextureArray.cpp b/Gems/Atom/Feature/Common/Code/Source/Decals/DecalTextureArray.cpp index 87ac0a9679..36a59bd07f 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Decals/DecalTextureArray.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/Decals/DecalTextureArray.cpp @@ -78,7 +78,7 @@ namespace AZ AZ_Warning("DecalTextureArray", false, "Material property: %s does not have a valid asset Id", propertyName.GetCStr()); return {}; } - return { imageAsset.GetAs< AZ::RPI::StreamingImageAsset>(), AZ::Data::AssetLoadBehavior::PreLoad }; + return Data::static_pointer_cast(imageAsset); } static AZ::Data::Asset GetStreamingImageAsset(const AZ::Data::Asset materialAssetData, const AZ::Name& propertyName) diff --git a/Gems/Atom/Feature/Common/Code/Source/EditorCommonSystemComponent.cpp b/Gems/Atom/Feature/Common/Code/Source/EditorCommonSystemComponent.cpp index 2f73269bdf..c9d1e5ae3d 100644 --- a/Gems/Atom/Feature/Common/Code/Source/EditorCommonSystemComponent.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/EditorCommonSystemComponent.cpp @@ -19,7 +19,6 @@ #include #include #include -#include #include #include @@ -101,13 +100,6 @@ namespace AZ materialFunctorRegistration->RegisterMaterialFunctor("ConvertEmissiveUnit", azrtti_typeid()); materialFunctorRegistration->RegisterMaterialFunctor("HandleSubsurfaceScatteringParameters", azrtti_typeid()); materialFunctorRegistration->RegisterMaterialFunctor("Lua", azrtti_typeid()); - - // Add asset types and extensions to AssetCatalog. Uses "AssetCatalogService". - auto assetCatalog = AZ::Data::AssetCatalogRequestBus::FindFirstHandler(); - if (assetCatalog) - { - assetCatalog->EnableCatalogForAsset(AZ::AzTypeInfo::Uuid()); - } } void EditorCommonSystemComponent::Deactivate() diff --git a/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignment.cpp b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignment.cpp index c79ceb39ba..d99ce211bf 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignment.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignment.cpp @@ -143,28 +143,34 @@ namespace AZ { bool wasRenamed = false; Name newName; - RPI::MaterialPropertyIndex materialPropertyIndex = m_materialInstance->FindPropertyIndex(propertyPair.first, &wasRenamed, &newName); + RPI::MaterialPropertyIndex materialPropertyIndex = + m_materialInstance->FindPropertyIndex(propertyPair.first, &wasRenamed, &newName); - // FindPropertyIndex will have already reported a message about what the old and new names are. Here we just add some extra info to help the user resolve it. - AZ_Warning("MaterialAssignment", !wasRenamed, + // FindPropertyIndex will have already reported a message about what the old and new names are. Here we just add + // some extra info to help the user resolve it. + AZ_Warning( + "MaterialAssignment", !wasRenamed, "Consider running \"Apply Automatic Property Updates\" to use the latest property names.", - propertyPair.first.GetCStr(), - newName.GetCStr()); + propertyPair.first.GetCStr(), newName.GetCStr()); if (wasRenamed && m_propertyOverrides.find(newName) != m_propertyOverrides.end()) { materialPropertyIndex.Reset(); - - AZ_Warning("MaterialAssignment", false, - "Material property '%s' has been renamed to '%s', and a property override exists for both. The one with the old name will be ignored.", - propertyPair.first.GetCStr(), - newName.GetCStr()); + + AZ_Warning( + "MaterialAssignment", false, + "Material property '%s' has been renamed to '%s', and a property override exists for both. The one with " + "the old name will be ignored.", + propertyPair.first.GetCStr(), newName.GetCStr()); } if (!materialPropertyIndex.IsNull()) { + const auto propertyDescriptor = + m_materialInstance->GetMaterialPropertiesLayout()->GetPropertyDescriptor(materialPropertyIndex); + m_materialInstance->SetPropertyValue( - materialPropertyIndex, AZ::RPI::MaterialPropertyValue::FromAny(propertyPair.second)); + materialPropertyIndex, ConvertMaterialPropertyValueFromScript(propertyDescriptor, propertyPair.second)); } } } @@ -284,5 +290,58 @@ namespace AZ return MaterialAssignmentId(); } + + template + AZ::RPI::MaterialPropertyValue ConvertMaterialPropertyValueNumericType(const AZStd::any& value) + { + if (value.is()) + { + return aznumeric_cast(AZStd::any_cast(value)); + } + if (value.is()) + { + return aznumeric_cast(AZStd::any_cast(value)); + } + if (value.is()) + { + return aznumeric_cast(AZStd::any_cast(value)); + } + if (value.is()) + { + return aznumeric_cast(AZStd::any_cast(value)); + } + + return AZ::RPI::MaterialPropertyValue::FromAny(value); + } + + AZ::RPI::MaterialPropertyValue ConvertMaterialPropertyValueFromScript( + const AZ::RPI::MaterialPropertyDescriptor* propertyDescriptor, const AZStd::any& value) + { + switch (propertyDescriptor->GetDataType()) + { + case AZ::RPI::MaterialPropertyDataType::Enum: + if (value.is()) + { + return propertyDescriptor->GetEnumValue(AZStd::any_cast(value)); + } + if (value.is()) + { + return propertyDescriptor->GetEnumValue(AZ::Name(AZStd::any_cast(value))); + } + return ConvertMaterialPropertyValueNumericType(value); + case AZ::RPI::MaterialPropertyDataType::Int: + return ConvertMaterialPropertyValueNumericType(value); + case AZ::RPI::MaterialPropertyDataType::UInt: + return ConvertMaterialPropertyValueNumericType(value); + case AZ::RPI::MaterialPropertyDataType::Float: + return ConvertMaterialPropertyValueNumericType(value); + case AZ::RPI::MaterialPropertyDataType::Bool: + return ConvertMaterialPropertyValueNumericType(value); + default: + break; + } + + return AZ::RPI::MaterialPropertyValue::FromAny(value); + } } // namespace Render } // namespace AZ diff --git a/Gems/Atom/Feature/Common/Editor/Scripts/ColorGrading/activate_lut_asset.py b/Gems/Atom/Feature/Common/Editor/Scripts/ColorGrading/activate_lut_asset.py index 016ad024aa..8fe3126a77 100644 --- a/Gems/Atom/Feature/Common/Editor/Scripts/ColorGrading/activate_lut_asset.py +++ b/Gems/Atom/Feature/Common/Editor/Scripts/ColorGrading/activate_lut_asset.py @@ -60,6 +60,11 @@ def activate_look_modification_lut(look_modification_component, asset_relative_p LOOK_MODIFICATION_ENABLE_PROPERTY_PATH, True ) + azlmbr.editor.EditorComponentAPIBus( + azlmbr.bus.Broadcast, + "EnableComponents", + [look_modification_component] + ) def activate_lut_asset(entity_id, asset_relative_path): disable_hdr_color_grading_component(entity_id) diff --git a/Gems/Atom/Feature/Common/gem.json b/Gems/Atom/Feature/Common/gem.json index 36a61fbbbb..84ab07a9f9 100644 --- a/Gems/Atom/Feature/Common/gem.json +++ b/Gems/Atom/Feature/Common/gem.json @@ -2,6 +2,7 @@ "gem_name": "Atom_Feature_Common", "display_name": "Atom Feature Common", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "", diff --git a/Gems/Atom/RHI/DX12/gem.json b/Gems/Atom/RHI/DX12/gem.json index eb876a1b9f..868acd43db 100644 --- a/Gems/Atom/RHI/DX12/gem.json +++ b/Gems/Atom/RHI/DX12/gem.json @@ -2,6 +2,7 @@ "gem_name": "Atom_RHI_DX12", "display_name": "Atom RHI DX12", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "", diff --git a/Gems/Atom/RHI/Metal/gem.json b/Gems/Atom/RHI/Metal/gem.json index 8da6bfabec..6e983ba505 100644 --- a/Gems/Atom/RHI/Metal/gem.json +++ b/Gems/Atom/RHI/Metal/gem.json @@ -2,6 +2,7 @@ "gem_name": "Atom_RHI_Metal", "display_name": "Atom RHI Metal", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "", diff --git a/Gems/Atom/RHI/Null/gem.json b/Gems/Atom/RHI/Null/gem.json index 0870efaae7..e9f22c5fcb 100644 --- a/Gems/Atom/RHI/Null/gem.json +++ b/Gems/Atom/RHI/Null/gem.json @@ -2,6 +2,7 @@ "gem_name": "Atom_RHI_Null", "display_name": "Atom RHI Null", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "", diff --git a/Gems/Atom/RHI/Vulkan/gem.json b/Gems/Atom/RHI/Vulkan/gem.json index 81e8dd22ea..1dfeb9eafa 100644 --- a/Gems/Atom/RHI/Vulkan/gem.json +++ b/Gems/Atom/RHI/Vulkan/gem.json @@ -2,6 +2,7 @@ "gem_name": "Atom_RHI_Vulkan", "display_name": "Atom RHI Vulkan", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "", diff --git a/Gems/Atom/RHI/gem.json b/Gems/Atom/RHI/gem.json index 5f916e5224..7b64476c65 100644 --- a/Gems/Atom/RHI/gem.json +++ b/Gems/Atom/RHI/gem.json @@ -2,6 +2,7 @@ "gem_name": "Atom_RHI", "display_name": "Atom RHI", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "", diff --git a/Gems/Atom/RPI/Assets/seedList.seed b/Gems/Atom/RPI/Assets/seedList.seed new file mode 100644 index 0000000000..300092e6c3 --- /dev/null +++ b/Gems/Atom/RPI/Assets/seedList.seed @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Shader/Shader.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Shader/Shader.h index e2568f3b61..edb1ac47d9 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Shader/Shader.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Shader/Shader.h @@ -52,9 +52,8 @@ namespace AZ */ class Shader final : public Data::InstanceData - , public Data::AssetBus::Handler + , public Data::AssetBus::MultiHandler , public ShaderVariantFinderNotificationBus::Handler - , public ShaderReloadNotificationBus::Handler { friend class ShaderSystem; public: @@ -165,15 +164,6 @@ namespace AZ void OnShaderVariantTreeAssetReady(Data::Asset /*shaderVariantTreeAsset*/, bool /*isError*/) override {}; void OnShaderVariantAssetReady(Data::Asset shaderVariantAsset, bool IsError) override; /////////////////////////////////////////////////////////////////// - - /////////////////////////////////////////////////////////////////// - // ShaderReloadNotificationBus overrides... - void OnShaderAssetReinitialized(const Data::Asset& shaderAsset) override; - // Note we don't need OnShaderVariantReinitialized because the Shader class doesn't do anything with the data inside - // the ShaderVariant object. The only thing we might want to do is propagate the message upward, but that's unnecessary - // because the ShaderReloadNotificationBus uses the Shader's AssetId as the ID for all messages including those from the variants. - // And of course we don't need to handle OnShaderReinitialized because this *is* this Shader. - /////////////////////////////////////////////////////////////////// //! A strong reference to the shader asset. Data::Asset m_asset; @@ -206,6 +196,12 @@ namespace AZ //! PipelineLibrary file name char m_pipelineLibraryPath[AZ_MAX_PATH_LEN] = { 0 }; + + //! During OnAssetReloaded, the internal references to ShaderVariantAsset inside + //! ShaderAsset are not updated correctly. We store here a reference to the root ShaderVariantAsset + //! when it got reloaded, later when We get OnAssetReloaded for the ShaderAsset We update its internal + //! reference to the root variant asset. + Data::Asset m_reloadedRootShaderVariantAsset; }; } } diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Shader/ShaderVariant.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Shader/ShaderVariant.h index 7cfa2f91f5..0fb76e45c2 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Shader/ShaderVariant.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Shader/ShaderVariant.h @@ -19,7 +19,6 @@ namespace AZ //! the RHI::PipelineStateType of the parent Shader instance. For shaders on the raster //! pipeline, the RHI::DrawFilterTag is also provided. class ShaderVariant final - : public Data::AssetBus::MultiHandler { friend class Shader; public: @@ -58,9 +57,6 @@ namespace AZ const Data::Asset& shaderVariantAsset, SupervariantIndex supervariantIndex); - // AssetBus overrides... - void OnAssetReloaded(Data::Asset asset) override; - //! A reference to the shader asset that this is a variant of. Data::Asset m_shaderAsset; diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderAsset.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderAsset.h index 2c24d6052a..c5df9a4b51 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderAsset.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderAsset.h @@ -53,12 +53,12 @@ namespace AZ class ShaderAsset final : public Data::AssetData , public ShaderVariantFinderNotificationBus::Handler - , public Data::AssetBus::Handler , public AssetInitBus::Handler { friend class ShaderAssetCreator; friend class ShaderAssetHandler; friend class ShaderAssetTester; + friend class Shader; public: AZ_RTTI(ShaderAsset, "{823395A3-D570-49F4-99A9-D820CD1DEF98}", Data::AssetData); static void Reflect(ReflectContext* context); @@ -212,22 +212,19 @@ namespace AZ return GetAttribute(shaderStage, attributeName, DefaultSupervariantIndex); } - private: - /////////////////////////////////////////////////////////////////// - /// AssetBus overrides - void OnAssetReloaded(Data::Asset asset) override; - void OnAssetReady(Data::Asset asset) override; - /////////////////////////////////////////////////////////////////// - - void ReinitializeRootShaderVariant(Data::Asset asset); - /////////////////////////////////////////////////////////////////// /// ShaderVariantFinderNotificationBus overrides void OnShaderVariantTreeAssetReady(Data::Asset shaderVariantTreeAsset, bool isError) override; void OnShaderVariantAssetReady(Data::Asset /*shaderVariantAsset*/, bool /*isError*/) override {}; /////////////////////////////////////////////////////////////////// + // Only Shader::OnAssetReloaded() should call this function, because it is pointless for an Asset to + // to refresh its own "serialized references" to other assets during OnAssetReloaded(). + // The problem is that OnAssetReloaded() doesn't do a good job at updating "serialized references" to other assets, + // So some other class must update the reference and that's why Shader() is the best class to do it. + void UpdateRootShaderVariantAsset(SupervariantIndex SupervariantIndex, Data::Asset newRootVariant); + //! A Supervariant represents a set of static shader compilation parameters. //! Those parameters can be predefined c-preprocessor macros or specific arguments //! for AZSLc. diff --git a/Gems/Atom/RPI/Code/Source/RPI.Builders/Pass/PassBuilder.cpp b/Gems/Atom/RPI/Code/Source/RPI.Builders/Pass/PassBuilder.cpp index d5e243c687..6a0b10633e 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Builders/Pass/PassBuilder.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Builders/Pass/PassBuilder.cpp @@ -10,8 +10,8 @@ #include #include - #include +#include #include #include @@ -33,11 +33,27 @@ namespace AZ static const char* PassAssetExtension = "pass"; } + namespace PassBuilderNamespace + { + enum PassDependencies + { + Shader, + AttachmentImage, + Count + }; + + static const AZStd::tuple DependencyExtensionJobKeyTable[PassDependencies::Count] = + { + {".shader", "Shader Asset"}, + {".attimage", "Any Asset Builder"} + }; + } + void PassBuilder::RegisterBuilder() { AssetBuilderSDK::AssetBuilderDesc builder; builder.m_name = PassBuilderJobKey; - builder.m_version = 13; // antonmic: making .pass files declare dependency on shaders they reference + builder.m_version = 14; // making .pass files emit product dependencies for the shaders they reference so they are picked up by the asset bundler builder.m_busId = azrtti_typeid(); builder.m_createJobFunction = AZStd::bind(&PassBuilder::CreateJobs, this, AZStd::placeholders::_1, AZStd::placeholders::_2); builder.m_processJobFunction = AZStd::bind(&PassBuilder::ProcessJob, this, AZStd::placeholders::_1, AZStd::placeholders::_2); @@ -104,8 +120,27 @@ namespace AZ } } + bool SetJobKeyForExtension(const AZStd::string& filePath, FindPassReferenceAssetParams& params) + { + AZStd::string extension; + StringFunc::Path::GetExtension(filePath.c_str(), extension); + for (const auto& [dependencyExtension, jobKey] : PassBuilderNamespace::DependencyExtensionJobKeyTable) + { + if (extension == dependencyExtension) + { + params.jobKey = jobKey; + return true; + } + } + + AZ_Error(PassBuilderName, false, "PassBuilder found a dependency with extension '%s', but does not know the corresponding job key. Add the job key for that extension to SetJobKeyForExtension in PassBuilder.cpp", extension.c_str()); + params.jobKey = "Unknown"; + return false; + } + // Helper function to find all assetId's and object references - bool FindReferencedAssets(FindPassReferenceAssetParams& params, AssetBuilderSDK::JobDescriptor* job) + bool FindReferencedAssets( + FindPassReferenceAssetParams& params, AssetBuilderSDK::JobDescriptor* job, AZStd::vector* productDependencies) { SerializeContext::ErrorHandler errorLogger; errorLogger.Reset(); @@ -129,8 +164,8 @@ namespace AZ if (job != nullptr) // Create Job Phase { params.dependencySourceFile = path; - bool dependencyAddedSuccessfully = AddDependency(params, job); - success = dependencyAddedSuccessfully && success; + success &= SetJobKeyForExtension(path, params); + success &= AddDependency(params, job); } else // Process Job Phase { @@ -139,6 +174,9 @@ namespace AZ if (assetIdOutcome) { assetReference->m_assetId = assetIdOutcome.GetValue(); + productDependencies->push_back( + AssetBuilderSDK::ProductDependency{assetReference->m_assetId, AZ::Data::ProductDependencyInfo::CreateFlags(Data::AssetLoadBehavior::NoLoad)} + ); } else { @@ -223,9 +261,9 @@ namespace AZ params.passAssetSourceFile = request.m_sourceFile; params.passAssetUuid = passAssetUuid; params.serializeContext = serializeContext; - params.jobKey = "Shader Asset"; + params.jobKey = "Unknown"; - if (!FindReferencedAssets(params, &job)) + if (!FindReferencedAssets(params, &job, nullptr)) { return; } @@ -287,9 +325,10 @@ namespace AZ params.passAssetSourceFile = request.m_sourceFile; params.passAssetUuid = passAssetUuid; params.serializeContext = serializeContext; - params.jobKey = "Shader Asset"; + params.jobKey = "Unknown"; - if (!FindReferencedAssets(params, nullptr)) + AZStd::vector productDependencies; + if (!FindReferencedAssets(params, nullptr, &productDependencies)) { return; } @@ -313,6 +352,7 @@ namespace AZ // --- Save output product(s) to response --- AssetBuilderSDK::JobProduct jobProduct(destPath, PassAsset::RTTI_Type(), 0); + jobProduct.m_dependencies = productDependencies; jobProduct.m_dependenciesHandled = true; response.m_outputProducts.push_back(jobProduct); response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success; diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Material/Material.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Material/Material.cpp index 050ae47749..63e55d379d 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Material/Material.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Material/Material.cpp @@ -234,7 +234,7 @@ namespace AZ { ShaderReloadDebugTracker::ScopedSection reloadSection("{%p}->Material::OnAssetReloaded %s", this, asset.GetHint().c_str()); - Data::Asset newMaterialAsset = { asset.GetAs(), AZ::Data::AssetLoadBehavior::PreLoad }; + Data::Asset newMaterialAsset = Data::static_pointer_cast(asset); if (newMaterialAsset) { @@ -610,7 +610,7 @@ namespace AZ } } - if (Data::Asset streamingImageAsset = { imageAsset.GetAs(), AZ::Data::AssetLoadBehavior::PreLoad }) + if (Data::Asset streamingImageAsset = Data::static_pointer_cast(imageAsset)) { Data::Instance image = StreamingImage::FindOrCreate(streamingImageAsset); if (!image) diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/PassLibrary.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/PassLibrary.cpp index 6a6f5f3ff9..87c6eee814 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/PassLibrary.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/PassLibrary.cpp @@ -281,7 +281,7 @@ namespace AZ void PassLibrary::OnAssetReloaded(Data::Asset asset) { // Handle pass asset reload - Data::Asset passAsset = { asset.GetAs(), AZ::Data::AssetLoadBehavior::PreLoad }; + Data::Asset passAsset = Data::static_pointer_cast(asset); if (passAsset && passAsset->GetPassTemplate()) { LoadPassAsset(passAsset->GetPassTemplate()->m_name, passAsset, true); diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/Specific/ImageAttachmentPreviewPass.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/Specific/ImageAttachmentPreviewPass.cpp index 8f83e4efe1..9a4428a03a 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/Specific/ImageAttachmentPreviewPass.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/Specific/ImageAttachmentPreviewPass.cpp @@ -231,7 +231,7 @@ namespace AZ void ImageAttachmentPreviewPass::OnAssetReloaded(Data::Asset asset) { - Data::Asset shaderAsset = { asset.GetAs(), AZ::Data::AssetLoadBehavior::PreLoad }; + Data::Asset shaderAsset = Data::static_pointer_cast(asset); if (shaderAsset) { m_needsShaderLoad = true; diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/Shader.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/Shader.cpp index 51dd9c36d3..a2d91fefd6 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/Shader.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/Shader.cpp @@ -15,6 +15,8 @@ #include #include +#include + namespace AZ { @@ -96,8 +98,7 @@ namespace AZ RHI::ResultCode Shader::Init(ShaderAsset& shaderAsset) { - Data::AssetBus::Handler::BusDisconnect(); - ShaderReloadNotificationBus::Handler::BusDisconnect(); + Data::AssetBus::MultiHandler::BusDisconnect(); ShaderVariantFinderNotificationBus::Handler::BusDisconnect(); RHI::RHISystemInterface* rhiSystem = RHI::RHISystemInterface::Get(); @@ -112,7 +113,8 @@ namespace AZ AZStd::unique_lock lock(m_variantCacheMutex); m_shaderVariants.clear(); } - m_rootVariant.Init(Data::Asset{&shaderAsset, AZ::Data::AssetLoadBehavior::PreLoad}, shaderAsset.GetRootVariant(m_supervariantIndex), m_supervariantIndex); + auto rootShaderVariantAsset = shaderAsset.GetRootVariant(m_supervariantIndex); + m_rootVariant.Init(m_asset, rootShaderVariantAsset, m_supervariantIndex); if (m_pipelineLibraryHandle.IsNull()) { @@ -146,8 +148,8 @@ namespace AZ } ShaderVariantFinderNotificationBus::Handler::BusConnect(m_asset.GetId()); - Data::AssetBus::Handler::BusConnect(m_asset.GetId()); - ShaderReloadNotificationBus::Handler::BusConnect(m_asset.GetId()); + Data::AssetBus::MultiHandler::BusConnect(rootShaderVariantAsset.GetId()); + Data::AssetBus::MultiHandler::BusConnect(m_asset.GetId()); return RHI::ResultCode::Success; } @@ -155,8 +157,7 @@ namespace AZ void Shader::Shutdown() { ShaderVariantFinderNotificationBus::Handler::BusDisconnect(); - Data::AssetBus::Handler::BusDisconnect(); - ShaderReloadNotificationBus::Handler::BusDisconnect(); + Data::AssetBus::MultiHandler::BusDisconnect(); if (m_pipelineLibraryHandle.IsValid()) { @@ -181,14 +182,52 @@ namespace AZ { ShaderReloadDebugTracker::ScopedSection reloadSection("{%p}->Shader::OnAssetReloaded %s", this, asset.GetHint().c_str()); - if (asset->GetId() == m_asset->GetId()) + if (asset.GetAs()) { - Data::Asset newAsset = { asset.GetAs(), AZ::Data::AssetLoadBehavior::PreLoad }; - AZ_Assert(newAsset, "Reloaded ShaderAsset is null"); + m_reloadedRootShaderVariantAsset = Data::static_pointer_cast(asset); + if (m_asset->m_shaderAssetBuildTimestamp == m_reloadedRootShaderVariantAsset->GetBuildTimestamp()) + { + Init(*m_asset.Get()); + ShaderReloadNotificationBus::Event(asset.GetId(), &ShaderReloadNotificationBus::Events::OnShaderReinitialized, *this); + } + return; + } - Init(*newAsset.Get()); + if (asset.GetAs()) + { + m_asset = Data::static_pointer_cast(asset); + if (!m_reloadedRootShaderVariantAsset.IsReady()) + { + // Do nothing, as We should not re-initilize until the root shader variant asset has been reloaded. + return; + } + AZ_Assert(m_asset->m_shaderAssetBuildTimestamp == m_reloadedRootShaderVariantAsset->GetBuildTimestamp(), + "shaderAsset timeStamp=%lld, but Root ShaderVariantAsset timeStamp=%lld", + m_asset->m_shaderAssetBuildTimestamp, m_reloadedRootShaderVariantAsset->GetBuildTimestamp()); + m_asset->UpdateRootShaderVariantAsset(m_supervariantIndex, m_reloadedRootShaderVariantAsset); + m_reloadedRootShaderVariantAsset = {}; // Clear the temporary reference. + + if (ShaderReloadDebugTracker::IsEnabled()) + { + auto makeTimeString = [](AZStd::sys_time_t timestamp, AZStd::sys_time_t now) + { + AZStd::sys_time_t elapsedMicroseconds = now - timestamp; + double elapsedSeconds = aznumeric_cast(elapsedMicroseconds / 1'000'000); + AZStd::string timeString = AZStd::string::format("%lld (%f seconds ago)", timestamp, elapsedSeconds); + return timeString; + }; + + AZStd::sys_time_t now = AZStd::GetTimeNowMicroSecond(); + + const auto shaderVariantAsset = m_asset->GetRootVariant(); + ShaderReloadDebugTracker::Printf("{%p}->Shader::OnAssetReloaded for shader '%s' [build time %s] found variant '%s' [build time %s]", this, + m_asset.GetHint().c_str(), makeTimeString(m_asset->m_shaderAssetBuildTimestamp, now).c_str(), + shaderVariantAsset.GetHint().c_str(), makeTimeString(shaderVariantAsset->GetBuildTimestamp(), now).c_str()); + } + Init(*m_asset.Get()); ShaderReloadNotificationBus::Event(asset.GetId(), &ShaderReloadNotificationBus::Events::OnShaderReinitialized, *this); } + } /////////////////////////////////////////////////////////////////////// @@ -253,23 +292,6 @@ namespace AZ ShaderReloadNotificationBus::Event(m_asset.GetId(), &ShaderReloadNotificationBus::Events::OnShaderVariantReinitialized, updatedVariant); } /////////////////////////////////////////////////////////////////// - - - /////////////////////////////////////////////////////////////////// - // ShaderReloadNotificationBus overrides... - void Shader::OnShaderAssetReinitialized(const Data::Asset& shaderAsset) - { - // When reloads occur, it's possible for old Asset objects to hang around and report reinitialization, - // so we can reduce unnecessary reinitialization in that case. - if (shaderAsset.Get() == m_asset.Get()) - { - ShaderReloadDebugTracker::ScopedSection reloadSection("{%p}->Shader::OnShaderAssetReinitialized %s", this, shaderAsset.GetHint().c_str()); - - Init(*m_asset.Get()); - ShaderReloadNotificationBus::Event(shaderAsset.GetId(), &ShaderReloadNotificationBus::Events::OnShaderReinitialized, *this); - } - } - /////////////////////////////////////////////////////////////////// ConstPtr Shader::LoadPipelineLibrary() const { diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/ShaderVariant.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/ShaderVariant.cpp index 7aa70de6f8..ce33a31fbb 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/ShaderVariant.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/ShaderVariant.cpp @@ -22,24 +22,20 @@ namespace AZ const Data::Asset& shaderAsset, const Data::Asset& shaderVariantAsset, SupervariantIndex supervariantIndex) - { + { + m_shaderAsset = shaderAsset; + m_shaderVariantAsset = shaderVariantAsset; + m_supervariantIndex = supervariantIndex; m_pipelineStateType = shaderAsset->GetPipelineStateType(); m_pipelineLayoutDescriptor = shaderAsset->GetPipelineLayoutDescriptor(supervariantIndex); - m_shaderVariantAsset = shaderVariantAsset; m_renderStates = &shaderAsset->GetRenderStates(supervariantIndex); - m_supervariantIndex = supervariantIndex; - Data::AssetBus::MultiHandler::BusDisconnect(); - Data::AssetBus::MultiHandler::BusConnect(shaderAsset.GetId()); - Data::AssetBus::MultiHandler::BusConnect(shaderVariantAsset.GetId()); - - m_shaderAsset = shaderAsset; return true; } ShaderVariant::~ShaderVariant() { - Data::AssetBus::MultiHandler::BusDisconnect(); + } void ShaderVariant::ConfigurePipelineState(RHI::PipelineStateDescriptor& descriptor) const @@ -82,25 +78,5 @@ namespace AZ } } - - void ShaderVariant::OnAssetReloaded(Data::Asset asset) - { - ShaderReloadDebugTracker::ScopedSection reloadSection("{%p}->ShaderVariant::OnAssetReloaded %s", this, asset.GetHint().c_str()); - - if (asset.GetAs()) - { - Data::Asset shaderVariantAsset = { asset.GetAs(), AZ::Data::AssetLoadBehavior::PreLoad }; - Init(m_shaderAsset, shaderVariantAsset, m_supervariantIndex); - ShaderReloadNotificationBus::Event(m_shaderAsset.GetId(), &ShaderReloadNotificationBus::Events::OnShaderVariantReinitialized, *this); - } - - if (asset.GetAs()) - { - Data::Asset shaderAsset = { asset.GetAs(), AZ::Data::AssetLoadBehavior::PreLoad }; - Init(shaderAsset, m_shaderVariantAsset, m_supervariantIndex); - ShaderReloadNotificationBus::Event(m_shaderAsset.GetId(), &ShaderReloadNotificationBus::Events::OnShaderVariantReinitialized, *this); - } - } - } // namespace RPI } // namespace AZ diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialAsset.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialAsset.cpp index 36f4947e3d..ac90333842 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialAsset.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialAsset.cpp @@ -237,7 +237,7 @@ namespace AZ void MaterialAsset::ReinitializeMaterialTypeAsset(Data::Asset asset) { - Data::Asset newMaterialTypeAsset = { asset.GetAs(), AZ::Data::AssetLoadBehavior::PreLoad }; + Data::Asset newMaterialTypeAsset = Data::static_pointer_cast(asset); if (newMaterialTypeAsset) { diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderAsset.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderAsset.cpp index 84757d58ab..10b748aa06 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderAsset.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderAsset.cpp @@ -108,7 +108,6 @@ namespace AZ ShaderAsset::~ShaderAsset() { - Data::AssetBus::Handler::BusDisconnect(); ShaderVariantFinderNotificationBus::Handler::BusDisconnect(); AssetInitBus::Handler::BusDisconnect(); } @@ -570,46 +569,16 @@ namespace AZ bool ShaderAsset::PostLoadInit() { - // Once the ShaderAsset is loaded, it is necessary to listen for changes in the Root Variant Asset. - Data::AssetBus::Handler::BusConnect(GetRootVariant().GetId()); ShaderVariantFinderNotificationBus::Handler::BusConnect(GetId()); - AssetInitBus::Handler::BusDisconnect(); - return true; } - - void ShaderAsset::ReinitializeRootShaderVariant(Data::Asset asset) - { - Data::Asset shaderVariantAsset = { asset.GetAs(), AZ::Data::AssetLoadBehavior::PreLoad }; - AZ_Assert(shaderVariantAsset->GetStableId() == RootShaderVariantStableId, "Was expecting to update the root variant"); - SupervariantIndex supervariantIndex = GetSupervariantIndexFromAssetId(asset.GetId()); - GetCurrentShaderApiData().m_supervariants[supervariantIndex.GetIndex()].m_rootShaderVariantAsset = asset; - ShaderReloadNotificationBus::Event(GetId(), &ShaderReloadNotificationBus::Events::OnShaderAssetReinitialized, Data::Asset{ this, AZ::Data::AssetLoadBehavior::PreLoad } ); - } - /////////////////////////////////////////////////////////////////////// - // AssetBus overrides... - void ShaderAsset::OnAssetReloaded(Data::Asset asset) + + void ShaderAsset::UpdateRootShaderVariantAsset(SupervariantIndex supervariantIndex, Data::Asset newRootVariant) { - ShaderReloadDebugTracker::ScopedSection reloadSection("{%p}->ShaderAsset::OnAssetReloaded %s", this, asset.GetHint().c_str()); - ReinitializeRootShaderVariant(asset); + GetCurrentShaderApiData().m_supervariants[supervariantIndex.GetIndex()].m_rootShaderVariantAsset = newRootVariant; } - void ShaderAsset::OnAssetReady(Data::Asset asset) - { - // We have to listen to OnAssetReady, OnAssetReloaded isn't enough, because of the following scenario: - // The user changes a .shader file, which causes the AP to rebuild the ShaderAsset and root ShaderVariantAsset. - // 1) Thread A creates the new ShaderAsset, loads it, and gets the old ShaderVariantAsset. - // 2) Thread B creates the new ShaderVariantAsset, loads it, and calls OnAssetReloaded. - // 3) Main thread calls ShaderAsset::PostLoadInit which connects to the AssetBus but it's too late to receive OnAssetReloaded, - // so it continues using the old ShaderVariantAsset instead of the new one. - // The OnAssetReady bus function is called automatically whenever a connection to AssetBus is made, so listening to this gives - // us the opportunity to assign the appropriate ShaderVariantAsset. - - ShaderReloadDebugTracker::ScopedSection reloadSection("{%p}->ShaderAsset::OnAssetReady %s", this, asset.GetHint().c_str()); - ReinitializeRootShaderVariant(asset); - } - /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////// /// ShaderVariantFinderNotificationBus overrides @@ -628,7 +597,6 @@ namespace AZ m_shaderVariantTree = shaderVariantTreeAsset; } lock.unlock(); - ShaderReloadNotificationBus::Event(GetId(), &ShaderReloadNotificationBus::Events::OnShaderAssetReinitialized, Data::Asset{ this, AZ::Data::AssetLoadBehavior::PreLoad }); } /////////////////////////////////////////////////////////////////// diff --git a/Gems/Atom/RPI/gem.json b/Gems/Atom/RPI/gem.json index b5a6fd5a1a..885f150508 100644 --- a/Gems/Atom/RPI/gem.json +++ b/Gems/Atom/RPI/gem.json @@ -3,6 +3,7 @@ "display_name": "Atom API", "summary": "", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "canonical_tags": [ diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Application/AtomToolsApplication.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Application/AtomToolsApplication.cpp index e8ec77e7bd..ba3bfe4718 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Application/AtomToolsApplication.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Application/AtomToolsApplication.cpp @@ -175,8 +175,6 @@ namespace AtomToolsFramework AzToolsFramework::AssetBrowser::AssetDatabaseLocationNotificationBus::Broadcast( &AzToolsFramework::AssetBrowser::AssetDatabaseLocationNotifications::OnDatabaseInitialized); - AZ::Data::AssetCatalogRequestBus::Broadcast(&AZ::Data::AssetCatalogRequestBus::Events::LoadCatalog, "@products@/assetcatalog.xml"); - if (!AZ::RPI::RPISystemInterface::Get()->IsInitialized()) { AZ::RPI::RPISystemInterface::Get()->InitializeSystemAssets(); diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/PreviewRenderer/PreviewRendererSystemComponent.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/PreviewRenderer/PreviewRendererSystemComponent.cpp index fc5b31297a..d46e7b27be 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/PreviewRenderer/PreviewRendererSystemComponent.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/PreviewRenderer/PreviewRendererSystemComponent.cpp @@ -47,30 +47,27 @@ namespace AtomToolsFramework void PreviewRendererSystemComponent::Activate() { - AzFramework::AssetCatalogEventBus::Handler::BusConnect(); AzFramework::ApplicationLifecycleEvents::Bus::Handler::BusConnect(); PreviewRendererSystemRequestBus::Handler::BusConnect(); + + AZ::TickBus::QueueFunction( + [this]() + { + if (!m_previewRenderer) + { + m_previewRenderer.reset(aznew AtomToolsFramework::PreviewRenderer( + "PreviewRendererSystemComponent Preview Scene", "PreviewRendererSystemComponent Preview Pipeline")); + } + }); } void PreviewRendererSystemComponent::Deactivate() { PreviewRendererSystemRequestBus::Handler::BusDisconnect(); AzFramework::ApplicationLifecycleEvents::Bus::Handler::BusDisconnect(); - AzFramework::AssetCatalogEventBus::Handler::BusDisconnect(); m_previewRenderer.reset(); } - void PreviewRendererSystemComponent::OnCatalogLoaded([[maybe_unused]] const char* catalogFile) - { - AZ::TickBus::QueueFunction([this](){ - if (!m_previewRenderer) - { - m_previewRenderer.reset(aznew AtomToolsFramework::PreviewRenderer( - "PreviewRendererSystemComponent Preview Scene", "PreviewRendererSystemComponent Preview Pipeline")); - } - }); - } - void PreviewRendererSystemComponent::OnApplicationAboutToStop() { m_previewRenderer.reset(); diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/PreviewRenderer/PreviewRendererSystemComponent.h b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/PreviewRenderer/PreviewRendererSystemComponent.h index 8110d84794..0b145bbdf1 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/PreviewRenderer/PreviewRendererSystemComponent.h +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/PreviewRenderer/PreviewRendererSystemComponent.h @@ -9,7 +9,6 @@ #pragma once #include -#include #include #include #include @@ -19,7 +18,6 @@ namespace AtomToolsFramework //! System component that manages a global PreviewRenderer. class PreviewRendererSystemComponent final : public AZ::Component - , public AzFramework::AssetCatalogEventBus::Handler , public AzFramework::ApplicationLifecycleEvents::Bus::Handler , public PreviewRendererSystemRequestBus::Handler { @@ -38,9 +36,6 @@ namespace AtomToolsFramework void Deactivate() override; private: - // AzFramework::AssetCatalogEventBus::Handler overrides ... - void OnCatalogLoaded(const char* catalogFile) override; - // AzFramework::ApplicationLifecycleEvents overrides... void OnApplicationAboutToStop() override; diff --git a/Gems/Atom/Tools/AtomToolsFramework/gem.json b/Gems/Atom/Tools/AtomToolsFramework/gem.json index 2b0380bdae..de2a9e06f2 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/gem.json +++ b/Gems/Atom/Tools/AtomToolsFramework/gem.json @@ -2,6 +2,7 @@ "gem_name": "AtomToolsFramework", "display_name": "Atom Tools Framework", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "", diff --git a/Gems/Atom/Tools/MaterialEditor/gem.json b/Gems/Atom/Tools/MaterialEditor/gem.json index 85ff434eab..807e3fa65f 100644 --- a/Gems/Atom/Tools/MaterialEditor/gem.json +++ b/Gems/Atom/Tools/MaterialEditor/gem.json @@ -2,6 +2,7 @@ "gem_name": "MaterialEditor", "display_name": "Atom Material Editor", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Tool", "summary": "Editor for creating, modifying, and previewing materials", diff --git a/Gems/Atom/gem.json b/Gems/Atom/gem.json index 1f5e4a37f3..0f409ad993 100644 --- a/Gems/Atom/gem.json +++ b/Gems/Atom/gem.json @@ -2,6 +2,7 @@ "gem_name": "Atom", "display_name": "Atom Renderer", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Atom Renderer Gem provides Atom Renderer and its associated tools (such as Material Editor), utilites, libraries, and interfaces.", diff --git a/Gems/AtomContent/ReferenceMaterials/gem.json b/Gems/AtomContent/ReferenceMaterials/gem.json index f697d50bc4..b75b01e1ae 100644 --- a/Gems/AtomContent/ReferenceMaterials/gem.json +++ b/Gems/AtomContent/ReferenceMaterials/gem.json @@ -5,8 +5,15 @@ "origin": "https://github.com/aws-lumberyard-dev/o3de.git", "type": "Asset", "summary": "Atom Asset Gem with a library of reference materials for StandardPBR (and others in the future)", - "canonical_tags": ["Gem"], - "user_tags": ["Assets", "PBR", "Materials"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Assets", + "PBR", + "Materials" + ], "icon_path": "preview.png", - "dependencies": [] + "dependencies": [], + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt" } diff --git a/Gems/AtomContent/Sponza/Assets/Prefabs/test_sponza_material_conversion.prefab b/Gems/AtomContent/Sponza/Assets/Prefabs/test_sponza_material_conversion.prefab index b5aed9d14b..92874574e4 100644 --- a/Gems/AtomContent/Sponza/Assets/Prefabs/test_sponza_material_conversion.prefab +++ b/Gems/AtomContent/Sponza/Assets/Prefabs/test_sponza_material_conversion.prefab @@ -581,7 +581,7 @@ { "id": { "materialAssetId": { - "guid": "{935F694A-8639-515B-8133-81CDC7948E5B}", + "guid": "{0CD745C0-6AA8-569A-A68A-73A3270986C4}", "subId": 803645540 } } @@ -593,7 +593,7 @@ "id": { "lodIndex": 0, "materialAssetId": { - "guid": "{935F694A-8639-515B-8133-81CDC7948E5B}", + "guid": "{0CD745C0-6AA8-569A-A68A-73A3270986C4}", "subId": 803645540 } } @@ -608,10 +608,10 @@ "Configuration": { "ModelAsset": { "assetId": { - "guid": "{935F694A-8639-515B-8133-81CDC7948E5B}", - "subId": 277333723 + "guid": "{0CD745C0-6AA8-569A-A68A-73A3270986C4}", + "subId": 277889906 }, - "assetHint": "objects/groudplane/groundplane_521x521m.azmodel" + "assetHint": "objects/groudplane/groundplane_512x512m.azmodel" } } } diff --git a/Gems/AtomContent/Sponza/gem.json b/Gems/AtomContent/Sponza/gem.json index 64de0e5da0..68749cd5f4 100644 --- a/Gems/AtomContent/Sponza/gem.json +++ b/Gems/AtomContent/Sponza/gem.json @@ -2,6 +2,7 @@ "gem_name": "Sponza", "display_name": "Sponza", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Asset", "summary": "A standard test scene for Global Illumination (forked from crytek sponza scene)", diff --git a/Gems/AtomContent/gem.json b/Gems/AtomContent/gem.json index 1bffe7d989..400e897a3b 100644 --- a/Gems/AtomContent/gem.json +++ b/Gems/AtomContent/gem.json @@ -2,6 +2,7 @@ "gem_name": "AtomContent", "display_name": "Atom Content", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Asset", "summary": "The Atom Content Gem provides assets for Atom Renderer and a modified version of the Pixar Look Development Studio.", diff --git a/Gems/AtomLyIntegration/AtomBridge/gem.json b/Gems/AtomLyIntegration/AtomBridge/gem.json index 1d48d8be61..e8ca413023 100644 --- a/Gems/AtomLyIntegration/AtomBridge/gem.json +++ b/Gems/AtomLyIntegration/AtomBridge/gem.json @@ -2,6 +2,7 @@ "gem_name": "Atom_AtomBridge", "display_name": "Atom Bridge", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "", diff --git a/Gems/AtomLyIntegration/AtomFont/Assets/seedList.seed b/Gems/AtomLyIntegration/AtomFont/Assets/seedList.seed new file mode 100644 index 0000000000..f879f523d0 --- /dev/null +++ b/Gems/AtomLyIntegration/AtomFont/Assets/seedList.seed @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/Gems/AtomLyIntegration/AtomFont/gem.json b/Gems/AtomLyIntegration/AtomFont/gem.json index ed5b488de7..8907ec0979 100644 --- a/Gems/AtomLyIntegration/AtomFont/gem.json +++ b/Gems/AtomLyIntegration/AtomFont/gem.json @@ -2,6 +2,7 @@ "gem_name": "AtomFont", "display_name": "Atom Font", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "", diff --git a/Gems/AtomLyIntegration/AtomImGuiTools/gem.json b/Gems/AtomLyIntegration/AtomImGuiTools/gem.json index 564eeedee2..e188ba0cb8 100644 --- a/Gems/AtomLyIntegration/AtomImGuiTools/gem.json +++ b/Gems/AtomLyIntegration/AtomImGuiTools/gem.json @@ -2,6 +2,7 @@ "gem_name": "AtomImGuiTools", "display_name": "Atom ImGui", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Tool", "summary": "", diff --git a/Gems/AtomLyIntegration/AtomViewportDisplayIcons/Assets/seedList.seed b/Gems/AtomLyIntegration/AtomViewportDisplayIcons/Assets/seedList.seed new file mode 100644 index 0000000000..2e22bca486 --- /dev/null +++ b/Gems/AtomLyIntegration/AtomViewportDisplayIcons/Assets/seedList.seed @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/Gems/AtomLyIntegration/AtomViewportDisplayIcons/gem.json b/Gems/AtomLyIntegration/AtomViewportDisplayIcons/gem.json index a2a7ba4b77..5a0763e0da 100644 --- a/Gems/AtomLyIntegration/AtomViewportDisplayIcons/gem.json +++ b/Gems/AtomLyIntegration/AtomViewportDisplayIcons/gem.json @@ -2,6 +2,7 @@ "gem_name": "AtomViewportDisplayIcons", "display_name": "Atom Viewport Display Icons", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "", diff --git a/Gems/AtomLyIntegration/AtomViewportDisplayInfo/gem.json b/Gems/AtomLyIntegration/AtomViewportDisplayInfo/gem.json index be5cc96f95..639d93d301 100644 --- a/Gems/AtomLyIntegration/AtomViewportDisplayInfo/gem.json +++ b/Gems/AtomLyIntegration/AtomViewportDisplayInfo/gem.json @@ -2,6 +2,7 @@ "gem_name": "AtomViewportDisplayInfo", "display_name": "Atom Viewport Display Info", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "", diff --git a/Gems/AtomLyIntegration/CommonFeatures/Assets/LevelAssets/default.slice b/Gems/AtomLyIntegration/CommonFeatures/Assets/LevelAssets/default.slice index b4c9eac10f..e0c7c9e456 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Assets/LevelAssets/default.slice +++ b/Gems/AtomLyIntegration/CommonFeatures/Assets/LevelAssets/default.slice @@ -836,7 +836,7 @@ - + diff --git a/Gems/AtomLyIntegration/CommonFeatures/Assets/seedList.seed b/Gems/AtomLyIntegration/CommonFeatures/Assets/seedList.seed new file mode 100644 index 0000000000..157172ad34 --- /dev/null +++ b/Gems/AtomLyIntegration/CommonFeatures/Assets/seedList.seed @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/Material/MaterialComponentBus.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/Material/MaterialComponentBus.h index 293080367d..d8d0c2dbc3 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/Material/MaterialComponentBus.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/Material/MaterialComponentBus.h @@ -60,52 +60,8 @@ namespace AZ virtual void ClearMaterialOverride(const MaterialAssignmentId& materialAssignmentId) = 0; //! Set a material property override value wrapped by an AZStd::any virtual void SetPropertyOverride(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZStd::any& value) = 0; - //! Set a material property override value to a bool - virtual void SetPropertyOverrideBool(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const bool& value) = 0; - //! Set a material property override value to a integer - virtual void SetPropertyOverrideInt32(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const int32_t& value) = 0; - //! Set a material property override value to a unsigned integer - virtual void SetPropertyOverrideUInt32(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const uint32_t& value) = 0; - //! Set a material property override value to a float - virtual void SetPropertyOverrideFloat(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const float& value) = 0; - //! Set a material property override value to a Vector2 - virtual void SetPropertyOverrideVector2(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZ::Vector2& value) = 0; - //! Set a material property override value to a Vector3 - virtual void SetPropertyOverrideVector3(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZ::Vector3& value) = 0; - //! Set a material property override value to a Vector4 - virtual void SetPropertyOverrideVector4(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZ::Vector4& value) = 0; - //! Set a material property override value to a color - virtual void SetPropertyOverrideColor(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZ::Color& value) = 0; - //! Set a material property override value to an image asset - virtual void SetPropertyOverrideImageAsset(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZ::Data::Asset& value) = 0; - //! Set a material property override value to an image instance - virtual void SetPropertyOverrideImageInstance(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZ::Data::Instance& value) = 0; - //! Set a material property override value to a string - virtual void SetPropertyOverrideString(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZStd::string& value) = 0; //! Get a material property override value wrapped by an AZStd::any virtual AZStd::any GetPropertyOverride(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const = 0; - //! Get a material property override value as a bool - virtual bool GetPropertyOverrideBool(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const = 0; - //! Get a material property override value as an integer - virtual int32_t GetPropertyOverrideInt32(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const = 0; - //! Get a material property override value as an unsigned integer - virtual uint32_t GetPropertyOverrideUInt32(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const = 0; - //! Get a material property override value as a float - virtual float GetPropertyOverrideFloat(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const = 0; - //! Get a material property override value as a Vector2 - virtual AZ::Vector2 GetPropertyOverrideVector2(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const = 0; - //! Get a material property override value as a Vector3 - virtual AZ::Vector3 GetPropertyOverrideVector3(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const = 0; - //! Get a material property override value as a Vector4 - virtual AZ::Vector4 GetPropertyOverrideVector4(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const = 0; - //! Get a material property override value as a Color - virtual AZ::Color GetPropertyOverrideColor(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const = 0; - //! Get a material property override value as an image asset - virtual AZ::Data::Asset GetPropertyOverrideImageAsset(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const = 0; - //! Get a material property override value as an image instance - virtual AZ::Data::Instance GetPropertyOverrideImageInstance(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const = 0; - //! Get a material property override value as a string - virtual AZStd::string GetPropertyOverrideString(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const = 0; //! Clear property override for a specific material assignment virtual void ClearPropertyOverride(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) = 0; //! Clear property overrides for a specific material assignment @@ -122,6 +78,21 @@ namespace AZ const MaterialAssignmentId& materialAssignmentId, const AZ::RPI::MaterialModelUvOverrideMap& modelUvOverrides) = 0; //! Get Model UV overrides for a specific material assignment virtual AZ::RPI::MaterialModelUvOverrideMap GetModelUvOverrides(const MaterialAssignmentId& materialAssignmentId) const = 0; + + //! Set material property override value with a specific type + template + void SetPropertyOverrideT(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const T& value) + { + SetPropertyOverride(materialAssignmentId, propertyName, AZStd::any(value)); + } + + //! Get material property override value with a specific type + template + T GetPropertyOverrideT(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const + { + const AZStd::any& value = GetPropertyOverride(materialAssignmentId, propertyName); + return !value.empty() && value.is() ? AZStd::any_cast(value) : T{}; + } }; using MaterialComponentRequestBus = EBus; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DiskLightDelegate.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DiskLightDelegate.cpp index ebc79abca5..dfb6cf6946 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DiskLightDelegate.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/DiskLightDelegate.cpp @@ -47,53 +47,60 @@ namespace AZ::Render return m_shapeBus->GetRadius() * GetTransform().GetUniformScale(); } - void DiskLightDelegate::DrawDebugDisplay(const Transform& transform, const Color& /*color*/, AzFramework::DebugDisplayRequests& debugDisplay, bool isSelected) const + void DiskLightDelegate::DrawDebugDisplay(const Transform& transform, [[maybe_unused]]const Color&, AzFramework::DebugDisplayRequests& debugDisplay, bool isSelected) const { - if (isSelected) + debugDisplay.PushMatrix(transform); + const float radius = GetConfig()->m_attenuationRadius; + const float shapeRadius = m_shapeBus->GetRadius(); + + auto DrawConicalFrustum = [&debugDisplay](uint32_t numRadiusLines, const Color& color, float brightness, float topRadius, float bottomRadius, float height) { - debugDisplay.PushMatrix(transform); - float radius = GetConfig()->m_attenuationRadius; + const Color displayColor = Color(color.GetAsVector3() * brightness); + debugDisplay.SetColor(displayColor); + debugDisplay.DrawWireDisk(Vector3(0.0, 0.0, height), Vector3::CreateAxisZ(), bottomRadius); - if (GetConfig()->m_enableShutters) + for (uint32_t i = 0; i < numRadiusLines; ++i) { - - float innerRadians = DegToRad(GetConfig()->m_innerShutterAngleDegrees); - float outerRadians = DegToRad(GetConfig()->m_outerShutterAngleDegrees); - - // Draw a cone using the cone angle and attenuation radius - innerRadians = GetMin(innerRadians, outerRadians); - float coneRadiusInner = sin(innerRadians) * radius; - float coneHeightInner = cos(innerRadians) * radius; - float coneRadiusOuter = sin(outerRadians) * radius; - float coneHeightOuter = cos(outerRadians) * radius; - - auto DrawConicalFrustum = [&debugDisplay](uint32_t numRadiusLines, float topRadius, float bottomRadius, float height, float brightness) - { - debugDisplay.SetColor(Color(brightness, brightness, brightness, 1.0f)); - debugDisplay.DrawWireDisk(Vector3(0.0, 0.0, height), Vector3::CreateAxisZ(), bottomRadius); - - for (uint32_t i = 0; i < numRadiusLines; ++i) - { - float radiusLineAngle = float(i) / numRadiusLines * Constants::TwoPi; - debugDisplay.DrawLine( - Vector3(cos(radiusLineAngle) * topRadius, sin(radiusLineAngle) * topRadius, 0), - Vector3(cos(radiusLineAngle) * bottomRadius, sin(radiusLineAngle) * bottomRadius, height) - ); - } - }; - - DrawConicalFrustum(16, m_shapeBus->GetRadius(), m_shapeBus->GetRadius() + coneRadiusInner, coneHeightInner, 1.0f); - DrawConicalFrustum(16, m_shapeBus->GetRadius(), m_shapeBus->GetRadius() + coneRadiusOuter, coneHeightOuter, 0.65f); - + float radiusLineAngle = float(i) / numRadiusLines * Constants::TwoPi; + float cosAngle = cos(radiusLineAngle); + float sinAngle = sin(radiusLineAngle); + debugDisplay.DrawLine( + Vector3(cosAngle * topRadius, sinAngle * topRadius, 0), + Vector3(cosAngle * bottomRadius,sinAngle * bottomRadius, height) + ); } - else - { - debugDisplay.DrawWireDisk(Vector3::CreateZero(), Vector3::CreateAxisZ(), radius); - debugDisplay.DrawArc(Vector3::CreateZero(), radius, 270.0f, 180.0f, 3.0f, 0); - debugDisplay.DrawArc(Vector3::CreateZero(), radius, 0.0f, 180.0f, 3.0f, 1); - } - debugDisplay.PopMatrix(); + }; + + const Color coneColor = isSelected ? Color::CreateOne() : Color(0.0f, 0.75f, 0.75f, 1.0); + const uint32_t innerConeLines = 8; + float innerRadians, outerRadians; + if (GetConfig()->m_enableShutters) + { // With shutters enabled, draw inner and outer debug display frustums + innerRadians = DegToRad(GetConfig()->m_innerShutterAngleDegrees); + outerRadians = DegToRad(GetConfig()->m_outerShutterAngleDegrees); + + // Draw a cone using the cone angle and attenuation radius + innerRadians = GetMin(innerRadians, outerRadians); + + float coneRadiusOuter = sin(outerRadians) * radius; + float coneHeightOuter = cos(outerRadians) * radius; + + // Outer cone frustum 'faded' debug cone + const uint32_t outerConeLines = 9; + DrawConicalFrustum(outerConeLines, coneColor, 0.75f, shapeRadius, shapeRadius + coneRadiusOuter, coneHeightOuter); } + else + { // Generic debug display frustum + const float coneAngle = 25.0f; + innerRadians = DegToRad(coneAngle); // 25 degrees debug display + } + + // Inner cone frustum + float coneRadiusInner = sin(innerRadians) * radius; + float coneHeightInner = cos(innerRadians) * radius; + DrawConicalFrustum(innerConeLines, coneColor, 1.0f, shapeRadius, shapeRadius + coneRadiusInner, coneHeightInner); + + debugDisplay.PopMatrix(); } void DiskLightDelegate::SetEnableShutters(bool enabled) diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/DiffuseGlobalIllumination/EditorDiffuseProbeGridComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/DiffuseGlobalIllumination/EditorDiffuseProbeGridComponent.cpp index c14510f195..f54d1f05e5 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/DiffuseGlobalIllumination/EditorDiffuseProbeGridComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/DiffuseGlobalIllumination/EditorDiffuseProbeGridComponent.cpp @@ -196,7 +196,7 @@ namespace AZ { // bake is complete, update configuration with the new baked texture asset AzToolsFramework::ScopedUndoBatch undoBatch("DiffuseProbeGrid Texture Bake"); - configurationAsset = { textureAsset.GetAs(), AZ::Data::AssetLoadBehavior::PreLoad }; + configurationAsset = textureAsset; SetDirty(); if (m_controller.m_configuration.m_bakedIrradianceTextureAsset.IsReady() && diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.cpp index 94beeb2430..7e6b3e4e4c 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.cpp @@ -6,23 +6,24 @@ * */ -#include -#include - -#include -#include -#include #include #include #include #include #include #include +#include +#include +#include +#include +#include +#include +#include AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") // disable warnings spawned by QT -#include #include #include +#include AZ_POP_DISABLE_WARNING namespace AZ @@ -59,7 +60,12 @@ namespace AZ BaseClass::Reflect(context); EditorMaterialComponentSlot::Reflect(context); - if (AZ::SerializeContext* serializeContext = azrtti_cast(context)) + if (auto jsonContext = azrtti_cast(context)) + { + jsonContext->Serializer()->HandlesType(); + } + + if (auto serializeContext = azrtti_cast(context)) { serializeContext->RegisterGenericType(); serializeContext->RegisterGenericType(); @@ -76,7 +82,7 @@ namespace AZ serializeContext->RegisterGenericType, AZStd::equal_to, AZStd::allocator>>(); serializeContext->RegisterGenericType, AZStd::equal_to, AZStd::allocator>>(); - if (AZ::EditContext* editContext = serializeContext->GetEditContext()) + if (auto editContext = serializeContext->GetEditContext()) { editContext->Class( "Material", "The material component specifies the material to use for this entity") @@ -129,7 +135,7 @@ namespace AZ } } - if (AZ::BehaviorContext* behaviorContext = azrtti_cast(context)) + if (auto behaviorContext = azrtti_cast(context)) { behaviorContext->ConstantProperty("EditorMaterialComponentTypeId", BehaviorConstant(Uuid(EditorMaterialComponentTypeId))) ->Attribute(AZ::Script::Attributes::Module, "render") diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.h index ce21ae82ac..4cd7870be5 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.h @@ -27,6 +27,8 @@ namespace AZ , public EditorMaterialSystemComponentNotificationBus::Handler { public: + friend class JsonEditorMaterialComponentSerializer; + using BaseClass = EditorRenderComponentAdapter; AZ_EDITOR_COMPONENT(EditorMaterialComponent, EditorMaterialComponentTypeId, BaseClass); diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSerializer.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSerializer.cpp new file mode 100644 index 0000000000..8ae6e44a93 --- /dev/null +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSerializer.cpp @@ -0,0 +1,109 @@ +/* + * 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 AZ +{ + namespace Render + { + AZ_CLASS_ALLOCATOR_IMPL(JsonEditorMaterialComponentSerializer, AZ::SystemAllocator, 0); + + AZ::JsonSerializationResult::Result JsonEditorMaterialComponentSerializer::Load( + void* outputValue, + [[maybe_unused]] const AZ::Uuid& outputValueTypeId, + const rapidjson::Value& inputValue, + AZ::JsonDeserializerContext& context) + { + namespace JSR = AZ::JsonSerializationResult; + + AZ_Assert( + azrtti_typeid() == outputValueTypeId, + "Unable to deserialize EditorMaterialComponent from json because the provided type is %s.", + outputValueTypeId.ToString().c_str()); + + auto componentInstance = reinterpret_cast(outputValue); + AZ_Assert(componentInstance, "Output value for JsonEditorMaterialComponentSerializer can't be null."); + + JSR::ResultCode result(JSR::Tasks::ReadField); + + result.Combine(ContinueLoadingFromJsonObjectField( + &componentInstance->m_id, azrtti_typeidm_id)>(), inputValue, "Id", context)); + + result.Combine(ContinueLoadingFromJsonObjectField( + &componentInstance->m_controller, azrtti_typeidm_controller)>(), inputValue, "Controller", + context)); + + result.Combine(ContinueLoadingFromJsonObjectField( + &componentInstance->m_materialSlotsByLodEnabled, azrtti_typeidm_materialSlotsByLodEnabled)>(), + inputValue, "materialSlotsByLodEnabled", context)); + + return context.Report( + result, + result.GetProcessing() != JSR::Processing::Halted ? "Successfully loaded EditorMaterialComponent information." + : "Failed to load EditorMaterialComponent information."); + } + + AZ::JsonSerializationResult::Result JsonEditorMaterialComponentSerializer::Store( + rapidjson::Value& outputValue, + const void* inputValue, + const void* defaultValue, + [[maybe_unused]] const AZ::Uuid& valueTypeId, + AZ::JsonSerializerContext& context) + { + namespace JSR = AZ::JsonSerializationResult; + + AZ_Assert( + azrtti_typeid() == valueTypeId, + "Unable to Serialize EditorMaterialComponent because the provided type is %s.", + valueTypeId.ToString().c_str()); + + auto componentInstance = reinterpret_cast(inputValue); + AZ_Assert(componentInstance, "Input value for JsonEditorMaterialComponentSerializer can't be null."); + auto defaultComponentInstance = reinterpret_cast(defaultValue); + + JSR::ResultCode result(JSR::Tasks::WriteValue); + { + AZ::ScopedContextPath subPathName(context, "m_id"); + const auto componentId = &componentInstance->m_id; + const auto defaultComponentId = defaultComponentInstance ? &defaultComponentInstance->m_id : nullptr; + + result.Combine(ContinueStoringToJsonObjectField( + outputValue, "Id", componentId, defaultComponentId, azrtti_typeidm_id)>(), context)); + } + + { + AZ::ScopedContextPath subPathName(context, "Controller"); + const auto controller = &componentInstance->m_controller; + const auto defaultController = defaultComponentInstance ? &defaultComponentInstance->m_controller : nullptr; + + result.Combine(ContinueStoringToJsonObjectField( + outputValue, "Controller", controller, defaultController, azrtti_typeidm_controller)>(), + context)); + } + + { + AZ::ScopedContextPath subPathName(context, "materialSlotsByLodEnabled"); + const auto enabled = &componentInstance->m_materialSlotsByLodEnabled; + const auto defaultEnabled = defaultComponentInstance ? &defaultComponentInstance->m_materialSlotsByLodEnabled : nullptr; + + result.Combine(ContinueStoringToJsonObjectField( + outputValue, "materialSlotsByLodEnabled", enabled, defaultEnabled, + azrtti_typeidm_materialSlotsByLodEnabled)>(), context)); + } + + return context.Report( + result, + result.GetProcessing() != JSR::Processing::Halted ? "Successfully stored EditorMaterialComponent information." + : "Failed to store EditorMaterialComponent information."); + } + + } // namespace Render +} // namespace AZ diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSerializer.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSerializer.h new file mode 100644 index 0000000000..2b6401c67f --- /dev/null +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSerializer.h @@ -0,0 +1,41 @@ +/* + * 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 AZ +{ + namespace Render + { + // JsonEditorMaterialComponentSerializer skips serialization of EditorMaterialComponentSlot(s) which are only needed at runtime in + // the editor + class JsonEditorMaterialComponentSerializer : public AZ::BaseJsonSerializer + { + public: + AZ_RTTI(JsonEditorMaterialComponentSerializer, "{D354FE3C-34D2-4E80-B3F9-49450D252336}", BaseJsonSerializer); + AZ_CLASS_ALLOCATOR_DECL; + + AZ::JsonSerializationResult::Result Load( + void* outputValue, + const AZ::Uuid& outputValueTypeId, + const rapidjson::Value& inputValue, + AZ::JsonDeserializerContext& context) override; + + AZ::JsonSerializationResult::Result Store( + rapidjson::Value& outputValue, + const void* inputValue, + const void* defaultValue, + const AZ::Uuid& valueTypeId, + AZ::JsonSerializerContext& context) override; + }; + + } // namespace Render +} // namespace AZ diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/MaterialComponentController.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/MaterialComponentController.cpp index 07082a87d5..0ccdae28de 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/MaterialComponentController.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/MaterialComponentController.cpp @@ -54,29 +54,29 @@ namespace AZ ->Event("GetMaterialOverride", &MaterialComponentRequestBus::Events::GetMaterialOverride) ->Event("ClearMaterialOverride", &MaterialComponentRequestBus::Events::ClearMaterialOverride) ->Event("SetPropertyOverride", &MaterialComponentRequestBus::Events::SetPropertyOverride) - ->Event("SetPropertyOverrideBool", &MaterialComponentRequestBus::Events::SetPropertyOverrideBool) - ->Event("SetPropertyOverrideInt32", &MaterialComponentRequestBus::Events::SetPropertyOverrideInt32) - ->Event("SetPropertyOverrideUInt32", &MaterialComponentRequestBus::Events::SetPropertyOverrideUInt32) - ->Event("SetPropertyOverrideFloat", &MaterialComponentRequestBus::Events::SetPropertyOverrideFloat) - ->Event("SetPropertyOverrideVector2", &MaterialComponentRequestBus::Events::SetPropertyOverrideVector2) - ->Event("SetPropertyOverrideVector3", &MaterialComponentRequestBus::Events::SetPropertyOverrideVector3) - ->Event("SetPropertyOverrideVector4", &MaterialComponentRequestBus::Events::SetPropertyOverrideVector4) - ->Event("SetPropertyOverrideColor", &MaterialComponentRequestBus::Events::SetPropertyOverrideColor) - ->Event("SetPropertyOverrideImageAsset", &MaterialComponentRequestBus::Events::SetPropertyOverrideImageAsset) - ->Event("SetPropertyOverrideImageInstance", &MaterialComponentRequestBus::Events::SetPropertyOverrideImageInstance) - ->Event("SetPropertyOverrideString", &MaterialComponentRequestBus::Events::SetPropertyOverrideString) + ->Event("SetPropertyOverrideBool", &MaterialComponentRequestBus::Events::SetPropertyOverrideT) + ->Event("SetPropertyOverrideInt32", &MaterialComponentRequestBus::Events::SetPropertyOverrideT) + ->Event("SetPropertyOverrideUInt32", &MaterialComponentRequestBus::Events::SetPropertyOverrideT) + ->Event("SetPropertyOverrideFloat", &MaterialComponentRequestBus::Events::SetPropertyOverrideT) + ->Event("SetPropertyOverrideVector2", &MaterialComponentRequestBus::Events::SetPropertyOverrideT) + ->Event("SetPropertyOverrideVector3", &MaterialComponentRequestBus::Events::SetPropertyOverrideT) + ->Event("SetPropertyOverrideVector4", &MaterialComponentRequestBus::Events::SetPropertyOverrideT) + ->Event("SetPropertyOverrideColor", &MaterialComponentRequestBus::Events::SetPropertyOverrideT) + ->Event("SetPropertyOverrideImage", &MaterialComponentRequestBus::Events::SetPropertyOverrideT) + ->Event("SetPropertyOverrideString", &MaterialComponentRequestBus::Events::SetPropertyOverrideT) + ->Event("SetPropertyOverrideEnum", &MaterialComponentRequestBus::Events::SetPropertyOverrideT) ->Event("GetPropertyOverride", &MaterialComponentRequestBus::Events::GetPropertyOverride) - ->Event("GetPropertyOverrideBool", &MaterialComponentRequestBus::Events::GetPropertyOverrideBool) - ->Event("GetPropertyOverrideInt32", &MaterialComponentRequestBus::Events::GetPropertyOverrideInt32) - ->Event("GetPropertyOverrideUInt32", &MaterialComponentRequestBus::Events::GetPropertyOverrideUInt32) - ->Event("GetPropertyOverrideFloat", &MaterialComponentRequestBus::Events::GetPropertyOverrideFloat) - ->Event("GetPropertyOverrideVector2", &MaterialComponentRequestBus::Events::GetPropertyOverrideVector2) - ->Event("GetPropertyOverrideVector3", &MaterialComponentRequestBus::Events::GetPropertyOverrideVector3) - ->Event("GetPropertyOverrideVector4", &MaterialComponentRequestBus::Events::GetPropertyOverrideVector4) - ->Event("GetPropertyOverrideColor", &MaterialComponentRequestBus::Events::GetPropertyOverrideColor) - ->Event("GetPropertyOverrideImageAsset", &MaterialComponentRequestBus::Events::GetPropertyOverrideImageAsset) - ->Event("GetPropertyOverrideImageInstance", &MaterialComponentRequestBus::Events::GetPropertyOverrideImageInstance) - ->Event("GetPropertyOverrideString", &MaterialComponentRequestBus::Events::GetPropertyOverrideString) + ->Event("GetPropertyOverrideBool", &MaterialComponentRequestBus::Events::GetPropertyOverrideT) + ->Event("GetPropertyOverrideInt32", &MaterialComponentRequestBus::Events::GetPropertyOverrideT) + ->Event("GetPropertyOverrideUInt32", &MaterialComponentRequestBus::Events::GetPropertyOverrideT) + ->Event("GetPropertyOverrideFloat", &MaterialComponentRequestBus::Events::GetPropertyOverrideT) + ->Event("GetPropertyOverrideVector2", &MaterialComponentRequestBus::Events::GetPropertyOverrideT) + ->Event("GetPropertyOverrideVector3", &MaterialComponentRequestBus::Events::GetPropertyOverrideT) + ->Event("GetPropertyOverrideVector4", &MaterialComponentRequestBus::Events::GetPropertyOverrideT) + ->Event("GetPropertyOverrideColor", &MaterialComponentRequestBus::Events::GetPropertyOverrideT) + ->Event("GetPropertyOverrideImage", &MaterialComponentRequestBus::Events::GetPropertyOverrideT) + ->Event("GetPropertyOverrideString", &MaterialComponentRequestBus::Events::GetPropertyOverrideT) + ->Event("GetPropertyOverrideEnum", &MaterialComponentRequestBus::Events::GetPropertyOverrideT) ->Event("ClearPropertyOverride", &MaterialComponentRequestBus::Events::ClearPropertyOverride) ->Event("ClearPropertyOverrides", &MaterialComponentRequestBus::Events::ClearPropertyOverrides) ->Event("ClearAllPropertyOverrides", &MaterialComponentRequestBus::Events::ClearAllPropertyOverrides) @@ -121,8 +121,13 @@ namespace AZ MaterialComponentRequestBus::Handler::BusDisconnect(); MaterialReceiverNotificationBus::Handler::BusDisconnect(); TickBus::Handler::BusDisconnect(); + ReleaseMaterials(); + // Sending notification to wipe any previously assigned material overrides + MaterialComponentNotificationBus::Event( + m_entityId, &MaterialComponentNotifications::OnMaterialsUpdated, MaterialAssignmentMap()); + m_queuedMaterialUpdateNotification = false; m_entityId = AZ::EntityId(AZ::EntityId::InvalidEntityId); } @@ -221,6 +226,11 @@ namespace AZ if (!anyQueued) { ReleaseMaterials(); + + // If no other materials were loaded, the notification must still be sent in case there are externally managed material + // instances in the configuration + MaterialComponentNotificationBus::Event( + m_entityId, &MaterialComponentNotifications::OnMaterialsUpdated, m_configuration.m_materials); } } @@ -268,8 +278,6 @@ namespace AZ { materialPair.second.Release(); } - - MaterialComponentNotificationBus::Event(m_entityId, &MaterialComponentNotifications::OnMaterialsUpdated, m_configuration.m_materials); } MaterialAssignmentMap MaterialComponentController::GetOriginalMaterialAssignments() const @@ -499,76 +507,6 @@ namespace AZ QueuePropertyChanges(materialAssignmentId); } - void MaterialComponentController::SetPropertyOverrideBool( - const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const bool& value) - { - SetPropertyOverride(materialAssignmentId, propertyName, AZStd::any(value)); - } - - void MaterialComponentController::SetPropertyOverrideInt32( - const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const int32_t& value) - { - SetPropertyOverride(materialAssignmentId, propertyName, AZStd::any(value)); - } - - void MaterialComponentController::SetPropertyOverrideUInt32( - const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const uint32_t& value) - { - SetPropertyOverride(materialAssignmentId, propertyName, AZStd::any(value)); - } - - void MaterialComponentController::SetPropertyOverrideFloat( - const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const float& value) - { - SetPropertyOverride(materialAssignmentId, propertyName, AZStd::any(value)); - } - - void MaterialComponentController::SetPropertyOverrideVector2( - const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZ::Vector2& value) - { - SetPropertyOverride(materialAssignmentId, propertyName, AZStd::any(value)); - } - - void MaterialComponentController::SetPropertyOverrideVector3( - const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZ::Vector3& value) - { - SetPropertyOverride(materialAssignmentId, propertyName, AZStd::any(value)); - } - - void MaterialComponentController::SetPropertyOverrideVector4( - const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZ::Vector4& value) - { - SetPropertyOverride(materialAssignmentId, propertyName, AZStd::any(value)); - } - - void MaterialComponentController::SetPropertyOverrideColor( - const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZ::Color& value) - { - SetPropertyOverride(materialAssignmentId, propertyName, AZStd::any(value)); - } - - void MaterialComponentController::SetPropertyOverrideImageAsset( - const MaterialAssignmentId& materialAssignmentId, - const AZStd::string& propertyName, - const AZ::Data::Asset& value) - { - SetPropertyOverride(materialAssignmentId, propertyName, AZStd::any(value)); - } - - void MaterialComponentController::SetPropertyOverrideImageInstance( - const MaterialAssignmentId& materialAssignmentId, - const AZStd::string& propertyName, - const AZ::Data::Instance& value) - { - SetPropertyOverride(materialAssignmentId, propertyName, AZStd::any(value)); - } - - void MaterialComponentController::SetPropertyOverrideString( - const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZStd::string& value) - { - SetPropertyOverride(materialAssignmentId, propertyName, AZStd::any(value)); - } - AZStd::any MaterialComponentController::GetPropertyOverride(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const { const auto materialIt = m_configuration.m_materials.find(materialAssignmentId); @@ -586,83 +524,6 @@ namespace AZ return propertyIt->second; } - bool MaterialComponentController::GetPropertyOverrideBool( - const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const - { - const AZStd::any& value = GetPropertyOverride(materialAssignmentId, propertyName); - return !value.empty() && value.is() ? AZStd::any_cast(value) : false; - } - - int32_t MaterialComponentController::GetPropertyOverrideInt32( - const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const - { - const AZStd::any& value = GetPropertyOverride(materialAssignmentId, propertyName); - return !value.empty() && value.is() ? AZStd::any_cast(value) : 0; - } - - uint32_t MaterialComponentController::GetPropertyOverrideUInt32( - const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const - { - const AZStd::any& value = GetPropertyOverride(materialAssignmentId, propertyName); - return !value.empty() && value.is() ? AZStd::any_cast(value) : 0; - } - - float MaterialComponentController::GetPropertyOverrideFloat( - const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const - { - const AZStd::any& value = GetPropertyOverride(materialAssignmentId, propertyName); - return !value.empty() && value.is() ? AZStd::any_cast(value) : 0.0f; - } - - AZ::Vector2 MaterialComponentController::GetPropertyOverrideVector2( - const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const - { - const AZStd::any& value = GetPropertyOverride(materialAssignmentId, propertyName); - return !value.empty() && value.is() ? AZStd::any_cast(value) : AZ::Vector2::CreateZero(); - } - - AZ::Vector3 MaterialComponentController::GetPropertyOverrideVector3( - const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const - { - const AZStd::any& value = GetPropertyOverride(materialAssignmentId, propertyName); - return !value.empty() && value.is() ? AZStd::any_cast(value) : AZ::Vector3::CreateZero(); - } - - AZ::Vector4 MaterialComponentController::GetPropertyOverrideVector4( - const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const - { - const AZStd::any& value = GetPropertyOverride(materialAssignmentId, propertyName); - return !value.empty() && value.is() ? AZStd::any_cast(value) : AZ::Vector4::CreateZero(); - } - - AZ::Color MaterialComponentController::GetPropertyOverrideColor( - const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const - { - const AZStd::any& value = GetPropertyOverride(materialAssignmentId, propertyName); - return !value.empty() && value.is() ? AZStd::any_cast(value) : AZ::Color::CreateZero(); - } - - AZ::Data::Asset MaterialComponentController::GetPropertyOverrideImageAsset( - const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const - { - const AZStd::any& value = GetPropertyOverride(materialAssignmentId, propertyName); - return !value.empty() && value.is>() ? AZStd::any_cast>(value) : AZ::Data::Asset(); - } - - AZ::Data::Instance MaterialComponentController::GetPropertyOverrideImageInstance( - const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const - { - const AZStd::any& value = GetPropertyOverride(materialAssignmentId, propertyName); - return !value.empty() && value.is>() ? AZStd::any_cast>(value) : AZ::Data::Instance(); - } - - AZStd::string MaterialComponentController::GetPropertyOverrideString( - const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const - { - const AZStd::any& value = GetPropertyOverride(materialAssignmentId, propertyName); - return !value.empty() && value.is() ? AZStd::any_cast(value) : AZStd::string(); - } - void MaterialComponentController::ClearPropertyOverride(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) { auto materialIt = m_configuration.m_materials.find(materialAssignmentId); diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/MaterialComponentController.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/MaterialComponentController.h index ec542c377c..74b1cfda4d 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/MaterialComponentController.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/MaterialComponentController.h @@ -65,33 +65,8 @@ namespace AZ void SetMaterialOverride(const MaterialAssignmentId& materialAssignmentId, const AZ::Data::AssetId& materialAssetId) override; AZ::Data::AssetId GetMaterialOverride(const MaterialAssignmentId& materialAssignmentId) const override; void ClearMaterialOverride(const MaterialAssignmentId& materialAssignmentId) override; - void SetPropertyOverride(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZStd::any& value) override; - void SetPropertyOverrideBool(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const bool& value) override; - void SetPropertyOverrideInt32(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const int32_t& value) override; - void SetPropertyOverrideUInt32(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const uint32_t& value) override; - void SetPropertyOverrideFloat(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const float& value) override; - void SetPropertyOverrideVector2(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZ::Vector2& value) override; - void SetPropertyOverrideVector3(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZ::Vector3& value) override; - void SetPropertyOverrideVector4(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZ::Vector4& value) override; - void SetPropertyOverrideColor(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZ::Color& value) override; - void SetPropertyOverrideImageAsset(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZ::Data::Asset& value) override; - void SetPropertyOverrideImageInstance(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZ::Data::Instance& value) override; - void SetPropertyOverrideString(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZStd::string& value) override; - AZStd::any GetPropertyOverride(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const override; - bool GetPropertyOverrideBool(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const override; - int32_t GetPropertyOverrideInt32(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const override; - uint32_t GetPropertyOverrideUInt32(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const override; - float GetPropertyOverrideFloat(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const override; - AZ::Vector2 GetPropertyOverrideVector2(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const override; - AZ::Vector3 GetPropertyOverrideVector3(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const override; - AZ::Vector4 GetPropertyOverrideVector4(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const override; - AZ::Color GetPropertyOverrideColor(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const override; - AZ::Data::Asset GetPropertyOverrideImageAsset(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const override; - AZ::Data::Instance GetPropertyOverrideImageInstance(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const override; - AZStd::string GetPropertyOverrideString(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const override; - void ClearPropertyOverride(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) override; void ClearPropertyOverrides(const MaterialAssignmentId& materialAssignmentId) override; void ClearAllPropertyOverrides() override; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ReflectionProbe/EditorReflectionProbeComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ReflectionProbe/EditorReflectionProbeComponent.cpp index 99e99abf4a..ae5c930096 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ReflectionProbe/EditorReflectionProbeComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ReflectionProbe/EditorReflectionProbeComponent.cpp @@ -178,7 +178,7 @@ namespace AZ if (notificationType == CubeMapAssetNotificationType::Ready) { // bake is complete, update configuration with the new baked cubemap asset - m_controller.m_configuration.m_bakedCubeMapAsset = { cubeMapAsset.GetAs(), AZ::Data::AssetLoadBehavior::PreLoad }; + m_controller.m_configuration.m_bakedCubeMapAsset = cubeMapAsset; // refresh the currently rendered cubemap m_controller.UpdateCubeMap(); diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/atomlyintegration_commonfeatures_editor_files.cmake b/Gems/AtomLyIntegration/CommonFeatures/Code/atomlyintegration_commonfeatures_editor_files.cmake index 2714b65a56..0dea725d70 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/atomlyintegration_commonfeatures_editor_files.cmake +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/atomlyintegration_commonfeatures_editor_files.cmake @@ -31,6 +31,8 @@ set(FILES Source/ImageBasedLights/EditorImageBasedLightComponent.cpp Source/Material/EditorMaterialComponent.cpp Source/Material/EditorMaterialComponent.h + Source/Material/EditorMaterialComponentSerializer.cpp + Source/Material/EditorMaterialComponentSerializer.h Source/Material/EditorMaterialComponentUtil.cpp Source/Material/EditorMaterialComponentUtil.h Source/Material/EditorMaterialComponentSlot.cpp diff --git a/Gems/AtomLyIntegration/CommonFeatures/gem.json b/Gems/AtomLyIntegration/CommonFeatures/gem.json index 30738ad14c..6fa8938332 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/gem.json +++ b/Gems/AtomLyIntegration/CommonFeatures/gem.json @@ -2,6 +2,7 @@ "gem_name": "CommonFeaturesAtom", "display_name": "Common Features Atom", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "", diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/gem.json b/Gems/AtomLyIntegration/EMotionFXAtom/gem.json index f921990360..3514d4c1b9 100644 --- a/Gems/AtomLyIntegration/EMotionFXAtom/gem.json +++ b/Gems/AtomLyIntegration/EMotionFXAtom/gem.json @@ -2,6 +2,7 @@ "gem_name": "EMotionFX_Atom", "display_name": "EMotionFX Atom", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "", diff --git a/Gems/AtomLyIntegration/ImguiAtom/gem.json b/Gems/AtomLyIntegration/ImguiAtom/gem.json index 6b032275b9..fa611d8224 100644 --- a/Gems/AtomLyIntegration/ImguiAtom/gem.json +++ b/Gems/AtomLyIntegration/ImguiAtom/gem.json @@ -2,6 +2,7 @@ "gem_name": "ImguiAtom", "display_name": "Imgui Atom", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "", diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/gem.json b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/gem.json index 1cee5c8298..4cc6fff169 100644 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/gem.json +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/gem.json @@ -3,6 +3,7 @@ "display_name": "Atom DccScriptingInterface (DCCsi)", "summary": "A python framework for working with various DCC tools and workflows.", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "canonical_tags": [ diff --git a/Gems/AtomLyIntegration/gem.json b/Gems/AtomLyIntegration/gem.json index 00b3a25f74..d9e526aee0 100644 --- a/Gems/AtomLyIntegration/gem.json +++ b/Gems/AtomLyIntegration/gem.json @@ -2,6 +2,7 @@ "gem_name": "AtomLyIntegration", "display_name": "Atom O3DE Integration", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Atom O3DE Integration Gem provides components, libraries, and functionality to support and integrate Atom Renderer in Open 3D Engine.", diff --git a/Gems/AtomTressFX/Assets/Passes/HairParentShortCutPass.pass b/Gems/AtomTressFX/Assets/Passes/HairParentShortCutPass.pass index 83f9f0d432..d8389a3da8 100644 --- a/Gems/AtomTressFX/Assets/Passes/HairParentShortCutPass.pass +++ b/Gems/AtomTressFX/Assets/Passes/HairParentShortCutPass.pass @@ -264,7 +264,7 @@ "Attachment": "HairColorRenderTarget" } }, - { + { // The final render target - this is MSAA mode RT - would it be cheaper to // use non-MSAA and then copy? "LocalSlot": "RenderTargetInputOutput", @@ -280,6 +280,13 @@ "Attachment": "DepthLinearInput" } }, + { + "LocalSlot": "AccumulatedInverseAlpha", + "AttachmentRef": { + "Pass": "HairShortCutGeometryDepthAlphaPass", + "Attachment": "InverseAlphaRTOutput" + } + }, { "LocalSlot": "Depth", "AttachmentRef": { diff --git a/Gems/AtomTressFX/Assets/Passes/HairShortCutGeometryShading.pass b/Gems/AtomTressFX/Assets/Passes/HairShortCutGeometryShading.pass index 5940f8c549..53fa2b358b 100644 --- a/Gems/AtomTressFX/Assets/Passes/HairShortCutGeometryShading.pass +++ b/Gems/AtomTressFX/Assets/Passes/HairShortCutGeometryShading.pass @@ -32,6 +32,12 @@ "SlotType": "Input", "ScopeAttachmentUsage": "Shader" }, + { // Used as the thickness accumulation to block TT (back) lobe lighting + "Name": "AccumulatedInverseAlpha", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader", + "ShaderInputName": "m_accumInvAlpha" + }, { // For comparing the depth to early disqualify but not to write "Name": "Depth", "SlotType": "Input", diff --git a/Gems/AtomTressFX/Assets/Shaders/HairRenderingFillPPLL.azsl b/Gems/AtomTressFX/Assets/Shaders/HairRenderingFillPPLL.azsl index 02777435f5..8dcd1ed372 100644 --- a/Gems/AtomTressFX/Assets/Shaders/HairRenderingFillPPLL.azsl +++ b/Gems/AtomTressFX/Assets/Shaders/HairRenderingFillPPLL.azsl @@ -51,9 +51,6 @@ ShaderResourceGroup PassSrg : SRG_PerPass_WithFallback RWTexture2D m_fragmentListHead; RWStructuredBuffer m_linkedListNodes; RWBuffer m_linkedListCounter; - - // Linear depth is used for getting the screen to world transform - Texture2D m_linearDepth; } //------------------------------------------------------------------------------ diff --git a/Gems/AtomTressFX/Assets/Shaders/HairShortCutGeometryShading.azsl b/Gems/AtomTressFX/Assets/Shaders/HairShortCutGeometryShading.azsl index c2a2958dfe..2a3e00b1e7 100644 --- a/Gems/AtomTressFX/Assets/Shaders/HairShortCutGeometryShading.azsl +++ b/Gems/AtomTressFX/Assets/Shaders/HairShortCutGeometryShading.azsl @@ -52,6 +52,9 @@ ShaderResourceGroup PassSrg : SRG_PerPass_WithFallback //! Originally in TressFXRendering.hlsl this is space 0 HairObjectShadeParams m_hairParams[AMD_TRESSFX_MAX_HAIR_GROUP_RENDER]; + // Will be used as thickness indication to block TT (back) lobe + Texture2D m_accumInvAlpha; + // Linear depth is used for getting the screen to world transform Texture2D m_linearDepth; @@ -164,9 +167,11 @@ float4 HairShortCutGeometryColorPS(PS_INPUT_HAIR input) : SV_Target float2 pixelCoord = input.Position.xy; float depth = input.Position.z; - // [To Do] - the thickness will need to be corrected somehow since this technique doesn't - // keeps track of the accumulated alpha / thickness - float thickness = alpha; + + // The following is a quick correction to remove the TT lobe (back lobe) contribution in case + // the hair is thick. We do that by accumulating alpha from the hair for the blend operation + // and this can be used here as an indication of thickness. + float thickness = saturate(1.0 - PassSrg::m_accumInvAlpha[int2(pixelCoord)]); float3 shadedFragment = TressFXShading(pixelCoord, depth, input.Tangent.xyz, strandColor.rgb, thickness, RenderParamsIndex); // Color channel: Pre-multiply with alpha to create non-normalized weighted sum. diff --git a/Gems/AtomTressFX/Assets/seedList.seed b/Gems/AtomTressFX/Assets/seedList.seed new file mode 100644 index 0000000000..95389a753a --- /dev/null +++ b/Gems/AtomTressFX/Assets/seedList.seed @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Gems/AtomTressFX/gem.json b/Gems/AtomTressFX/gem.json index d3e1294568..b7588ea374 100644 --- a/Gems/AtomTressFX/gem.json +++ b/Gems/AtomTressFX/gem.json @@ -2,11 +2,18 @@ "gem_name": "AtomTressFX", "display_name": "Atom TressFX", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "Atom TressFX Gem provides a cutting edge hair and fur simulation and rendering in Atom enhancing the AMD TressFX 4.1. The open source TressFX can be found here: https://github.com/GPUOpen-Effects/TressFX", - "canonical_tags": ["Gem"], - "user_tags": ["Rendering", "Physics", "Animation"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Rendering", + "Physics", + "Animation" + ], "requirements": "", "documentation_url": "https://o3de.org/docs/user-guide/gems/reference/rendering/amd/atom-tressfx/" } diff --git a/Gems/AudioEngineWwise/gem.json b/Gems/AudioEngineWwise/gem.json index 0588af1908..2b0af30d40 100644 --- a/Gems/AudioEngineWwise/gem.json +++ b/Gems/AudioEngineWwise/gem.json @@ -2,6 +2,7 @@ "gem_name": "AudioEngineWwise", "display_name": "Wwise Audio Engine", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Wwise Audio Engine Gem provides support for Audiokinetic Wave Works Interactive Sound Engine (Wwise).", diff --git a/Gems/AudioSystem/gem.json b/Gems/AudioSystem/gem.json index 64d4d9af5a..ed028068a1 100644 --- a/Gems/AudioSystem/gem.json +++ b/Gems/AudioSystem/gem.json @@ -2,6 +2,7 @@ "gem_name": "AudioSystem", "display_name": "Audio System", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Audio System Gem provides the Audio Translation Layer (ATL) and Audio Controls Editor, which add support for audio in Open 3D Engine.", diff --git a/Gems/BarrierInput/gem.json b/Gems/BarrierInput/gem.json index 7fdc58e8b3..72d6398550 100644 --- a/Gems/BarrierInput/gem.json +++ b/Gems/BarrierInput/gem.json @@ -2,6 +2,7 @@ "gem_name": "BarrierInput", "display_name": "Barrier Input", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Barrier Input Gem allows the Open 3D Engine to function as a Barrier client so that it can receive input from a remote Barrier server.", diff --git a/Gems/Blast/Code/Source/Components/BlastSystemComponent.cpp b/Gems/Blast/Code/Source/Components/BlastSystemComponent.cpp index b7ee805b3c..00629eb65d 100644 --- a/Gems/Blast/Code/Source/Components/BlastSystemComponent.cpp +++ b/Gems/Blast/Code/Source/Components/BlastSystemComponent.cpp @@ -143,6 +143,7 @@ namespace Blast void BlastSystemComponent::Deactivate() { AZ_PROFILE_FUNCTION(Physics); + AZ::Data::AssetBus::MultiHandler::BusDisconnect(); CrySystemEventBus::Handler::BusDisconnect(); AZ::TickBus::Handler::BusDisconnect(); BlastSystemRequestBus::Handler::BusDisconnect(); diff --git a/Gems/Blast/gem.json b/Gems/Blast/gem.json index d6af47f482..761eb04761 100644 --- a/Gems/Blast/gem.json +++ b/Gems/Blast/gem.json @@ -2,6 +2,7 @@ "gem_name": "Blast", "display_name": "NVIDIA Blast", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The NVIDIA Blast Gem provides tools to author fractured mesh assets in Houdini, and functionality to create realistic destruction simulations in Open 3D Engine.", diff --git a/Gems/Camera/Code/Source/CameraEditorSystemComponent.cpp b/Gems/Camera/Code/Source/CameraEditorSystemComponent.cpp index 00a147c17a..fe49d1737a 100644 --- a/Gems/Camera/Code/Source/CameraEditorSystemComponent.cpp +++ b/Gems/Camera/Code/Source/CameraEditorSystemComponent.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include "ViewportCameraSelectorWindow.h" @@ -70,7 +71,20 @@ namespace Camera if (!(flags & AzToolsFramework::EditorEvents::eECMF_HIDE_ENTITY_CREATION)) { QAction* action = menu->addAction(QObject::tr("Create camera entity from view")); - QObject::connect(action, &QAction::triggered, [this]() { CreateCameraEntityFromViewport(); }); + const auto prefabEditorEntityOwnershipInterface = AZ::Interface::Get(); + if (prefabEditorEntityOwnershipInterface && !prefabEditorEntityOwnershipInterface->IsRootPrefabAssigned()) + { + action->setEnabled(false); + } + else + { + QObject::connect( + action, &QAction::triggered, + [this]() + { + CreateCameraEntityFromViewport(); + }); + } } } diff --git a/Gems/Camera/gem.json b/Gems/Camera/gem.json index 9f8ea22412..4cf0747c7b 100644 --- a/Gems/Camera/gem.json +++ b/Gems/Camera/gem.json @@ -2,6 +2,7 @@ "gem_name": "Camera", "display_name": "Camera", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Camera Gem provides a basic camera component that defines a frustum for runtime rendering.", diff --git a/Gems/CameraFramework/gem.json b/Gems/CameraFramework/gem.json index c5520fd5b4..d24014be61 100644 --- a/Gems/CameraFramework/gem.json +++ b/Gems/CameraFramework/gem.json @@ -2,6 +2,7 @@ "gem_name": "CameraFramework", "display_name": "Camera Framework", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Camera Framework Gem provides a base for implementing more complex camera systems.", diff --git a/Gems/CertificateManager/gem.json b/Gems/CertificateManager/gem.json index 968da2788a..11ea14ea5e 100644 --- a/Gems/CertificateManager/gem.json +++ b/Gems/CertificateManager/gem.json @@ -2,6 +2,7 @@ "gem_name": "CertificateManager", "display_name": "Certificate Manager", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Certificate Manager Gem provides access to authentication files for secure game connections from Amazon S3, files on disk, and other 3rd party sources.", diff --git a/Gems/CrashReporting/gem.json b/Gems/CrashReporting/gem.json index 9b8d3a9e0a..b8c75548a4 100644 --- a/Gems/CrashReporting/gem.json +++ b/Gems/CrashReporting/gem.json @@ -2,6 +2,7 @@ "gem_name": "CrashReporting", "display_name": "Crash Reporting", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Crash Reporting Gem provides support for external crash reporting for Open 3D Engine projects.", diff --git a/Gems/CustomAssetExample/gem.json b/Gems/CustomAssetExample/gem.json index ab5ed7002d..89492ee6ea 100644 --- a/Gems/CustomAssetExample/gem.json +++ b/Gems/CustomAssetExample/gem.json @@ -2,6 +2,7 @@ "gem_name": "CustomAssetExample", "display_name": "Custom Asset Example", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Custom Asset Example Gem provides example code for creating a custom asset for Open 3D Engine's asset pipeline.", diff --git a/Gems/DebugDraw/gem.json b/Gems/DebugDraw/gem.json index 7e0da07103..58eff5f87a 100644 --- a/Gems/DebugDraw/gem.json +++ b/Gems/DebugDraw/gem.json @@ -2,6 +2,7 @@ "gem_name": "DebugDraw", "display_name": "Debug Draw", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Debug Draw Gem provides Editor and runtime debug visualization features for Open 3D Engine.", diff --git a/Gems/DevTextures/gem.json b/Gems/DevTextures/gem.json index 8b40badbf1..00cafbd6fb 100644 --- a/Gems/DevTextures/gem.json +++ b/Gems/DevTextures/gem.json @@ -2,6 +2,7 @@ "gem_name": "DevTextures", "display_name": "Dev Textures", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Asset", "summary": "The Dev Textures Gem provides a collection of general purpose texture assets useful for prototypes and preproduction.", diff --git a/Gems/EMotionFX/Code/Source/Integration/Components/SimpleLODComponent.cpp b/Gems/EMotionFX/Code/Source/Integration/Components/SimpleLODComponent.cpp index aa17058adf..fdc6426e4c 100644 --- a/Gems/EMotionFX/Code/Source/Integration/Components/SimpleLODComponent.cpp +++ b/Gems/EMotionFX/Code/Source/Integration/Components/SimpleLODComponent.cpp @@ -85,10 +85,13 @@ namespace EMotionFX if (numLODs != m_lodSampleRates.size()) { - // Generate the default LOD Sample Rate to 140, 60, 45, 25, 15, 10 + // Generate the default LOD Sample Rate to 140, 60, 45, 25, 15, 10, 10, 10, ... constexpr AZStd::array defaultSampleRate {140.0f, 60.0f, 45.0f, 25.0f, 15.0f, 10.0f}; - m_lodSampleRates.resize(numLODs); - AZStd::copy(begin(defaultSampleRate), end(defaultSampleRate), begin(m_lodSampleRates)); + m_lodSampleRates.resize(numLODs, 10.0f); + + // Do not copy more than what fits in defaultSampleRates or numLODs. + size_t copyCount = std::min(defaultSampleRate.size(), numLODs); + AZStd::copy(begin(defaultSampleRate), begin(defaultSampleRate) + copyCount, begin(m_lodSampleRates)); } } diff --git a/Gems/EMotionFX/Code/Source/Integration/Editor/Components/EditorActorComponent.cpp b/Gems/EMotionFX/Code/Source/Integration/Editor/Components/EditorActorComponent.cpp index 5da9716d15..11cf3feb3b 100644 --- a/Gems/EMotionFX/Code/Source/Integration/Editor/Components/EditorActorComponent.cpp +++ b/Gems/EMotionFX/Code/Source/Integration/Editor/Components/EditorActorComponent.cpp @@ -195,6 +195,8 @@ namespace EMotionFX ////////////////////////////////////////////////////////////////////////// void EditorActorComponent::Activate() { + AzToolsFramework::Components::EditorComponentBase::Activate(); + LoadActorAsset(); const AZ::EntityId entityId = GetEntityId(); @@ -225,6 +227,8 @@ namespace EMotionFX DestroyActorInstance(); m_actorAsset.Release(); + + AzToolsFramework::Components::EditorComponentBase::Deactivate(); } ////////////////////////////////////////////////////////////////////////// @@ -587,7 +591,15 @@ namespace EMotionFX if (asset) { m_actorAsset = asset; - OnAssetSelected(); + + // SetPrimaryAsset function can be called while this component is not activated + // due to incompatible services. For example by dragging and dropping a FBX to an + // entity that already has an actor or mesh component in it. Only proceed to load actor + // asset if the component is activated (by checking if it's connected to EditorActorComponentRequestBus). + if (EditorActorComponentRequestBus::Handler::BusIsConnected()) + { + OnAssetSelected(); + } } } diff --git a/Gems/EMotionFX/Code/Tests/SystemComponentFixture.h b/Gems/EMotionFX/Code/Tests/SystemComponentFixture.h index 034fd2045b..62ebffa7e9 100644 --- a/Gems/EMotionFX/Code/Tests/SystemComponentFixture.h +++ b/Gems/EMotionFX/Code/Tests/SystemComponentFixture.h @@ -61,7 +61,9 @@ namespace EMotionFX constexpr auto projectPathKey = FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path"; if(auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr) { - settingsRegistry->Set(projectPathKey, "AutomatedTesting"); + AZ::IO::FixedMaxPath enginePath; + settingsRegistry->Get(enginePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder); + settingsRegistry->Set(projectPathKey, (enginePath / "AutomatedTesting").Native()); AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*settingsRegistry); } } diff --git a/Gems/EMotionFX/gem.json b/Gems/EMotionFX/gem.json index f1734d854d..ed80517af1 100644 --- a/Gems/EMotionFX/gem.json +++ b/Gems/EMotionFX/gem.json @@ -2,6 +2,7 @@ "gem_name": "EMotionFX", "display_name": "EMotion FX Animation", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The EMotion FX Animation Gem provides Open 3D Engine's animation system for rigged actors and includes Animation Editor, a tool for creating animated behaviors, simulated objects, and colliders for rigged actors.", diff --git a/Gems/EditorPythonBindings/Code/Tests/EditorPythonBindingsTest.cpp b/Gems/EditorPythonBindings/Code/Tests/EditorPythonBindingsTest.cpp index d630605cbc..9dbbb34e6c 100644 --- a/Gems/EditorPythonBindings/Code/Tests/EditorPythonBindingsTest.cpp +++ b/Gems/EditorPythonBindings/Code/Tests/EditorPythonBindingsTest.cpp @@ -323,7 +323,9 @@ sys.version auto registry = AZ::SettingsRegistry::Get(); auto projectPathKey = AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path"; - registry->Set(projectPathKey, "AutomatedTesting"); + AZ::IO::FixedMaxPath enginePath; + registry->Get(enginePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder); + registry->Set(projectPathKey, (enginePath / "AutomatedTesting").Native()); AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry); m_app.RegisterComponentDescriptor(EditorPythonBindings::PythonSystemComponent::CreateDescriptor()); diff --git a/Gems/EditorPythonBindings/gem.json b/Gems/EditorPythonBindings/gem.json index 13c5800dd7..475483f13b 100644 --- a/Gems/EditorPythonBindings/gem.json +++ b/Gems/EditorPythonBindings/gem.json @@ -2,6 +2,7 @@ "gem_name": "EditorPythonBindings", "display_name": "Editor Python Bindings", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Tool", "summary": "The Editor Python Bindings Gem provides Python commands for Open 3D Engine Editor functions.", diff --git a/Gems/ExpressionEvaluation/gem.json b/Gems/ExpressionEvaluation/gem.json index 6bcc666a4d..cd712f4da9 100644 --- a/Gems/ExpressionEvaluation/gem.json +++ b/Gems/ExpressionEvaluation/gem.json @@ -2,6 +2,7 @@ "gem_name": "ExpressionEvaluation", "display_name": "Expression Evaluation", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Expression Evaluation Gem provides a method for parsing and executing string expressions in Open 3D Engine.", diff --git a/Gems/FastNoise/gem.json b/Gems/FastNoise/gem.json index ac59fe804e..d8dfb878b4 100644 --- a/Gems/FastNoise/gem.json +++ b/Gems/FastNoise/gem.json @@ -2,6 +2,7 @@ "gem_name": "FastNoise", "display_name": "Fast Noise", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The FastNoise Gradient Gem uses the third-party, open source FastNoise library to provide a variety of high-performance noise generation algorithms.", diff --git a/Gems/GameState/gem.json b/Gems/GameState/gem.json index 7bb3cf4214..fe3a338997 100644 --- a/Gems/GameState/gem.json +++ b/Gems/GameState/gem.json @@ -2,6 +2,7 @@ "gem_name": "GameState", "display_name": "Game State", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Game State Gem provides a generic framework to determine and manage game states and game state transitions in Open 3D Engine.", diff --git a/Gems/GameStateSamples/gem.json b/Gems/GameStateSamples/gem.json index be982b9c5b..80018ff1a8 100644 --- a/Gems/GameStateSamples/gem.json +++ b/Gems/GameStateSamples/gem.json @@ -2,6 +2,7 @@ "gem_name": "GameStateSamples", "display_name": "Game State Samples", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Game State Samples Gem provides a set of sample game states (built on top of the Game State Gem), including primary user selection, main menu, level loading, level running, and level paused.", diff --git a/Gems/Gestures/gem.json b/Gems/Gestures/gem.json index fcc56b4704..8efd389d53 100644 --- a/Gems/Gestures/gem.json +++ b/Gems/Gestures/gem.json @@ -2,6 +2,7 @@ "gem_name": "Gestures", "display_name": "Gestures", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Gestures Gem provides detection for common gesture-based input actions on iOS and Android devices.", diff --git a/Gems/GradientSignal/gem.json b/Gems/GradientSignal/gem.json index e87ccfe13a..aac4c652c5 100644 --- a/Gems/GradientSignal/gem.json +++ b/Gems/GradientSignal/gem.json @@ -2,6 +2,7 @@ "gem_name": "GradientSignal", "display_name": "Gradient Signal", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Gradient Signal Gem provides a number of components for generating, modifying, and mixing gradient signals.", diff --git a/Gems/GraphCanvas/Code/Source/Translation/TranslationBuilder.cpp b/Gems/GraphCanvas/Code/Source/Translation/TranslationBuilder.cpp index bac58c3ba1..1b01f7885d 100644 --- a/Gems/GraphCanvas/Code/Source/Translation/TranslationBuilder.cpp +++ b/Gems/GraphCanvas/Code/Source/Translation/TranslationBuilder.cpp @@ -40,10 +40,14 @@ namespace GraphCanvas { AZ::Data::AssetManager::Instance().RegisterHandler(m_assetHandler.get(), assetType); } + + AssetBuilderSDK::AssetBuilderCommandBus::Handler::BusConnect(GetUUID()); } void TranslationAssetWorker::Deactivate() { + AssetBuilderSDK::AssetBuilderCommandBus::Handler::BusDisconnect(); + if (AZ::Data::AssetManager::Instance().GetHandler(AZ::Data::AssetType{ azrtti_typeid() })) { AZ::Data::AssetManager::Instance().UnregisterHandler(m_assetHandler.get()); diff --git a/Gems/GraphCanvas/gem.json b/Gems/GraphCanvas/gem.json index 760bd157df..4762cfef35 100644 --- a/Gems/GraphCanvas/gem.json +++ b/Gems/GraphCanvas/gem.json @@ -2,6 +2,7 @@ "gem_name": "GraphCanvas", "display_name": "Graph Canvas", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Tool", "summary": "The Graph Canvas Gem provides a C++ framework for creating custom graphical node based editors for Open 3D Engine.", diff --git a/Gems/GraphModel/gem.json b/Gems/GraphModel/gem.json index 256de75f6c..ad6b592430 100644 --- a/Gems/GraphModel/gem.json +++ b/Gems/GraphModel/gem.json @@ -2,6 +2,7 @@ "gem_name": "GraphModel", "display_name": "Graph Model", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Graph Model Gem provides a generic node graph data model framework for Open 3D Engine.", diff --git a/Gems/HttpRequestor/gem.json b/Gems/HttpRequestor/gem.json index eb1a112b0e..582bfa0071 100644 --- a/Gems/HttpRequestor/gem.json +++ b/Gems/HttpRequestor/gem.json @@ -2,6 +2,7 @@ "gem_name": "HttpRequestor", "display_name": "HTTP Requestor", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The HTTP Requestor Gem provides functionality to make asynchronous HTTP/HTTPS requests and return data through a user-provided call back function.", diff --git a/Gems/ImGui/Code/Source/LYCommonMenu/ImGuiLYCommonMenu.cpp b/Gems/ImGui/Code/Source/LYCommonMenu/ImGuiLYCommonMenu.cpp index 81ec4b17c8..d2f86a65cd 100644 --- a/Gems/ImGui/Code/Source/LYCommonMenu/ImGuiLYCommonMenu.cpp +++ b/Gems/ImGui/Code/Source/LYCommonMenu/ImGuiLYCommonMenu.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include "ImGuiColorDefines.h" #include "LYImGuiUtils/ImGuiDrawHelpers.h" @@ -92,8 +93,36 @@ namespace ImGui void ImGuiLYCommonMenu::OnImGuiUpdate() { + float dpiScalingFactor = 1.0f; + ImGuiManagerBus::BroadcastResult(dpiScalingFactor, &ImGuiManagerBus::Events::GetDpiScalingFactor); + + // Utility function to calculate the size in device pixels based on the current DPI + const auto dpiAwareSizeFn = [dpiScalingFactor](float size) + { + return dpiScalingFactor * size; + }; + + AZStd::optional viewportBorderPaddingOpt; + AzFramework::ViewportBorderRequestBus::BroadcastResult( + viewportBorderPaddingOpt, &AzFramework::ViewportBorderRequestBus::Events::GetViewportBorderPadding); + + AzFramework::ViewportBorderPadding viewportBorderPadding = viewportBorderPaddingOpt.value_or(AzFramework::ViewportBorderPadding{}); + // Utility function to return the current offset (scaled by DPI) if a viewport border + // is active (otherwise 0.0) + auto dpiAwareBorderOffsetFn = [&viewportBorderPaddingOpt, &dpiAwareSizeFn](float size) + { + return viewportBorderPaddingOpt.has_value() ? dpiAwareSizeFn(size) : 0.0f; + }; + + // Shift the menu down if a viewport border is active + ImVec2 cachedSafeArea = ImGui::GetStyle().DisplaySafeAreaPadding; + ImGui::GetStyle().DisplaySafeAreaPadding = ImVec2(cachedSafeArea.x, cachedSafeArea.y + dpiAwareSizeFn(viewportBorderPadding.m_top)); + if (ImGui::BeginMainMenuBar()) { + // Constant to shift right aligned menu items by (distance to the left) when a viewport border is active + const float rightAlignedBorderOffset = dpiAwareBorderOffsetFn(36.0f); + // Get Discrete Input state now, we will use it both inside the ImGui SubMenu, and along the main task bar ( when it is on ) bool discreteInputEnabled = false; ImGuiManagerBus::BroadcastResult(discreteInputEnabled, &IImGuiManager::GetEnableDiscreteInputMode); @@ -101,7 +130,8 @@ namespace ImGui // Input Mode Display { const float prevCursorPos = ImGui::GetCursorPosX(); - ImGui::SetCursorPosX(ImGui::GetWindowWidth() - 300.0f); + ImGui::SetCursorPosX( + ImGui::GetWindowWidth() - dpiAwareSizeFn(300.0f + viewportBorderPadding.m_right) - rightAlignedBorderOffset); AZStd::string inputTitle = "Input: "; if (!discreteInputEnabled) @@ -152,7 +182,7 @@ namespace ImGui } // Add some space before the first menu so it won't overlap with view control buttons - ImGui::SetCursorPosX(40.f); + ImGui::SetCursorPosX(dpiAwareSizeFn(40.0f + viewportBorderPadding.m_left)); // Main Open 3D Engine menu if (ImGui::BeginMenu("O3DE")) @@ -557,11 +587,12 @@ namespace ImGui // End LY Common Tools menu ImGui::EndMenu(); } - const int labelSize{ 100 }; - const int buttonSize{ 40 }; + + const float labelSize = dpiAwareSizeFn(100.0f + viewportBorderPadding.m_right) + rightAlignedBorderOffset; + const float buttonSize = dpiAwareSizeFn(40.0f + viewportBorderPadding.m_right) + rightAlignedBorderOffset; ImGuiUpdateListenerBus::Broadcast(&IImGuiUpdateListener::OnImGuiMainMenuUpdate); ImGui::SameLine(ImGui::GetWindowContentRegionMax().x - labelSize); - float backgroundHeight = ImGui::GetTextLineHeight() + 3; + float backgroundHeight = ImGui::GetTextLineHeight() + dpiAwareSizeFn(3.0f); ImVec2 cursorPos = ImGui::GetCursorScreenPos(); ImGui::GetWindowDrawList()->AddRectFilled( cursorPos, ImVec2(cursorPos.x + labelSize, cursorPos.y + backgroundHeight), IM_COL32(0, 115, 187, 255)); @@ -580,6 +611,9 @@ namespace ImGui ImGui::EndMainMenuBar(); } + // Restore original safe area. + ImGui::GetStyle().DisplaySafeAreaPadding = cachedSafeArea; + // Update Contextual Controller Window if (m_controllerLegendWindowVisible) { diff --git a/Gems/ImGui/gem.json b/Gems/ImGui/gem.json index c1d89d1728..dfc12f64b4 100644 --- a/Gems/ImGui/gem.json +++ b/Gems/ImGui/gem.json @@ -2,6 +2,7 @@ "gem_name": "ImGui", "display_name": "Immediate Mode GUI (IMGUI)", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Tool", "summary": "The Immediate Mode GUI Gem provides the 3rdParty library IMGUI which can be used to create run time immediate mode overlays for debugging and profiling information in Open 3D Engine.", diff --git a/Gems/InAppPurchases/gem.json b/Gems/InAppPurchases/gem.json index 1f1debd5fb..21febbfeca 100644 --- a/Gems/InAppPurchases/gem.json +++ b/Gems/InAppPurchases/gem.json @@ -2,6 +2,7 @@ "gem_name": "InAppPurchases", "display_name": "In-App Purchases", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The In-App Purchases Gem provides functionality for in app purchases for iOS and Android.", diff --git a/Gems/LandscapeCanvas/gem.json b/Gems/LandscapeCanvas/gem.json index ce0c64b75d..8653da40e1 100644 --- a/Gems/LandscapeCanvas/gem.json +++ b/Gems/LandscapeCanvas/gem.json @@ -2,6 +2,7 @@ "gem_name": "LandscapeCanvas", "display_name": "Landscape Canvas", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Tool", "summary": "The Landscape Canvas Gem provides the Landscape Canvas editor, a node-based graph tool for authoring workflows to populate landscape with dynamic vegetation.", diff --git a/Gems/LmbrCentral/Code/Source/Bundling/BundlingSystemComponent.cpp b/Gems/LmbrCentral/Code/Source/Bundling/BundlingSystemComponent.cpp index c9fc656488..168a3640a8 100644 --- a/Gems/LmbrCentral/Code/Source/Bundling/BundlingSystemComponent.cpp +++ b/Gems/LmbrCentral/Code/Source/Bundling/BundlingSystemComponent.cpp @@ -9,43 +9,40 @@ #include "BundlingSystemComponent.h" #include +#include +#include #include #include +#include #include -#include - -#include -#include - #include + namespace LmbrCentral { const char bundleRoot[] = "@products@"; + // Calls the LoadBundles method + static void ConsoleCommandLoadBundles(const AZ::ConsoleCommandContainer& commandArgs); + // Calls the UnloadBundles method + static void ConsoleCommandUnloadBundles(const AZ::ConsoleCommandContainer& commandArgs); + + AZ_CONSOLEFREEFUNC("loadbundles", ConsoleCommandLoadBundles, AZ::ConsoleFunctorFlags::Null, "Load Asset Bundles"); + AZ_CONSOLEFREEFUNC("unloadbundles", ConsoleCommandUnloadBundles, AZ::ConsoleFunctorFlags::Null, "Unload Asset Bundles"); + void BundlingSystemComponent::Activate() { BundlingSystemRequestBus::Handler::BusConnect(); - CrySystemEventBus::Handler::BusConnect(); AZ::IO::ArchiveNotificationBus::Handler::BusConnect(); } void BundlingSystemComponent::Deactivate() { AZ::IO::ArchiveNotificationBus::Handler::BusDisconnect(); - CrySystemEventBus::Handler::BusDisconnect(); BundlingSystemRequestBus::Handler::BusDisconnect(); } - void BundlingSystemComponent::OnCrySystemInitialized(ISystem& system, const SSystemInitParams& systemInitParams) - { - AZ_UNUSED(systemInitParams); - - system.GetIConsole()->AddCommand("loadbundles", ConsoleCommandLoadBundles); - system.GetIConsole()->AddCommand("unloadbundles", ConsoleCommandUnloadBundles); - } - void BundlingSystemComponent::Reflect(AZ::ReflectContext* context) { if (auto serializeContext = azrtti_cast(context)) @@ -58,7 +55,7 @@ namespace LmbrCentral AZStd::vector BundlingSystemComponent::GetBundleList(const char* bundlePath, const char* bundleExtension) const { - AZStd::string fileFilter{ AZStd::string::format("*%s",bundleExtension) }; + AZStd::string fileFilter{ AZStd::string::format("*%s", bundleExtension) }; AZStd::vector bundleList; AZ::IO::FileIOBase::GetInstance()->FindFiles(bundlePath, fileFilter.c_str(), [&bundleList](const char* foundPath) -> bool @@ -73,29 +70,28 @@ namespace LmbrCentral return bundleList; } - void BundlingSystemComponent::ConsoleCommandLoadBundles(IConsoleCmdArgs* pCmdArgs) + void ConsoleCommandLoadBundles(const AZ::ConsoleCommandContainer& commandArgs) { const char defaultBundleFolder[] = "bundles"; const char defaultBundleExtension[] = ".pak"; - const char* bundleFolder = pCmdArgs->GetArgCount() > 1 ? pCmdArgs->GetArg(1) : defaultBundleFolder; - const char* bundleExtension = pCmdArgs->GetArgCount() > 2 ? pCmdArgs->GetArg(2) : defaultBundleExtension; + AZ::CVarFixedString bundleFolder = commandArgs.size() > 0 ? AZ::CVarFixedString(commandArgs[0]) : defaultBundleFolder; + AZ::CVarFixedString bundleExtension = commandArgs.size() > 1 ? AZ::CVarFixedString(commandArgs[1]) : defaultBundleExtension; - BundlingSystemRequestBus::Broadcast(&BundlingSystemRequestBus::Events::LoadBundles, bundleFolder, bundleExtension); + BundlingSystemRequestBus::Broadcast(&BundlingSystemRequestBus::Events::LoadBundles, bundleFolder.c_str(), bundleExtension.c_str()); } - void BundlingSystemComponent::ConsoleCommandUnloadBundles(IConsoleCmdArgs* pCmdArgs) + void ConsoleCommandUnloadBundles([[maybe_unused]] const AZ::ConsoleCommandContainer& commandArgs) { - AZ_UNUSED(pCmdArgs); BundlingSystemRequestBus::Broadcast(&BundlingSystemRequestBus::Events::UnloadBundles); } void BundlingSystemComponent::UnloadBundles() { - ISystem* crySystem{ GetISystem() }; - if (!crySystem) + auto archive = AZ::Interface::Get(); + if (!archive) { - AZ_Error("BundlingSystem", false, "Couldn't Get ISystem to unload bundles!"); + AZ_Error("BundlingSystem", false, "Couldn't Get IArchive to load bundles!"); return; } if (!m_bundleModeBundles.size()) @@ -106,7 +102,7 @@ namespace LmbrCentral AZStd::lock_guard openBundleLock(m_bundleModeMutex); for (const auto& thisBundle : m_bundleModeBundles) { - if (crySystem->GetIPak()->ClosePack(thisBundle.c_str())) + if (archive->ClosePack(thisBundle.c_str())) { AZ_TracePrintf("BundlingSystem", "Unloaded %s\n",thisBundle.c_str()); } @@ -128,15 +124,8 @@ namespace LmbrCentral return; } - ISystem* crySystem{ GetISystem() }; - if (!crySystem) - { - AZ_Error("BundlingSystem", false, "Couldn't Get ISystem to load bundles!"); - return; - } - - auto cryPak = crySystem->GetIPak(); - if (!cryPak) + auto archive = AZ::Interface::Get(); + if (!archive) { AZ_Error("BundlingSystem", false, "Couldn't Get IArchive to load bundles!"); return; @@ -152,8 +141,8 @@ namespace LmbrCentral } } AZStd::string bundlePath; - AzFramework::StringFunc::Path::Join(bundleRoot, thisBundle.c_str(), bundlePath); - if (cryPak->OpenPack(bundleRoot, thisBundle.c_str())) + AZ::StringFunc::Path::Join(bundleRoot, thisBundle.c_str(), bundlePath); + if (archive->OpenPack(bundleRoot, thisBundle.c_str())) { AZ_TracePrintf("BundlingSystem", "Loaded bundle %s\n",bundlePath.c_str()); m_bundleModeBundles.emplace_back(AZStd::move(bundlePath)); @@ -230,28 +219,21 @@ namespace LmbrCentral void BundlingSystemComponent::OpenDependentBundles(const char* bundleName, AZStd::shared_ptr bundleManifest) { - ISystem* crySystem{ GetISystem() }; - if (!crySystem) - { - AZ_Error("BundlingSystem", false, "Couldn't Get ISystem to load dependent bundles for %s", bundleName); - return; - } - - auto cryPak{ crySystem->GetIPak() }; - if (!cryPak) + auto archive = AZ::Interface::Get(); + if (!archive) { AZ_Error("BundlingSystem", false, "Couldn't Get IArchive to load dependent bundles for %s", bundleName); return; } AZStd::string folderPath; - AzFramework::StringFunc::Path::GetFolderPath(bundleName, folderPath); + AZ::StringFunc::Path::GetFolderPath(bundleName, folderPath); for (const auto& thisBundle : bundleManifest->GetDependentBundleNames()) { AZStd::string bundlePath; - AzFramework::StringFunc::Path::Join(folderPath.c_str(), thisBundle.c_str(), bundlePath); + AZ::StringFunc::Path::Join(folderPath.c_str(), thisBundle.c_str(), bundlePath); - if (!cryPak->OpenPack(bundleRoot, bundlePath.c_str())) + if (!archive->OpenPack(bundleRoot, bundlePath.c_str())) { // We're not bailing here intentionally - try to open the remaining bundles AZ_Warning("BundlingSystem", false, "Failed to open dependent bundle %s of bundle %s", bundlePath.c_str(), bundleName); @@ -300,28 +282,21 @@ namespace LmbrCentral void BundlingSystemComponent::CloseDependentBundles(const char* bundleName, AZStd::shared_ptr bundleManifest) { - ISystem* crySystem{ GetISystem() }; - if (!crySystem) - { - AZ_Error("BundlingSystem", false, "Couldn't get ISystem to close dependent bundles for %s", bundleName); - return; - } - - auto cryPak{ crySystem->GetIPak() }; - if (!cryPak) + auto archive = AZ::Interface::Get(); + if (!archive) { AZ_Error("BundlingSystem", false, "Couldn't get IArchive to close dependent bundles for %s", bundleName); return; } AZStd::string folderPath; - AzFramework::StringFunc::Path::GetFolderPath(bundleName, folderPath); + AZ::StringFunc::Path::GetFolderPath(bundleName, folderPath); for (const auto& thisBundle : bundleManifest->GetDependentBundleNames()) { AZStd::string bundlePath; - AzFramework::StringFunc::Path::Join(folderPath.c_str(), thisBundle.c_str(), bundlePath); + AZ::StringFunc::Path::Join(folderPath.c_str(), thisBundle.c_str(), bundlePath); - if (!cryPak->ClosePack(bundlePath.c_str())) + if (!archive->ClosePack(bundlePath.c_str())) { // We're not bailing here intentionally - try to close the remaining bundles AZ_Warning("BundlingSystem", false, "Failed to close dependent bundle %s of bundle %s", bundlePath.c_str(), bundleName); diff --git a/Gems/LmbrCentral/Code/Source/Bundling/BundlingSystemComponent.h b/Gems/LmbrCentral/Code/Source/Bundling/BundlingSystemComponent.h index 15fdf1103c..e7c8775530 100644 --- a/Gems/LmbrCentral/Code/Source/Bundling/BundlingSystemComponent.h +++ b/Gems/LmbrCentral/Code/Source/Bundling/BundlingSystemComponent.h @@ -19,11 +19,8 @@ #include -#include #include -struct IConsoleCmdArgs; - namespace AzFramework { class AssetBundleManifest; @@ -42,10 +39,9 @@ namespace LmbrCentral * System component for managing bundles */ class BundlingSystemComponent - : public AZ::Component, - public BundlingSystemRequestBus::Handler, - public CrySystemEventBus::Handler, - public AZ::IO::ArchiveNotificationBus::Handler + : public AZ::Component + , public BundlingSystemRequestBus::Handler + , public AZ::IO::ArchiveNotificationBus::Handler { public: AZ_COMPONENT(BundlingSystemComponent, "{0FB7153D-EE80-4B1C-9584-134270401AAF}"); @@ -70,13 +66,6 @@ namespace LmbrCentral void BundleOpened(const char* bundleName, AZStd::shared_ptr bundleManifest, const char* nextBundle, AZStd::shared_ptr bundleCatalog) override; void BundleClosed(const char* bundleName) override; - // CrySystemEventBus - void OnCrySystemInitialized(ISystem& system, const SSystemInitParams& systemInitParams) override; - - // Calls the LoadBundles method - static void ConsoleCommandLoadBundles(IConsoleCmdArgs* pCmdArgs); - // Calls the UnloadBundles method - static void ConsoleCommandUnloadBundles(IConsoleCmdArgs* pCmdArgs); AZStd::vector GetBundleList(const char* bundlePath, const char* bundleExtension) const; diff --git a/Gems/LmbrCentral/Code/Source/LmbrCentral.cpp b/Gems/LmbrCentral/Code/Source/LmbrCentral.cpp index a5884ccce9..027d19fdef 100644 --- a/Gems/LmbrCentral/Code/Source/LmbrCentral.cpp +++ b/Gems/LmbrCentral/Code/Source/LmbrCentral.cpp @@ -61,7 +61,6 @@ // Asset types #include -#include #include #include #include @@ -84,8 +83,6 @@ namespace LmbrCentral { - static const char* s_assetCatalogFilename = "assetcatalog.xml"; - using LmbrCentralAllocatorScope = AZ::AllocatorScope; // This component boots the required allocators for LmbrCentral everywhere but AssetBuilders @@ -354,10 +351,8 @@ namespace LmbrCentral AZ_Assert(AZ::Data::AssetManager::IsReady(), "Asset manager isn't ready!"); // Add asset types and extensions to AssetCatalog. Uses "AssetCatalogService". - auto assetCatalog = AZ::Data::AssetCatalogRequestBus::FindFirstHandler(); - if (assetCatalog) + if (auto assetCatalog = AZ::Data::AssetCatalogRequestBus::FindFirstHandler(); assetCatalog) { - assetCatalog->EnableCatalogForAsset(AZ::AzTypeInfo::Uuid()); assetCatalog->EnableCatalogForAsset(AZ::AzTypeInfo::Uuid()); assetCatalog->EnableCatalogForAsset(AZ::AzTypeInfo::Uuid()); assetCatalog->EnableCatalogForAsset(AZ::AzTypeInfo::Uuid()); @@ -371,12 +366,10 @@ namespace LmbrCentral assetCatalog->AddExtension("xml"); assetCatalog->AddExtension("mtl"); assetCatalog->AddExtension("dccmtl"); - assetCatalog->AddExtension("lua"); assetCatalog->AddExtension("sprite"); assetCatalog->AddExtension("cax"); } - CrySystemEventBus::Handler::BusConnect(); AZ::Data::AssetManagerNotificationBus::Handler::BusConnect(); @@ -448,7 +441,6 @@ namespace LmbrCentral m_unhandledAssetInfo.clear(); AZ::Data::AssetManagerNotificationBus::Handler::BusDisconnect(); - CrySystemEventBus::Handler::BusDisconnect(); // AssetHandler's destructor calls Unregister() m_assetHandlers.clear(); @@ -459,42 +451,6 @@ namespace LmbrCentral } m_allocatorShutdowns.clear(); } - - void LmbrCentralSystemComponent::OnCrySystemPreInitialize([[maybe_unused]] ISystem& system, [[maybe_unused]] const SSystemInitParams& systemInitParams) - { - EBUS_EVENT(AZ::Data::AssetCatalogRequestBus, StartMonitoringAssets); - } - - void LmbrCentralSystemComponent::OnCrySystemInitialized(ISystem& system, const SSystemInitParams& systemInitParams) - { -#if !defined(AZ_MONOLITHIC_BUILD) - // When module is linked dynamically, we must set our gEnv pointer. - // When module is linked statically, we'll share the application's gEnv pointer. - gEnv = system.GetGlobalEnvironment(); -#endif - - // Enable catalog now that application's asset root is set. - if (system.GetGlobalEnvironment()->IsEditor()) - { - // In the editor, we build the catalog by scanning the disk. - if (systemInitParams.pUserCallback) - { - systemInitParams.pUserCallback->OnInitProgress("Refreshing asset catalog..."); - } - } - - // load the catalog from disk (supported over VFS). - EBUS_EVENT(AZ::Data::AssetCatalogRequestBus, LoadCatalog, AZStd::string::format("@products@/%s", s_assetCatalogFilename).c_str()); - } - - void LmbrCentralSystemComponent::OnCrySystemShutdown([[maybe_unused]] ISystem& system) - { - EBUS_EVENT(AZ::Data::AssetCatalogRequestBus, StopMonitoringAssets); - -#if !defined(AZ_MONOLITHIC_BUILD) - gEnv = nullptr; -#endif - } } // namespace LmbrCentral #if !defined(LMBR_CENTRAL_EDITOR) diff --git a/Gems/LmbrCentral/Code/Source/LmbrCentral.h b/Gems/LmbrCentral/Code/Source/LmbrCentral.h index 9a0c327ea7..650061dce6 100644 --- a/Gems/LmbrCentral/Code/Source/LmbrCentral.h +++ b/Gems/LmbrCentral/Code/Source/LmbrCentral.h @@ -15,8 +15,6 @@ #include #include -#include - /*! * \namespace LmbrCentral * LmbrCentral ties together systems from CryEngine and systems from the AZ framework. @@ -49,7 +47,6 @@ namespace LmbrCentral */ class LmbrCentralSystemComponent : public AZ::Component - , private CrySystemEventBus::Handler , private AZ::Data::AssetManagerNotificationBus::Handler { public: @@ -71,13 +68,6 @@ namespace LmbrCentral void Deactivate() override; //////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////// - // CrySystemEvents - void OnCrySystemPreInitialize(ISystem& system, const SSystemInitParams& systemInitParams) override; - void OnCrySystemInitialized(ISystem& system, const SSystemInitParams& systemInitParams) override; - void OnCrySystemShutdown(ISystem& system) override; - //////////////////////////////////////////////////////////////////////////// - AZStd::vector > m_assetHandlers; AZStd::vector > m_unhandledAssetInfo; AZStd::vector> m_allocatorShutdowns; diff --git a/Gems/LmbrCentral/Code/Tests/Builders/LevelBuilderTest.cpp b/Gems/LmbrCentral/Code/Tests/Builders/LevelBuilderTest.cpp index e50fbbe56c..697c5c3b06 100644 --- a/Gems/LmbrCentral/Code/Tests/Builders/LevelBuilderTest.cpp +++ b/Gems/LmbrCentral/Code/Tests/Builders/LevelBuilderTest.cpp @@ -99,7 +99,9 @@ namespace UnitTest AZ::SettingsRegistryInterface* registry = AZ::SettingsRegistry::Get(); auto projectPathKey = AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path"; - registry->Set(projectPathKey, "AutomatedTesting"); + AZ::IO::FixedMaxPath enginePath; + registry->Get(enginePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder); + registry->Set(projectPathKey, (enginePath / "AutomatedTesting").Native()); AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry); m_app.Start(m_descriptor); diff --git a/Gems/LmbrCentral/Code/Tests/Builders/LuaBuilderTests.cpp b/Gems/LmbrCentral/Code/Tests/Builders/LuaBuilderTests.cpp index a9b36d4624..c62ed92c83 100644 --- a/Gems/LmbrCentral/Code/Tests/Builders/LuaBuilderTests.cpp +++ b/Gems/LmbrCentral/Code/Tests/Builders/LuaBuilderTests.cpp @@ -31,7 +31,9 @@ namespace UnitTest AZ::SettingsRegistryInterface* registry = AZ::SettingsRegistry::Get(); auto projectPathKey = AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path"; - registry->Set(projectPathKey, "AutomatedTesting"); + AZ::IO::FixedMaxPath enginePath; + registry->Get(enginePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder); + registry->Set(projectPathKey, (enginePath / "AutomatedTesting").Native()); AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry); m_app.Start(m_descriptor); diff --git a/Gems/LmbrCentral/Code/Tests/Builders/SeedBuilderTests.cpp b/Gems/LmbrCentral/Code/Tests/Builders/SeedBuilderTests.cpp index f679a3502d..574fd4edfb 100644 --- a/Gems/LmbrCentral/Code/Tests/Builders/SeedBuilderTests.cpp +++ b/Gems/LmbrCentral/Code/Tests/Builders/SeedBuilderTests.cpp @@ -22,7 +22,9 @@ class SeedBuilderTests AZ::SettingsRegistryInterface* registry = AZ::SettingsRegistry::Get(); auto projectPathKey = AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path"; - registry->Set(projectPathKey, "AutomatedTesting"); + AZ::IO::FixedMaxPath enginePath; + registry->Get(enginePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder); + registry->Set(projectPathKey, (enginePath / "AutomatedTesting").Native()); AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry); m_app.Start(AZ::ComponentApplication::Descriptor()); diff --git a/Gems/LmbrCentral/gem.json b/Gems/LmbrCentral/gem.json index 9fca421538..a0a6ea813f 100644 --- a/Gems/LmbrCentral/gem.json +++ b/Gems/LmbrCentral/gem.json @@ -2,6 +2,7 @@ "gem_name": "LmbrCentral", "display_name": "O3DE Core (LmbrCentral)", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The O3DE Core (LmbrCentral) Gem provides required code and assets for running Open 3D Engine Editor.", diff --git a/Gems/LocalUser/gem.json b/Gems/LocalUser/gem.json index f86e6e1bf6..5199b4f777 100644 --- a/Gems/LocalUser/gem.json +++ b/Gems/LocalUser/gem.json @@ -2,6 +2,7 @@ "gem_name": "LocalUser", "display_name": "Local User", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Local User Gem provides functionality for mapping local user ids to local player slots and managing local user profiles.", diff --git a/Gems/LyShine/Assets/seedList.seed b/Gems/LyShine/Assets/seedList.seed index 499469bd63..b19aa77191 100644 --- a/Gems/LyShine/Assets/seedList.seed +++ b/Gems/LyShine/Assets/seedList.seed @@ -16,6 +16,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Gems/LyShine/Code/Tests/LyShineEditorTest.cpp b/Gems/LyShine/Code/Tests/LyShineEditorTest.cpp index 60074ac0a0..59be8a85e1 100644 --- a/Gems/LyShine/Code/Tests/LyShineEditorTest.cpp +++ b/Gems/LyShine/Code/Tests/LyShineEditorTest.cpp @@ -85,7 +85,9 @@ protected: AZ::SettingsRegistryInterface* registry = AZ::SettingsRegistry::Get(); auto projectPathKey = AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path"; - registry->Set(projectPathKey, "AutomatedTesting"); + AZ::IO::FixedMaxPath enginePath; + registry->Get(enginePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder); + registry->Set(projectPathKey, (enginePath / "AutomatedTesting").Native()); AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry); m_app.Start(m_descriptor); diff --git a/Gems/LyShine/gem.json b/Gems/LyShine/gem.json index e2da8afa4d..f3c3fc2c67 100644 --- a/Gems/LyShine/gem.json +++ b/Gems/LyShine/gem.json @@ -2,6 +2,7 @@ "gem_name": "LyShine", "display_name": "LyShine", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The LyShine Gem provides the runtime UI system and creation tools for Open 3D Engine projects.", diff --git a/Gems/LyShineExamples/gem.json b/Gems/LyShineExamples/gem.json index 122273f6d9..74d2b6fe51 100644 --- a/Gems/LyShineExamples/gem.json +++ b/Gems/LyShineExamples/gem.json @@ -2,6 +2,7 @@ "gem_name": "LyShineExamples", "display_name": "LyShine Examples", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Asset", "summary": "The LyShine Examples Gem provides example code and assets for LyShine, the runtime UI system and editor for Open 3D Engine projects.", diff --git a/Gems/Maestro/gem.json b/Gems/Maestro/gem.json index 5149df7c14..d8f884e271 100644 --- a/Gems/Maestro/gem.json +++ b/Gems/Maestro/gem.json @@ -2,6 +2,7 @@ "gem_name": "Maestro", "display_name": "Maestro Cinematics", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Maestro Cinematics Gem provides Track View, Open 3D Engine's animated sequence and cinematics editor.", diff --git a/Gems/MessagePopup/gem.json b/Gems/MessagePopup/gem.json index 05d36bc2df..fb44dd93a5 100644 --- a/Gems/MessagePopup/gem.json +++ b/Gems/MessagePopup/gem.json @@ -2,6 +2,7 @@ "gem_name": "MessagePopup", "display_name": "Message Popup", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Message Popup Gem provides an example implementation of popup messages using LyShine in Open 3D Engine.", diff --git a/Gems/Metastream/gem.json b/Gems/Metastream/gem.json index 862b17dd1d..309f16b221 100644 --- a/Gems/Metastream/gem.json +++ b/Gems/Metastream/gem.json @@ -2,6 +2,7 @@ "gem_name": "Metastream", "display_name": "Metastream", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Metastream Gem provides functionality for an HTTP server that allows broadcasters to customize game streams with overlays of statistics and event data from a game session.", diff --git a/Gems/Microphone/gem.json b/Gems/Microphone/gem.json index 68492ea786..6e57bec854 100644 --- a/Gems/Microphone/gem.json +++ b/Gems/Microphone/gem.json @@ -2,6 +2,7 @@ "gem_name": "Microphone", "display_name": "Microphone", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Microphone Gem provides support for audio input through microphones.", diff --git a/Gems/Multiplayer/gem.json b/Gems/Multiplayer/gem.json index 47dbddfcbd..f895c78c5d 100644 --- a/Gems/Multiplayer/gem.json +++ b/Gems/Multiplayer/gem.json @@ -2,6 +2,7 @@ "gem_name": "Multiplayer", "display_name": "Multiplayer", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Multiplayer Gem provides a public API for multiplayer functionality such as connecting and hosting.", diff --git a/Gems/MultiplayerCompression/gem.json b/Gems/MultiplayerCompression/gem.json index 7dd31476e3..98156cc404 100644 --- a/Gems/MultiplayerCompression/gem.json +++ b/Gems/MultiplayerCompression/gem.json @@ -2,6 +2,7 @@ "gem_name": "MultiplayerCompression", "display_name": "Multiplayer Compression", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Multiplayer Compression Gem provides an open source Compressor for use with AzNetworking's transport layer.", diff --git a/Gems/NvCloth/Assets/Objects/cloth/Chicken/Actor/chicken_mohawkmat.material b/Gems/NvCloth/Assets/Objects/cloth/Chicken/Actor/chicken_mohawkmat.material index 7e12d7fdee..22c673469c 100644 --- a/Gems/NvCloth/Assets/Objects/cloth/Chicken/Actor/chicken_mohawkmat.material +++ b/Gems/NvCloth/Assets/Objects/cloth/Chicken/Actor/chicken_mohawkmat.material @@ -1,8 +1,8 @@ { "description": "", - "materialType": "Materials/Types/StandardPBR.materialtype", "parentMaterial": "", - "propertyLayoutVersion": 3, + "materialType": "Materials/Types/StandardPBR.materialtype", + "materialTypeVersion": 4, "properties": { "baseColor": { "color": [ @@ -22,11 +22,8 @@ "intensity": 6.742737293243408, "textureMap": "Objects/cloth/Chicken/Actor/chicken_diff.png" }, - "opacity": { - "alphaSource": "None", - "doubleSided": true, - "factor": 1.0, - "mode": "Blended" + "general": { + "doubleSided": true } } -} +} \ No newline at end of file diff --git a/Gems/NvCloth/gem.json b/Gems/NvCloth/gem.json index 019ce23742..86a70b9373 100644 --- a/Gems/NvCloth/gem.json +++ b/Gems/NvCloth/gem.json @@ -3,6 +3,7 @@ "display_name": "NVIDIA Cloth (NvCloth)", "license": "Apache-2.0 Or MIT", "origin": "Open 3D Engine - o3de.org", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "type": "Code", "summary": "The NVIDIA Cloth Gem provides functionality to create fast, realistic cloth simulation with the NVIDIA Cloth library.", "canonical_tags": [ diff --git a/Gems/PhysX/gem.json b/Gems/PhysX/gem.json index bacbcf2dee..990d7502d8 100644 --- a/Gems/PhysX/gem.json +++ b/Gems/PhysX/gem.json @@ -2,6 +2,7 @@ "gem_name": "PhysX", "display_name": "PhysX", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The PhysX Gem provides physics simulation with NVIDIA PhysX including static and dynamic rigid body simulation, force regions, ragdolls, and dynamic PhysX joints.", diff --git a/Gems/PhysXDebug/gem.json b/Gems/PhysXDebug/gem.json index ece0774210..2d9f4dc24d 100644 --- a/Gems/PhysXDebug/gem.json +++ b/Gems/PhysXDebug/gem.json @@ -2,6 +2,7 @@ "gem_name": "PhysXDebug", "display_name": "PhysX Debug", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Tool", "summary": "The PhysX Debug Gem provides debugging functionality and visualizations for NVIDIA PhysX in Open 3D Engine.", diff --git a/Gems/Prefab/PrefabBuilder/PrefabBuilderTests.cpp b/Gems/Prefab/PrefabBuilder/PrefabBuilderTests.cpp index ab6770c3fe..d33b453c01 100644 --- a/Gems/Prefab/PrefabBuilder/PrefabBuilderTests.cpp +++ b/Gems/Prefab/PrefabBuilder/PrefabBuilderTests.cpp @@ -174,7 +174,9 @@ namespace UnitTest AZ::SettingsRegistryInterface* registry = AZ::SettingsRegistry::Get(); auto projectPathKey = AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path"; - registry->Set(projectPathKey, "AutomatedTesting"); + AZ::IO::FixedMaxPath enginePath; + registry->Get(enginePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder); + registry->Set(projectPathKey, (enginePath / "AutomatedTesting").Native()); AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry); AZ::ComponentApplication::Descriptor desc; diff --git a/Gems/Prefab/PrefabBuilder/gem.json b/Gems/Prefab/PrefabBuilder/gem.json index ba78f96358..2233a7235e 100644 --- a/Gems/Prefab/PrefabBuilder/gem.json +++ b/Gems/Prefab/PrefabBuilder/gem.json @@ -2,6 +2,7 @@ "gem_name": "PrefabBuilder", "display_name": "Prefab Builder", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Prefab Builder Gem provides an Asset Processor module for prefabs, which are complex assets built by combining smaller entities.", diff --git a/Gems/Presence/gem.json b/Gems/Presence/gem.json index a70953ae4e..624af2e62d 100644 --- a/Gems/Presence/gem.json +++ b/Gems/Presence/gem.json @@ -2,6 +2,7 @@ "gem_name": "Presence", "display_name": "Presence", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Presence Gem provides a target platform agnostic interface for Presence services.", diff --git a/Gems/PrimitiveAssets/gem.json b/Gems/PrimitiveAssets/gem.json index 4ad3cb62ad..0e4c4689dc 100644 --- a/Gems/PrimitiveAssets/gem.json +++ b/Gems/PrimitiveAssets/gem.json @@ -2,6 +2,7 @@ "gem_name": "PrimitiveAssets", "display_name": "Primitive Assets", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Asset", "summary": "The Primitive Assets Gem provides primitive shape mesh assets with physics enabled.", diff --git a/Gems/Profiler/gem.json b/Gems/Profiler/gem.json index 2f121d7618..b160bc4b3c 100644 --- a/Gems/Profiler/gem.json +++ b/Gems/Profiler/gem.json @@ -2,6 +2,7 @@ "gem_name": "Profiler", "display_name": "Profiler", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "A collection of utilities for capturing performance data", diff --git a/Gems/PythonAssetBuilder/gem.json b/Gems/PythonAssetBuilder/gem.json index ce30ba9e82..8046104f03 100644 --- a/Gems/PythonAssetBuilder/gem.json +++ b/Gems/PythonAssetBuilder/gem.json @@ -2,6 +2,7 @@ "gem_name": "PythonAssetBuilder", "display_name": "Python Asset Builder", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Python Asset Builder Gem provides functionality to implement custom asset builders in Python for Asset Processor.", diff --git a/Gems/QtForPython/Editor/Scripts/az_qt_helpers.py b/Gems/QtForPython/Editor/Scripts/az_qt_helpers.py index 758cc539d7..96f55f14b0 100755 --- a/Gems/QtForPython/Editor/Scripts/az_qt_helpers.py +++ b/Gems/QtForPython/Editor/Scripts/az_qt_helpers.py @@ -27,7 +27,7 @@ def get_editor_main_window(): return editor_main_window # Helper method for registering a Python widget as a tool/view pane with the Editor -def register_view_pane(name, widget_type, options=editor.ViewPaneOptions()): +def register_view_pane(name, widget_type, category="Tools", options=editor.ViewPaneOptions()): global view_pane_handlers # The view pane names are unique in the Editor, so make sure one with the same name doesn't exist already @@ -45,10 +45,10 @@ def register_view_pane(name, widget_type, options=editor.ViewPaneOptions()): return new_widget.winId() - def on_notify_register_views(parameters, my_name=name, my_options=options): + def on_notify_register_views(parameters, my_name=name, my_category=category, my_options=options): # Register our widget as an Editor view pane print('Calling on_notify_register_views RegisterCustomViewPane') - editor.EditorRequestBus(azlmbr.bus.Broadcast, 'RegisterCustomViewPane', my_name, 'Tools', my_options) + editor.EditorRequestBus(azlmbr.bus.Broadcast, 'RegisterCustomViewPane', my_name, my_category, my_options) # We keep a handler around in case a request for registering custom view panes comes later print('Initializing callback for RegisterCustomViewPane') @@ -57,7 +57,7 @@ def register_view_pane(name, widget_type, options=editor.ViewPaneOptions()): registration_handler.add_callback("NotifyRegisterViews", on_notify_register_views) global registration_handlers registration_handlers[name] = registration_handler - editor.EditorRequestBus(azlmbr.bus.Broadcast, 'RegisterCustomViewPane', name, 'Tools', options) + editor.EditorRequestBus(azlmbr.bus.Broadcast, 'RegisterCustomViewPane', name, category, options) # Connect to the ViewPaneCallbackBus in order to respond to requests to create our widget # We also need to store our handler so it will exist for the life of the Editor diff --git a/Gems/QtForPython/gem.json b/Gems/QtForPython/gem.json index f83be43342..17d3a26f21 100644 --- a/Gems/QtForPython/gem.json +++ b/Gems/QtForPython/gem.json @@ -2,6 +2,7 @@ "gem_name": "QtForPython", "display_name": "Qt for Python", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Tool", "summary": "The Qt for Python Gem provides the PySide2 Python libraries to manage Qt widgets.", diff --git a/Gems/SaveData/gem.json b/Gems/SaveData/gem.json index 333b4682d2..1ee5a54cec 100644 --- a/Gems/SaveData/gem.json +++ b/Gems/SaveData/gem.json @@ -2,6 +2,7 @@ "gem_name": "SaveData", "display_name": "Save Data", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Save Data Gem provides a platform independent API to save and load persistent user data in Open 3D Engine projects.", diff --git a/Gems/SceneLoggingExample/gem.json b/Gems/SceneLoggingExample/gem.json index 16961b9c5b..ad950f3992 100644 --- a/Gems/SceneLoggingExample/gem.json +++ b/Gems/SceneLoggingExample/gem.json @@ -2,6 +2,7 @@ "gem_name": "SceneLoggingExample", "display_name": "Scene Logging Example", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Asset", "summary": "The Scene Logging Example Gem demonstrates the basics of extending the Open 3D Engine Scene API by adding additional logging to the pipeline.", diff --git a/Gems/SceneProcessing/Code/Tests/SceneBuilder/SceneBuilderPhasesTests.cpp b/Gems/SceneProcessing/Code/Tests/SceneBuilder/SceneBuilderPhasesTests.cpp index 2709cd40b7..15d50bcfa3 100644 --- a/Gems/SceneProcessing/Code/Tests/SceneBuilder/SceneBuilderPhasesTests.cpp +++ b/Gems/SceneProcessing/Code/Tests/SceneBuilder/SceneBuilderPhasesTests.cpp @@ -139,7 +139,9 @@ public: AZ::SettingsRegistryInterface* registry = AZ::SettingsRegistry::Get(); auto projectPathKey = AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path"; - registry->Set(projectPathKey, "AutomatedTesting"); + AZ::IO::FixedMaxPath enginePath; + registry->Get(enginePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder); + registry->Set(projectPathKey, (enginePath / "AutomatedTesting").Native()); AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry); m_app.Start(AZ::ComponentApplication::Descriptor()); diff --git a/Gems/SceneProcessing/Code/Tests/SceneBuilder/SceneBuilderTests.cpp b/Gems/SceneProcessing/Code/Tests/SceneBuilder/SceneBuilderTests.cpp index 3a7b20553e..a1ca5be766 100644 --- a/Gems/SceneProcessing/Code/Tests/SceneBuilder/SceneBuilderTests.cpp +++ b/Gems/SceneProcessing/Code/Tests/SceneBuilder/SceneBuilderTests.cpp @@ -35,7 +35,9 @@ protected: AZ::SettingsRegistryInterface* registry = AZ::SettingsRegistry::Get(); auto projectPathKey = AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path"; - registry->Set(projectPathKey, "AutomatedTesting"); + AZ::IO::FixedMaxPath enginePath; + registry->Get(enginePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder); + registry->Set(projectPathKey, (enginePath / "AutomatedTesting").Native()); AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry); m_app.Start(AZ::ComponentApplication::Descriptor()); diff --git a/Gems/SceneProcessing/gem.json b/Gems/SceneProcessing/gem.json index de1dfeb23f..576460d0a9 100644 --- a/Gems/SceneProcessing/gem.json +++ b/Gems/SceneProcessing/gem.json @@ -2,6 +2,7 @@ "gem_name": "SceneProcessing", "display_name": "Scene Processing", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Tool", "summary": "The Scene Processing Gem provides Scene Settings, a tool you can use to specify the default settings for processing asset files for actors, meshes, motions, and PhysX.", diff --git a/Gems/ScriptCanvas/gem.json b/Gems/ScriptCanvas/gem.json index 621ea42961..fc8a504917 100644 --- a/Gems/ScriptCanvas/gem.json +++ b/Gems/ScriptCanvas/gem.json @@ -2,6 +2,7 @@ "gem_name": "ScriptCanvas", "display_name": "Script Canvas", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Tool", "summary": "The Script Canvas Gem provides Open 3D Engine's visual scripting environment, Script Canvas.", diff --git a/Gems/ScriptCanvasDeveloper/gem.json b/Gems/ScriptCanvasDeveloper/gem.json index ee1bfcec84..51aed9d9fa 100644 --- a/Gems/ScriptCanvasDeveloper/gem.json +++ b/Gems/ScriptCanvasDeveloper/gem.json @@ -2,6 +2,7 @@ "gem_name": "ScriptCanvasDeveloperGem", "display_name": "Script Canvas Developer", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Tool", "summary": "The Script Canvas Developer Gem provides a suite of utility features for the development and debugging of Script Canvas systems.", diff --git a/Gems/ScriptCanvasPhysics/gem.json b/Gems/ScriptCanvasPhysics/gem.json index 417fa6893a..2e35e85c46 100644 --- a/Gems/ScriptCanvasPhysics/gem.json +++ b/Gems/ScriptCanvasPhysics/gem.json @@ -2,6 +2,7 @@ "gem_name": "ScriptCanvasPhysics", "display_name": "Script Canvas Physics", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Script Canvas Physics Gem provides Script Canvas nodes for physics scene queries such as raycasts.", diff --git a/Gems/ScriptCanvasTesting/gem.json b/Gems/ScriptCanvasTesting/gem.json index c45d2ac165..5cb718e674 100644 --- a/Gems/ScriptCanvasTesting/gem.json +++ b/Gems/ScriptCanvasTesting/gem.json @@ -2,6 +2,7 @@ "gem_name": "ScriptCanvasTesting", "display_name": "Script Canvas Testing", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Tool", "summary": "The Script Canvas Testing Gem provides a framework for testing for and with Script Canvas.", diff --git a/Gems/ScriptEvents/gem.json b/Gems/ScriptEvents/gem.json index 386d9b5614..5640e08489 100644 --- a/Gems/ScriptEvents/gem.json +++ b/Gems/ScriptEvents/gem.json @@ -2,6 +2,7 @@ "gem_name": "ScriptEvents", "display_name": "Script Events", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Script Events Gem provides a framework for creating event assets usable from any scripting solution in Open 3D Engine.", diff --git a/Gems/ScriptedEntityTweener/gem.json b/Gems/ScriptedEntityTweener/gem.json index c51f05df9a..477345093c 100644 --- a/Gems/ScriptedEntityTweener/gem.json +++ b/Gems/ScriptedEntityTweener/gem.json @@ -2,6 +2,7 @@ "gem_name": "ScriptedEntityTweener", "display_name": "Scripted Entity Tweener", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Tool", "summary": "The Scripted Entity Tweener Gem provides a script driven animation system for Open 3D Engine projects.", diff --git a/Gems/SliceFavorites/gem.json b/Gems/SliceFavorites/gem.json index 84f42fc1a2..c4536dc042 100644 --- a/Gems/SliceFavorites/gem.json +++ b/Gems/SliceFavorites/gem.json @@ -2,6 +2,7 @@ "gem_name": "SliceFavorites", "display_name": "SliceFavorites", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "Add the ability to favorite a slice to allow easy access and instantiation", diff --git a/Gems/StartingPointCamera/gem.json b/Gems/StartingPointCamera/gem.json index 613eb76267..1033770022 100644 --- a/Gems/StartingPointCamera/gem.json +++ b/Gems/StartingPointCamera/gem.json @@ -2,6 +2,7 @@ "gem_name": "StartingPointCamera", "display_name": "Starting Point Camera", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Starting Point Camera Gem provides the behaviors used with the Camera Framework Gem to define a camera rig.", diff --git a/Gems/StartingPointInput/gem.json b/Gems/StartingPointInput/gem.json index d2641ea27b..ee1655b794 100644 --- a/Gems/StartingPointInput/gem.json +++ b/Gems/StartingPointInput/gem.json @@ -2,6 +2,7 @@ "gem_name": "StartingPointInput", "display_name": "Starting Point Input", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Starting Point Input Gem provides functionality to map low-level input events to high-level actions.", diff --git a/Gems/StartingPointMovement/gem.json b/Gems/StartingPointMovement/gem.json index 7def6da768..188d8483bc 100644 --- a/Gems/StartingPointMovement/gem.json +++ b/Gems/StartingPointMovement/gem.json @@ -2,6 +2,7 @@ "gem_name": "StartingPointMovement", "display_name": "Starting Point Movement", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Starting Point Movement Gem provides a series of Lua scripts that listen and respond to input events and trigger transform operations such as translation and rotation.", diff --git a/Gems/SurfaceData/gem.json b/Gems/SurfaceData/gem.json index 51a134d5df..d16254040f 100644 --- a/Gems/SurfaceData/gem.json +++ b/Gems/SurfaceData/gem.json @@ -2,6 +2,7 @@ "gem_name": "SurfaceData", "display_name": "Surface Data", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Surface Data Gem provides functionality to emit signals or tags from surfaces such as meshes and terrain.", diff --git a/Gems/Terrain/Assets/Shaders/Terrain/TerrainPBR_ForwardPass.azsl b/Gems/Terrain/Assets/Shaders/Terrain/TerrainPBR_ForwardPass.azsl index 750cd2fb29..9cfa568b9a 100644 --- a/Gems/Terrain/Assets/Shaders/Terrain/TerrainPBR_ForwardPass.azsl +++ b/Gems/Terrain/Assets/Shaders/Terrain/TerrainPBR_ForwardPass.azsl @@ -22,11 +22,9 @@ struct VSOutput { float4 m_position : SV_Position; float3 m_normal: NORMAL; - float3 m_tangent : TANGENT; - float3 m_bitangent : BITANGENT; float3 m_worldPosition : UV0; - float3 m_shadowCoords[ViewSrg::MaxCascadeCount] : UV2; float2 m_uv : UV1; + float3 m_shadowCoords[ViewSrg::MaxCascadeCount] : UV2; }; VSOutput TerrainPBR_MainPassVS(VertexInput IN) @@ -47,9 +45,9 @@ VSOutput TerrainPBR_MainPassVS(VertexInput IN) float down = GetHeight(origUv + terrainData.m_uvStep * float2( 0.0f, 1.0f)); float left = GetHeight(origUv + terrainData.m_uvStep * float2(-1.0f, 0.0f)); - OUT.m_bitangent = normalize(float3(0.0, terrainData.m_sampleSpacing * 2.0f, down - up)); - OUT.m_tangent = normalize(float3(terrainData.m_sampleSpacing * 2.0f, 0.0, right - left)); - OUT.m_normal = cross(OUT.m_tangent, OUT.m_bitangent); + float3 bitangent = normalize(float3(0.0, terrainData.m_sampleSpacing * 2.0f, down - up)); + float3 tangent = normalize(float3(terrainData.m_sampleSpacing * 2.0f, 0.0, right - left)); + OUT.m_normal = normalize(cross(tangent, bitangent)); OUT.m_uv = uv; // directional light shadow @@ -75,18 +73,14 @@ ForwardPassOutput TerrainPBR_MainPassPS(VSOutput IN) surface.position = IN.m_worldPosition.xyz; float viewDistance = length(ViewSrg::m_worldPosition - surface.position); float detailFactor = saturate((viewDistance - TerrainMaterialSrg::m_detailFadeDistance) / max(TerrainMaterialSrg::m_detailFadeLength, EPSILON)); - - ObjectSrg::TerrainData terrainData = ObjectSrg::m_terrainData; - float2 origUv = lerp(terrainData.m_uvMin, terrainData.m_uvMax, IN.m_uv); - origUv.y = 1.0 - origUv.y; float2 detailUv = IN.m_uv * TerrainMaterialSrg::m_detailTextureMultiplier; // ------- Normal ------- - float3 macroNormal = IN.m_normal; + float3 macroNormal = normalize(IN.m_normal); // ------- Macro Color / Normal ------- float3 macroColor = TerrainMaterialSrg::m_baseColor.rgb; - [unroll] for (uint i = 0; i < 4; ++i) + [unroll] for (uint i = 0; i < 4 && (i < ObjectSrg::m_macroMaterialCount); ++i) { float2 macroUvMin = ObjectSrg::m_macroMaterialData[i].m_uvMin; float2 macroUvMax = ObjectSrg::m_macroMaterialData[i].m_uvMax; diff --git a/Gems/Terrain/Code/Mocks/Terrain/MockTerrain.h b/Gems/Terrain/Code/Mocks/Terrain/MockTerrain.h index 53d93be8ea..f9607fdafb 100644 --- a/Gems/Terrain/Code/Mocks/Terrain/MockTerrain.h +++ b/Gems/Terrain/Code/Mocks/Terrain/MockTerrain.h @@ -34,7 +34,8 @@ namespace UnitTest MOCK_METHOD1(RegisterArea, void(AZ::EntityId areaId)); MOCK_METHOD1(UnregisterArea, void(AZ::EntityId areaId)); - MOCK_METHOD1(RefreshArea, void(AZ::EntityId areaId)); + MOCK_METHOD2(RefreshArea, + void(AZ::EntityId areaId, AzFramework::Terrain::TerrainDataNotifications::TerrainDataChangedMask changeMask)); }; class MockTerrainDataNotificationListener : public AzFramework::Terrain::TerrainDataNotificationBus::Handler diff --git a/Gems/Terrain/Code/Source/Components/TerrainHeightGradientListComponent.cpp b/Gems/Terrain/Code/Source/Components/TerrainHeightGradientListComponent.cpp index 01ad0bb77f..231d5abc28 100644 --- a/Gems/Terrain/Code/Source/Components/TerrainHeightGradientListComponent.cpp +++ b/Gems/Terrain/Code/Source/Components/TerrainHeightGradientListComponent.cpp @@ -119,7 +119,9 @@ namespace Terrain LmbrCentral::DependencyNotificationBus::Handler::BusDisconnect(); // Since this height data will no longer exist, notify the terrain system to refresh the area. - TerrainSystemServiceRequestBus::Broadcast(&TerrainSystemServiceRequestBus::Events::RefreshArea, GetEntityId()); + TerrainSystemServiceRequestBus::Broadcast( + &TerrainSystemServiceRequestBus::Events::RefreshArea, GetEntityId(), + AzFramework::Terrain::TerrainDataNotifications::HeightData); } bool TerrainHeightGradientListComponent::ReadInConfig(const AZ::ComponentConfig* baseConfig) @@ -176,7 +178,9 @@ namespace Terrain void TerrainHeightGradientListComponent::OnCompositionChanged() { RefreshMinMaxHeights(); - TerrainSystemServiceRequestBus::Broadcast(&TerrainSystemServiceRequestBus::Events::RefreshArea, GetEntityId()); + TerrainSystemServiceRequestBus::Broadcast( + &TerrainSystemServiceRequestBus::Events::RefreshArea, GetEntityId(), + AzFramework::Terrain::TerrainDataNotifications::HeightData); } void TerrainHeightGradientListComponent::RefreshMinMaxHeights() diff --git a/Gems/Terrain/Code/Source/Components/TerrainLayerSpawnerComponent.cpp b/Gems/Terrain/Code/Source/Components/TerrainLayerSpawnerComponent.cpp index 00ed9c004f..c3803c25e8 100644 --- a/Gems/Terrain/Code/Source/Components/TerrainLayerSpawnerComponent.cpp +++ b/Gems/Terrain/Code/Source/Components/TerrainLayerSpawnerComponent.cpp @@ -157,6 +157,12 @@ namespace Terrain void TerrainLayerSpawnerComponent::RefreshArea() { - TerrainSystemServiceRequestBus::Broadcast(&TerrainSystemServiceRequestBus::Events::RefreshArea, GetEntityId()); + using Terrain = AzFramework::Terrain::TerrainDataNotifications; + + // Notify the terrain system that the entire layer has changed, so both height and surface data can be affected. + TerrainSystemServiceRequestBus::Broadcast( + &TerrainSystemServiceRequestBus::Events::RefreshArea, GetEntityId(), + static_cast(Terrain::HeightData | Terrain::SurfaceData) + ); } } diff --git a/Gems/Terrain/Code/Source/Components/TerrainSurfaceGradientListComponent.cpp b/Gems/Terrain/Code/Source/Components/TerrainSurfaceGradientListComponent.cpp index 5b76f15d74..748221d69e 100644 --- a/Gems/Terrain/Code/Source/Components/TerrainSurfaceGradientListComponent.cpp +++ b/Gems/Terrain/Code/Source/Components/TerrainSurfaceGradientListComponent.cpp @@ -184,7 +184,9 @@ namespace Terrain void TerrainSurfaceGradientListComponent::OnCompositionChanged() { - TerrainSystemServiceRequestBus::Broadcast(&TerrainSystemServiceRequestBus::Events::RefreshArea, GetEntityId()); + TerrainSystemServiceRequestBus::Broadcast( + &TerrainSystemServiceRequestBus::Events::RefreshArea, GetEntityId(), + AzFramework::Terrain::TerrainDataNotifications::SurfaceData); } } // namespace Terrain diff --git a/Gems/Terrain/Code/Source/Components/TerrainWorldComponent.cpp b/Gems/Terrain/Code/Source/Components/TerrainWorldComponent.cpp index d8b7308ef7..bd65cf6abc 100644 --- a/Gems/Terrain/Code/Source/Components/TerrainWorldComponent.cpp +++ b/Gems/Terrain/Code/Source/Components/TerrainWorldComponent.cpp @@ -32,16 +32,22 @@ namespace Terrain AZ::EditContext* edit = serialize->GetEditContext(); if (edit) { - edit->Class( - "Terrain World Component", "Data required for the terrain system to run") + edit->Class("Terrain World Component", "Data required for the terrain system to run") ->ClassElement(AZ::Edit::ClassElements::EditorData, "") ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZStd::vector({ AZ_CRC_CE("Level") })) ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) ->Attribute(AZ::Edit::Attributes::AutoExpand, true) ->DataElement(AZ::Edit::UIHandlers::Default, &TerrainWorldConfig::m_worldMin, "World Bounds (Min)", "") + // Temporary constraint until the rest of the Terrain system is updated to support larger worlds. + ->Attribute(AZ::Edit::Attributes::Min, -2048.0f) + ->Attribute(AZ::Edit::Attributes::Max, 2048.0f) ->DataElement(AZ::Edit::UIHandlers::Default, &TerrainWorldConfig::m_worldMax, "World Bounds (Max)", "") - ->DataElement(AZ::Edit::UIHandlers::Default, &TerrainWorldConfig::m_heightQueryResolution, "Height Query Resolution (m)", "") + // Temporary constraint until the rest of the Terrain system is updated to support larger worlds. + ->Attribute(AZ::Edit::Attributes::Min, -2048.0f) + ->Attribute(AZ::Edit::Attributes::Max, 2048.0f) + ->DataElement( + AZ::Edit::UIHandlers::Default, &TerrainWorldConfig::m_heightQueryResolution, "Height Query Resolution (m)", "") ; } } diff --git a/Gems/Terrain/Code/Source/TerrainRenderer/Components/TerrainMacroMaterialComponent.cpp b/Gems/Terrain/Code/Source/TerrainRenderer/Components/TerrainMacroMaterialComponent.cpp index 0d161b6b2b..a65cbad50b 100644 --- a/Gems/Terrain/Code/Source/TerrainRenderer/Components/TerrainMacroMaterialComponent.cpp +++ b/Gems/Terrain/Code/Source/TerrainRenderer/Components/TerrainMacroMaterialComponent.cpp @@ -248,6 +248,7 @@ namespace Terrain { m_configuration.m_macroColorAsset = asset; m_colorImage = AZ::RPI::StreamingImage::FindOrCreate(m_configuration.m_macroColorAsset); + m_colorImage->GetRHIImage()->SetName(AZ::Name(m_configuration.m_macroColorAsset.GetHint())); // Clear the texture asset reference to make sure we don't prevent hot-reloading. m_configuration.m_macroColorAsset.Release(); @@ -256,6 +257,7 @@ namespace Terrain { m_configuration.m_macroNormalAsset = asset; m_normalImage = AZ::RPI::StreamingImage::FindOrCreate(m_configuration.m_macroNormalAsset); + m_normalImage->GetRHIImage()->SetName(AZ::Name(m_configuration.m_macroNormalAsset.GetHint())); // Clear the texture asset reference to make sure we don't prevent hot-reloading. m_configuration.m_macroColorAsset.Release(); diff --git a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.cpp b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.cpp index 1b13d3bb98..73f1c3967c 100644 --- a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.cpp +++ b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.cpp @@ -535,7 +535,9 @@ namespace Terrain sectorData.m_srg->SetConstant(m_terrainDataIndex, terrainDataForSrg); AZStd::array macroMaterialData; - for (uint32_t i = 0; i < sectorData.m_macroMaterials.size(); ++i) + + uint32_t i = 0; + for (; i < sectorData.m_macroMaterials.size(); ++i) { const MacroMaterialData& materialData = m_macroMaterials.GetData(sectorData.m_macroMaterials.at(i)); ShaderMacroMaterialData& shaderData = macroMaterialData.at(i); @@ -564,6 +566,11 @@ namespace Terrain // set flags for which images are used. shaderData.m_mapsInUse = (colorImageView ? ColorImageUsed : 0) | (normalImageView ? NormalImageUsed : 0); } + for (; i < sectorData.m_macroMaterials.capacity(); ++i) + { + sectorData.m_srg->SetImageView(m_macroColorMapIndex, nullptr, i); + sectorData.m_srg->SetImageView(m_macroNormalMapIndex, nullptr, i); + } sectorData.m_srg->SetConstantArray(m_macroMaterialDataIndex, macroMaterialData); sectorData.m_srg->SetConstant(m_macroMaterialCountIndex, aznumeric_cast(sectorData.m_macroMaterials.size())); diff --git a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.h b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.h index 91e3ce9a5c..f6836fdd28 100644 --- a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.h +++ b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.h @@ -68,18 +68,18 @@ namespace Terrain struct ShaderTerrainData // Must align with struct in Object Srg { - AZStd::array m_uvMin; - AZStd::array m_uvMax; - AZStd::array m_uvStep; - float m_sampleSpacing; - float m_heightScale; + AZStd::array m_uvMin{ 0.0f, 0.0f }; + AZStd::array m_uvMax{ 1.0f, 1.0f }; + AZStd::array m_uvStep{ 1.0f, 1.0f }; + float m_sampleSpacing{ 1.0f }; + float m_heightScale{ 1.0f }; }; - struct ShaderMacroMaterialData + struct ShaderMacroMaterialData // Must align with struct in Object Srg { - AZStd::array m_uvMin; - AZStd::array m_uvMax; - float m_normalFactor; + AZStd::array m_uvMin{ 0.0f, 0.0f }; + AZStd::array m_uvMax{ 1.0f, 1.0f }; + float m_normalFactor{ 0.0f }; uint32_t m_flipNormalX{ 0 }; // bool in shader uint32_t m_flipNormalY{ 0 }; // bool in shader uint32_t m_mapsInUse{ 0b00 }; // 0b01 = color, 0b10 = normal diff --git a/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.cpp b/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.cpp index fa1d483a5d..8d39340b06 100644 --- a/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.cpp +++ b/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.cpp @@ -76,6 +76,7 @@ void TerrainSystem::Activate() m_dirtyRegion = AZ::Aabb::CreateNull(); m_terrainHeightDirty = true; m_terrainSettingsDirty = true; + m_terrainSurfacesDirty = true; m_requestedSettings.m_systemActive = true; { @@ -115,6 +116,7 @@ void TerrainSystem::Deactivate() m_dirtyRegion = AZ::Aabb::CreateNull(); m_terrainHeightDirty = true; m_terrainSettingsDirty = true; + m_terrainSurfacesDirty = true; m_requestedSettings.m_systemActive = false; AzFramework::Terrain::TerrainDataNotificationBus::Broadcast( @@ -549,6 +551,7 @@ void TerrainSystem::RegisterArea(AZ::EntityId areaId) m_registeredAreas[areaId] = aabb; m_dirtyRegion.AddAabb(aabb); m_terrainHeightDirty = true; + m_terrainSurfacesDirty = true; } void TerrainSystem::UnregisterArea(AZ::EntityId areaId) @@ -567,14 +570,17 @@ void TerrainSystem::UnregisterArea(AZ::EntityId areaId) { m_dirtyRegion.AddAabb(aabb); m_terrainHeightDirty = true; + m_terrainSurfacesDirty = true; return true; } return false; }); } -void TerrainSystem::RefreshArea(AZ::EntityId areaId) +void TerrainSystem::RefreshArea(AZ::EntityId areaId, AzFramework::Terrain::TerrainDataNotifications::TerrainDataChangedMask changeMask) { + using Terrain = AzFramework::Terrain::TerrainDataNotifications; + AZStd::unique_lock lock(m_areaMutex); auto areaAabb = m_registeredAreas.find(areaId); @@ -588,11 +594,18 @@ void TerrainSystem::RefreshArea(AZ::EntityId areaId) expandedAabb.AddAabb(newAabb); m_dirtyRegion.AddAabb(expandedAabb); - m_terrainHeightDirty = true; + + // Keep track of which types of data have changed so that we can send out the appropriate notifications later. + + m_terrainHeightDirty = m_terrainHeightDirty || ((changeMask & Terrain::HeightData) == Terrain::HeightData); + + m_terrainSurfacesDirty = m_terrainSurfacesDirty || ((changeMask & Terrain::SurfaceData) == Terrain::SurfaceData); } void TerrainSystem::OnTick(float /*deltaTime*/, AZ::ScriptTimePoint /*time*/) { + using Terrain = AzFramework::Terrain::TerrainDataNotifications; + bool terrainSettingsChanged = false; if (m_terrainSettingsDirty) @@ -607,6 +620,7 @@ void TerrainSystem::OnTick(float /*deltaTime*/, AZ::ScriptTimePoint /*time*/) m_dirtyRegion = m_currentSettings.m_worldBounds; m_dirtyRegion.AddAabb(m_requestedSettings.m_worldBounds); m_terrainHeightDirty = true; + m_terrainSurfacesDirty = true; m_currentSettings.m_worldBounds = m_requestedSettings.m_worldBounds; } @@ -614,12 +628,13 @@ void TerrainSystem::OnTick(float /*deltaTime*/, AZ::ScriptTimePoint /*time*/) { m_dirtyRegion = AZ::Aabb::CreateNull(); m_terrainHeightDirty = true; + m_terrainSurfacesDirty = true; } m_currentSettings = m_requestedSettings; } - if (terrainSettingsChanged || m_terrainHeightDirty) + if (terrainSettingsChanged || m_terrainHeightDirty || m_terrainSurfacesDirty) { // Block other threads from accessing the surface data bus while we are in GetValue (which may call into the SurfaceData bus). // We lock our surface data mutex *before* checking / setting "isRequestInProgress" so that we prevent race conditions @@ -629,24 +644,27 @@ void TerrainSystem::OnTick(float /*deltaTime*/, AZ::ScriptTimePoint /*time*/) auto& surfaceDataContext = SurfaceData::SurfaceDataSystemRequestBus::GetOrCreateContext(false); typename SurfaceData::SurfaceDataSystemRequestBus::Context::DispatchLockGuard scopeLock(surfaceDataContext.m_contextMutex); - AzFramework::Terrain::TerrainDataNotifications::TerrainDataChangedMask changeMask = - AzFramework::Terrain::TerrainDataNotifications::TerrainDataChangedMask::None; + Terrain::TerrainDataChangedMask changeMask = Terrain::TerrainDataChangedMask::None; if (terrainSettingsChanged) { - changeMask = static_cast( - changeMask | AzFramework::Terrain::TerrainDataNotifications::TerrainDataChangedMask::Settings); + changeMask = static_cast(changeMask | Terrain::TerrainDataChangedMask::Settings); } if (m_terrainHeightDirty) { - changeMask = static_cast( - changeMask | AzFramework::Terrain::TerrainDataNotifications::TerrainDataChangedMask::HeightData); + changeMask = static_cast(changeMask | Terrain::TerrainDataChangedMask::HeightData); + } + + if (m_terrainSurfacesDirty) + { + changeMask = static_cast(changeMask | Terrain::TerrainDataChangedMask::SurfaceData); } // Make sure to set these *before* calling OnTerrainDataChanged, since it's possible that subsystems reacting to that call will // cause the data to become dirty again. AZ::Aabb dirtyRegion = m_dirtyRegion; m_terrainHeightDirty = false; + m_terrainSurfacesDirty = false; m_dirtyRegion = AZ::Aabb::CreateNull(); AzFramework::Terrain::TerrainDataNotificationBus::Broadcast( diff --git a/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.h b/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.h index 956424f048..022cd218cc 100644 --- a/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.h +++ b/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.h @@ -47,7 +47,8 @@ namespace Terrain void RegisterArea(AZ::EntityId areaId) override; void UnregisterArea(AZ::EntityId areaId) override; - void RefreshArea(AZ::EntityId areaId) override; + void RefreshArea( + AZ::EntityId areaId, AzFramework::Terrain::TerrainDataNotifications::TerrainDataChangedMask changeMask) override; /////////////////////////////////////////// // TerrainDataRequestBus::Handler Impl @@ -164,6 +165,7 @@ namespace Terrain bool m_terrainSettingsDirty = true; bool m_terrainHeightDirty = false; + bool m_terrainSurfacesDirty = false; AZ::Aabb m_dirtyRegion; mutable AZStd::shared_mutex m_areaMutex; diff --git a/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystemBus.h b/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystemBus.h index cda6d65a1e..013d82d94d 100644 --- a/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystemBus.h +++ b/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystemBus.h @@ -44,7 +44,7 @@ namespace Terrain // register an area to override terrain virtual void RegisterArea(AZ::EntityId areaId) = 0; virtual void UnregisterArea(AZ::EntityId areaId) = 0; - virtual void RefreshArea(AZ::EntityId areaId) = 0; + virtual void RefreshArea(AZ::EntityId areaId, AzFramework::Terrain::TerrainDataNotifications::TerrainDataChangedMask changeMask) = 0; }; using TerrainSystemServiceRequestBus = AZ::EBus; diff --git a/Gems/Terrain/Code/Tests/LayerSpawnerTests.cpp b/Gems/Terrain/Code/Tests/LayerSpawnerTests.cpp index 0ca5e7d99a..3778ba860f 100644 --- a/Gems/Terrain/Code/Tests/LayerSpawnerTests.cpp +++ b/Gems/Terrain/Code/Tests/LayerSpawnerTests.cpp @@ -190,7 +190,7 @@ TEST_F(LayerSpawnerComponentTest, LayerSpawnerTransformChangedUpdatesTerrainSyst CreateMockTerrainSystem(); // The TransformChanged call should refresh the area. - EXPECT_CALL(*m_terrainSystem, RefreshArea(_)).Times(1); + EXPECT_CALL(*m_terrainSystem, RefreshArea(_, _)).Times(1); AddLayerSpawnerAndShapeComponentToEntity(); @@ -211,7 +211,7 @@ TEST_F(LayerSpawnerComponentTest, LayerSpawnerShapeChangedUpdatesTerrainSystem) CreateMockTerrainSystem(); // The ShapeChanged call should refresh the area. - EXPECT_CALL(*m_terrainSystem, RefreshArea(_)).Times(1); + EXPECT_CALL(*m_terrainSystem, RefreshArea(_, _)).Times(1); AddLayerSpawnerAndShapeComponentToEntity(); diff --git a/Gems/Terrain/Code/Tests/TerrainHeightGradientListTests.cpp b/Gems/Terrain/Code/Tests/TerrainHeightGradientListTests.cpp index c314e4e968..ec500d6ada 100644 --- a/Gems/Terrain/Code/Tests/TerrainHeightGradientListTests.cpp +++ b/Gems/Terrain/Code/Tests/TerrainHeightGradientListTests.cpp @@ -93,7 +93,7 @@ TEST_F(TerrainHeightGradientListComponentTest, TerrainHeightGradientRefreshesTer // As the TerrainHeightGradientListComponent subscribes to the dependency monitor, RefreshArea will be called twice: // once due to OnCompositionChanged being picked up by the the dependency monitor and resending the notification, // and once when the HeightGradientListComponent gets the OnCompositionChanged directly through the DependencyNotificationBus. - EXPECT_CALL(terrainSystem, RefreshArea(_)).Times(2); + EXPECT_CALL(terrainSystem, RefreshArea(_, _)).Times(2); LmbrCentral::DependencyNotificationBus::Event(m_entity->GetId(), &LmbrCentral::DependencyNotificationBus::Events::OnCompositionChanged); diff --git a/Gems/Terrain/gem.json b/Gems/Terrain/gem.json index cd72a91708..0ca470c4d3 100644 --- a/Gems/Terrain/gem.json +++ b/Gems/Terrain/gem.json @@ -2,6 +2,7 @@ "gem_name": "Terrain", "display_name": "Terrain", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "summary": "The Terrain Gem is an experimental terrain system. The terrain system maps height, color, and surface data to regions of the world, provides gradient-based and shape-based authoring tools and workflows, includes specialized rendering for efficient display, and integrates with physics for physical simulation.", "canonical_tags": [ diff --git a/Gems/TestAssetBuilder/gem.json b/Gems/TestAssetBuilder/gem.json index ba7568005f..68ed706499 100644 --- a/Gems/TestAssetBuilder/gem.json +++ b/Gems/TestAssetBuilder/gem.json @@ -2,6 +2,7 @@ "gem_name": "TestAssetBuilder", "display_name": "Test Asset Builder", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Test Asset Builder Gem is used to feature test Asset Processor.", diff --git a/Gems/TextureAtlas/gem.json b/Gems/TextureAtlas/gem.json index 345e085359..142f0fb082 100644 --- a/Gems/TextureAtlas/gem.json +++ b/Gems/TextureAtlas/gem.json @@ -2,6 +2,7 @@ "gem_name": "TextureAtlas", "display_name": "Texture Atlas", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Texture Atlas Gem provides the formatting for texture atlases from 2D textures for LyShine.", diff --git a/Gems/TickBusOrderViewer/gem.json b/Gems/TickBusOrderViewer/gem.json index dc5cb6f66c..a6a6fd23ac 100644 --- a/Gems/TickBusOrderViewer/gem.json +++ b/Gems/TickBusOrderViewer/gem.json @@ -2,6 +2,7 @@ "gem_name": "TickBusOrderViewer", "display_name": "Tick Bus Order Viewer", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Tool", "summary": "The Tick Bus Order Viewer Gem provides a console variable that displays the order of runtime tick events.", diff --git a/Gems/Twitch/gem.json b/Gems/Twitch/gem.json index f45433bc3f..42ec1b971e 100644 --- a/Gems/Twitch/gem.json +++ b/Gems/Twitch/gem.json @@ -2,6 +2,7 @@ "gem_name": "Twitch", "display_name": "Twitch", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Twitch Gem provides access to the Twitch API v5 SDK including social functions, channels, and other APIs.", diff --git a/Gems/UiBasics/gem.json b/Gems/UiBasics/gem.json index 9a1e16a462..bb2416c235 100644 --- a/Gems/UiBasics/gem.json +++ b/Gems/UiBasics/gem.json @@ -2,6 +2,7 @@ "gem_name": "UiBasics", "display_name": "UI Basics", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Asset", "summary": "The UI Basics Gem provides a collection of basic UI prefabs such as image, text, and button, that can be used with LyShine, the Open 3D Engine runtime User Interface system and editor.", diff --git a/Gems/Vegetation/gem.json b/Gems/Vegetation/gem.json index 9dbcc3450b..75416933f0 100644 --- a/Gems/Vegetation/gem.json +++ b/Gems/Vegetation/gem.json @@ -2,6 +2,7 @@ "gem_name": "Vegetation", "display_name": "Vegetation", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Vegetation Gem provides tools to place natural-looking vegetation in Open 3D Engine.", diff --git a/Gems/VideoPlaybackFramework/gem.json b/Gems/VideoPlaybackFramework/gem.json index 9f491f47cb..381738ab4b 100644 --- a/Gems/VideoPlaybackFramework/gem.json +++ b/Gems/VideoPlaybackFramework/gem.json @@ -2,6 +2,7 @@ "gem_name": "VideoPlaybackFramework", "display_name": "Video Playback Framework", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Video Playback Framework Gem provides the interface to play back video.", diff --git a/Gems/VirtualGamepad/gem.json b/Gems/VirtualGamepad/gem.json index c6305f1754..637776ac85 100644 --- a/Gems/VirtualGamepad/gem.json +++ b/Gems/VirtualGamepad/gem.json @@ -2,6 +2,7 @@ "gem_name": "VirtualGamepad", "display_name": "Virtual Gamepad", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Code", "summary": "The Virtual Gamepad Gem provides controls that emulate a gamepad on touch screen devices.", diff --git a/Gems/WhiteBox/gem.json b/Gems/WhiteBox/gem.json index 81e0ad5f88..41865efc56 100644 --- a/Gems/WhiteBox/gem.json +++ b/Gems/WhiteBox/gem.json @@ -2,6 +2,7 @@ "gem_name": "WhiteBox", "display_name": "White Box", "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", "origin": "Open 3D Engine - o3de.org", "type": "Tool", "summary": "The White Box Gem provides White Box rapid design components for Open 3D Engine.", diff --git a/Registry/application_lifecycle_events.setreg b/Registry/application_lifecycle_events.setreg new file mode 100644 index 0000000000..0d9cd0f170 --- /dev/null +++ b/Registry/application_lifecycle_events.setreg @@ -0,0 +1,30 @@ +// The Lifecycle events contains the name of the event as a string +// ComponentApplication derived classes +// will set these these keys to a JSON Object indicate an event has occured +// A callback can be registered with the SettingsRegistry +// to be notified when that key is set +// The JSON object that is set will contain any payload data +// related to the event +{ + "O3DE" : { + "Application": { + "LifecycleEvents": { + "SystemComponentsActivated": {}, + "SystemComponentsDeactivated": {}, + "ReflectionManagerAvailable": {}, + "ReflectionManagerUnavailable": {}, + "SystemAllocatorCreated": {}, + "SystemAllocatorPendingDestruction": {}, + "SettingsRegistryAvailable": {}, + "SettingsRegistryUnavailable": {}, + "ConsoleAvailable": {}, + "ConsoleUnavailable": {}, + "GemsLoaded": {}, + "GemsUnloaded": {}, + "FileIOAvailable": {}, + "FileIOUnavailable": {}, + "LegacySystemInterfaceCreated": {} + } + } + } +} diff --git a/Templates/AssetGem/Template/gem.json b/Templates/AssetGem/Template/gem.json index 2a688857f2..2a02362256 100644 --- a/Templates/AssetGem/Template/gem.json +++ b/Templates/AssetGem/Template/gem.json @@ -1,7 +1,8 @@ { "gem_name": "${Name}", "display_name": "${Name}", - "license": "What license ${Name} uses goes here: i.e. https://opensource.org/licenses/MIT", + "license": "What license ${Name} uses goes here: i.e. Apache-2.0 Or MIT", + "license_url": "", "origin": "The primary repo for ${Name} goes here: i.e. http://www.mydomain.com", "type": "Asset", "summary": "A short description of ${Name}.", diff --git a/Templates/CppToolGem/Template/Code/Source/${Name}EditorSystemComponent.cpp b/Templates/CppToolGem/Template/Code/Source/${Name}EditorSystemComponent.cpp index f12fa04929..4206340c57 100644 --- a/Templates/CppToolGem/Template/Code/Source/${Name}EditorSystemComponent.cpp +++ b/Templates/CppToolGem/Template/Code/Source/${Name}EditorSystemComponent.cpp @@ -71,8 +71,8 @@ namespace ${SanitizedCppName} options.showOnToolsToolbar = true; options.toolbarIcon = ":/${Name}/toolbar_icon.svg"; - // Register our custom widget as a dockable tool with the Editor - AzToolsFramework::RegisterViewPane<${SanitizedCppName}Widget>("${Name}", "Tools", options); + // Register our custom widget as a dockable tool with the Editor under an Examples sub-menu + AzToolsFramework::RegisterViewPane<${SanitizedCppName}Widget>("${Name}", "Examples", options); } } // namespace ${SanitizedCppName} diff --git a/Templates/CppToolGem/Template/Code/Source/${Name}Widget.cpp b/Templates/CppToolGem/Template/Code/Source/${Name}Widget.cpp index bd6dd6c86a..a5128fb192 100644 --- a/Templates/CppToolGem/Template/Code/Source/${Name}Widget.cpp +++ b/Templates/CppToolGem/Template/Code/Source/${Name}Widget.cpp @@ -20,8 +20,6 @@ namespace ${SanitizedCppName} ${SanitizedCppName}Widget::${SanitizedCppName}Widget(QWidget* parent) : QWidget(parent) { - setWindowTitle(QObject::tr("${Name}")); - QVBoxLayout* mainLayout = new QVBoxLayout(this); QLabel* introLabel = new QLabel(QObject::tr("Put your cool stuff here!"), this); diff --git a/Templates/CppToolGem/Template/gem.json b/Templates/CppToolGem/Template/gem.json index 518d831e0f..079b7152ff 100644 --- a/Templates/CppToolGem/Template/gem.json +++ b/Templates/CppToolGem/Template/gem.json @@ -1,7 +1,8 @@ { "gem_name": "${Name}", "display_name": "${Name}", - "license": "What license ${Name} uses goes here: i.e. https://opensource.org/licenses/MIT", + "license": "What license ${Name} uses goes here: i.e. Apache-2.0 Or MIT", + "license_url": "", "origin": "The primary repo for ${Name} goes here: i.e. http://www.mydomain.com", "type": "Code", "summary": "A short description of ${Name}.", diff --git a/Templates/DefaultGem/Template/gem.json b/Templates/DefaultGem/Template/gem.json index 353ad6bf8d..d4ff637bee 100644 --- a/Templates/DefaultGem/Template/gem.json +++ b/Templates/DefaultGem/Template/gem.json @@ -1,7 +1,8 @@ { "gem_name": "${Name}", "display_name": "${Name}", - "license": "What license ${Name} uses goes here: i.e. https://opensource.org/licenses/MIT", + "license": "What license ${Name} uses goes here: i.e. Apache-2.0 Or MIT", + "license_url": "", "origin": "The primary repo for ${Name} goes here: i.e. http://www.mydomain.com", "type": "Code", "summary": "A short description of ${Name}.", diff --git a/Templates/PythonToolGem/Template/.gitignore b/Templates/PythonToolGem/Template/.gitignore new file mode 100644 index 0000000000..7a60b85e14 --- /dev/null +++ b/Templates/PythonToolGem/Template/.gitignore @@ -0,0 +1,2 @@ +__pycache__/ +*.pyc diff --git a/Templates/PythonToolGem/Template/Code/${NameLower}_editor_files.cmake b/Templates/PythonToolGem/Template/Code/${NameLower}_editor_files.cmake index 8362d37f52..88aaea843f 100644 --- a/Templates/PythonToolGem/Template/Code/${NameLower}_editor_files.cmake +++ b/Templates/PythonToolGem/Template/Code/${NameLower}_editor_files.cmake @@ -11,4 +11,5 @@ set(FILES Source/${Name}ModuleInterface.h Source/${Name}EditorSystemComponent.cpp Source/${Name}EditorSystemComponent.h + Source/${Name}.qrc ) diff --git a/Templates/PythonToolGem/Template/Code/CMakeLists.txt b/Templates/PythonToolGem/Template/Code/CMakeLists.txt index b7a5ac89a9..a6044e717b 100644 --- a/Templates/PythonToolGem/Template/Code/CMakeLists.txt +++ b/Templates/PythonToolGem/Template/Code/CMakeLists.txt @@ -26,6 +26,7 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS) ly_add_target( NAME ${Name}.Editor.Static STATIC NAMESPACE Gem + AUTORCC FILES_CMAKE ${NameLower}_editor_files.cmake INCLUDE_DIRECTORIES diff --git a/Templates/PythonToolGem/Template/Code/Source/${Name}.qrc b/Templates/PythonToolGem/Template/Code/Source/${Name}.qrc new file mode 100644 index 0000000000..90d7695b88 --- /dev/null +++ b/Templates/PythonToolGem/Template/Code/Source/${Name}.qrc @@ -0,0 +1,5 @@ + + + toolbar_icon.svg + + diff --git a/Templates/PythonToolGem/Template/Code/Source/${Name}EditorModule.cpp b/Templates/PythonToolGem/Template/Code/Source/${Name}EditorModule.cpp index 644c513747..0027af011a 100644 --- a/Templates/PythonToolGem/Template/Code/Source/${Name}EditorModule.cpp +++ b/Templates/PythonToolGem/Template/Code/Source/${Name}EditorModule.cpp @@ -11,6 +11,12 @@ #include <${Name}ModuleInterface.h> #include <${Name}EditorSystemComponent.h> +void Init${SanitizedCppName}Resources() +{ + // We must register our Qt resources (.qrc file) since this is being loaded from a separate module (gem) + Q_INIT_RESOURCE(${SanitizedCppName}); +} + namespace ${SanitizedCppName} { class ${SanitizedCppName}EditorModule @@ -22,6 +28,8 @@ namespace ${SanitizedCppName} ${SanitizedCppName}EditorModule() { + Init${SanitizedCppName}Resources(); + // Push results of [MyComponent]::CreateDescriptor() into m_descriptors here. // Add ALL components descriptors associated with this gem to m_descriptors. // This will associate the AzTypeInfo information for the components with the the SerializeContext, BehaviorContext and EditContext. diff --git a/Templates/PythonToolGem/Template/Code/Source/toolbar_icon.svg b/Templates/PythonToolGem/Template/Code/Source/toolbar_icon.svg new file mode 100644 index 0000000000..59de66961c --- /dev/null +++ b/Templates/PythonToolGem/Template/Code/Source/toolbar_icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Templates/PythonToolGem/Template/Editor/Scripts/${NameLower}_dialog.py b/Templates/PythonToolGem/Template/Editor/Scripts/${NameLower}_dialog.py index 39515711ae..3a0e6c9a7b 100644 --- a/Templates/PythonToolGem/Template/Editor/Scripts/${NameLower}_dialog.py +++ b/Templates/PythonToolGem/Template/Editor/Scripts/${NameLower}_dialog.py @@ -6,24 +6,15 @@ SPDX-License-Identifier: Apache-2.0 OR MIT """ # ------------------------------------------------------------------------- """${SanitizedCppName}\\editor\\scripts\\${SanitizedCppName}_dialog.py -Generated from O3DE PythonGem Template""" +Generated from O3DE PythonToolGem Template""" -import azlmbr -from shiboken2 import wrapInstance, getCppPointer -from PySide2 import QtCore, QtWidgets, QtGui -from PySide2.QtCore import QEvent, Qt -from PySide2.QtWidgets import QVBoxLayout, QAction, QDialog, QHeaderView, QLabel, QLineEdit, QPushButton, QSplitter, QTreeWidget, QTreeWidgetItem, QWidget, QAbstractButton - -# Once PySide2 has been bootstrapped, register our ${SanitizedCppName}Dialog with the Editor +from PySide2.QtCore import Qt +from PySide2.QtWidgets import QDialog, QLabel, QVBoxLayout class ${SanitizedCppName}Dialog(QDialog): def __init__(self, parent=None): super(${SanitizedCppName}Dialog, self).__init__(parent) - self.setObjectName("${SanitizedCppName}Dialog") - - self.setWindowTitle("HelloWorld, ${SanitizedCppName} Dialog") - self.mainLayout = QVBoxLayout(self) self.introLabel = QLabel("Put your cool stuff here!") @@ -43,4 +34,11 @@ class ${SanitizedCppName}Dialog(QDialog): self.setLayout(self.mainLayout) - return \ No newline at end of file + +if __name__ == "__main__": + # Create a new instance of the tool if launched from the Python Scripts window, + # which allows for quick iteration without having to close/re-launch the Editor + test_dialog = ${SanitizedCppName}Dialog() + test_dialog.setWindowTitle("${SanitizedCppName}") + test_dialog.show() + test_dialog.adjustSize() diff --git a/Templates/PythonToolGem/Template/Editor/Scripts/bootstrap.py b/Templates/PythonToolGem/Template/Editor/Scripts/bootstrap.py index 060116d36c..d49babfa96 100644 --- a/Templates/PythonToolGem/Template/Editor/Scripts/bootstrap.py +++ b/Templates/PythonToolGem/Template/Editor/Scripts/bootstrap.py @@ -6,112 +6,17 @@ SPDX-License-Identifier: Apache-2.0 OR MIT """ # ------------------------------------------------------------------------- """${SanitizedCppName}\\editor\\scripts\\boostrap.py -Generated from O3DE PythonGem Template""" +Generated from O3DE PythonToolGem Template""" -import azlmbr import az_qt_helpers -from PySide2 import QtCore, QtWidgets, QtGui -from PySide2.QtCore import QEvent, Qt -from PySide2.QtWidgets import QMainWindow, QAction, QDialog, QHeaderView, QLabel, QLineEdit, QPushButton, QSplitter, QTreeWidget, QTreeWidgetItem, QWidget, QAbstractButton -# ------------------------------------------------------------------------- - - -# ------------------------------------------------------------------------- -class SampleUI(QtWidgets.QDialog): - """Lightweight UI Test Class created a button""" - def __init__(self, parent, title='Not Set'): - super(SampleUI, self).__init__(parent) - self.setWindowTitle(title) - self.initUI() - - def initUI(self): - mainLayout = QtWidgets.QHBoxLayout() - testBtn = QtWidgets.QPushButton("I am just a Button man!") - mainLayout.addWidget(testBtn) - self.setLayout(mainLayout) -# ------------------------------------------------------------------------- +import azlmbr.editor as editor +from ${NameLower}_dialog import ${SanitizedCppName}Dialog if __name__ == "__main__": - print("${SanitizedCppName}.boostrap, Generated from O3DE PythonGem Template") - - # --------------------------------------------------------------------- - # validate pyside before continuing - try: - azlmbr.qt.QtForPythonRequestBus(azlmbr.bus.Broadcast, 'IsActive') - params = azlmbr.qt.QtForPythonRequestBus(azlmbr.bus.Broadcast, 'GetQtBootstrapParameters') - params is not None and params.mainWindowId is not 0 - from PySide2 import QtWidgets - except Exception as e: - _LOGGER.error(f'Pyside not available, exception: {e}') - raise e - - # keep going, import the other PySide2 bits we will use - from PySide2 import QtGui - from PySide2.QtCore import Slot - from shiboken2 import wrapInstance, getCppPointer + print("${SanitizedCppName}.boostrap, Generated from O3DE PythonToolGem Template") - # Get our Editor main window - _widget_main_window = None - try: - _widget_main_window = az_qt_helpers.get_editor_main_window() - except: - pass # may be booting in the AP? - # --------------------------------------------------------------------- - - - # --------------------------------------------------------------------- - if _widget_main_window: - # creat a custom menu - _tag_str = '${SanitizedCppName}' - - # create our own menuBar - ${SanitizedCppName}_menu = _widget_main_window.menuBar().addMenu(f"&{_tag_str}") - - # nest a menu for util/tool launching - ${SanitizedCppName}_launch_menu = ${SanitizedCppName}_menu.addMenu("examples") - else: - print('No O3DE MainWindow') - # --------------------------------------------------------------------- - - - # --------------------------------------------------------------------- - if _widget_main_window: - # (1) add the first SampleUI - action_launch_sample_ui = ${SanitizedCppName}_launch_menu.addAction("O3DE:SampleUI") - - @Slot() - def clicked_sample_ui(): - while 1: # simple PySide2 test, set to 0 to disable - ui = SampleUI(parent=_widget_main_window, title='O3DE:SampleUI') - ui.show() - break - return - # Add click event to menu bar - action_launch_sample_ui.triggered.connect(clicked_sample_ui) - # --------------------------------------------------------------------- - - - # --------------------------------------------------------------------- - if _widget_main_window: - # (1) and custom external module Qwidget - action_launch_${SanitizedCppName}_dialog = ${SanitizedCppName}_launch_menu.addAction("O3DE:${SanitizedCppName}_dialog") - - @Slot() - def clicked_${SanitizedCppName}_dialog(): - while 1: # simple PySide2 test, set to 0 to disable - try: - import az_qt_helpers - from ${NameLower}_dialog import ${SanitizedCppName}Dialog - az_qt_helpers.register_view_pane('${SanitizedCppName} Popup', ${SanitizedCppName}Dialog) - except Exception as e: - print(f'Error: {e}') - print('Skipping register our ${SanitizedCppName}Dialog with the Editor.') - ${SanitizedCppName}_dialog = ${SanitizedCppName}Dialog(parent=_widget_main_window) - ${SanitizedCppName}_dialog.show() - break - return - # Add click event to menu bar - action_launch_${SanitizedCppName}_dialog.triggered.connect(clicked_${SanitizedCppName}_dialog) - # --------------------------------------------------------------------- - - # end \ No newline at end of file + # Register our custom widget as a dockable tool with the Editor under an Examples sub-menu + options = editor.ViewPaneOptions() + options.showOnToolsToolbar = True + options.toolbarIcon = ":/${Name}/toolbar_icon.svg" + az_qt_helpers.register_view_pane('${SanitizedCppName}', ${SanitizedCppName}Dialog, category="Examples", options=options) diff --git a/Templates/PythonToolGem/Template/gem.json b/Templates/PythonToolGem/Template/gem.json index 353ad6bf8d..d4ff637bee 100644 --- a/Templates/PythonToolGem/Template/gem.json +++ b/Templates/PythonToolGem/Template/gem.json @@ -1,7 +1,8 @@ { "gem_name": "${Name}", "display_name": "${Name}", - "license": "What license ${Name} uses goes here: i.e. https://opensource.org/licenses/MIT", + "license": "What license ${Name} uses goes here: i.e. Apache-2.0 Or MIT", + "license_url": "", "origin": "The primary repo for ${Name} goes here: i.e. http://www.mydomain.com", "type": "Code", "summary": "A short description of ${Name}.", diff --git a/Templates/PythonToolGem/template.json b/Templates/PythonToolGem/template.json index 4d85373ead..6dc68de3fc 100644 --- a/Templates/PythonToolGem/template.json +++ b/Templates/PythonToolGem/template.json @@ -12,6 +12,12 @@ ], "icon_path": "preview.png", "copyFiles": [ + { + "file": ".gitignore", + "origin": ".gitignore", + "isTemplated": false, + "isOptional": false + }, { "file": "CMakeLists.txt", "origin": "CMakeLists.txt", @@ -102,6 +108,12 @@ "isTemplated": true, "isOptional": false }, + { + "file": "Code/Source/${Name}.qrc", + "origin": "Code/Source/${Name}.qrc", + "isTemplated": true, + "isOptional": false + }, { "file": "Code/Source/${Name}EditorModule.cpp", "origin": "Code/Source/${Name}EditorModule.cpp", @@ -126,6 +138,12 @@ "isTemplated": true, "isOptional": false }, + { + "file": "Code/Source/toolbar_icon.svg", + "origin": "Code/Source/toolbar_icon.svg", + "isTemplated": false, + "isOptional": false + }, { "file": "Code/Tests/${Name}EditorTest.cpp", "origin": "Code/Tests/${Name}EditorTest.cpp", diff --git a/Tools/LyTestTools/ly_test_tools/o3de/asset_processor.py b/Tools/LyTestTools/ly_test_tools/o3de/asset_processor.py index 682fcd3560..2019f21004 100644 --- a/Tools/LyTestTools/ly_test_tools/o3de/asset_processor.py +++ b/Tools/LyTestTools/ly_test_tools/o3de/asset_processor.py @@ -195,8 +195,11 @@ class AssetProcessor(object): logger.debug("Failed to read port from file", exc_info=ex) return False + # the timeout needs to be large enough to load all the dynamic libraries the AP-GUI loads since the control port + # is opened after all the DLL loads, this can take a long time in a Debug build + ap_max_activate_time = 60 err = AssetProcessorError(f"Failed to read port type {port_type} from {self._workspace.paths.ap_gui_log()}") - waiter.wait_for(_get_port_from_log, timeout=10, exc=err) + waiter.wait_for(_get_port_from_log, timeout=ap_max_activate_time, exc=err) return port def set_control_connection(self, connection): diff --git a/cmake/LYWrappers.cmake b/cmake/LYWrappers.cmake index fb3d420c26..0416d41ece 100644 --- a/cmake/LYWrappers.cmake +++ b/cmake/LYWrappers.cmake @@ -403,7 +403,7 @@ function(ly_target_link_libraries TARGET) message(FATAL_ERROR "You must provide a target") endif() - set_property(GLOBAL APPEND PROPERTY LY_DELAYED_LINK_${TARGET} ${ARGN}) + set_property(TARGET ${TARGET} APPEND PROPERTY LY_DELAYED_LINK ${ARGN}) set_property(GLOBAL APPEND PROPERTY LY_DELAYED_LINK_TARGETS ${TARGET}) # to walk them at the end endfunction() @@ -430,7 +430,7 @@ function(ly_delayed_target_link_libraries) get_property(delayed_targets GLOBAL PROPERTY LY_DELAYED_LINK_TARGETS) foreach(target ${delayed_targets}) - get_property(delayed_link GLOBAL PROPERTY LY_DELAYED_LINK_${target}) + get_property(delayed_link TARGET ${target} PROPERTY LY_DELAYED_LINK) if(delayed_link) cmake_parse_arguments(ly_delayed_target_link_libraries "" "" "${visibilities}" ${delayed_link}) @@ -458,7 +458,6 @@ function(ly_delayed_target_link_libraries) endforeach() endforeach() - set_property(GLOBAL PROPERTY LY_DELAYED_LINK_${target}) endif() diff --git a/cmake/Platform/Common/Install_common.cmake b/cmake/Platform/Common/Install_common.cmake index 44cdeb1994..f22f4c43c9 100644 --- a/cmake/Platform/Common/Install_common.cmake +++ b/cmake/Platform/Common/Install_common.cmake @@ -157,16 +157,15 @@ function(ly_setup_target OUTPUT_CONFIGURED_TARGET ALIAS_TARGET_NAME absolute_tar endif() # Includes need additional processing to add the install root - if(include_directories) - foreach(include ${include_directories}) - string(GENEX_STRIP ${include} include_genex_expr) - if(include_genex_expr STREQUAL include) # only for cases where there are no generation expressions - # Make the include path relative to the source dir where the target will be declared - cmake_path(RELATIVE_PATH include BASE_DIRECTORY ${absolute_target_source_dir} OUTPUT_VARIABLE target_include) - string(APPEND INCLUDE_DIRECTORIES_PLACEHOLDER "${PLACEHOLDER_INDENT}${target_include}\n") - endif() - endforeach() - endif() + foreach(include IN LISTS include_directories) + string(GENEX_STRIP ${include} include_genex_expr) + if(include_genex_expr STREQUAL include) # only for cases where there are no generation expressions + # Make the include path relative to the source dir where the target will be declared + cmake_path(RELATIVE_PATH include BASE_DIRECTORY ${absolute_target_source_dir} OUTPUT_VARIABLE target_include) + list(APPEND INCLUDE_DIRECTORIES_PLACEHOLDER "${PLACEHOLDER_INDENT}${target_include}") + endif() + endforeach() + list(JOIN INCLUDE_DIRECTORIES_PLACEHOLDER "\n" INCLUDE_DIRECTORIES_PLACEHOLDER) string(REPEAT " " 8 PLACEHOLDER_INDENT) get_target_property(RUNTIME_DEPENDENCIES_PLACEHOLDER ${TARGET_NAME} MANUALLY_ADDED_DEPENDENCIES) @@ -178,27 +177,27 @@ function(ly_setup_target OUTPUT_CONFIGURED_TARGET ALIAS_TARGET_NAME absolute_tar endif() string(REPEAT " " 12 PLACEHOLDER_INDENT) - get_target_property(inteface_build_dependencies_props ${TARGET_NAME} INTERFACE_LINK_LIBRARIES) + get_property(interface_build_dependencies_props TARGET ${TARGET_NAME} PROPERTY LY_DELAYED_LINK) unset(INTERFACE_BUILD_DEPENDENCIES_PLACEHOLDER) - if(inteface_build_dependencies_props) - foreach(build_dependency ${inteface_build_dependencies_props}) + if(interface_build_dependencies_props) + cmake_parse_arguments(build_deps "" "" "PRIVATE;PUBLIC;INTERFACE" ${interface_build_dependencies_props}) + # Interface and public dependencies should always be exposed + set(build_deps_target ${build_deps_INTERFACE}) + if(build_deps_PUBLIC) + set(build_deps_target "${build_deps_target};${build_deps_PUBLIC}") + endif() + # Private dependencies should only be exposed if it is a static library, since in those cases, link + # dependencies are transfered to the downstream dependencies + if("${target_type}" STREQUAL "STATIC_LIBRARY") + set(build_deps_target "${build_deps_target};${build_deps_PRIVATE}") + endif() + foreach(build_dependency IN LISTS build_deps_target) # Skip wrapping produced when targets are not created in the same directory - if(NOT ${build_dependency} MATCHES "^::@") + if(build_dependency) list(APPEND INTERFACE_BUILD_DEPENDENCIES_PLACEHOLDER "${PLACEHOLDER_INDENT}${build_dependency}") endif() endforeach() endif() - # We also need to pass the private link libraries since we will use that to generate the runtime dependencies - get_target_property(private_build_dependencies_props ${TARGET_NAME} LINK_LIBRARIES) - if(private_build_dependencies_props) - foreach(build_dependency ${private_build_dependencies_props}) - # Skip wrapping produced when targets are not created in the same directory - if(NOT ${build_dependency} MATCHES "^::@") - list(APPEND INTERFACE_BUILD_DEPENDENCIES_PLACEHOLDER "${PLACEHOLDER_INDENT}${build_dependency}") - endif() - endforeach() - endif() - list(REMOVE_DUPLICATES INTERFACE_BUILD_DEPENDENCIES_PLACEHOLDER) list(JOIN INTERFACE_BUILD_DEPENDENCIES_PLACEHOLDER "\n" INTERFACE_BUILD_DEPENDENCIES_PLACEHOLDER) string(REPEAT " " 8 PLACEHOLDER_INDENT) @@ -322,7 +321,7 @@ include(Platform/${PAL_PLATFORM_NAME}/platform_${PAL_PLATFORM_NAME_LOWERCASE}.cm file(CONFIGURE OUTPUT "${target_install_source_dir}/Platform/${PAL_PLATFORM_NAME}/platform_${PAL_PLATFORM_NAME_LOWERCASE}.cmake" CONTENT [[ @cmake_copyright_comment@ if(LY_MONOLITHIC_GAME) - include(Platform/${PAL_PLATFORM_NAME}/Monolithic/permutation.cmake) + include(Platform/${PAL_PLATFORM_NAME}/Monolithic/permutation.cmake OPTIONAL) else() include(Platform/${PAL_PLATFORM_NAME}/Default/permutation.cmake) endif() @@ -354,29 +353,6 @@ endif() endfunction() -#! ly_setup_o3de_install: orchestrates the installation of the different parts. This is the entry point from the root CMakeLists.txt -function(ly_setup_o3de_install) - - ly_setup_subdirectories() - ly_setup_cmake_install() - ly_setup_runtime_dependencies() - ly_setup_assets() - - # Misc - install(FILES - ${LY_ROOT_FOLDER}/pytest.ini - ${LY_ROOT_FOLDER}/LICENSE.txt - ${LY_ROOT_FOLDER}/README.md - DESTINATION . - COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} - ) - - if(COMMAND ly_post_install_steps) - ly_post_install_steps() - endif() - -endfunction() - #! ly_setup_cmake_install: install the "cmake" folder function(ly_setup_cmake_install) @@ -385,8 +361,10 @@ function(ly_setup_cmake_install) COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} PATTERN "__pycache__" EXCLUDE PATTERN "Findo3de.cmake" EXCLUDE + PATTERN "cmake/ConfigurationTypes.cmake" EXCLUDE REGEX "3rdParty/Platform\/.*\/BuiltInPackages_.*\.cmake" EXCLUDE ) + # Connect configuration types install(FILES "${LY_ROOT_FOLDER}/cmake/install/ConfigurationTypes.cmake" DESTINATION cmake @@ -446,6 +424,7 @@ function(ly_setup_cmake_install) list(APPEND additional_platform_files "${plat_files}") endforeach() endforeach() + install(FILES ${additional_find_files} DESTINATION cmake/3rdParty COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} @@ -455,40 +434,68 @@ function(ly_setup_cmake_install) COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} ) - # Findo3de.cmake file: we generate a different Findo3de.camke file than the one we have in cmake. This one is going to expose all - # targets that are pre-built - unset(FIND_PACKAGES_PLACEHOLDER) - - # Add to the FIND_PACKAGES_PLACEHOLDER all directories in which ly_add_target were called in - get_property(all_subdirectories GLOBAL PROPERTY LY_ALL_TARGET_DIRECTORIES) - foreach(target_subdirectory IN LISTS all_subdirectories) - cmake_path(RELATIVE_PATH target_subdirectory BASE_DIRECTORY ${LY_ROOT_FOLDER} OUTPUT_VARIABLE relative_target_subdirectory) - string(APPEND FIND_PACKAGES_PLACEHOLDER " add_subdirectory(${relative_target_subdirectory})\n") - endforeach() - + # Findo3de.cmake file: we generate a different Findo3de.cmake file than the one we have in the source dir. configure_file(${LY_ROOT_FOLDER}/cmake/install/Findo3de.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/cmake/Findo3de.cmake @ONLY) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/cmake/Findo3de.cmake" DESTINATION cmake COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} ) - # BuiltInPackage_.cmake: since associations could happen in any cmake file across the engine. We collect - # all the associations in ly_associate_package and then generate them into BuiltInPackages_.cmake. This - # will consolidate all associations in one file + unset(find_subdirectories) + # Add to find_subdirectories all directories in which ly_add_target were called in + get_property(all_subdirectories GLOBAL PROPERTY LY_ALL_TARGET_DIRECTORIES) + foreach(target_subdirectory IN LISTS all_subdirectories) + cmake_path(RELATIVE_PATH target_subdirectory BASE_DIRECTORY ${LY_ROOT_FOLDER} OUTPUT_VARIABLE relative_target_subdirectory) + string(APPEND find_subdirectories "add_subdirectory(${relative_target_subdirectory})\n") + endforeach() + set(permutation_find_subdirectories ${CMAKE_CURRENT_BINARY_DIR}/cmake/Platform/${PAL_PLATFORM_NAME}/${LY_BUILD_PERMUTATION}/o3de_subdirectories_${PAL_PLATFORM_NAME_LOWERCASE}.cmake) + file(GENERATE OUTPUT ${permutation_find_subdirectories} + CONTENT +"# Generated by O3DE install\n +${find_subdirectories} +" + ) + install(FILES "${permutation_find_subdirectories}" + DESTINATION cmake/Platform/${PAL_PLATFORM_NAME}/${LY_BUILD_PERMUTATION} + COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} + ) + + set(pal_builtin_file ${CMAKE_CURRENT_BINARY_DIR}/cmake/3rdParty/Platform/${PAL_PLATFORM_NAME}/BuiltInPackages_${PAL_PLATFORM_NAME_LOWERCASE}.cmake) + file(GENERATE OUTPUT ${pal_builtin_file} + CONTENT +"# Generated by O3DE install\n +if(LY_MONOLITHIC_GAME) + include(cmake/3rdParty/Platform/${PAL_PLATFORM_NAME}/Monolithic/BuiltInPackages_${PAL_PLATFORM_NAME_LOWERCASE}.cmake) +else() + include(cmake/3rdParty/Platform/${PAL_PLATFORM_NAME}/Default/BuiltInPackages_${PAL_PLATFORM_NAME_LOWERCASE}.cmake) +endif() +" + ) + install(FILES "${pal_builtin_file}" + DESTINATION cmake/3rdParty/Platform/${PAL_PLATFORM_NAME} + COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} + ) + + # ${LY_BUILD_PERMUTATION}/BuiltInPackage_.cmake: since associations could happen in any cmake file across the engine. We collect + # all the associations in ly_associate_package and then generate them into BuiltInPackages_.cmake. This will consolidate all + # associations in one file + # Associations are sensitive to platform and build permutation, so we make different files for each. get_property(all_package_names GLOBAL PROPERTY LY_PACKAGE_NAMES) + list(REMOVE_DUPLICATES all_package_names) set(builtinpackages "# Generated by O3DE install\n\n") foreach(package_name IN LISTS all_package_names) get_property(package_hash GLOBAL PROPERTY LY_PACKAGE_HASH_${package_name}) get_property(targets GLOBAL PROPERTY LY_PACKAGE_TARGETS_${package_name}) + list(REMOVE_DUPLICATES targets) string(APPEND builtinpackages "ly_associate_package(PACKAGE_NAME ${package_name} TARGETS ${targets} PACKAGE_HASH ${package_hash})\n") endforeach() - set(pal_builtin_file ${CMAKE_CURRENT_BINARY_DIR}/cmake/3rdParty/Platform/${PAL_PLATFORM_NAME}/BuiltInPackages_${PAL_PLATFORM_NAME_LOWERCASE}.cmake) - file(GENERATE OUTPUT ${pal_builtin_file} + set(permutation_builtin_file ${CMAKE_CURRENT_BINARY_DIR}/cmake/3rdParty/Platform/${PAL_PLATFORM_NAME}/${LY_BUILD_PERMUTATION}/BuiltInPackages_${PAL_PLATFORM_NAME_LOWERCASE}.cmake) + file(GENERATE OUTPUT ${permutation_builtin_file} CONTENT ${builtinpackages} ) - install(FILES "${pal_builtin_file}" - DESTINATION cmake/3rdParty/Platform/${PAL_PLATFORM_NAME} + install(FILES "${permutation_builtin_file}" + DESTINATION cmake/3rdParty/Platform/${PAL_PLATFORM_NAME}/${LY_BUILD_PERMUTATION} COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} ) @@ -632,6 +639,7 @@ function(ly_setup_assets) if (NOT gem_install_dest_dir) cmake_path(SET gem_install_dest_dir .) endif() + if(IS_DIRECTORY ${gem_absolute_path}) install(DIRECTORY "${gem_absolute_path}" DESTINATION ${gem_install_dest_dir} @@ -702,4 +710,37 @@ function(ly_setup_subdirectory_enable_gems absolute_target_source_dir output_scr string(APPEND enable_gems_calls ${enable_gems_command}) endforeach() set(${output_script} ${enable_gems_calls} PARENT_SCOPE) +endfunction() + +#! ly_setup_o3de_install: orchestrates the installation of the different parts. This is the entry point from the root CMakeLists.txt +function(ly_setup_o3de_install) + + ly_setup_subdirectories() + ly_setup_cmake_install() + ly_setup_runtime_dependencies() + ly_setup_assets() + + # Misc + install(FILES + ${LY_ROOT_FOLDER}/pytest.ini + ${LY_ROOT_FOLDER}/LICENSE.txt + ${LY_ROOT_FOLDER}/README.md + DESTINATION . + COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} + ) + + # Inject other build directories + foreach(external_dir ${LY_INSTALL_EXTERNAL_BUILD_DIRS}) + install(CODE +"set(LY_CORE_COMPONENT_ALREADY_INCLUDED TRUE) +include(${external_dir}/cmake_install.cmake) +set(LY_CORE_COMPONENT_ALREADY_INCLUDED FALSE)" + ALL_COMPONENTS + ) + endforeach() + + if(COMMAND ly_post_install_steps) + ly_post_install_steps() + endif() + endfunction() \ No newline at end of file diff --git a/cmake/Projects.cmake b/cmake/Projects.cmake index c09fe0fc6f..2c42533e35 100644 --- a/cmake/Projects.cmake +++ b/cmake/Projects.cmake @@ -132,6 +132,65 @@ function(add_project_json_external_subdirectories project_path) endif() endfunction() +function(install_project_asset_artifacts project_real_path) + # The cmake tar command has a bit of a flaw + # Any paths within the archive files it creates are relative to the current working directory. + # That means with the setup of: + # cwd = "/Cache/pc" + # project product assets = "/Cache/pc/*" + # cmake dependency registry files = "/build/bin/Release/Registry/*" + # Running the tar command would result in the assets being placed in the to layout + # correctly, but the registry files + # engine.pak/ + # ../...build/bin/Release/Registry/cmake_dependencies.*.setreg -> Not correct + # project.json -> Correct + + # Generate pak for project in release installs + cmake_path(RELATIVE_PATH CMAKE_RUNTIME_OUTPUT_DIRECTORY BASE_DIRECTORY ${CMAKE_BINARY_DIR} OUTPUT_VARIABLE install_base_runtime_output_directory) + set(install_engine_pak_template [=[ +if("${CMAKE_INSTALL_CONFIG_NAME}" MATCHES "^([Rr][Ee][Ll][Ee][Aa][Ss][Ee])$") + set(install_output_folder "${CMAKE_INSTALL_PREFIX}/@install_base_runtime_output_directory@/@PAL_PLATFORM_NAME@/${CMAKE_INSTALL_CONFIG_NAME}/@LY_BUILD_PERMUTATION@") + set(install_pak_output_folder "${install_output_folder}/Cache/@LY_ASSET_DEPLOY_ASSET_TYPE@") + set(runtime_output_directory_RELEASE @CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE@) + if(NOT DEFINED LY_ASSET_DEPLOY_ASSET_TYPE) + set(LY_ASSET_DEPLOY_ASSET_TYPE @LY_ASSET_DEPLOY_ASSET_TYPE@) + endif() + message(STATUS "Generating ${install_pak_output_folder}/engine.pak from @project_real_path@/Cache/${LY_ASSET_DEPLOY_ASSET_TYPE}") + file(MAKE_DIRECTORY "${install_pak_output_folder}") + cmake_path(SET cache_product_path "@project_real_path@/Cache/${LY_ASSET_DEPLOY_ASSET_TYPE}") + # Copy the generated cmake_dependencies.*.setreg files for loading gems in non-monolithic to the cache + file(GLOB gem_source_paths_setreg "${runtime_output_directory_RELEASE}/Registry/*.setreg") + # The MergeSettingsToRegistry_TargetBuildDependencyRegistry function looks for lowercase "registry" directory + file(MAKE_DIRECTORY "${cache_product_path}/registry") + file(COPY ${gem_source_paths_setreg} DESTINATION "${cache_product_path}/registry") + + file(GLOB product_assets "${cache_product_path}/*") + list(APPEND pak_artifacts ${product_assets}) + if(pak_artifacts) + execute_process( + COMMAND ${CMAKE_COMMAND} -E tar "cf" "${install_pak_output_folder}/engine.pak" --format=zip -- ${pak_artifacts} + WORKING_DIRECTORY "${cache_product_path}" + RESULT_VARIABLE archive_creation_result + ) + if(archive_creation_result EQUAL 0) + message(STATUS "${install_output_folder}/engine.pak generated") + endif() + endif() + + # Remove copied .setreg files from the Cache directory + unset(artifacts_to_remove) + foreach(gem_source_path_setreg IN LISTS gem_source_paths_setreg) + cmake_path(GET gem_source_path_setreg FILENAME setreg_filename) + list(APPEND artifacts_to_remove "${cache_product_path}/registry/${setreg_filename}") + endforeach() + file(REMOVE ${artifacts_to_remove}) +endif() +]=]) + + string(CONFIGURE "${install_engine_pak_template}" install_engine_pak_code @ONLY) + ly_install_run_code("${install_engine_pak_code}") +endfunction() + # Add the projects here so the above function is found foreach(project ${LY_PROJECTS}) file(REAL_PATH ${project} full_directory_path BASE_DIRECTORY ${CMAKE_SOURCE_DIR}) @@ -150,34 +209,7 @@ foreach(project ${LY_PROJECTS}) # Get project name o3de_read_json_key(project_name ${full_directory_path}/project.json "project_name") - # Generate pak for project in release installs - cmake_path(RELATIVE_PATH CMAKE_RUNTIME_OUTPUT_DIRECTORY BASE_DIRECTORY ${CMAKE_BINARY_DIR} OUTPUT_VARIABLE install_base_runtime_output_directory) - set(install_engine_pak_template [=[ -if("${CMAKE_INSTALL_CONFIG_NAME}" MATCHES "^([Rr][Ee][Ll][Ee][Aa][Ss][Ee])$") - set(install_output_folder "${CMAKE_INSTALL_PREFIX}/@install_base_runtime_output_directory@/@PAL_PLATFORM_NAME@/${CMAKE_INSTALL_CONFIG_NAME}/@LY_BUILD_PERMUTATION@") - set(install_pak_output_folder "${install_output_folder}/Cache/@LY_ASSET_DEPLOY_ASSET_TYPE@") - if(NOT DEFINED LY_ASSET_DEPLOY_ASSET_TYPE) - set(LY_ASSET_DEPLOY_ASSET_TYPE @LY_ASSET_DEPLOY_ASSET_TYPE@) - endif() - message(STATUS "Generating ${install_pak_output_folder}/engine.pak from @full_directory_path@/Cache/${LY_ASSET_DEPLOY_ASSET_TYPE}") - file(MAKE_DIRECTORY "${install_pak_output_folder}") - cmake_path(SET cache_product_path "@full_directory_path@/Cache/${LY_ASSET_DEPLOY_ASSET_TYPE}") - file(GLOB product_assets "${cache_product_path}/*") - if(product_assets) - execute_process( - COMMAND ${CMAKE_COMMAND} -E tar "cf" "${install_pak_output_folder}/engine.pak" --format=zip -- ${product_assets} - WORKING_DIRECTORY "${cache_product_path}" - RESULT_VARIABLE archive_creation_result - ) - if(archive_creation_result EQUAL 0) - message(STATUS "${install_output_folder}/engine.pak generated") - endif() - endif() -endif() -]=]) - - string(CONFIGURE "${install_engine_pak_template}" install_engine_pak_code @ONLY) - ly_install_run_code("${install_engine_pak_code}") + install_project_asset_artifacts(${full_directory_path}) endforeach() diff --git a/cmake/install/Findo3de.cmake.in b/cmake/install/Findo3de.cmake.in index e267b6b5c6..c3db7f1ec6 100644 --- a/cmake/install/Findo3de.cmake.in +++ b/cmake/install/Findo3de.cmake.in @@ -12,7 +12,15 @@ include(FindPackageHandleStandardArgs) # This will be called from within the installed engine's CMakeLists.txt macro(ly_find_o3de_packages) -@FIND_PACKAGES_PLACEHOLDER@ + if(LY_MONOLITHIC_GAME) + set(monolithic_file "${LY_ROOT_FOLDER}/cmake/Platform/${PAL_PLATFORM_NAME}/Monolithic/o3de_subdirectories_${PAL_PLATFORM_NAME_LOWERCASE}.cmake") + if(NOT EXISTS ${monolithic_file}) + message(FATAL_ERROR "O3DE SDK was not generated to support monolithic builds") + endif() + include("${monolithic_file}") + else() + include("${LY_ROOT_FOLDER}/cmake/Platform/${PAL_PLATFORM_NAME}/Default/o3de_subdirectories_${PAL_PLATFORM_NAME_LOWERCASE}.cmake") + endif() find_package(LauncherGenerator) endmacro() diff --git a/scripts/build/Platform/Linux/asset_linux.sh b/scripts/build/Platform/Linux/asset_linux.sh index df910db646..10f7ee6b6f 100755 --- a/scripts/build/Platform/Linux/asset_linux.sh +++ b/scripts/build/Platform/Linux/asset_linux.sh @@ -9,6 +9,8 @@ set -o errexit # exit on the first failure encountered +SOURCE_DIRECTORY=${PWD} + if [[ ! -d $OUTPUT_DIRECTORY ]]; then echo [ci_build] Error: $OUTPUT_DIRECTORY was not found exit 1 @@ -22,8 +24,8 @@ fi for project in $(echo $CMAKE_LY_PROJECTS | sed "s/;/ /g") do - echo [ci_build] ${ASSET_PROCESSOR_BINARY} $ASSET_PROCESSOR_OPTIONS --project-path=$project --platforms=$ASSET_PROCESSOR_PLATFORMS - ${ASSET_PROCESSOR_BINARY} $ASSET_PROCESSOR_OPTIONS --project-path=$project --platforms=$ASSET_PROCESSOR_PLATFORMS + echo [ci_build] ${ASSET_PROCESSOR_BINARY} $ASSET_PROCESSOR_OPTIONS --project-path=$SOURCE_DIRECTORY/$project --platforms=$ASSET_PROCESSOR_PLATFORMS + ${ASSET_PROCESSOR_BINARY} $ASSET_PROCESSOR_OPTIONS --project-path=$SOURCE_DIRECTORY/$project --platforms=$ASSET_PROCESSOR_PLATFORMS done popd diff --git a/scripts/build/Platform/Linux/build_config.json b/scripts/build/Platform/Linux/build_config.json index b76a950beb..84c5976215 100644 --- a/scripts/build/Platform/Linux/build_config.json +++ b/scripts/build/Platform/Linux/build_config.json @@ -37,7 +37,7 @@ "PARAMETERS": { "CONFIGURATION": "debug", "OUTPUT_DIRECTORY": "build/linux", - "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4", + "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-12 -DCMAKE_CXX_COMPILER=clang++-12 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "all" } @@ -53,7 +53,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/linux", - "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4", + "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-12 -DCMAKE_CXX_COMPILER=clang++-12 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "all" } @@ -66,7 +66,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/linux", - "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=FALSE -DLY_PARALLEL_LINK_JOBS=4", + "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-12 -DCMAKE_CXX_COMPILER=clang++-12 -DLY_UNITY_BUILD=FALSE -DLY_PARALLEL_LINK_JOBS=4", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "all" } @@ -80,7 +80,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/linux", - "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4", + "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-12 -DCMAKE_CXX_COMPILER=clang++-12 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "all", "CTEST_OPTIONS": "-E Gem::EMotionFX.Editor.Tests -LE (SUITE_sandbox|SUITE_awsi) -L FRAMEWORK_googletest --no-tests=error", @@ -93,7 +93,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/linux", - "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=FALSE -DLY_PARALLEL_LINK_JOBS=4", + "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-12 -DCMAKE_CXX_COMPILER=clang++-12 -DLY_UNITY_BUILD=FALSE -DLY_PARALLEL_LINK_JOBS=4", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "all", "CTEST_OPTIONS": "-E Gem::EMotionFX.Editor.Tests -LE (SUITE_sandbox|SUITE_awsi) -L FRAMEWORK_googletest --no-tests=error", @@ -110,7 +110,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/linux", - "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4", + "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-12 -DCMAKE_CXX_COMPILER=clang++-12 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "AssetProcessorBatch", "ASSET_PROCESSOR_BINARY": "bin/profile/AssetProcessorBatch", @@ -124,7 +124,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/linux", - "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=FALSE -DLY_PARALLEL_LINK_JOBS=4", + "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-12 -DCMAKE_CXX_COMPILER=clang++-12 -DLY_UNITY_BUILD=FALSE -DLY_PARALLEL_LINK_JOBS=4", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "AssetProcessorBatch", "ASSET_PROCESSOR_BINARY": "bin/profile/AssetProcessorBatch", @@ -142,7 +142,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/linux", - "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4", + "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-12 -DCMAKE_CXX_COMPILER=clang++-12 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "TEST_SUITE_periodic", "CTEST_OPTIONS": "-L (SUITE_periodic) --no-tests=error", @@ -162,7 +162,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/linux", - "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4 -DO3DE_HOME_PATH=\"${WORKSPACE}/home\" -DO3DE_REGISTER_ENGINE_PATH=\"${WORKSPACE}/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", + "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-12 -DCMAKE_CXX_COMPILER=clang++-12 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4 -DO3DE_HOME_PATH=\"${WORKSPACE}/home\" -DO3DE_REGISTER_ENGINE_PATH=\"${WORKSPACE}/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "all", "CTEST_OPTIONS": "-L (SUITE_sandbox) --no-tests=error" @@ -178,7 +178,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/linux", - "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4", + "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-12 -DCMAKE_CXX_COMPILER=clang++-12 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "TEST_SUITE_benchmark", "CTEST_OPTIONS": "-L (SUITE_benchmark) --no-tests=error", @@ -195,7 +195,7 @@ "PARAMETERS": { "CONFIGURATION": "release", "OUTPUT_DIRECTORY": "build/linux", - "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4", + "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-12 -DCMAKE_CXX_COMPILER=clang++-12 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "all" } @@ -210,7 +210,7 @@ "PARAMETERS": { "CONFIGURATION": "release", "OUTPUT_DIRECTORY": "build/mono_linux", - "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_MONOLITHIC_GAME=TRUE -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4", + "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-12 -DCMAKE_CXX_COMPILER=clang++-12 -DLY_MONOLITHIC_GAME=TRUE -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "all" } diff --git a/scripts/build/Platform/Linux/pipeline.json b/scripts/build/Platform/Linux/pipeline.json index d964a693ce..7f16ec6ab5 100644 --- a/scripts/build/Platform/Linux/pipeline.json +++ b/scripts/build/Platform/Linux/pipeline.json @@ -1,6 +1,6 @@ { "ENV": { - "NODE_LABEL": "linux", + "NODE_LABEL": "linux-707531fc7", "LY_3RDPARTY_PATH": "/home/lybuilder/ly/workspace/3rdParty", "TIMEOUT": 30, "WORKSPACE": "/data/workspace", @@ -17,4 +17,4 @@ "CLEAN_WORKSPACE": true } } -} \ No newline at end of file +} diff --git a/scripts/build/Platform/Mac/asset_mac.sh b/scripts/build/Platform/Mac/asset_mac.sh index f70d898e0c..96eaeab5aa 100755 --- a/scripts/build/Platform/Mac/asset_mac.sh +++ b/scripts/build/Platform/Mac/asset_mac.sh @@ -9,6 +9,8 @@ set -o errexit # exit on the first failure encountered +SOURCE_DIRECTORY=${PWD} + if [[ ! -d $OUTPUT_DIRECTORY ]]; then echo [ci_build] Error: $OUTPUT_DIRECTORY was not found exit 1 @@ -22,8 +24,8 @@ fi for project in $(echo $CMAKE_LY_PROJECTS | sed "s/;/ /g") do - echo [ci_build] ${ASSET_PROCESSOR_BINARY} $ASSET_PROCESSOR_OPTIONS --project-path=$project --platforms=$ASSET_PROCESSOR_PLATFORMS - ${ASSET_PROCESSOR_BINARY} $ASSET_PROCESSOR_OPTIONS --project-path=$project --platforms=$ASSET_PROCESSOR_PLATFORMS + echo [ci_build] ${ASSET_PROCESSOR_BINARY} $ASSET_PROCESSOR_OPTIONS --project-path=$SOURCE_DIRECTORY/$project --platforms=$ASSET_PROCESSOR_PLATFORMS + ${ASSET_PROCESSOR_BINARY} $ASSET_PROCESSOR_OPTIONS --project-path=$SOURCE_DIRECTORY/$project --platforms=$ASSET_PROCESSOR_PLATFORMS done popd diff --git a/scripts/build/Platform/Windows/asset_windows.cmd b/scripts/build/Platform/Windows/asset_windows.cmd index 8db0e43e31..cc266ba42a 100644 --- a/scripts/build/Platform/Windows/asset_windows.cmd +++ b/scripts/build/Platform/Windows/asset_windows.cmd @@ -9,6 +9,8 @@ REM SETLOCAL EnableDelayedExpansion +SET SOURCE_DIRECTORY=%CD% + IF NOT EXIST %OUTPUT_DIRECTORY% ( ECHO [ci_build] Error: %OUTPUT_DIRECTORY% was not found GOTO :error @@ -21,8 +23,8 @@ IF NOT EXIST %ASSET_PROCESSOR_BINARY% ( ) FOR %%P in (%CMAKE_LY_PROJECTS%) do ( - ECHO [ci_build] %ASSET_PROCESSOR_BINARY% %ASSET_PROCESSOR_OPTIONS% --project-path=%%P --platforms=%ASSET_PROCESSOR_PLATFORMS% - %ASSET_PROCESSOR_BINARY% %ASSET_PROCESSOR_OPTIONS% --project-path=%%P --platforms=%ASSET_PROCESSOR_PLATFORMS% + ECHO [ci_build] %ASSET_PROCESSOR_BINARY% %ASSET_PROCESSOR_OPTIONS% --project-path=%SOURCE_DIRECTORY%/%%P --platforms=%ASSET_PROCESSOR_PLATFORMS% + %ASSET_PROCESSOR_BINARY% %ASSET_PROCESSOR_OPTIONS% --project-path=%SOURCE_DIRECTORY%/%%P --platforms=%ASSET_PROCESSOR_PLATFORMS% IF NOT !ERRORLEVEL!==0 GOTO :popd_error ) diff --git a/scripts/o3de/o3de/gem_properties.py b/scripts/o3de/o3de/gem_properties.py index 39011a213a..faaa4f86ec 100644 --- a/scripts/o3de/o3de/gem_properties.py +++ b/scripts/o3de/o3de/gem_properties.py @@ -54,6 +54,8 @@ def edit_gem_props(gem_path: pathlib.Path = None, new_icon: str = None, new_requirements: str = None, new_documentation_url: str = None, + new_license: str = None, + new_license_url: str = None, new_tags: list or str = None, remove_tags: list or str = None, replace_tags: list or str = None, @@ -94,6 +96,10 @@ def edit_gem_props(gem_path: pathlib.Path = None, update_key_dict['requirements'] = new_requirements if new_documentation_url: update_key_dict['documentation_url'] = new_documentation_url + if new_license: + update_key_dict['license'] = new_license + if new_license_url: + update_key_dict['license_url'] = new_license_url update_key_dict['user_tags'] = update_values_in_key_list(gem_json_data.get('user_tags', []), new_tags, remove_tags, replace_tags) @@ -114,6 +120,8 @@ def _edit_gem_props(args: argparse) -> int: args.gem_icon, args.gem_requirements, args.gem_documentation_url, + args.gem_license, + args.gem_license_url, args.add_tags, args.remove_tags, args.replace_tags) @@ -142,6 +150,10 @@ def add_parser_args(parser): help='Sets the description of the requirements needed to use the gem.') group.add_argument('-gdu', '--gem-documentation-url', type=str, required=False, help='Sets the url for documentation of the gem.') + group.add_argument('-gl', '--gem-license', type=str, required=False, + help='Sets the name for the license of the gem.') + group.add_argument('-glu', '--gem-license-url', type=str, required=False, + help='Sets the url for the license of the gem.') group = parser.add_mutually_exclusive_group(required=False) group.add_argument('-at', '--add-tags', type=str, nargs='*', required=False, help='Adds tag(s) to user_tags property. Can be specified multiple times.') diff --git a/scripts/o3de/o3de/register.py b/scripts/o3de/o3de/register.py index 6ad54a11f3..8a2bb788aa 100644 --- a/scripts/o3de/o3de/register.py +++ b/scripts/o3de/o3de/register.py @@ -477,11 +477,11 @@ def register_repo(json_data: dict, parsed_uri = urllib.parse.urlparse(url) if parsed_uri.scheme in ['http', 'https', 'ftp', 'ftps']: - while repo_uri in json_data['repos']: + while repo_uri in json_data.get('repos', []): json_data['repos'].remove(repo_uri) else: repo_uri = pathlib.Path(repo_uri).resolve().as_posix() - while repo_uri in json_data['repos']: + while repo_uri in json_data.get('repos', []): json_data['repos'].remove(repo_uri) if remove: @@ -492,7 +492,7 @@ def register_repo(json_data: dict, result = utils.download_file(parsed_uri, cache_file) if result == 0: - json_data['repos'].insert(0, repo_uri) + json_data.setdefault('repos', []).insert(0, repo_uri) repo_set = set() result = repo.process_add_o3de_repo(cache_file, repo_set) diff --git a/scripts/o3de/o3de/repo.py b/scripts/o3de/o3de/repo.py index 32c2cba428..a6b1505761 100644 --- a/scripts/o3de/o3de/repo.py +++ b/scripts/o3de/o3de/repo.py @@ -147,7 +147,7 @@ def get_gem_json_paths_from_all_cached_repos() -> set: json_data = manifest.load_o3de_manifest() gem_set = set() - for repo_uri in json_data['repos']: + for repo_uri in json_data.get('repos', []): gem_set.update(get_gem_json_paths_from_cached_repo(repo_uri)) return gem_set @@ -189,7 +189,7 @@ def refresh_repos() -> int: # set will stop circular references repo_set = set() - for repo_uri in json_data['repos']: + for repo_uri in json_data.get('repos', []): if repo_uri not in repo_set: repo_set.add(repo_uri) diff --git a/scripts/o3de/tests/unit_test_gem_properties.py b/scripts/o3de/tests/unit_test_gem_properties.py index 5bfbe5573a..dee5811b65 100644 --- a/scripts/o3de/tests/unit_test_gem_properties.py +++ b/scripts/o3de/tests/unit_test_gem_properties.py @@ -18,7 +18,8 @@ TEST_GEM_JSON_PAYLOAD = ''' { "gem_name": "TestGem", "display_name": "TestGem", - "license": "What license TestGem uses goes here: i.e. https://opensource.org/licenses/MIT", + "license": "MIT", + "license_url": "https://opensource.org/licenses/MIT", "origin": "The primary repo for TestGem goes here: i.e. http://www.mydomain.com", "type": "Code", "summary": "A short description of TestGem.", @@ -46,26 +47,30 @@ def init_gem_json_data(request): class TestEditGemProperties: @pytest.mark.parametrize("gem_path, gem_name, gem_new_name, gem_display, gem_origin,\ gem_type, gem_summary, gem_icon, gem_requirements, gem_documentation_url,\ - add_tags, remove_tags, replace_tags, expected_tags, expected_result", [ + gem_license, gem_license_url, add_tags, remove_tags, replace_tags,\ + expected_tags, expected_result", [ pytest.param(pathlib.PurePath('D:/TestProject'), None, 'TestGem2', 'New Gem Name', 'O3DE', 'Code', 'Gem that exercises Default Gem Template', 'new_preview.png', 'Do this extra thing', 'https://o3de.org/docs/user-guide/gems/', + 'Apache 2.0', 'https://www.apache.org/licenses/LICENSE-2.0', ['Physics', 'Rendering', 'Scripting'], None, None, ['TestGem', 'Physics', 'Rendering', 'Scripting'], 0), pytest.param(None, 'TestGem2', None, 'New Gem Name', 'O3DE', 'Asset', 'Gem that exercises Default Gem Template', - 'new_preview.png', 'Do this extra thing', 'https://o3de.org/docs/user-guide/gems/', None, - ['Physics'], None, ['TestGem', 'Rendering', 'Scripting'], 0), + 'new_preview.png', 'Do this extra thing', 'https://o3de.org/docs/user-guide/gems/', + 'Apache 2.0', 'https://www.apache.org/licenses/LICENSE-2.0', + None, ['Physics'], None, ['TestGem', 'Rendering', 'Scripting'], 0), pytest.param(None, 'TestGem2', None, 'New Gem Name', 'O3DE', 'Tool', 'Gem that exercises Default Gem Template', - 'new_preview.png', 'Do this extra thing', 'https://o3de.org/docs/user-guide/gems/', None, - None, ['Animation', 'TestGem'], ['Animation', 'TestGem'], 0) + 'new_preview.png', 'Do this extra thing', 'https://o3de.org/docs/user-guide/gems/', + 'Apache 2.0', 'https://www.apache.org/licenses/LICENSE-2.0', + None, None, ['Animation', 'TestGem'], ['Animation', 'TestGem'], 0) ] ) def test_edit_gem_properties(self, gem_path, gem_name, gem_new_name, gem_display, gem_origin, gem_type, gem_summary, gem_icon, gem_requirements, - gem_documentation_url, add_tags, remove_tags, replace_tags, - expected_tags, expected_result): + gem_documentation_url, gem_license, gem_license_url, add_tags, remove_tags, + replace_tags, expected_tags, expected_result): def get_gem_json_data(gem_path: pathlib.Path) -> dict: return self.gem_json.data @@ -82,7 +87,8 @@ class TestEditGemProperties: patch('o3de.manifest.get_registered', side_effect=get_gem_path) as get_registered_patch: result = gem_properties.edit_gem_props(gem_path, gem_name, gem_new_name, gem_display, gem_origin, gem_type, gem_summary, gem_icon, gem_requirements, - gem_documentation_url, add_tags, remove_tags, replace_tags) + gem_documentation_url, gem_license, gem_license_url, + add_tags, remove_tags, replace_tags) assert result == expected_result if gem_new_name: assert self.gem_json.data.get('gem_name', '') == gem_new_name @@ -100,5 +106,9 @@ class TestEditGemProperties: assert self.gem_json.data.get('requirements', '') == gem_requirements if gem_documentation_url: assert self.gem_json.data.get('documentation_url', '') == gem_documentation_url + if gem_license: + assert self.gem_json.data.get('license', '') == gem_license + if gem_license_url: + assert self.gem_json.data.get('license_url', '') == gem_license_url assert set(self.gem_json.data.get('user_tags', [])) == set(expected_tags)