diff --git a/Assets/Editor/Translation/scriptcanvas_en_us.ts b/Assets/Editor/Translation/scriptcanvas_en_us.ts index 7b8361c879..6d436b7caf 100644 --- a/Assets/Editor/Translation/scriptcanvas_en_us.ts +++ b/Assets/Editor/Translation/scriptcanvas_en_us.ts @@ -62164,7 +62164,7 @@ An Entity can be selected by using the pick button, or by dragging an Entity fro HANDLER_TAGGLOBALNOTIFICATIONBUS_ONENTITYTAGADDED_OUTPUT0_NAME Simple Type: EntityID C++ Type: const EntityId& - Entity + EntityID HANDLER_TAGGLOBALNOTIFICATIONBUS_ONENTITYTAGADDED_OUTPUT0_TOOLTIP @@ -62202,7 +62202,7 @@ An Entity can be selected by using the pick button, or by dragging an Entity fro HANDLER_TAGGLOBALNOTIFICATIONBUS_ONENTITYTAGREMOVED_OUTPUT0_NAME Simple Type: EntityID C++ Type: const EntityId& - Entity + EntityId HANDLER_TAGGLOBALNOTIFICATIONBUS_ONENTITYTAGREMOVED_OUTPUT0_TOOLTIP @@ -81852,7 +81852,7 @@ The element is removed from its current parent and added as a child of the new p HANDLER_SPAWNERCOMPONENTNOTIFICATIONBUS_ONENTITYSPAWNED_OUTPUT1_NAME Simple Type: EntityID C++ Type: const EntityId& - Entity + EntityID HANDLER_SPAWNERCOMPONENTNOTIFICATIONBUS_ONENTITYSPAWNED_OUTPUT1_TOOLTIP @@ -89198,7 +89198,7 @@ The element is removed from its current parent and added as a child of the new p HANDLER_ENTITYBUS_ONENTITYACTIVATED_OUTPUT0_NAME Simple Type: EntityID C++ Type: const EntityId& - Entity + EntityID HANDLER_ENTITYBUS_ONENTITYACTIVATED_OUTPUT0_TOOLTIP @@ -89236,7 +89236,7 @@ The element is removed from its current parent and added as a child of the new p HANDLER_ENTITYBUS_ONENTITYDEACTIVATED_OUTPUT0_NAME Simple Type: EntityID C++ Type: const EntityId& - Entity + EntityID HANDLER_ENTITYBUS_ONENTITYDEACTIVATED_OUTPUT0_TOOLTIP diff --git a/AutomatedTesting/Gem/Code/enabled_gems.cmake b/AutomatedTesting/Gem/Code/enabled_gems.cmake index f653a54505..7d33a65bc7 100644 --- a/AutomatedTesting/Gem/Code/enabled_gems.cmake +++ b/AutomatedTesting/Gem/Code/enabled_gems.cmake @@ -54,5 +54,6 @@ set(ENABLED_GEMS AWSMetrics PrefabBuilder AudioSystem + Terrain Profiler ) diff --git a/AutomatedTesting/Gem/PythonTests/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/CMakeLists.txt index fd3222ba83..d2b7075e22 100644 --- a/AutomatedTesting/Gem/PythonTests/CMakeLists.txt +++ b/AutomatedTesting/Gem/PythonTests/CMakeLists.txt @@ -56,6 +56,9 @@ add_subdirectory(streaming) ## Smoke ## add_subdirectory(smoke) +## Terrain ## +add_subdirectory(Terrain) + ## AWS ## add_subdirectory(AWS) diff --git a/AutomatedTesting/Gem/PythonTests/Terrain/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/Terrain/CMakeLists.txt new file mode 100644 index 0000000000..9f8ba06829 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Terrain/CMakeLists.txt @@ -0,0 +1,24 @@ +# +# 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 +# +# + +if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) + + ly_add_pytest( + NAME AutomatedTesting::TerrainTests_Main + TEST_SUITE main + TEST_SERIAL + PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Main.py + RUNTIME_DEPENDENCIES + Legacy::Editor + AZ::AssetProcessor + AutomatedTesting.Assets + COMPONENT + Terrain + ) + +endif() diff --git a/AutomatedTesting/Gem/PythonTests/Terrain/EditorScripts/TerrainPhysicsCollider_ChangesSizeWithAxisAlignedBoxShapeChanges.py b/AutomatedTesting/Gem/PythonTests/Terrain/EditorScripts/TerrainPhysicsCollider_ChangesSizeWithAxisAlignedBoxShapeChanges.py new file mode 100644 index 0000000000..aba506ea20 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Terrain/EditorScripts/TerrainPhysicsCollider_ChangesSizeWithAxisAlignedBoxShapeChanges.py @@ -0,0 +1,90 @@ +""" +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 +""" + +#fmt: off +class Tests(): + create_test_entity = ("Entity created successfully", "Failed to create Entity") + add_axis_aligned_box_shape = ("Axis Aligned Box Shape component added", "Failed to add Axis Aligned Box Shape component") + add_terrain_collider = ("Terrain Physics Heightfield Collider component added", "Failed to add a Terrain Physics Heightfield Collider component") + box_dimensions_changed = ("Aabb dimensions changed successfully", "Failed change Aabb dimensions") + configuration_changed = ("Terrain size changed successfully", "Failed terrain size change") + no_errors_and_warnings_found = ("No errors and warnings found", "Found errors and warnings") +#fmt: on + +def TerrainPhysicsCollider_ChangesSizeWithAxisAlignedBoxShapeChanges(): + """ + Summary: + Test aspects of the TerrainHeightGradientList through the BehaviorContext and the Property Tree. + + Test Steps: + Expected Behavior: + The Editor is stable there are no warnings or errors. + + Test Steps: + 1) Load the base level + 2) Create test entity + 3) Start the Tracer to catch any errors and warnings + 4) Add the Axis Aligned Box Shape and Terrain Physics Heightfield Collider components + 5) Change the Axis Aligned Box Shape dimensions + 6) Check the Heightfield provider is returning the correct size + 7) Verify there are no errors and warnings in the logs + + + :return: None + """ + + from editor_python_test_tools.editor_entity_utils import EditorEntity + from editor_python_test_tools.utils import TestHelper as helper + from editor_python_test_tools.utils import Report, Tracer + import azlmbr.legacy.general as general + import azlmbr.physics as physics + import azlmbr.math as azmath + import azlmbr.bus as bus + import sys + import math + + SET_BOX_X_SIZE = 5.0 + SET_BOX_Y_SIZE = 6.0 + EXPECTED_COLUMN_SIZE = SET_BOX_X_SIZE + 1 + EXPECTED_ROW_SIZE = SET_BOX_Y_SIZE + 1 + helper.init_idle() + + # 1) Load the level + helper.open_level("", "Base") + + # 2) Create test entity + test_entity = EditorEntity.create_editor_entity("TestEntity") + Report.result(Tests.create_test_entity, test_entity.id.IsValid()) + + # 3) Start the Tracer to catch any errors and warnings + with Tracer() as section_tracer: + # 4) Add the Axis Aligned Box Shape and Terrain Physics Heightfield Collider components + aaBoxShape_component = test_entity.add_component("Axis Aligned Box Shape") + Report.result(Tests.add_axis_aligned_box_shape, test_entity.has_component("Axis Aligned Box Shape")) + terrainPhysics_component = test_entity.add_component("Terrain Physics Heightfield Collider") + Report.result(Tests.add_terrain_collider, test_entity.has_component("Terrain Physics Heightfield Collider")) + + # 5) Change the Axis Aligned Box Shape dimensions + aaBoxShape_component.set_component_property_value("Axis Aligned Box Shape|Box Configuration|Dimensions", azmath.Vector3(SET_BOX_X_SIZE, SET_BOX_Y_SIZE, 1.0)) + add_check = aaBoxShape_component.get_component_property_value("Axis Aligned Box Shape|Box Configuration|Dimensions") == azmath.Vector3(SET_BOX_X_SIZE, SET_BOX_Y_SIZE, 1.0) + Report.result(Tests.box_dimensions_changed, add_check) + + # 6) Check the Heightfield provider is returning the correct size + columns = physics.HeightfieldProviderRequestsBus(bus.Broadcast, "GetHeightfieldGridColumns") + rows = physics.HeightfieldProviderRequestsBus(bus.Broadcast, "GetHeightfieldGridRows") + Report.result(Tests.configuration_changed, math.isclose(columns, EXPECTED_COLUMN_SIZE) and math.isclose(rows, EXPECTED_ROW_SIZE)) + + helper.wait_for_condition(lambda: section_tracer.has_errors or section_tracer.has_asserts, 1.0) + for error_info in section_tracer.errors: + Report.info(f"Error: {error_info.filename} {error_info.function} | {error_info.message}") + for assert_info in section_tracer.asserts: + Report.info(f"Assert: {assert_info.filename} {assert_info.function} | {assert_info.message}") + +if __name__ == "__main__": + + from editor_python_test_tools.utils import Report + Report.start_test(TerrainPhysicsCollider_ChangesSizeWithAxisAlignedBoxShapeChanges) diff --git a/AutomatedTesting/Gem/PythonTests/Terrain/TestSuite_Main.py b/AutomatedTesting/Gem/PythonTests/Terrain/TestSuite_Main.py new file mode 100644 index 0000000000..620d84d7db --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Terrain/TestSuite_Main.py @@ -0,0 +1,25 @@ +""" +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 + +""" + +# This suite consists of all test cases that are passing and have been verified. + +import pytest +import os +import sys + +from ly_test_tools import LAUNCHERS +from ly_test_tools.o3de.editor_test import EditorTestSuite, EditorSingleTest + +@pytest.mark.SUITE_main +@pytest.mark.parametrize("launcher_platform", ['windows_editor']) +@pytest.mark.parametrize("project", ["AutomatedTesting"]) +class TestAutomation(EditorTestSuite): + #global_extra_cmdline_args=["--regset=/Amazon/Preferences/EnablePrefabSystem=true"] + + class test_AxisAlignedBoxShape_ConfigurationWorks(EditorSingleTest): + from .EditorScripts import TerrainPhysicsCollider_ChangesSizeWithAxisAlignedBoxShapeChanges as test_module diff --git a/AutomatedTesting/Gem/PythonTests/Terrain/__init__.py b/AutomatedTesting/Gem/PythonTests/Terrain/__init__.py new file mode 100644 index 0000000000..f5193b300e --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Terrain/__init__.py @@ -0,0 +1,6 @@ +""" +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 +""" diff --git a/AutomatedTesting/Gem/PythonTests/editor/EditorScripts/EntityOutliner_EntityOrdering.py b/AutomatedTesting/Gem/PythonTests/editor/EditorScripts/EntityOutliner_EntityOrdering.py new file mode 100644 index 0000000000..e4575d4d17 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/editor/EditorScripts/EntityOutliner_EntityOrdering.py @@ -0,0 +1,146 @@ +""" +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 +""" + + +class Tests: + entities_sorted = ( + "Entities sorted in the expected order", + "Entities sorted in an incorrect order", + ) + + +def EntityOutliner_EntityOrdering(): + """ + Summary: + Verify that manual entity ordering in the entity outliner works and is stable. + + Expected Behavior: + Several entities are created, some are manually ordered, and their order + is maintained, even when new entities are added. + + Test Steps: + 1) Open the empty Prefab Base level + 2) Add 5 entities to the outliner + 3) Move "Entity1" to the top of the order + 4) Move "Entity4" to the bottom of the order + 5) Add another new entity, ensure the rest of the order is unchanged + """ + + import editor_python_test_tools.pyside_utils as pyside_utils + import azlmbr.legacy.general as general + from editor_python_test_tools.utils import Report + from editor_python_test_tools.utils import TestHelper as helper + from PySide2 import QtCore, QtWidgets, QtGui, QtTest + + # Grab the Editor, Entity Outliner, and Outliner Model + editor_window = pyside_utils.get_editor_main_window() + entity_outliner = pyside_utils.find_child_by_hierarchy( + editor_window, ..., "EntityOutlinerWidgetUI", ..., "m_objectTree" + ) + entity_outliner_model = entity_outliner.model() + + # Get the outliner index for the root prefab container entity + def get_root_prefab_container_index(): + return entity_outliner_model.index(0, 0) + + # Get the outliner index for the top level entity of a given name + def index_for_name(name): + root_index = get_root_prefab_container_index() + for row in range(entity_outliner_model.rowCount(root_index)): + row_index = entity_outliner_model.index(row, 0, root_index) + if row_index.data() == name: + return row_index + return None + + # Validate that the outliner top level entity order matches the expected order + def verify_entities_sorted(expected_order): + actual_order = [] + root_index = get_root_prefab_container_index() + for row in range(entity_outliner_model.rowCount(root_index)): + row_index = entity_outliner_model.index(row, 0, root_index) + actual_order.append(row_index.data()) + + sorted_correctly = actual_order == expected_order + Report.result(Tests.entities_sorted, sorted_correctly) + if not sorted_correctly: + print(f"Expected entity order: {expected_order}") + print(f"Actual entity order: {actual_order}") + + # Creates an entity from the outliner context menu + def create_entity(): + pyside_utils.trigger_context_menu_entry( + entity_outliner, "Create entity", index=get_root_prefab_container_index() + ) + # Wait a tick after entity creation to let events process + general.idle_wait(0.0) + + # Moves an entity (wrapped by move_entity_before and move_entity_after) + def _move_entity(source_name, target_name, move_after=False): + source_index = index_for_name(source_name) + target_index = index_for_name(target_name) + + target_row = target_index.row() + if move_after: + target_row += 1 + + # Generate MIME data and directly inject it into the model instead of + # generating mouse click operations, as it's more reliable and we're + # testing the underlying drag & drop logic as opposed to Qt's mouse + # handling here + mime_data = entity_outliner_model.mimeData([source_index]) + entity_outliner_model.dropMimeData( + mime_data, QtCore.Qt.MoveAction, target_row, 0, target_index.parent() + ) + QtWidgets.QApplication.processEvents() + + # Move an entity before another entity in the order by dragging the source above the target + move_entity_before = lambda source_name, target_name: _move_entity( + source_name, target_name, move_after=False + ) + # Move an entity after another entity in the order by dragging the source beloew the target + move_entity_after = lambda source_name, target_name: _move_entity( + source_name, target_name, move_after=True + ) + + expected_order = [] + + # 1) Open the empty Prefab Base level + helper.init_idle() + helper.open_level("Prefab", "Base") + + # 2) Add 5 entities to the outliner + ENTITIES_TO_ADD = 5 + for i in range(ENTITIES_TO_ADD): + create_entity() + + # Our new entity should be given a name with a number automatically + new_entity = f"Entity{i+1}" + # The new entity should be added to the top of its parent entity + expected_order = [new_entity] + expected_order + + verify_entities_sorted(expected_order) + + # 3) Move "Entity1" to the top of the order + move_entity_before("Entity1", "Entity5") + expected_order = ["Entity1", "Entity5", "Entity4", "Entity3", "Entity2"] + verify_entities_sorted(expected_order) + + # 4) Move "Entity4" to the bottom of the order + move_entity_after("Entity4", "Entity2") + expected_order = ["Entity1", "Entity5", "Entity3", "Entity2", "Entity4"] + verify_entities_sorted(expected_order) + + # 5) Add another new entity, ensure the rest of the order is unchanged + create_entity() + expected_order = ["Entity6", "Entity1", "Entity5", "Entity3", "Entity2", "Entity4"] + verify_entities_sorted(expected_order) + + +if __name__ == "__main__": + from editor_python_test_tools.utils import Report + + Report.start_test(EntityOutliner_EntityOrdering) diff --git a/AutomatedTesting/Gem/PythonTests/editor/TestSuite_Main.py b/AutomatedTesting/Gem/PythonTests/editor/TestSuite_Main.py index 26b254ae71..49069569eb 100644 --- a/AutomatedTesting/Gem/PythonTests/editor/TestSuite_Main.py +++ b/AutomatedTesting/Gem/PythonTests/editor/TestSuite_Main.py @@ -41,3 +41,15 @@ class TestAutomation(TestAutomationBase): from .EditorScripts import BasicEditorWorkflows_LevelEntityComponentCRUD as test_module self._run_test(request, workspace, editor, test_module, batch_mode=False, autotest_mode=False, use_null_renderer=False) + + def test_EntityOutlienr_EntityOrdering(self, request, workspace, editor, launcher_platform): + from .EditorScripts import EntityOutliner_EntityOrdering as test_module + self._run_test( + request, + workspace, + editor, + test_module, + batch_mode=False, + autotest_mode=True, + extra_cmdline_args=["--regset=/Amazon/Preferences/EnablePrefabSystem=true"] + ) diff --git a/AutomatedTesting/Gem/PythonTests/smoke/test_Editor_NewExistingLevels_Works.py b/AutomatedTesting/Gem/PythonTests/smoke/test_Editor_NewExistingLevels_Works.py index 6f654b9107..72e548615c 100644 --- a/AutomatedTesting/Gem/PythonTests/smoke/test_Editor_NewExistingLevels_Works.py +++ b/AutomatedTesting/Gem/PythonTests/smoke/test_Editor_NewExistingLevels_Works.py @@ -28,4 +28,4 @@ class TestAutomation(TestAutomationBase): from . import Editor_NewExistingLevels_Works as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, extra_cmdline_args=["--regset=/Amazon/Preferences/EnablePrefabSystem=false"]) diff --git a/AutomatedTesting/Levels/Base/Base.prefab b/AutomatedTesting/Levels/Base/Base.prefab new file mode 100644 index 0000000000..f7e42e7731 --- /dev/null +++ b/AutomatedTesting/Levels/Base/Base.prefab @@ -0,0 +1,53 @@ +{ + "ContainerEntity": { + "Id": "Entity_[1146574390643]", + "Name": "Level", + "Components": { + "Component_[10641544592923449938]": { + "$type": "EditorInspectorComponent", + "Id": 10641544592923449938 + }, + "Component_[12039882709170782873]": { + "$type": "EditorOnlyEntityComponent", + "Id": 12039882709170782873 + }, + "Component_[12265484671603697631]": { + "$type": "EditorPendingCompositionComponent", + "Id": 12265484671603697631 + }, + "Component_[14126657869720434043]": { + "$type": "EditorEntitySortComponent", + "Id": 14126657869720434043 + }, + "Component_[15230859088967841193]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 15230859088967841193, + "Parent Entity": "" + }, + "Component_[16239496886950819870]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 16239496886950819870 + }, + "Component_[5688118765544765547]": { + "$type": "EditorEntityIconComponent", + "Id": 5688118765544765547 + }, + "Component_[6545738857812235305]": { + "$type": "SelectionComponent", + "Id": 6545738857812235305 + }, + "Component_[7247035804068349658]": { + "$type": "EditorPrefabComponent", + "Id": 7247035804068349658 + }, + "Component_[9307224322037797205]": { + "$type": "EditorLockComponent", + "Id": 9307224322037797205 + }, + "Component_[9562516168917670048]": { + "$type": "EditorVisibilityComponent", + "Id": 9562516168917670048 + } + } + } +} \ No newline at end of file diff --git a/Code/Editor/CryEdit.cpp b/Code/Editor/CryEdit.cpp index 5cffedd774..0ea22e8ce3 100644 --- a/Code/Editor/CryEdit.cpp +++ b/Code/Editor/CryEdit.cpp @@ -3974,9 +3974,8 @@ void CCryEditApp::OpenLUAEditor(const char* files) } } - const char* engineRoot = nullptr; - AzFramework::ApplicationRequests::Bus::BroadcastResult(engineRoot, &AzFramework::ApplicationRequests::GetEngineRoot); - AZ_Assert(engineRoot != nullptr, "Unable to communicate to AzFramework::ApplicationRequests::Bus"); + AZ::IO::FixedMaxPathString engineRoot = AZ::Utils::GetEnginePath(); + AZ_Assert(!engineRoot.empty(), "Unable to query Engine Path"); AZStd::string_view exePath; AZ::ComponentApplicationBus::BroadcastResult(exePath, &AZ::ComponentApplicationRequests::GetExecutableFolder); @@ -3995,7 +3994,7 @@ void CCryEditApp::OpenLUAEditor(const char* files) #endif "%s", argumentQuoteString, aznumeric_cast(exePath.size()), exePath.data(), argumentQuoteString); - AZStd::string processArgs = AZStd::string::format("%s -engine-path \"%s\"", args.c_str(), engineRoot); + AZStd::string processArgs = AZStd::string::format("%s -engine-path \"%s\"", args.c_str(), engineRoot.c_str()); StartProcessDetached(process.c_str(), processArgs.c_str()); } diff --git a/Code/Editor/Include/IFileUtil.h b/Code/Editor/Include/IFileUtil.h index e179f892d9..4d5f6e23c4 100644 --- a/Code/Editor/Include/IFileUtil.h +++ b/Code/Editor/Include/IFileUtil.h @@ -114,9 +114,7 @@ struct IFileUtil virtual void ShowInExplorer(const QString& path) = 0; - virtual bool CompileLuaFile(const char* luaFilename) = 0; virtual bool ExtractFile(QString& file, bool bMsgBoxAskForExtraction = true, const char* pDestinationFilename = nullptr) = 0; - virtual void EditTextFile(const char* txtFile, int line = 0, ETextFileType fileType = FILE_TYPE_SCRIPT) = 0; virtual void EditTextureFile(const char* txtureFile, bool bUseGameFolder) = 0; //! dcc filename calculation and extraction sub-routines diff --git a/Code/Editor/Util/FileUtil.cpp b/Code/Editor/Util/FileUtil.cpp index baca69d628..36c1879407 100644 --- a/Code/Editor/Util/FileUtil.cpp +++ b/Code/Editor/Util/FileUtil.cpp @@ -23,9 +23,9 @@ // AzCore #include #include +#include // AzFramework -#include // AzQtComponents #include @@ -62,84 +62,6 @@ CAutoRestorePrimaryCDRoot::~CAutoRestorePrimaryCDRoot() QDir::setCurrent(GetIEditor()->GetPrimaryCDFolder()); } -bool CFileUtil::CompileLuaFile(const char* luaFilename) -{ - QString luaFile = luaFilename; - - if (luaFile.isEmpty()) - { - return false; - } - - // Check if this file is in Archive. - { - CCryFile file; - if (file.Open(luaFilename, "rb")) - { - // Check if in pack. - if (file.IsInPak()) - { - return true; - } - } - } - - luaFile = Path::GamePathToFullPath(luaFilename); - - // First try compiling script and see if it have any errors. - QString LuaCompiler; - QString CompilerOutput; - - // Create the filepath of the lua compiler - QString szExeFileName = qApp->applicationFilePath(); - QString exePath = Path::GetPath(szExeFileName); - -#if defined(AZ_PLATFORM_WINDOWS) - const char* luaCompiler = "LuaCompiler.exe"; -#else - const char* luaCompiler = "lua"; -#endif - LuaCompiler = Path::AddPathSlash(exePath) + luaCompiler + " "; - - AZStd::string path = luaFile.toUtf8().data(); - EBUS_EVENT(AzFramework::ApplicationRequests::Bus, NormalizePath, path); - - QString finalPath = path.c_str(); - finalPath = "\"" + finalPath + "\""; - - // Add the name of the Lua file - QString cmdLine = LuaCompiler + finalPath; - - // Execute the compiler and capture the output - if (!GetIEditor()->ExecuteConsoleApp(cmdLine, CompilerOutput)) - { - QMessageBox::critical(QApplication::activeWindow(), QString(), QObject::tr("Error while executing '%1', make sure the file is in" \ - " your Primary CD folder !").arg(luaCompiler)); - return false; - } - - // Check return string - if (!CompilerOutput.isEmpty()) - { - // Errors while compiling file. - - // Show output from Lua compiler - if (QMessageBox::critical(QApplication::activeWindow(), QObject::tr("Lua Compiler"), - QObject::tr("Error output from Lua compiler:\r\n%1\r\nDo you want to edit the file ?").arg(CompilerOutput), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) - { - int line = 0; - int index = CompilerOutput.indexOf("at line"); - if (index >= 0) - { - azsscanf(CompilerOutput.mid(index).toUtf8().data(), "at line %d", &line); - } - // Open the Lua file for editing - EditTextFile(luaFile.toUtf8().data(), line); - } - return false; - } - return true; -} ////////////////////////////////////////////////////////////////////////// bool CFileUtil::ExtractFile(QString& file, bool bMsgBoxAskForExtraction, const char* pDestinationFilename) { @@ -205,7 +127,7 @@ void CFileUtil::EditTextFile(const char* txtFile, int line, IFileUtil::ETextFile { QString file = txtFile; - QString fullPathName = Path::GamePathToFullPath(file); + QString fullPathName = Path::GamePathToFullPath(file); ExtractFile(fullPathName); QString cmd(fullPathName); #if defined (AZ_PLATFORM_WINDOWS) @@ -301,64 +223,6 @@ void CFileUtil::EditTextureFile(const char* textureFile, [[maybe_unused]] bool b } } -////////////////////////////////////////////////////////////////////////// -bool CFileUtil::EditMayaFile(const char* filepath, const bool bExtractFromPak, const bool bUseGameFolder) -{ - QString dosFilepath = PathUtil::ToDosPath(filepath).c_str(); - if (bExtractFromPak) - { - ExtractFile(dosFilepath); - } - - if (bUseGameFolder) - { - const QString sGameFolder = Path::GetEditingGameDataFolder().c_str(); - int nLength = sGameFolder.toUtf8().count(); - if (azstrnicmp(filepath, sGameFolder.toUtf8().data(), nLength) != 0) - { - dosFilepath = sGameFolder + '\\' + filepath; - } - - dosFilepath = PathUtil::ToDosPath(dosFilepath.toUtf8().data()).c_str(); - } - - const char* engineRoot; - EBUS_EVENT_RESULT(engineRoot, AzFramework::ApplicationRequests::Bus, GetEngineRoot); - - const QString fullPath = QString(engineRoot) + '\\' + dosFilepath; - - if (gSettings.animEditor.isEmpty()) - { - AzQtComponents::ShowFileOnDesktop(fullPath); - } - else - { - if (!QProcess::startDetached(gSettings.animEditor, { fullPath })) - { - CryMessageBox("Can't open the file. You can specify a source editor in Sandbox Preferences or create an association in Windows.", "Cannot open file!", MB_OK | MB_ICONERROR); - } - } - return true; -} - -////////////////////////////////////////////////////////////////////////// -bool CFileUtil::EditFile(const char* filePath, const bool bExtrackFromPak, const bool bUseGameFolder) -{ - QString extension = filePath; - extension.remove(0, extension.lastIndexOf('.')); - - if (extension.compare(".ma") == 0) - { - return EditMayaFile(filePath, bExtrackFromPak, bUseGameFolder); - } - else if ((extension.compare(".bspace") == 0) || (extension.compare(".comb") == 0)) - { - EditTextFile(filePath, 0, IFileUtil::FILE_TYPE_BSPACE); - return true; - } - - return false; -} ////////////////////////////////////////////////////////////////////////// bool CFileUtil::CalculateDccFilename(const QString& assetFilename, QString& dccFilename) diff --git a/Code/Editor/Util/FileUtil.h b/Code/Editor/Util/FileUtil.h index 5820c32081..fc0fc942fe 100644 --- a/Code/Editor/Util/FileUtil.h +++ b/Code/Editor/Util/FileUtil.h @@ -25,14 +25,9 @@ public: static void ShowInExplorer(const QString& path); - // Try to compile the given lua file: returns true if compilation succeeded, false on failure. - static bool CompileLuaFile(const char* luaFilename); - static bool ExtractFile(QString& file, bool bMsgBoxAskForExtraction = true, const char* pDestinationFilename = nullptr); static void EditTextFile(const char* txtFile, int line = 0, IFileUtil::ETextFileType fileType = IFileUtil::FILE_TYPE_SCRIPT); static void EditTextureFile(const char* txtureFile, bool bUseGameFolder); - static bool EditMayaFile(const char* mayaFile, const bool bExtractFromPak, const bool bUseGameFolder); - static bool EditFile(const char* filePath, const bool bExtrackFromPak, const bool bUseGameFolder); //! dcc filename calculation and extraction sub-routines static bool CalculateDccFilename(const QString& assetFilename, QString& dccFilename); diff --git a/Code/Editor/Util/FileUtil_impl.cpp b/Code/Editor/Util/FileUtil_impl.cpp index 0dd3a0ca87..28090d28d5 100644 --- a/Code/Editor/Util/FileUtil_impl.cpp +++ b/Code/Editor/Util/FileUtil_impl.cpp @@ -20,21 +20,11 @@ void CFileUtil_impl::ShowInExplorer(const QString& path) CFileUtil::ShowInExplorer(path); } -bool CFileUtil_impl::CompileLuaFile(const char* luaFilename) -{ - return CFileUtil::CompileLuaFile(luaFilename); -} - bool CFileUtil_impl::ExtractFile(QString& file, bool bMsgBoxAskForExtraction, const char* pDestinationFilename) { return CFileUtil::ExtractFile(file, bMsgBoxAskForExtraction, pDestinationFilename); } -void CFileUtil_impl::EditTextFile(const char* txtFile, int line, ETextFileType fileType) -{ - CFileUtil::EditTextFile(txtFile, line, fileType); -} - void CFileUtil_impl::EditTextureFile(const char* txtureFile, bool bUseGameFolder) { CFileUtil::EditTextureFile(txtureFile, bUseGameFolder); diff --git a/Code/Editor/Util/FileUtil_impl.h b/Code/Editor/Util/FileUtil_impl.h index 04d9e829b9..aa8d0bf3b5 100644 --- a/Code/Editor/Util/FileUtil_impl.h +++ b/Code/Editor/Util/FileUtil_impl.h @@ -36,9 +36,7 @@ public: void ShowInExplorer(const QString& path) override; - bool CompileLuaFile(const char* luaFilename) override; bool ExtractFile(QString& file, bool bMsgBoxAskForExtraction = true, const char* pDestinationFilename = nullptr) override; - void EditTextFile(const char* txtFile, int line = 0, ETextFileType fileType = FILE_TYPE_SCRIPT) override; void EditTextureFile(const char* txtureFile, bool bUseGameFolder) override; //! dcc filename calculation and extraction sub-routines diff --git a/Code/Editor/Util/PathUtil.cpp b/Code/Editor/Util/PathUtil.cpp index ca3481ddae..8b745ee1bc 100644 --- a/Code/Editor/Util/PathUtil.cpp +++ b/Code/Editor/Util/PathUtil.cpp @@ -14,7 +14,6 @@ #include #include #include // for ebus events -#include #include #include @@ -175,9 +174,8 @@ namespace Path ////////////////////////////////////////////////////////////////////////// QString GetEngineRootPath() { - const char* engineRoot; - EBUS_EVENT_RESULT(engineRoot, AzFramework::ApplicationRequests::Bus, GetEngineRoot); - return QString(engineRoot); + const AZ::IO::FixedMaxPathString engineRoot = AZ::Utils::GetEnginePath(); + return QString::fromUtf8(engineRoot.c_str(), static_cast(engineRoot.size())); } ////////////////////////////////////////////////////////////////////////// diff --git a/Code/Editor/WelcomeScreen/WelcomeScreenDialog.cpp b/Code/Editor/WelcomeScreen/WelcomeScreenDialog.cpp index 17f576b5ee..89dfcaffd1 100644 --- a/Code/Editor/WelcomeScreen/WelcomeScreenDialog.cpp +++ b/Code/Editor/WelcomeScreen/WelcomeScreenDialog.cpp @@ -25,8 +25,6 @@ #include -// AzFramework -#include // AzToolsFramework #include @@ -173,9 +171,6 @@ void WelcomeScreenDialog::SetRecentFileList(RecentFileList* pList) m_pRecentList = pList; - const char* engineRoot; - EBUS_EVENT_RESULT(engineRoot, AzFramework::ApplicationRequests::Bus, GetEngineRoot); - auto projectPath = AZ::Utils::GetProjectPath(); QString gamePath{projectPath.c_str()}; Path::ConvertSlashToBackSlash(gamePath); diff --git a/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp b/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp index ad7e44c2ae..bd225cf634 100644 --- a/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp +++ b/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp @@ -485,16 +485,6 @@ namespace AZ constexpr bool executeRegDumpCommands = false; SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(*m_settingsRegistry, m_commandLine, executeRegDumpCommands); - // Query for the Executable Path using OS specific functions - CalculateExecutablePath(); - - // Determine the path to the engine - CalculateEngineRoot(); - - // If the current platform returns an engaged optional from Utils::GetDefaultAppRootPath(), that is used - // for the application root. - CalculateAppRoot(); - SettingsRegistryMergeUtils::MergeSettingsToRegistry_O3deUserRegistry(*m_settingsRegistry, AZ_TRAIT_OS_PLATFORM_CODENAME, {}); SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(*m_settingsRegistry, m_commandLine, executeRegDumpCommands); SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*m_settingsRegistry); @@ -614,7 +604,8 @@ namespace AZ { AZ_Assert(!m_isStarted, "Component application already started!"); - if (m_engineRoot.empty()) + using Type = AZ::SettingsRegistryInterface::Type; + if (m_settingsRegistry->GetType(SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder) == Type::NoType) { ReportBadEngineRoot(); return nullptr; @@ -1180,6 +1171,24 @@ namespace AZ return ReflectionEnvironment::GetReflectionManager() ? ReflectionEnvironment::GetReflectionManager()->GetReflectContext() : nullptr; } + /// Returns the path to the engine. + + const char* ComponentApplication::GetEngineRoot() const + { + static IO::FixedMaxPathString engineRoot; + engineRoot.clear(); + m_settingsRegistry->Get(engineRoot, SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder); + return engineRoot.c_str(); + } + + const char* ComponentApplication::GetExecutableFolder() const + { + static IO::FixedMaxPathString exeFolder; + exeFolder.clear(); + m_settingsRegistry->Get(exeFolder, SettingsRegistryMergeUtils::FilePathKey_BinaryFolder); + return exeFolder.c_str(); + } + //========================================================================= // CreateReflectionManager //========================================================================= @@ -1485,27 +1494,6 @@ namespace AZ } } - //========================================================================= - // CalculateExecutablePath - //========================================================================= - void ComponentApplication::CalculateExecutablePath() - { - m_exeDirectory = Utils::GetExecutableDirectory(); - } - - void ComponentApplication::CalculateAppRoot() - { - if (AZStd::optional appRootPath = Utils::GetDefaultAppRootPath(); appRootPath) - { - m_appRoot = AZStd::move(*appRootPath); - } - } - - void ComponentApplication::CalculateEngineRoot() - { - m_engineRoot = AZ::SettingsRegistryMergeUtils::FindEngineRoot(*m_settingsRegistry).Native(); - } - void ComponentApplication::ResolveModulePath([[maybe_unused]] AZ::OSString& modulePath) { // No special parsing of the Module Path is done by the Component Application anymore diff --git a/Code/Framework/AzCore/AzCore/Component/ComponentApplication.h b/Code/Framework/AzCore/AzCore/Component/ComponentApplication.h index 6df93aff4e..4e551b6c47 100644 --- a/Code/Framework/AzCore/AzCore/Component/ComponentApplication.h +++ b/Code/Framework/AzCore/AzCore/Component/ComponentApplication.h @@ -221,13 +221,10 @@ namespace AZ BehaviorContext* GetBehaviorContext() override; /// Returns the json registration context that has been registered with the app, if there is one. JsonRegistrationContext* GetJsonRegistrationContext() override; - /// Returns the working root folder that has been registered with the app, if there is one. - /// It's expected that derived applications will implement an application root. - const char* GetAppRoot() const override { return m_appRoot.c_str(); } /// Returns the path to the engine. - const char* GetEngineRoot() const override { return m_engineRoot.c_str(); } + const char* GetEngineRoot() const override; /// Returns the path to the folder the executable is in. - const char* GetExecutableFolder() const override { return m_exeDirectory.c_str(); } + const char* GetExecutableFolder() const override; ////////////////////////////////////////////////////////////////////////// /// TickRequestBus @@ -352,15 +349,6 @@ namespace AZ /// Adds system components requested by modules and the application to the system entity. void AddRequiredSystemComponents(AZ::Entity* systemEntity); - /// Calculates the directory the application executable comes from. - void CalculateExecutablePath(); - - /// Calculates the root directory of the engine. - void CalculateEngineRoot(); - - /// Deprecated: The term "AppRoot" has no meaning - void CalculateAppRoot(); - template static void NormalizePath(Iterator begin, Iterator end, bool doLowercase = true) { @@ -388,9 +376,6 @@ namespace AZ void* m_fixedMemoryBlock{ nullptr }; //!< Pointer to the memory block allocator, so we can free it OnDestroy. IAllocatorAllocate* m_osAllocator{ nullptr }; EntitySetType m_entities; - AZ::IO::FixedMaxPath m_exeDirectory; - AZ::IO::FixedMaxPath m_engineRoot; - AZ::IO::FixedMaxPath m_appRoot; AZ::SettingsRegistryInterface::NotifyEventHandler m_projectPathChangedHandler; AZ::SettingsRegistryInterface::NotifyEventHandler m_projectNameChangedHandler; diff --git a/Code/Framework/AzCore/AzCore/Component/ComponentApplicationBus.h b/Code/Framework/AzCore/AzCore/Component/ComponentApplicationBus.h index 0c0977384a..feefa95973 100644 --- a/Code/Framework/AzCore/AzCore/Component/ComponentApplicationBus.h +++ b/Code/Framework/AzCore/AzCore/Component/ComponentApplicationBus.h @@ -175,10 +175,6 @@ namespace AZ //! the serializers used by the best-effort json serialization. virtual class JsonRegistrationContext* GetJsonRegistrationContext() = 0; - //! Gets the name of the working root folder that was registered with the app. - //! @return a pointer to the name of the app's root folder, if a root folder was registered. - virtual const char* GetAppRoot() const = 0; - //! Gets the path of the working engine folder that the app is a part of. //! @return a pointer to the engine path. virtual const char* GetEngineRoot() const = 0; diff --git a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.cpp b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.cpp index 3668ab14fd..975217a131 100644 --- a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.cpp +++ b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.cpp @@ -266,7 +266,8 @@ namespace AZ::SettingsRegistryMergeUtils // Step 3 locate the project root and attempt to find the engine root using the registered engine // for the project in the project.json file - AZ::IO::FixedMaxPath projectRoot = FindProjectRoot(settingsRegistry); + AZ::IO::FixedMaxPath projectRoot; + settingsRegistry.Get(projectRoot.Native(), FilePathKey_ProjectPath); if (projectRoot.empty()) { return {}; @@ -668,7 +669,7 @@ namespace AZ::SettingsRegistryMergeUtils // 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"; + if ([[maybe_unused]] constexpr auto projectPathKey = FixedValueString(BootstrapSettingsRootKey) + "/project_path"; !projectPath.empty()) { if (projectPath.IsRelative()) @@ -693,6 +694,7 @@ namespace AZ::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)); + projectPath = exePath; registry.Set(FilePathKey_ProjectPath, exePath.Native()); } diff --git a/Code/Framework/AzCore/AzCore/StringFunc/StringFunc.cpp b/Code/Framework/AzCore/AzCore/StringFunc/StringFunc.cpp index ff30291a70..a953d53c33 100644 --- a/Code/Framework/AzCore/AzCore/StringFunc/StringFunc.cpp +++ b/Code/Framework/AzCore/AzCore/StringFunc/StringFunc.cpp @@ -1427,26 +1427,36 @@ namespace AZ namespace AssetPath { - void CalculateBranchToken(const AZStd::string& appRootPath, AZStd::string& token) + namespace Internal { - // Normalize the token to prepare for CRC32 calculation - AZStd::string normalized = appRootPath; - - // Strip out any trailing path separators - AZ::StringFunc::Strip(normalized, AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING AZ_WRONG_FILESYSTEM_SEPARATOR_STRING,false, false, true); - - // Lower case always - AZStd::to_lower(normalized.begin(), normalized.end()); + AZ::u32 CalculateBranchTokenHash(AZStd::string_view engineRootPath) + { + // Normalize the token to prepare for CRC32 calculation + auto NormalizeEnginePath = [](const char element) -> char + { + // Substitute path separators with '_' and lower case + return element == AZ::IO::WindowsPathSeparator || element == AZ::IO::PosixPathSeparator + ? '_' : static_cast(std::tolower(element)); + }; - // Substitute path separators with '_' - AZStd::replace(normalized.begin(), normalized.end(), '\\', '_'); - AZStd::replace(normalized.begin(), normalized.end(), '/', '_'); + // Trim off trailing path separators + engineRootPath = RStrip(engineRootPath, AZ_CORRECT_AND_WRONG_FILESYSTEM_SEPARATOR); + AZ::IO::FixedMaxPathString enginePath; + AZStd::transform(engineRootPath.begin(), engineRootPath.end(), + AZStd::back_inserter(enginePath), AZStd::move(NormalizeEnginePath)); - // Perform the CRC32 calculation - const AZ::Crc32 branchTokenCrc(normalized.c_str(), normalized.size(), true); - char branchToken[12]; - azsnprintf(branchToken, AZ_ARRAY_SIZE(branchToken), "0x%08X", static_cast(branchTokenCrc)); - token = AZStd::string(branchToken); + // Perform the CRC32 calculation + constexpr bool forceLowercase = true; + return static_cast(AZ::Crc32(enginePath.c_str(), enginePath.size(), forceLowercase)); + } + } + void CalculateBranchToken(AZStd::string_view engineRootPath, AZStd::string& token) + { + token = AZStd::string::format("0x%08X", Internal::CalculateBranchTokenHash(engineRootPath)); + } + void CalculateBranchToken(AZStd::string_view engineRootPath, AZ::IO::FixedMaxPathString& token) + { + token = AZ::IO::FixedMaxPathString::format("0x%08X", Internal::CalculateBranchTokenHash(engineRootPath)); } } diff --git a/Code/Framework/AzCore/AzCore/StringFunc/StringFunc.h b/Code/Framework/AzCore/AzCore/StringFunc/StringFunc.h index 1e651afc93..55236a0fff 100644 --- a/Code/Framework/AzCore/AzCore/StringFunc/StringFunc.h +++ b/Code/Framework/AzCore/AzCore/StringFunc/StringFunc.h @@ -485,10 +485,11 @@ namespace AZ //! CalculateBranchToken /*! Calculate the branch token that is used for asset processor connection negotiations * - * \param appRootPath - The absolute path of the app root to base the token calculation on + * \param engineRootPath - The absolute path to the engine root to base the token calculation on * \param token - The result of the branch token calculation */ - void CalculateBranchToken(const AZStd::string& appRootPath, AZStd::string& token); + void CalculateBranchToken(AZStd::string_view engineRootPath, AZStd::string& token); + void CalculateBranchToken(AZStd::string_view engineRootPath, AZ::IO::FixedMaxPathString& token); } ////////////////////////////////////////////////////////////////////////// diff --git a/Code/Framework/AzCore/AzCore/UnitTest/MockComponentApplication.h b/Code/Framework/AzCore/AzCore/UnitTest/MockComponentApplication.h index 8f069da9dd..190fa09cb7 100644 --- a/Code/Framework/AzCore/AzCore/UnitTest/MockComponentApplication.h +++ b/Code/Framework/AzCore/AzCore/UnitTest/MockComponentApplication.h @@ -41,7 +41,6 @@ namespace UnitTest MOCK_METHOD0(GetSerializeContext, AZ::SerializeContext* ()); MOCK_METHOD0(GetJsonRegistrationContext, AZ::JsonRegistrationContext* ()); MOCK_METHOD0(GetBehaviorContext, AZ::BehaviorContext* ()); - MOCK_CONST_METHOD0(GetAppRoot, const char* ()); MOCK_CONST_METHOD0(GetEngineRoot, const char* ()); MOCK_CONST_METHOD0(GetExecutableFolder, const char* ()); MOCK_CONST_METHOD1(QueryApplicationType, void(AZ::ApplicationTypeQuery&)); diff --git a/Code/Framework/AzCore/Tests/BehaviorContextFixture.h b/Code/Framework/AzCore/Tests/BehaviorContextFixture.h index 1895f2e35b..3c6d48add7 100644 --- a/Code/Framework/AzCore/Tests/BehaviorContextFixture.h +++ b/Code/Framework/AzCore/Tests/BehaviorContextFixture.h @@ -59,7 +59,6 @@ namespace UnitTest AZ::SerializeContext* GetSerializeContext() override { return nullptr; } AZ::BehaviorContext* GetBehaviorContext() override { return m_behaviorContext; } AZ::JsonRegistrationContext* GetJsonRegistrationContext() override { return nullptr; } - const char* GetAppRoot() const override { return nullptr; } const char* GetEngineRoot() const override { return nullptr; } const char* GetExecutableFolder() const override { return nullptr; } void EnumerateEntities(const EntityCallback& /*callback*/) override {} diff --git a/Code/Framework/AzCore/Tests/Components.cpp b/Code/Framework/AzCore/Tests/Components.cpp index 55b1c193b3..013d998c52 100644 --- a/Code/Framework/AzCore/Tests/Components.cpp +++ b/Code/Framework/AzCore/Tests/Components.cpp @@ -1060,26 +1060,21 @@ namespace UnitTest /** * UserSettingsComponent test */ - class UserSettingsTestApp - : public ComponentApplication - , public UserSettingsFileLocatorBus::Handler - { - public: - void SetExecutableFolder(const char* path) - { - m_exeDirectory = path; - } - + class UserSettingsTestApp + : public ComponentApplication + , public UserSettingsFileLocatorBus::Handler + { + public: AZStd::string ResolveFilePath(u32 providerId) override { AZStd::string filePath; if (providerId == UserSettings::CT_GLOBAL) { - filePath = (m_exeDirectory / "GlobalUserSettings.xml").String(); + filePath = (AZ::IO::Path(GetTestFolderPath()) / "GlobalUserSettings.xml").Native(); } else if (providerId == UserSettings::CT_LOCAL) { - filePath = (m_exeDirectory / "LocalUserSettings.xml").String(); + filePath = (AZ::IO::Path(GetTestFolderPath()) / "LocalUserSettings.xml").Native(); } return filePath; } @@ -1117,7 +1112,6 @@ namespace UnitTest ComponentApplication::Descriptor appDesc; appDesc.m_memoryBlocksByteSize = 10 * 1024 * 1024; Entity* systemEntity = app.Create(appDesc); - app.SetExecutableFolder(GetTestFolderPath().c_str()); app.UserSettingsFileLocatorBus::Handler::BusConnect(); // Make sure user settings file does not exist at this point diff --git a/Code/Framework/AzCore/Tests/Serialization.cpp b/Code/Framework/AzCore/Tests/Serialization.cpp index f1d5edc490..219744a480 100644 --- a/Code/Framework/AzCore/Tests/Serialization.cpp +++ b/Code/Framework/AzCore/Tests/Serialization.cpp @@ -1240,7 +1240,6 @@ namespace UnitTest SerializeContext* GetSerializeContext() override { return m_serializeContext.get(); } BehaviorContext* GetBehaviorContext() override { return nullptr; } JsonRegistrationContext* GetJsonRegistrationContext() override { return nullptr; } - const char* GetAppRoot() const override { return nullptr; } const char* GetEngineRoot() const override { return nullptr; } const char* GetExecutableFolder() const override { return nullptr; } void EnumerateEntities(const EntityCallback& /*callback*/) override {} diff --git a/Code/Framework/AzFramework/AzFramework/API/ApplicationAPI.h b/Code/Framework/AzFramework/AzFramework/API/ApplicationAPI.h index 1c5db0a82b..e536082d61 100644 --- a/Code/Framework/AzFramework/AzFramework/API/ApplicationAPI.h +++ b/Code/Framework/AzFramework/AzFramework/API/ApplicationAPI.h @@ -67,12 +67,6 @@ namespace AzFramework /// Make path relative to the provided root. virtual void MakePathRelative(AZStd::string& /*fullPath*/, const char* /*rootPath*/) {} - /// Gets the engine root path where the modules for the current engine are located. - virtual const char* GetEngineRoot() const { return nullptr; } - - /// Retrieves the app root path for the application. - virtual const char* GetAppRoot() const { return nullptr; } - /// Get the Command Line arguments passed in. virtual const CommandLine* GetCommandLine() { return nullptr; } diff --git a/Code/Framework/AzFramework/AzFramework/Application/Application.cpp b/Code/Framework/AzFramework/AzFramework/Application/Application.cpp index 323e834413..768ef1f0b6 100644 --- a/Code/Framework/AzFramework/AzFramework/Application/Application.cpp +++ b/Code/Framework/AzFramework/AzFramework/Application/Application.cpp @@ -69,6 +69,7 @@ #include #include #include +#include #include "Application.h" #include @@ -224,13 +225,6 @@ namespace AzFramework } } - void Application::PreModuleLoad() - { - SetRootPath(RootPathType::EngineRoot, m_engineRoot.c_str()); - AZ_TracePrintf(s_azFrameworkWarningWindow, "Engine Path: %s\n", m_engineRoot.c_str()); - } - - void Application::Stop() { if (m_isStarted) @@ -318,6 +312,8 @@ namespace AzFramework AzFramework::SurfaceData::SurfaceTagWeight::Reflect(context); AzFramework::SurfaceData::SurfacePoint::Reflect(context); AzFramework::Terrain::TerrainDataRequests::Reflect(context); + Physics::HeightfieldProviderRequests::Reflect(context); + Physics::HeightMaterialPoint::Reflect(context); if (AZ::SerializeContext* serializeContext = azrtti_cast(context)) { @@ -394,11 +390,6 @@ namespace AzFramework outModules.emplace_back(aznew AzFrameworkModule()); } - const char* Application::GetAppRoot() const - { - return m_appRoot.c_str(); - } - const char* Application::GetCurrentConfigurationName() const { #if defined(_RELEASE) @@ -434,19 +425,19 @@ namespace AzFramework void Application::ResolveEnginePath(AZStd::string& engineRelativePath) const { - AZ::IO::FixedMaxPath fullPath = m_engineRoot / engineRelativePath; + auto fullPath = AZ::IO::FixedMaxPath(GetEngineRoot()) / engineRelativePath; engineRelativePath = fullPath.String(); } void Application::CalculateBranchTokenForEngineRoot(AZStd::string& token) const { - AzFramework::StringFunc::AssetPath::CalculateBranchToken(m_engineRoot.String(), token); + AZ::StringFunc::AssetPath::CalculateBranchToken(GetEngineRoot(), token); } //////////////////////////////////////////////////////////////////////////// void Application::MakePathRootRelative(AZStd::string& fullPath) { - MakePathRelative(fullPath, m_engineRoot.c_str()); + MakePathRelative(fullPath, GetEngineRoot()); } //////////////////////////////////////////////////////////////////////////// @@ -582,30 +573,6 @@ namespace AzFramework } } - void Application::SetRootPath(RootPathType type, const char* source) - { - [[maybe_unused]] const size_t sourceLen = strlen(source); - - // Copy the source path to the intended root path and correct the path separators as well - switch (type) - { - case RootPathType::AppRoot: - { - AZ_Assert(sourceLen < m_appRoot.Native().max_size(), "String overflow for App Root: %s", source); - m_appRoot = AZ::IO::PathView(source).LexicallyNormal(); - } - break; - case RootPathType::EngineRoot: - { - AZ_Assert(sourceLen < m_engineRoot.Native().max_size(), "String overflow for Engine Root: %s", source); - m_engineRoot = AZ::IO::PathView(source).LexicallyNormal(); - } - break; - default: - AZ_Assert(false, "Invalid RootPathType (%d)", static_cast(type)); - } - } - struct DeprecatedAliasesKeyVisitor : AZ::SettingsRegistryInterface::Visitor { diff --git a/Code/Framework/AzFramework/AzFramework/Application/Application.h b/Code/Framework/AzFramework/AzFramework/Application/Application.h index c6b1dfeaae..a318ede4a2 100644 --- a/Code/Framework/AzFramework/AzFramework/Application/Application.h +++ b/Code/Framework/AzFramework/AzFramework/Application/Application.h @@ -95,8 +95,6 @@ namespace AzFramework ////////////////////////////////////////////////////////////////////////// //! ApplicationRequests::Bus::Handler - const char* GetEngineRoot() const override { return m_engineRoot.c_str(); } - const char* GetAppRoot() const override; void ResolveEnginePath(AZStd::string& engineRelativePath) const override; void CalculateBranchTokenForEngineRoot(AZStd::string& token) const override; bool IsPrefabSystemEnabled() const override; @@ -146,8 +144,6 @@ namespace AzFramework */ void SetFileIOAliases(); - void PreModuleLoad() override; - ////////////////////////////////////////////////////////////////////////// //! AZ::ComponentApplication void RegisterCoreComponents() override; @@ -181,13 +177,7 @@ namespace AzFramework bool m_ownsConsole = false; bool m_exitMainLoopRequested = false; - - enum class RootPathType - { - AppRoot, - EngineRoot - }; - void SetRootPath(RootPathType type, const char* source); + }; } // namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/Physics/HeightfieldProviderBus.cpp b/Code/Framework/AzFramework/AzFramework/Physics/HeightfieldProviderBus.cpp new file mode 100644 index 0000000000..38fac9655c --- /dev/null +++ b/Code/Framework/AzFramework/AzFramework/Physics/HeightfieldProviderBus.cpp @@ -0,0 +1,46 @@ +/* + * 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 "HeightfieldProviderBus.h" +#include +#include +#include + +namespace Physics +{ + void HeightfieldProviderRequests::Reflect(AZ::ReflectContext* context) + { + if (AZ::BehaviorContext* behaviorContext = azrtti_cast(context)) + { + behaviorContext->EBus("HeightfieldProviderRequestsBus") + ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common) + ->Attribute(AZ::Script::Attributes::Module, "physics") + ->Attribute(AZ::Script::Attributes::Category, "PhysX") + ->Event("GetHeightfieldGridSpacing", &Physics::HeightfieldProviderRequestsBus::Events::GetHeightfieldGridSpacing) + ->Event("GetHeightfieldAabb", &Physics::HeightfieldProviderRequestsBus::Events::GetHeightfieldAabb) + ->Event("GetHeightfieldTransform", &Physics::HeightfieldProviderRequestsBus::Events::GetHeightfieldTransform) + ->Event("GetMaterialList", &Physics::HeightfieldProviderRequestsBus::Events::GetMaterialList) + ->Event("GetHeights", &Physics::HeightfieldProviderRequestsBus::Events::GetHeights) + ->Event("GetHeightsAndMaterials", &Physics::HeightfieldProviderRequestsBus::Events::GetHeightsAndMaterials) + ->Event("GetHeightfieldMinHeight", &Physics::HeightfieldProviderRequestsBus::Events::GetHeightfieldMinHeight) + ->Event("GetHeightfieldMaxHeight", &Physics::HeightfieldProviderRequestsBus::Events::GetHeightfieldMaxHeight) + ->Event("GetHeightfieldGridColumns", &Physics::HeightfieldProviderRequestsBus::Events::GetHeightfieldGridColumns) + ->Event("GetHeightfieldGridRows", &Physics::HeightfieldProviderRequestsBus::Events::GetHeightfieldGridRows) + ; + } + } + + void HeightMaterialPoint::Reflect(AZ::ReflectContext* context) + { + if (AZ::BehaviorContext* behaviorContext = azrtti_cast(context)) + { + behaviorContext->Class()->Attribute(AZ::Script::Attributes::Category, "Physics"); + } + } + +} // namespace Physics diff --git a/Code/Framework/AzFramework/AzFramework/Physics/HeightfieldProviderBus.h b/Code/Framework/AzFramework/AzFramework/Physics/HeightfieldProviderBus.h index 73523ee1ba..da361f0a2b 100644 --- a/Code/Framework/AzFramework/AzFramework/Physics/HeightfieldProviderBus.h +++ b/Code/Framework/AzFramework/AzFramework/Physics/HeightfieldProviderBus.h @@ -26,10 +26,25 @@ namespace Physics struct HeightMaterialPoint { + HeightMaterialPoint( + float height = 0.0f, QuadMeshType type = QuadMeshType::SubdivideUpperLeftToBottomRight, uint8_t index = 0) + : m_height(height) + , m_quadMeshType(type) + , m_materialIndex(index) + , m_padding(0) + { + } + + virtual ~HeightMaterialPoint() = default; + + static void Reflect(AZ::ReflectContext* context); + + AZ_RTTI(HeightMaterialPoint, "{DF167ED4-24E6-4F7B-8AB7-42622F7DBAD3}"); float m_height{ 0.0f }; //!< Holds the height of this point in the heightfield relative to the heightfield entity location. QuadMeshType m_quadMeshType{ QuadMeshType::SubdivideUpperLeftToBottomRight }; //!< By default, create two triangles like this |\|, where this point is in the upper left corner. uint8_t m_materialIndex{ 0 }; //!< The surface material index for the upper left corner of this quad. uint16_t m_padding{ 0 }; //!< available for future use. + }; //! An interface to provide heightfield values. @@ -37,6 +52,8 @@ namespace Physics : public AZ::ComponentBus { public: + static void Reflect(AZ::ReflectContext* context); + //! Returns the distance between each height in the map. //! @return Vector containing Column Spacing, Rows Spacing. virtual AZ::Vector2 GetHeightfieldGridSpacing() const = 0; @@ -46,11 +63,27 @@ namespace Physics //! @param numRows contains the size of the grid in the y direction. virtual void GetHeightfieldGridSize(int32_t& numColumns, int32_t& numRows) const = 0; + //! Returns the height field gridsize columns. + //! @return the size of the grid in the x direction. + virtual int32_t GetHeightfieldGridColumns() const = 0; + + //! Returns the height field gridsize rows. + //! @return the size of the grid in the y direction. + virtual int32_t GetHeightfieldGridRows() const = 0; + //! Returns the height field min and max height bounds. //! @param minHeightBounds contains the minimum height that the heightfield can contain. //! @param maxHeightBounds contains the maximum height that the heightfield can contain. virtual void GetHeightfieldHeightBounds(float& minHeightBounds, float& maxHeightBounds) const = 0; + //! Returns the height field min height bounds. + //! @return the minimum height that the heightfield can contain. + virtual float GetHeightfieldMinHeight() const = 0; + + //! Returns the height field max height bounds. + //! @return the maximum height that the heightfield can contain. + virtual float GetHeightfieldMaxHeight() const = 0; + //! Returns the AABB of the heightfield. //! This is provided separately from the shape AABB because the heightfield might choose to modify the AABB bounds. //! @return AABB of the heightfield. diff --git a/Code/Framework/AzFramework/AzFramework/Physics/Material.cpp b/Code/Framework/AzFramework/AzFramework/Physics/Material.cpp index 222bc48dda..d78d4e0943 100644 --- a/Code/Framework/AzFramework/AzFramework/Physics/Material.cpp +++ b/Code/Framework/AzFramework/AzFramework/Physics/Material.cpp @@ -360,6 +360,11 @@ namespace Physics ->Field("MaterialId", &Physics::MaterialId::m_id) ; } + + if (AZ::BehaviorContext* behaviorContext = azrtti_cast(context)) + { + behaviorContext->Class()->Attribute(AZ::Script::Attributes::Category, "Physics"); + } } MaterialId MaterialId::Create() diff --git a/Code/Framework/AzFramework/AzFramework/azframework_files.cmake b/Code/Framework/AzFramework/AzFramework/azframework_files.cmake index e03d166cfc..13d9003031 100644 --- a/Code/Framework/AzFramework/AzFramework/azframework_files.cmake +++ b/Code/Framework/AzFramework/AzFramework/azframework_files.cmake @@ -229,6 +229,7 @@ set(FILES Physics/Configuration/SystemConfiguration.h Physics/Configuration/SystemConfiguration.cpp Physics/HeightfieldProviderBus.h + Physics/HeightfieldProviderBus.cpp Physics/SimulatedBodies/RigidBody.h Physics/SimulatedBodies/RigidBody.cpp Physics/SimulatedBodies/StaticRigidBody.h diff --git a/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbInputDeviceMouse.h b/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbInputDeviceMouse.h index a69a8a9ec5..f5b805a7a5 100644 --- a/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbInputDeviceMouse.h +++ b/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbInputDeviceMouse.h @@ -6,6 +6,8 @@ * */ +#pragma once + #include #include #include diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Components/ToastNotification.cpp b/Code/Framework/AzQtComponents/AzQtComponents/Components/ToastNotification.cpp index 8831bef89c..11fd1e60d8 100644 --- a/Code/Framework/AzQtComponents/AzQtComponents/Components/ToastNotification.cpp +++ b/Code/Framework/AzQtComponents/AzQtComponents/Components/ToastNotification.cpp @@ -27,7 +27,6 @@ namespace AzQtComponents setProperty("HasNoWindowDecorations", true); setAttribute(Qt::WA_ShowWithoutActivating); - setAttribute(Qt::WA_DeleteOnClose); m_borderRadius = toastConfiguration.m_borderRadius; if (m_borderRadius > 0) diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Components/ToastNotification.h b/Code/Framework/AzQtComponents/AzQtComponents/Components/ToastNotification.h index 4343f37df4..81b1cb4055 100644 --- a/Code/Framework/AzQtComponents/AzQtComponents/Components/ToastNotification.h +++ b/Code/Framework/AzQtComponents/AzQtComponents/Components/ToastNotification.h @@ -31,7 +31,6 @@ namespace AzQtComponents { Q_OBJECT public: - AZ_CLASS_ALLOCATOR(ToastNotification, AZ::SystemAllocator, 0); ToastNotification(QWidget* parent, const ToastConfiguration& toastConfiguration); virtual ~ToastNotification(); @@ -73,7 +72,7 @@ namespace AzQtComponents AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING AZStd::chrono::milliseconds m_fadeDuration; - AZStd::unique_ptr m_ui; + QScopedPointer m_ui; AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING }; } // namespace AzQtComponents diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Components/ToastNotificationConfiguration.h b/Code/Framework/AzQtComponents/AzQtComponents/Components/ToastNotificationConfiguration.h index 5ace9d1be2..2db374640e 100644 --- a/Code/Framework/AzQtComponents/AzQtComponents/Components/ToastNotificationConfiguration.h +++ b/Code/Framework/AzQtComponents/AzQtComponents/Components/ToastNotificationConfiguration.h @@ -27,7 +27,6 @@ namespace AzQtComponents class AZ_QT_COMPONENTS_API ToastConfiguration { public: - AZ_CLASS_ALLOCATOR(ToastConfiguration, AZ::SystemAllocator, 0); ToastConfiguration(ToastType toastType, const QString& title, const QString& description); bool m_closeOnClick = true; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Asset/AssetUtils.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Asset/AssetUtils.cpp index 139bbb563c..6fd881dbb9 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Asset/AssetUtils.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Asset/AssetUtils.cpp @@ -14,7 +14,7 @@ #include #include #include -#include +#include #include #include #include @@ -205,7 +205,7 @@ namespace AzToolsFramework::AssetUtils return platformConfigFilePathsAdded; } - AZStd::vector GetConfigFiles(AZStd::string_view engineRoot, AZStd::string_view assetRoot, AZStd::string_view projectPath, + AZStd::vector GetConfigFiles(AZStd::string_view engineRoot, AZStd::string_view projectPath, bool addPlatformConfigs, bool addGemsConfigs, AZ::SettingsRegistryInterface* settingsRegistry) { constexpr const char* AssetProcessorGamePlatformConfigFileName = "AssetProcessorGamePlatformConfig.ini"; @@ -232,14 +232,13 @@ namespace AzToolsFramework::AssetUtils Internal::AddGemConfigFiles(gemInfoList, configFiles); } - AZ::IO::Path assetRootDir(assetRoot); - assetRootDir /= projectPath; + AZ::IO::Path projectRoot(projectPath); - AZ::IO::Path projectConfigFile = assetRootDir / AssetProcessorGamePlatformConfigFileName; + AZ::IO::Path projectConfigFile = projectRoot / AssetProcessorGamePlatformConfigFileName; configFiles.push_back(projectConfigFile); // Add a file entry for the Project AssetProcessor setreg file - projectConfigFile = assetRootDir / AssetProcessorGamePlatformConfigSetreg; + projectConfigFile = projectRoot / AssetProcessorGamePlatformConfigSetreg; configFiles.push_back(projectConfigFile); return configFiles; @@ -251,10 +250,10 @@ namespace AzToolsFramework::AssetUtils AZStd::vector tokens; AZ::StringFunc::Tokenize(relPathFromRoot.c_str(), tokens, AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING); - AZStd::string validatedPath; + AZ::IO::FixedMaxPath validatedPath; if (rootPath.empty()) { - AzFramework::ApplicationRequests::Bus::BroadcastResult(validatedPath, &AzFramework::ApplicationRequests::GetEngineRoot); + validatedPath = AZ::Utils::GetEnginePath(); } else { @@ -299,10 +298,7 @@ namespace AzToolsFramework::AssetUtils break; } - AZStd::string absoluteFilePath; - AZ::StringFunc::Path::ConstructFull(validatedPath.c_str(), element.c_str(), absoluteFilePath); - - validatedPath = absoluteFilePath; // go one step deeper. + validatedPath /= element; // go one step deeper. } if (success) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Asset/AssetUtils.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Asset/AssetUtils.h index 39aab4d3fb..31ee9dcf60 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Asset/AssetUtils.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Asset/AssetUtils.h @@ -40,7 +40,7 @@ namespace AzToolsFramework::AssetUtils //! Also note that if the project has any "game project gems", then those will also be inserted last, //! and thus have a higher priority than the root or non - project gems. //! Also note that the game project could be in a different location to the engine therefore we need the assetRoot param. - AZStd::vector GetConfigFiles(AZStd::string_view engineRoot, AZStd::string_view assetRoot, AZStd::string_view projectPath, + AZStd::vector GetConfigFiles(AZStd::string_view engineRoot, AZStd::string_view projectPath, bool addPlatformConfigs = true, bool addGemsConfigs = true, AZ::SettingsRegistryInterface* settingsRegistry = nullptr); //! A utility function which checks the given path starting at the root and updates the relative path to be the actual case correct path. diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/AssetBrowserFilterModel.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/AssetBrowserFilterModel.cpp index acf935e6dc..c6772ea2d7 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/AssetBrowserFilterModel.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/AssetBrowserFilterModel.cpp @@ -20,7 +20,7 @@ AZ_PUSH_DISABLE_WARNING(4251, "-Wunknown-warning-option") AZ_POP_DISABLE_WARNING AZ_CVAR( - bool, ed_useNewAssetBrowserTableView, false, nullptr, AZ::ConsoleFunctorFlags::Null, + bool, ed_useNewAssetBrowserTableView, true, nullptr, AZ::ConsoleFunctorFlags::Null, "Use the new AssetBrowser TableView for searching assets."); namespace AzToolsFramework { diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/Thumbnails/SourceThumbnail.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/Thumbnails/SourceThumbnail.cpp index a5206c9608..4869e0d09f 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/Thumbnails/SourceThumbnail.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/Thumbnails/SourceThumbnail.cpp @@ -9,11 +9,11 @@ #include #include #include +#include #include #include #include #include -#include #include namespace AzToolsFramework @@ -113,11 +113,9 @@ namespace AzToolsFramework if (iconPathToUse.isEmpty()) { - const char* engineRoot = nullptr; - AzFramework::ApplicationRequests::Bus::BroadcastResult(engineRoot, &AzFramework::ApplicationRequests::GetEngineRoot); - AZ_Assert(engineRoot, "Engine Root not initialized"); - AZStd::string iconPath = AZStd::string::format("%s%s", engineRoot, DefaultFileIconPath); - iconPathToUse = iconPath.c_str(); + AZ::IO::FixedMaxPath engineRoot = AZ::Utils::GetEnginePath(); + AZ_Assert(!engineRoot.empty(), "Engine Root not initialized"); + iconPathToUse = (engineRoot / DefaultFileIconPath).c_str(); } m_pixmap.load(iconPathToUse); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/EditorEntityHelpers.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/EditorEntityHelpers.cpp index 28d6e2bc08..3e256430a2 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/EditorEntityHelpers.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/EditorEntityHelpers.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -468,6 +469,18 @@ namespace AzToolsFramework EntityIdList children; EditorEntityInfoRequestBus::EventResult(children, parentId, &EditorEntityInfoRequestBus::Events::GetChildren); + // If Prefabs are enabled, don't check the order for an invalid parent, just return its children (i.e. the root container entity) + // There will currently always be one root container entity, so there's no order to retrieve + if (!parentId.IsValid()) + { + bool isPrefabEnabled = false; + AzFramework::ApplicationRequests::Bus::BroadcastResult(isPrefabEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled); + if (isPrefabEnabled) + { + return children; + } + } + EntityIdList entityChildOrder; AZ::EntityId sortEntityId = GetEntityIdForSortInfo(parentId); EditorEntitySortRequestBus::EventResult(entityChildOrder, sortEntityId, &EditorEntitySortRequestBus::Events::GetChildEntityOrderArray); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/EditorEntitySortComponent.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/EditorEntitySortComponent.cpp index b747469f4d..6936397187 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/EditorEntitySortComponent.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/EditorEntitySortComponent.cpp @@ -11,6 +11,8 @@ #include #include #include +#include +#include static_assert(sizeof(AZ::u64) == sizeof(AZ::EntityId), "We use AZ::EntityId for Persistent ID, which is a u64 under the hood. These must be the same size otherwise the persistent id will have to be rewritten"); @@ -144,6 +146,12 @@ namespace AzToolsFramework bool EditorEntitySortComponent::AddChildEntityInternal(const AZ::EntityId& entityId, bool addToBack, EntityOrderArray::iterator insertPosition) { AZ_PROFILE_FUNCTION(AzToolsFramework); + + if (m_ignoreIncomingOrderChanges) + { + return true; + } + auto entityItr = m_childEntityOrderCache.find(entityId); if (entityItr == m_childEntityOrderCache.end()) { @@ -198,6 +206,12 @@ namespace AzToolsFramework bool EditorEntitySortComponent::RemoveChildEntity(const AZ::EntityId& entityId) { AZ_PROFILE_FUNCTION(AzToolsFramework); + + if (m_ignoreIncomingOrderChanges) + { + return true; + } + auto entityItr = m_childEntityOrderCache.find(entityId); if (entityItr != m_childEntityOrderCache.end()) { @@ -250,11 +264,30 @@ namespace AzToolsFramework } } + void EditorEntitySortComponent::OnPrefabInstancePropagationBegin() + { + m_ignoreIncomingOrderChanges = true; + } + + void EditorEntitySortComponent::OnPrefabInstancePropagationEnd() + { + m_ignoreIncomingOrderChanges = false; + } + void EditorEntitySortComponent::MarkDirtyAndSendChangedEvent() { // mark the order as dirty before sending the ChildEntityOrderArrayUpdated event in order for PrepareSave to be properly handled in the case // one of the event listeners needs to build the InstanceDataHierarchy m_entityOrderIsDirty = true; + + // Force an immediate update for prefabs, which won't receive PrepareSave + bool isPrefabEnabled = false; + AzFramework::ApplicationRequests::Bus::BroadcastResult( + isPrefabEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled); + if (isPrefabEnabled) + { + PrepareSave(); + } EditorEntitySortNotificationBus::Event(GetEntityId(), &EditorEntitySortNotificationBus::Events::ChildEntityOrderArrayUpdated); } @@ -264,10 +297,20 @@ namespace AzToolsFramework // This is a special case for certain EditorComponents only! EditorEntitySortRequestBus::Handler::BusConnect(GetEntityId()); EditorEntityContextNotificationBus::Handler::BusConnect(); + AzToolsFramework::Prefab::PrefabPublicNotificationBus::Handler::BusConnect(); } void EditorEntitySortComponent::Activate() { + // Run the post-serialize handler if prefabs are enabled because PostLoad won't be called automatically + bool isPrefabEnabled = false; + AzFramework::ApplicationRequests::Bus::BroadcastResult( + isPrefabEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled); + if (isPrefabEnabled) + { + PostLoad(); + } + // Send out that the order for our entity is now updated EditorEntitySortNotificationBus::Event(GetEntityId(), &EditorEntitySortNotificationBus::Events::ChildEntityOrderArrayUpdated); } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/EditorEntitySortComponent.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/EditorEntitySortComponent.h index d728191c64..806e903c96 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/EditorEntitySortComponent.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/EditorEntitySortComponent.h @@ -10,6 +10,7 @@ #include "EditorEntitySortBus.h" #include #include +#include #include namespace AzToolsFramework @@ -20,6 +21,7 @@ namespace AzToolsFramework : public AzToolsFramework::Components::EditorComponentBase , public EditorEntitySortRequestBus::Handler , public EditorEntityContextNotificationBus::Handler + , public AzToolsFramework::Prefab::PrefabPublicNotificationBus::Handler { public: AZ_COMPONENT(EditorEntitySortComponent, "{6EA1E03D-68B2-466D-97F7-83998C8C27F0}", EditorComponentBase); @@ -45,6 +47,9 @@ namespace AzToolsFramework // EditorEntityContextNotificationBus::Handler void OnEntityStreamLoadSuccess() override; ////////////////////////////////////////////////////////////////////////// + + void OnPrefabInstancePropagationBegin() override; + void OnPrefabInstancePropagationEnd() override; private: void MarkDirtyAndSendChangedEvent(); bool AddChildEntityInternal(const AZ::EntityId& entityId, bool addToBack, EntityOrderArray::iterator insertPosition); @@ -106,6 +111,7 @@ namespace AzToolsFramework EntityOrderCache m_childEntityOrderCache; ///< The map of entity id to index for quick look up bool m_entityOrderIsDirty = true; ///< This flag indicates our stored serialization order data is out of date and must be rebuilt before serialization occurs + bool m_ignoreIncomingOrderChanges = false; ///< This is set when prefab propagation occurs so that non-authored order changes can be ignored }; } } // namespace AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp index 5ac9f772dc..32600853fd 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp @@ -1144,6 +1144,10 @@ namespace AzToolsFramework AZ::EntityId firstEntityIdToDelete = entityIdsNoFocusContainer[0]; InstanceOptionalReference commonOwningInstance = GetOwnerInstanceByEntityId(firstEntityIdToDelete); + if (!commonOwningInstance.has_value()) + { + return AZ::Failure(AZStd::string("Cannot delete entities belonging to an invalid instance")); + } // If the first entity id is a container entity id, then we need to mark its parent as the common owning instance because you // cannot delete an instance from itself. diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Thumbnails/SourceControlThumbnail.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Thumbnails/SourceControlThumbnail.cpp index 655fd27a0f..c2636d6d52 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Thumbnails/SourceControlThumbnail.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Thumbnails/SourceControlThumbnail.cpp @@ -6,10 +6,10 @@ * */ -#include +#include +#include #include #include -#include namespace AzToolsFramework { @@ -68,12 +68,12 @@ namespace AzToolsFramework SourceControlThumbnail::SourceControlThumbnail(SharedThumbnailKey key) : Thumbnail(key) { - const char* engineRoot = nullptr; - AzFramework::ApplicationRequests::Bus::BroadcastResult(engineRoot, &AzFramework::ApplicationRequests::GetEngineRoot); - AZ_Assert(engineRoot, "Engine Root not initialized"); + AZ::IO::FixedMaxPath engineRoot = AZ::Utils::GetEnginePath(); + AZ_Assert(!engineRoot.empty(), "Engine Root not initialized"); + + m_writableIconPath = (engineRoot / WRITABLE_ICON_PATH).String(); + m_nonWritableIconPath = (engineRoot / NONWRITABLE_ICON_PATH).String(); - AzFramework::StringFunc::Path::Join(engineRoot, WRITABLE_ICON_PATH, m_writableIconPath); - AzFramework::StringFunc::Path::Join(engineRoot, NONWRITABLE_ICON_PATH, m_nonWritableIconPath); BusConnect(); } @@ -90,8 +90,8 @@ namespace AzToolsFramework AZ_Assert(sourceControlKey, "Incorrect key type, excpected SourceControlThumbnailKey"); AZStd::string myFileName(sourceControlKey->GetFileName()); - AzFramework::StringFunc::Path::Normalize(myFileName); - if (AzFramework::StringFunc::Equal(myFileName.c_str(), filename)) + AZ::StringFunc::Path::Normalize(myFileName); + if (AZ::StringFunc::Equal(myFileName.c_str(), filename)) { Update(); } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Notifications/ToastNotificationsView.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Notifications/ToastNotificationsView.cpp index a88711a9ed..277d3fa3c5 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Notifications/ToastNotificationsView.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Notifications/ToastNotificationsView.cpp @@ -129,7 +129,7 @@ namespace AzToolsFramework ToastId ToastNotificationsView::CreateToastNotification(const AzQtComponents::ToastConfiguration& toastConfiguration) { - AzQtComponents::ToastNotification* notification = aznew AzQtComponents::ToastNotification(parentWidget(), toastConfiguration); + AzQtComponents::ToastNotification* notification = new AzQtComponents::ToastNotification(this, toastConfiguration); ToastId toastId = AZ::Entity::MakeId(); m_notifications[toastId] = notification; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/EntityPropertyEditor.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/EntityPropertyEditor.cpp index 9ffe8021e3..a449fa0055 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/EntityPropertyEditor.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/EntityPropertyEditor.cpp @@ -606,6 +606,7 @@ namespace AzToolsFramework AzToolsFramework::ComponentModeFramework::EditorComponentModeNotificationBus::Handler::BusConnect( AzToolsFramework::GetEntityContextId()); + ViewportEditorModeNotificationsBus::Handler::BusConnect(GetEntityContextId()); } EntityPropertyEditor::~EntityPropertyEditor() @@ -618,7 +619,8 @@ namespace AzToolsFramework AZ::EntitySystemBus::Handler::BusDisconnect(); EditorEntityContextNotificationBus::Handler::BusDisconnect(); AzToolsFramework::ComponentModeFramework::EditorComponentModeNotificationBus::Handler::BusDisconnect(); - + ViewportEditorModeNotificationsBus::Handler::BusDisconnect(); + for (auto& entityId : m_overrideSelectedEntityIds) { DisconnectFromEntityBuses(entityId); diff --git a/Code/Framework/AzToolsFramework/Tests/ComponentAddRemove.cpp b/Code/Framework/AzToolsFramework/Tests/ComponentAddRemove.cpp index 9d6c8a4bff..06353b17c5 100644 --- a/Code/Framework/AzToolsFramework/Tests/ComponentAddRemove.cpp +++ b/Code/Framework/AzToolsFramework/Tests/ComponentAddRemove.cpp @@ -1116,7 +1116,6 @@ namespace UnitTest SerializeContext* GetSerializeContext() override { return m_serializeContext.get(); } BehaviorContext* GetBehaviorContext() override { return nullptr; } JsonRegistrationContext* GetJsonRegistrationContext() override { return nullptr; } - const char* GetAppRoot() const override { return nullptr; } const char* GetEngineRoot() const override { return nullptr; } const char* GetExecutableFolder() const override { return nullptr; } void EnumerateEntities(const EntityCallback& /*callback*/) override {} diff --git a/Code/Framework/AzToolsFramework/Tests/UI/EntityPropertyEditorTests.cpp b/Code/Framework/AzToolsFramework/Tests/UI/EntityPropertyEditorTests.cpp index c24bab9299..a3c6666a40 100644 --- a/Code/Framework/AzToolsFramework/Tests/UI/EntityPropertyEditorTests.cpp +++ b/Code/Framework/AzToolsFramework/Tests/UI/EntityPropertyEditorTests.cpp @@ -36,11 +36,6 @@ namespace UnitTest : public ComponentApplication { public: - void SetExecutableFolder(const char* path) - { - m_exeDirectory = path; - } - void SetSettingsRegistrySpecializations(SettingsRegistryInterface::Specializations& specializations) override { ComponentApplication::SetSettingsRegistrySpecializations(specializations); diff --git a/Code/Tools/AssetBundler/source/utils/GUIApplicationManager.cpp b/Code/Tools/AssetBundler/source/utils/GUIApplicationManager.cpp index b17832526f..17e621f7d6 100644 --- a/Code/Tools/AssetBundler/source/utils/GUIApplicationManager.cpp +++ b/Code/Tools/AssetBundler/source/utils/GUIApplicationManager.cpp @@ -346,9 +346,7 @@ namespace AssetBundler } // Determine the enabled platforms - const char* appRoot = nullptr; - AzFramework::ApplicationRequests::Bus::BroadcastResult(appRoot, &AzFramework::ApplicationRequests::GetAppRoot); - m_enabledPlatforms = GetEnabledPlatformFlags(GetEngineRoot(), appRoot, AZ::Utils::GetProjectPath().c_str()); + m_enabledPlatforms = GetEnabledPlatformFlags(GetEngineRoot(), AZStd::string_view(AZ::Utils::GetProjectPath())); // Determine which Gems are enabled for the current project if (!AzFramework::GetGemsInfo(m_gemInfoList, *m_settingsRegistry)) diff --git a/Code/Tools/AssetBundler/source/utils/applicationManager.cpp b/Code/Tools/AssetBundler/source/utils/applicationManager.cpp index 0388570fdd..2df9e64ac5 100644 --- a/Code/Tools/AssetBundler/source/utils/applicationManager.cpp +++ b/Code/Tools/AssetBundler/source/utils/applicationManager.cpp @@ -1401,7 +1401,6 @@ namespace AssetBundler // If no platform was specified, defaulting to platforms specified in the asset processor config files AzFramework::PlatformFlags platformFlags = GetEnabledPlatformFlags( - AZStd::string_view{ AZ::Utils::GetEnginePath() }, AZStd::string_view{ AZ::Utils::GetEnginePath() }, AZStd::string_view{ AZ::Utils::GetProjectPath() }); [[maybe_unused]] auto platformsString = AzFramework::PlatformHelper::GetCommaSeparatedPlatformList(platformFlags); diff --git a/Code/Tools/AssetBundler/source/utils/utils.cpp b/Code/Tools/AssetBundler/source/utils/utils.cpp index 6daf3c571b..7e56f5450e 100644 --- a/Code/Tools/AssetBundler/source/utils/utils.cpp +++ b/Code/Tools/AssetBundler/source/utils/utils.cpp @@ -377,7 +377,6 @@ namespace AssetBundler AzFramework::PlatformFlags GetEnabledPlatformFlags( AZStd::string_view engineRoot, - AZStd::string_view assetRoot, AZStd::string_view projectPath) { auto settingsRegistry = AZ::SettingsRegistry::Get(); @@ -387,7 +386,7 @@ namespace AssetBundler return AzFramework::PlatformFlags::Platform_NONE; } - auto configFiles = AzToolsFramework::AssetUtils::GetConfigFiles(engineRoot, assetRoot, projectPath, true, true, settingsRegistry); + auto configFiles = AzToolsFramework::AssetUtils::GetConfigFiles(engineRoot, projectPath, true, true, settingsRegistry); auto enabledPlatformList = AzToolsFramework::AssetUtils::GetEnabledPlatforms(*settingsRegistry, configFiles); AzFramework::PlatformFlags platformFlags = AzFramework::PlatformFlags::Platform_NONE; for (const auto& enabledPlatform : enabledPlatformList) diff --git a/Code/Tools/AssetBundler/source/utils/utils.h b/Code/Tools/AssetBundler/source/utils/utils.h index bfdf252014..0986d70ca8 100644 --- a/Code/Tools/AssetBundler/source/utils/utils.h +++ b/Code/Tools/AssetBundler/source/utils/utils.h @@ -221,7 +221,6 @@ namespace AssetBundler //! Please note that the game project could be in a different location to the engine therefore we need the assetRoot param. AzFramework::PlatformFlags GetEnabledPlatformFlags( AZStd::string_view enginePath, - AZStd::string_view assetRoot, AZStd::string_view projectPath); QJsonObject ReadJson(const AZStd::string& filePath); diff --git a/Code/Tools/AssetBundler/tests/UtilsTests.cpp b/Code/Tools/AssetBundler/tests/UtilsTests.cpp index 560d399613..60fc79579b 100644 --- a/Code/Tools/AssetBundler/tests/UtilsTests.cpp +++ b/Code/Tools/AssetBundler/tests/UtilsTests.cpp @@ -67,7 +67,7 @@ namespace AssetBundler void NormalizePathKeepCase(AZStd::string& /*path*/) override {} void CalculateBranchTokenForEngineRoot(AZStd::string& /*token*/) const override {} - const char* GetEngineRoot() const override + const char* GetTempDir() const { return m_tempDir->GetDirectory(); } @@ -83,7 +83,7 @@ namespace AssetBundler TEST_F(MockUtilsTest, DISABLED_TestFilePath_StartsWithAFileSeparator_Valid) { AZ::IO::Path relFilePath = "Foo/foo.xml"; - AZ::IO::Path absoluteFilePath = AZ::IO::PathView(GetEngineRoot()).RootPath(); + AZ::IO::Path absoluteFilePath = AZ::IO::PathView(GetTempDir()).RootPath(); absoluteFilePath /= relFilePath; absoluteFilePath = absoluteFilePath.LexicallyNormal(); @@ -95,7 +95,7 @@ namespace AssetBundler TEST_F(MockUtilsTest, TestFilePath_RelativePath_Valid) { AZ::IO::Path relFilePath = "Foo\\foo.xml"; - AZ::IO::Path absoluteFilePath = (AZ::IO::Path(GetEngineRoot()) / relFilePath).LexicallyNormal(); + AZ::IO::Path absoluteFilePath = (AZ::IO::Path(GetTempDir()) / relFilePath).LexicallyNormal(); FilePath filePath(relFilePath.Native()); EXPECT_EQ(AZ::IO::PathView{ filePath.AbsolutePath() }, absoluteFilePath); } @@ -107,8 +107,8 @@ namespace AssetBundler AZ::IO::Path relFilePath = "Foo\\Foo.xml"; AZ::IO::Path wrongCaseRelFilePath = "Foo\\foo.xml"; - AZ::IO::Path correctAbsoluteFilePath = (AZ::IO::Path(GetEngineRoot()) / relFilePath).LexicallyNormal(); - AZ::IO::Path wrongCaseAbsoluteFilePath = (AZ::IO::Path(GetEngineRoot()) / wrongCaseRelFilePath).LexicallyNormal(); + AZ::IO::Path correctAbsoluteFilePath = (AZ::IO::Path(GetTempDir()) / relFilePath).LexicallyNormal(); + AZ::IO::Path wrongCaseAbsoluteFilePath = (AZ::IO::Path(GetTempDir()) / wrongCaseRelFilePath).LexicallyNormal(); AZ::IO::HandleType fileHandle = AZ::IO::InvalidHandle; AZ::IO::FileIOBase::GetInstance()->Open(correctAbsoluteFilePath.c_str(), AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeCreatePath, fileHandle); @@ -121,7 +121,7 @@ namespace AssetBundler TEST_F(MockUtilsTest, TestFilePath_NoFileExists_NoError_valid) { AZ::IO::Path relFilePath = "Foo\\Foo.xml"; - AZ::IO::Path absoluteFilePath = (AZ::IO::Path(GetEngineRoot()) / relFilePath).LexicallyNormal(); + AZ::IO::Path absoluteFilePath = (AZ::IO::Path(GetTempDir()) / relFilePath).LexicallyNormal(); FilePath filePath(absoluteFilePath.Native(), true, false); EXPECT_TRUE(filePath.IsValid()); @@ -132,8 +132,8 @@ namespace AssetBundler { AZStd::string relFilePath = "Foo\\Foo.xml"; AZStd::string wrongCaseRelFilePath = "Foo\\foo.xml"; - AZ::IO::Path correctAbsoluteFilePath = (AZ::IO::Path(GetEngineRoot()) / relFilePath).LexicallyNormal(); - AZ::IO::Path wrongCaseAbsoluteFilePath = (AZ::IO::Path(GetEngineRoot()) / wrongCaseRelFilePath).LexicallyNormal(); + AZ::IO::Path correctAbsoluteFilePath = (AZ::IO::Path(GetTempDir()) / relFilePath).LexicallyNormal(); + AZ::IO::Path wrongCaseAbsoluteFilePath = (AZ::IO::Path(GetTempDir()) / wrongCaseRelFilePath).LexicallyNormal(); AZ::IO::HandleType fileHandle = AZ::IO::InvalidHandle; AZ::IO::FileIOBase::GetInstance()->Open(correctAbsoluteFilePath.c_str(), AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeCreatePath, fileHandle); diff --git a/Code/Tools/AssetBundler/tests/applicationManagerTests.cpp b/Code/Tools/AssetBundler/tests/applicationManagerTests.cpp index 07eae67a81..fd587195af 100644 --- a/Code/Tools/AssetBundler/tests/applicationManagerTests.cpp +++ b/Code/Tools/AssetBundler/tests/applicationManagerTests.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -84,10 +85,9 @@ namespace AssetBundler // in the unit tests. AZ::UserSettingsComponentRequestBus::Broadcast(&AZ::UserSettingsComponentRequests::DisableSaveOnFinalize); - const char* engineRoot = nullptr; - AzFramework::ApplicationRequests::Bus::BroadcastResult(engineRoot, &AzFramework::ApplicationRequests::GetEngineRoot); - ASSERT_TRUE(engineRoot) << "Unable to locate engine root.\n"; - AzFramework::StringFunc::Path::Join(engineRoot, RelativeTestFolder, m_data->m_testEngineRoot); + AZ::IO::FixedMaxPath engineRoot = AZ::Utils::GetEnginePath(); + ASSERT_TRUE(!engineRoot.empty()) << "Unable to locate engine root.\n"; + m_data->m_testEngineRoot = (engineRoot / RelativeTestFolder).String(); m_data->m_localFileIO = aznew AZ::IO::LocalFileIO(); m_data->m_priorFileIO = AZ::IO::FileIOBase::GetInstance(); @@ -150,7 +150,8 @@ namespace AssetBundler EXPECT_EQ(0, gemsNameMap.size()); - AzFramework::PlatformFlags platformFlags = GetEnabledPlatformFlags(m_data->m_testEngineRoot.c_str(), m_data->m_testEngineRoot.c_str(), DummyProjectName); + const auto testProjectPath = AZ::IO::Path(m_data->m_testEngineRoot) / DummyProjectName; + AzFramework::PlatformFlags platformFlags = GetEnabledPlatformFlags(m_data->m_testEngineRoot, testProjectPath.Native()); AzFramework::PlatformFlags hostPlatformFlag = AzFramework::PlatformHelper::GetPlatformFlag(AzToolsFramework::AssetSystem::GetHostAssetPlatform()); AzFramework::PlatformFlags expectedFlags = AzFramework::PlatformFlags::Platform_ANDROID | AzFramework::PlatformFlags::Platform_IOS | AzFramework::PlatformFlags::Platform_PROVO | hostPlatformFlag; ASSERT_EQ(platformFlags, expectedFlags); diff --git a/Code/Tools/AssetProcessor/native/tests/assetmanager/AssetProcessorManagerTest.cpp b/Code/Tools/AssetProcessor/native/tests/assetmanager/AssetProcessorManagerTest.cpp index f8d758e092..1a6063cff0 100644 --- a/Code/Tools/AssetProcessor/native/tests/assetmanager/AssetProcessorManagerTest.cpp +++ b/Code/Tools/AssetProcessor/native/tests/assetmanager/AssetProcessorManagerTest.cpp @@ -243,7 +243,7 @@ void AssetProcessorManagerTest::SetUp() m_mockApplicationManager->BusConnect(); m_assetProcessorManager.reset(new AssetProcessorManager_Test(m_config.get())); - m_assertAbsorber.Clear(); + m_errorAbsorber->Clear(); m_isIdling = false; @@ -334,9 +334,9 @@ TEST_F(AssetProcessorManagerTest, UnitTestForGettingJobInfoBySourceUUIDSuccess) EXPECT_STRCASEEQ(relFileName.toUtf8().data(), response.m_jobList[0].m_sourceFile.c_str()); EXPECT_STRCASEEQ(tempPath.filePath("subfolder1").toUtf8().data(), response.m_jobList[0].m_watchFolder.c_str()); - ASSERT_EQ(m_assertAbsorber.m_numWarningsAbsorbed, 0); - ASSERT_EQ(m_assertAbsorber.m_numErrorsAbsorbed, 0); - ASSERT_EQ(m_assertAbsorber.m_numAssertsAbsorbed, 0); + ASSERT_EQ(m_errorAbsorber->m_numWarningsAbsorbed, 0); + ASSERT_EQ(m_errorAbsorber->m_numErrorsAbsorbed, 0); + ASSERT_EQ(m_errorAbsorber->m_numAssertsAbsorbed, 0); } TEST_F(AssetProcessorManagerTest, WarningsAndErrorsReported_SuccessfullySavedToDatabase) @@ -388,9 +388,9 @@ TEST_F(AssetProcessorManagerTest, WarningsAndErrorsReported_SuccessfullySavedToD ASSERT_EQ(response.m_jobList[0].m_warningCount, 11); ASSERT_EQ(response.m_jobList[0].m_errorCount, 22); - ASSERT_EQ(m_assertAbsorber.m_numWarningsAbsorbed, 0); - ASSERT_EQ(m_assertAbsorber.m_numErrorsAbsorbed, 0); - ASSERT_EQ(m_assertAbsorber.m_numAssertsAbsorbed, 0); + ASSERT_EQ(m_errorAbsorber->m_numWarningsAbsorbed, 0); + ASSERT_EQ(m_errorAbsorber->m_numErrorsAbsorbed, 0); + ASSERT_EQ(m_errorAbsorber->m_numAssertsAbsorbed, 0); } @@ -1312,8 +1312,8 @@ void PathDependencyTest::SetUp() void PathDependencyTest::TearDown() { - ASSERT_EQ(m_assertAbsorber.m_numAssertsAbsorbed, 0); - ASSERT_EQ(m_assertAbsorber.m_numErrorsAbsorbed, 0); + ASSERT_EQ(m_errorAbsorber->m_numAssertsAbsorbed, 0); + ASSERT_EQ(m_errorAbsorber->m_numErrorsAbsorbed, 0); AssetProcessorManagerTest::TearDown(); } @@ -1617,7 +1617,7 @@ TEST_F(PathDependencyTest, AssetProcessed_Impl_SelfReferrentialProductDependency mainFile.m_products.push_back(productAssetId); // tell the APM that the asset has been processed and allow it to bubble through its event queue: - m_assertAbsorber.Clear(); + m_errorAbsorber->Clear(); m_assetProcessorManager->AssetProcessed(jobDetails.m_jobEntry, processJobResponse); ASSERT_TRUE(BlockUntilIdle(5000)); @@ -1627,8 +1627,8 @@ TEST_F(PathDependencyTest, AssetProcessed_Impl_SelfReferrentialProductDependency ASSERT_TRUE(dependencyContainer.empty()); // We are testing 2 different dependencies, so we should get 2 warnings - ASSERT_EQ(m_assertAbsorber.m_numWarningsAbsorbed, 2); - m_assertAbsorber.Clear(); + ASSERT_EQ(m_errorAbsorber->m_numWarningsAbsorbed, 2); + m_errorAbsorber->Clear(); } // This test shows the process of deferring resolution of a path dependency works. @@ -1945,8 +1945,8 @@ TEST_F(PathDependencyTest, WildcardDependencies_ExcludePathsExisting_ResolveCorr ); // Test asset PrimaryFile1 has 4 conflict dependencies - ASSERT_EQ(m_assertAbsorber.m_numErrorsAbsorbed, 4); - m_assertAbsorber.Clear(); + ASSERT_EQ(m_errorAbsorber->m_numErrorsAbsorbed, 4); + m_errorAbsorber->Clear(); } TEST_F(PathDependencyTest, WildcardDependencies_Deferred_ResolveCorrectly) @@ -2093,8 +2093,8 @@ TEST_F(PathDependencyTest, WildcardDependencies_ExcludedPathDeferred_ResolveCorr // Test asset PrimaryFile1 has 4 conflict dependencies // After test assets dep2 and dep3 are processed, // another 2 errors will be raised because of the confliction - ASSERT_EQ(m_assertAbsorber.m_numErrorsAbsorbed, 6); - m_assertAbsorber.Clear(); + ASSERT_EQ(m_errorAbsorber->m_numErrorsAbsorbed, 6); + m_errorAbsorber->Clear(); } void PathDependencyTest::RunWildcardTest(bool useCorrectDatabaseSeparator, AssetBuilderSDK::ProductPathDependencyType pathDependencyType, bool buildDependenciesFirst) diff --git a/Code/Tools/AssetProcessor/native/tests/assetmanager/AssetProcessorManagerTest.h b/Code/Tools/AssetProcessor/native/tests/assetmanager/AssetProcessorManagerTest.h index 3443a4c519..2f0121485e 100644 --- a/Code/Tools/AssetProcessor/native/tests/assetmanager/AssetProcessorManagerTest.h +++ b/Code/Tools/AssetProcessor/native/tests/assetmanager/AssetProcessorManagerTest.h @@ -58,7 +58,6 @@ protected: AZStd::unique_ptr m_assetProcessorManager; AZStd::unique_ptr m_mockApplicationManager; AZStd::unique_ptr m_config; - UnitTestUtils::AssertAbsorber m_assertAbsorber; // absorb asserts/warnings/errors so that the unit test output is not cluttered QString m_gameName; QDir m_normalizedCacheRootDir; AZStd::atomic_bool m_isIdling; diff --git a/Code/Tools/AssetProcessor/native/tests/platformconfiguration/platformconfigurationtests.cpp b/Code/Tools/AssetProcessor/native/tests/platformconfiguration/platformconfigurationtests.cpp index 69397745f1..e519f48ce5 100644 --- a/Code/Tools/AssetProcessor/native/tests/platformconfiguration/platformconfigurationtests.cpp +++ b/Code/Tools/AssetProcessor/native/tests/platformconfiguration/platformconfigurationtests.cpp @@ -52,11 +52,12 @@ TEST_F(PlatformConfigurationUnitTests, TestFailReadConfigFile_BadPlatform) using namespace AssetProcessor; const auto testExeFolder = AZ::IO::FileIOBase::GetInstance()->ResolvePath(TestAppRoot); + const AZ::IO::FixedMaxPath projectPath = (*testExeFolder) / EmptyDummyProjectName; auto configRoot = AZ::IO::FileIOBase::GetInstance()->ResolvePath("@exefolder@/testdata/config_broken_badplatform"); ASSERT_TRUE(configRoot); UnitTestPlatformConfiguration config; m_absorber.Clear(); - ASSERT_FALSE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), EmptyDummyProjectName, false, false)); + ASSERT_FALSE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), projectPath.c_str(), false, false)); ASSERT_GT(m_absorber.m_numErrorsAbsorbed, 0); } @@ -67,11 +68,12 @@ TEST_F(PlatformConfigurationUnitTests, TestFailReadConfigFile_NoPlatform) using namespace AssetProcessor; const auto testExeFolder = AZ::IO::FileIOBase::GetInstance()->ResolvePath(TestAppRoot); + const AZ::IO::FixedMaxPath projectPath = (*testExeFolder) / EmptyDummyProjectName; auto configRoot = AZ::IO::FileIOBase::GetInstance()->ResolvePath("@exefolder@/testdata/config_broken_noplatform"); ASSERT_TRUE(configRoot); UnitTestPlatformConfiguration config; m_absorber.Clear(); - ASSERT_FALSE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), EmptyDummyProjectName, false, false)); + ASSERT_FALSE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), projectPath.c_str(), false, false)); ASSERT_GT(m_absorber.m_numErrorsAbsorbed, 0); } @@ -81,11 +83,12 @@ TEST_F(PlatformConfigurationUnitTests, TestFailReadConfigFile_NoScanFolders) using namespace AssetProcessor; const auto testExeFolder = AZ::IO::FileIOBase::GetInstance()->ResolvePath(TestAppRoot); + const AZ::IO::FixedMaxPath projectPath = (*testExeFolder) / EmptyDummyProjectName; auto configRoot = AZ::IO::FileIOBase::GetInstance()->ResolvePath("@exefolder@/testdata/config_broken_noscans"); ASSERT_TRUE(configRoot); UnitTestPlatformConfiguration config; m_absorber.Clear(); - ASSERT_FALSE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), EmptyDummyProjectName, false, false)); + ASSERT_FALSE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), projectPath.c_str(), false, false)); ASSERT_GT(m_absorber.m_numErrorsAbsorbed, 0); } @@ -95,11 +98,12 @@ TEST_F(PlatformConfigurationUnitTests, TestFailReadConfigFile_BrokenRecognizers) using namespace AssetProcessor; const auto testExeFolder = AZ::IO::FileIOBase::GetInstance()->ResolvePath(TestAppRoot); + const AZ::IO::FixedMaxPath projectPath = (*testExeFolder) / EmptyDummyProjectName; auto configRoot = AZ::IO::FileIOBase::GetInstance()->ResolvePath("@exefolder@/testdata/config_broken_recognizers"); ASSERT_TRUE(configRoot); UnitTestPlatformConfiguration config; m_absorber.Clear(); - ASSERT_FALSE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), EmptyDummyProjectName, false, false)); + ASSERT_FALSE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), projectPath.c_str(), false, false)); ASSERT_GT(m_absorber.m_numErrorsAbsorbed, 0); } @@ -109,11 +113,12 @@ TEST_F(PlatformConfigurationUnitTests, TestFailReadConfigFile_Regular_Platforms) using namespace AssetProcessor; const auto testExeFolder = AZ::IO::FileIOBase::GetInstance()->ResolvePath(TestAppRoot); + const AZ::IO::FixedMaxPath projectPath = (*testExeFolder) / EmptyDummyProjectName; auto configRoot = AZ::IO::FileIOBase::GetInstance()->ResolvePath("@exefolder@/testdata/config_regular"); ASSERT_TRUE(configRoot); UnitTestPlatformConfiguration config; m_absorber.Clear(); - ASSERT_TRUE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), EmptyDummyProjectName, false, false)); + ASSERT_TRUE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), projectPath.c_str(), false, false)); ASSERT_EQ(m_absorber.m_numErrorsAbsorbed, 0); // verify the data. @@ -322,12 +327,13 @@ TEST_F(PlatformConfigurationUnitTests, TestFailReadConfigFile_RegularScanfolder) using namespace AssetProcessor; const auto testExeFolder = AZ::IO::FileIOBase::GetInstance()->ResolvePath(TestAppRoot); + const AZ::IO::FixedMaxPath projectPath = (*testExeFolder) / EmptyDummyProjectName; auto configRoot = AZ::IO::FileIOBase::GetInstance()->ResolvePath("@exefolder@/testdata/config_regular"); ASSERT_TRUE(configRoot); UnitTestPlatformConfiguration config; m_absorber.Clear(); AssetUtilities::ComputeProjectName(EmptyDummyProjectName, true); - ASSERT_TRUE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), EmptyDummyProjectName, false, false)); + ASSERT_TRUE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), projectPath.c_str(), false, false)); ASSERT_EQ(m_absorber.m_numErrorsAbsorbed, 0); ASSERT_EQ(config.GetScanFolderCount(), 3); // the two, and then the one that has the same data as prior but different identifier. @@ -356,11 +362,12 @@ TEST_F(PlatformConfigurationUnitTests, TestFailReadConfigFile_RegularScanfolderP using namespace AssetProcessor; const auto testExeFolder = AZ::IO::FileIOBase::GetInstance()->ResolvePath(TestAppRoot); + const AZ::IO::FixedMaxPath projectPath = (*testExeFolder) / EmptyDummyProjectName; auto configRoot = AZ::IO::FileIOBase::GetInstance()->ResolvePath("@exefolder@/testdata/config_regular_platform_scanfolder"); ASSERT_TRUE(configRoot); UnitTestPlatformConfiguration config; m_absorber.Clear(); - ASSERT_TRUE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), EmptyDummyProjectName, false, false)); + ASSERT_TRUE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), projectPath.c_str(), false, false)); ASSERT_EQ(m_absorber.m_numErrorsAbsorbed, 0); ASSERT_EQ(config.GetScanFolderCount(), 5); @@ -402,11 +409,12 @@ TEST_F(PlatformConfigurationUnitTests, TestFailReadConfigFile_RegularExcludes) using namespace AssetProcessor; const auto testExeFolder = AZ::IO::FileIOBase::GetInstance()->ResolvePath(TestAppRoot); + const AZ::IO::FixedMaxPath projectPath = (*testExeFolder) / EmptyDummyProjectName; auto configRoot = AZ::IO::FileIOBase::GetInstance()->ResolvePath("@exefolder@/testdata/config_regular"); ASSERT_TRUE(configRoot); UnitTestPlatformConfiguration config; m_absorber.Clear(); - ASSERT_TRUE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), EmptyDummyProjectName, false, false)); + ASSERT_TRUE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), projectPath.c_str(), false, false)); ASSERT_EQ(m_absorber.m_numErrorsAbsorbed, 0); ASSERT_TRUE(config.IsFileExcluded("blahblah/$tmp_01.test")); @@ -427,11 +435,12 @@ TEST_F(PlatformConfigurationUnitTests, TestFailReadConfigFile_Recognizers) #endif const auto testExeFolder = AZ::IO::FileIOBase::GetInstance()->ResolvePath(TestAppRoot); + const AZ::IO::FixedMaxPath projectPath = (*testExeFolder) / EmptyDummyProjectName; auto configRoot = AZ::IO::FileIOBase::GetInstance()->ResolvePath("@exefolder@/testdata/config_regular"); ASSERT_TRUE(configRoot); UnitTestPlatformConfiguration config; m_absorber.Clear(); - ASSERT_TRUE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), EmptyDummyProjectName, false, false)); + ASSERT_TRUE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), projectPath.c_str(), false, false)); ASSERT_EQ(m_absorber.m_numErrorsAbsorbed, 0); const AssetProcessor::RecognizerContainer& recogs = config.GetAssetRecognizerContainer(); @@ -518,12 +527,13 @@ TEST_F(PlatformConfigurationUnitTests, TestFailReadConfigFile_Overrides) using namespace AzToolsFramework::AssetSystem; using namespace AssetProcessor; const auto testExeFolder = AZ::IO::FileIOBase::GetInstance()->ResolvePath(TestAppRoot); + const AZ::IO::FixedMaxPath projectPath = (*testExeFolder) / DummyProjectName; auto configRoot = AZ::IO::FileIOBase::GetInstance()->ResolvePath("@exefolder@/testdata/config_regular"); ASSERT_TRUE(configRoot); UnitTestPlatformConfiguration config; m_absorber.Clear(); - ASSERT_TRUE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), DummyProjectName, false, false)); + ASSERT_TRUE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), projectPath.c_str(), false, false)); ASSERT_EQ(m_absorber.m_numErrorsAbsorbed, 0); const AssetProcessor::RecognizerContainer& recogs = config.GetAssetRecognizerContainer(); @@ -625,11 +635,12 @@ TEST_F(PlatformConfigurationUnitTests, ReadCheckServer_FromConfig_Valid) using namespace AssetProcessor; const auto testExeFolder = AZ::IO::FileIOBase::GetInstance()->ResolvePath(TestAppRoot); + const AZ::IO::FixedMaxPath projectPath = (*testExeFolder) / EmptyDummyProjectName; auto configRoot = AZ::IO::FileIOBase::GetInstance()->ResolvePath("@exefolder@/testdata/config_regular"); ASSERT_TRUE(configRoot); UnitTestPlatformConfiguration config; m_absorber.Clear(); - ASSERT_TRUE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), EmptyDummyProjectName, false, false)); + ASSERT_TRUE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), projectPath.c_str(), false, false)); ASSERT_EQ(m_absorber.m_numErrorsAbsorbed, 0); const AssetProcessor::RecognizerContainer& recogs = config.GetAssetRecognizerContainer(); @@ -674,11 +685,12 @@ TEST_F(PlatformConfigurationUnitTests, Test_MetaFileTypes_AssetImporterExtension using namespace AssetProcessor; const auto testExeFolder = AZ::IO::FileIOBase::GetInstance()->ResolvePath(TestAppRoot); + const AZ::IO::FixedMaxPath projectPath = (*testExeFolder) / EmptyDummyProjectName; auto configRoot = AZ::IO::FileIOBase::GetInstance()->ResolvePath("@exefolder@/testdata/config_metadata"); ASSERT_TRUE(configRoot); UnitTestPlatformConfiguration config; m_absorber.Clear(); - ASSERT_FALSE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), EmptyDummyProjectName, false, false)); + ASSERT_FALSE(config.InitializeFromConfigFiles(configRoot->c_str(), testExeFolder->c_str(), projectPath.c_str(), false, false)); ASSERT_GT(m_absorber.m_numErrorsAbsorbed, 0); ASSERT_TRUE(config.MetaDataFileTypesCount() == 2); diff --git a/Code/Tools/AssetProcessor/native/utilities/PlatformConfiguration.cpp b/Code/Tools/AssetProcessor/native/utilities/PlatformConfiguration.cpp index 9a60bf3110..483d5a46aa 100644 --- a/Code/Tools/AssetProcessor/native/utilities/PlatformConfiguration.cpp +++ b/Code/Tools/AssetProcessor/native/utilities/PlatformConfiguration.cpp @@ -749,7 +749,7 @@ namespace AssetProcessor } AZStd::vector configFiles = AzToolsFramework::AssetUtils::GetConfigFiles(absoluteSystemRoot.toUtf8().constData(), - absoluteAssetRoot.toUtf8().constData(), projectPath.toUtf8().constData(), + projectPath.toUtf8().constData(), addPlatformConfigs, addGemsConfigs && !noGemScanFolders, settingsRegistry); // First Merge all Engine, Gem and Project specific AssetProcessor*Config.setreg/.inifiles diff --git a/Code/Tools/ProjectManager/Source/CreateProjectCtrl.cpp b/Code/Tools/ProjectManager/Source/CreateProjectCtrl.cpp index 51e1163713..6b19e839d7 100644 --- a/Code/Tools/ProjectManager/Source/CreateProjectCtrl.cpp +++ b/Code/Tools/ProjectManager/Source/CreateProjectCtrl.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -47,9 +48,14 @@ namespace O3DE::ProjectManager m_gemCatalogScreen = new GemCatalogScreen(this); m_stack->addWidget(m_gemCatalogScreen); + + m_gemRepoScreen = new GemRepoScreen(this); + m_stack->addWidget(m_gemRepoScreen); + vLayout->addWidget(m_stack); connect(m_gemCatalogScreen, &ScreenWidget::ChangeScreenRequest, this, &CreateProjectCtrl::OnChangeScreenRequest); + connect(m_gemRepoScreen, &GemRepoScreen::OnRefresh, m_gemCatalogScreen, &GemCatalogScreen::Refresh); // When there are multiple project templates present, we re-gather the gems when changing the selected the project template. connect(m_newProjectSettingsScreen, &NewProjectSettingsScreen::OnTemplateSelectionChanged, this, [=](int oldIndex, [[maybe_unused]] int newIndex) @@ -89,6 +95,9 @@ namespace O3DE::ProjectManager buttons->setObjectName("footer"); vLayout->addWidget(buttons); + m_primaryButton = buttons->addButton(tr("Create Project"), QDialogButtonBox::ApplyRole); + connect(m_primaryButton, &QPushButton::clicked, this, &CreateProjectCtrl::HandlePrimaryButton); + #ifdef TEMPLATE_GEM_CONFIGURATION_ENABLED connect(m_newProjectSettingsScreen, &ScreenWidget::ChangeScreenRequest, this, &CreateProjectCtrl::OnChangeScreenRequest); @@ -100,8 +109,6 @@ namespace O3DE::ProjectManager Update(); #endif // TEMPLATE_GEM_CONFIGURATION_ENABLED - m_primaryButton = buttons->addButton(tr("Create Project"), QDialogButtonBox::ApplyRole); - connect(m_primaryButton, &QPushButton::clicked, this, &CreateProjectCtrl::HandlePrimaryButton); setLayout(vLayout); } @@ -122,6 +129,9 @@ namespace O3DE::ProjectManager // Gather the enabled gems from the default project template when starting the create new project workflow. ReinitGemCatalogForSelectedTemplate(); + + // make sure the gem repo has the latest details + m_gemRepoScreen->Reinit(); } void CreateProjectCtrl::HandleBackButton() @@ -160,12 +170,21 @@ namespace O3DE::ProjectManager { m_header->setSubTitle(tr("Configure project with Gems")); m_secondaryButton->setVisible(false); + m_primaryButton->setVisible(true); + } + else if (m_stack->currentWidget() == m_gemRepoScreen) + { + m_header->setSubTitle(tr("Gem Repositories")); + m_secondaryButton->setVisible(true); + m_secondaryButton->setText(tr("Back")); + m_primaryButton->setVisible(false); } else { m_header->setSubTitle(tr("Enter Project Details")); m_secondaryButton->setVisible(true); m_secondaryButton->setText(tr("Configure Gems")); + m_primaryButton->setVisible(true); } } @@ -175,6 +194,10 @@ namespace O3DE::ProjectManager { HandleSecondaryButton(); } + else if (screen == ProjectManagerScreen::GemRepos) + { + NextScreen(); + } else { emit ChangeScreenRequest(screen); @@ -230,6 +253,12 @@ namespace O3DE::ProjectManager { if (m_newProjectSettingsScreen->Validate()) { + if (!m_gemCatalogScreen->GetDownloadController()->IsDownloadQueueEmpty()) + { + QMessageBox::critical(this, tr("Gems downloading"), tr("You must wait for gems to finish downloading before continuing.")); + return; + } + ProjectInfo projectInfo = m_newProjectSettingsScreen->GetProjectInfo(); QString projectTemplatePath = m_newProjectSettingsScreen->GetProjectTemplatePath(); diff --git a/Code/Tools/ProjectManager/Source/CreateProjectCtrl.h b/Code/Tools/ProjectManager/Source/CreateProjectCtrl.h index 58f4758edd..b43e0e2858 100644 --- a/Code/Tools/ProjectManager/Source/CreateProjectCtrl.h +++ b/Code/Tools/ProjectManager/Source/CreateProjectCtrl.h @@ -23,6 +23,7 @@ namespace O3DE::ProjectManager QT_FORWARD_DECLARE_CLASS(ScreenHeader) QT_FORWARD_DECLARE_CLASS(NewProjectSettingsScreen) QT_FORWARD_DECLARE_CLASS(GemCatalogScreen) + QT_FORWARD_DECLARE_CLASS(GemRepoScreen) class CreateProjectCtrl : public ScreenWidget @@ -64,6 +65,7 @@ namespace O3DE::ProjectManager NewProjectSettingsScreen* m_newProjectSettingsScreen = nullptr; GemCatalogScreen* m_gemCatalogScreen = nullptr; + GemRepoScreen* m_gemRepoScreen = nullptr; }; } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/DownloadController.cpp b/Code/Tools/ProjectManager/Source/DownloadController.cpp index 224b90299c..6326b2fc11 100644 --- a/Code/Tools/ProjectManager/Source/DownloadController.cpp +++ b/Code/Tools/ProjectManager/Source/DownloadController.cpp @@ -41,9 +41,11 @@ namespace O3DE::ProjectManager void DownloadController::AddGemDownload(const QString& gemName) { m_gemNames.push_back(gemName); + emit GemDownloadAdded(gemName); + if (m_gemNames.size() == 1) { - m_worker->SetGemToDownload(m_gemNames[0], false); + m_worker->SetGemToDownload(m_gemNames.front(), false); m_workerThread.start(); } } @@ -62,6 +64,7 @@ namespace O3DE::ProjectManager else { m_gemNames.erase(findResult); + emit GemDownloadRemoved(gemName); } } } @@ -69,7 +72,7 @@ namespace O3DE::ProjectManager void DownloadController::UpdateUIProgress(int progress) { m_lastProgress = progress; - emit GemDownloadProgress(progress); + emit GemDownloadProgress(m_gemNames.front(), progress); } void DownloadController::HandleResults(const QString& result) @@ -82,12 +85,13 @@ namespace O3DE::ProjectManager succeeded = false; } + QString gemName = m_gemNames.front(); m_gemNames.erase(m_gemNames.begin()); - emit Done(succeeded); + emit Done(gemName, succeeded); if (!m_gemNames.empty()) { - emit StartGemDownload(m_gemNames[0]); + emit StartGemDownload(m_gemNames.front()); } else { diff --git a/Code/Tools/ProjectManager/Source/DownloadController.h b/Code/Tools/ProjectManager/Source/DownloadController.h index 11ceaacddb..0bf0ae473c 100644 --- a/Code/Tools/ProjectManager/Source/DownloadController.h +++ b/Code/Tools/ProjectManager/Source/DownloadController.h @@ -58,8 +58,10 @@ namespace O3DE::ProjectManager signals: void StartGemDownload(const QString& gemName); - void Done(bool success = true); - void GemDownloadProgress(int percentage); + void Done(const QString& gemName, bool success = true); + void GemDownloadAdded(const QString& gemName); + void GemDownloadRemoved(const QString& gemName); + void GemDownloadProgress(const QString& gemName, int percentage); private: DownloadWorker* m_worker; diff --git a/Code/Tools/ProjectManager/Source/EngineScreenCtrl.cpp b/Code/Tools/ProjectManager/Source/EngineScreenCtrl.cpp index f30a8e0daa..9d9110922f 100644 --- a/Code/Tools/ProjectManager/Source/EngineScreenCtrl.cpp +++ b/Code/Tools/ProjectManager/Source/EngineScreenCtrl.cpp @@ -39,6 +39,10 @@ namespace O3DE::ProjectManager m_tabWidget->addTab(m_engineSettingsScreen, tr("General")); m_tabWidget->addTab(m_gemRepoScreen, tr("Gem Repositories")); + + // when tab changes, notify the current screen so it can refresh + connect(m_tabWidget, &QTabWidget::currentChanged, this, &EngineScreenCtrl::TabChanged); + topBarHLayout->addWidget(m_tabWidget); vLayout->addWidget(topBarFrameWidget); @@ -46,6 +50,11 @@ namespace O3DE::ProjectManager setLayout(vLayout); } + void EngineScreenCtrl::TabChanged([[maybe_unused]] int index) + { + NotifyCurrentScreen(); + } + ProjectManagerScreen EngineScreenCtrl::GetScreenEnum() { return ProjectManagerScreen::UpdateProject; @@ -71,6 +80,15 @@ namespace O3DE::ProjectManager return false; } + void EngineScreenCtrl::NotifyCurrentScreen() + { + ScreenWidget* screen = reinterpret_cast(m_tabWidget->currentWidget()); + if (screen) + { + screen->NotifyCurrentScreen(); + } + } + void EngineScreenCtrl::GoToScreen(ProjectManagerScreen screen) { if (screen == m_engineSettingsScreen->GetScreenEnum()) diff --git a/Code/Tools/ProjectManager/Source/EngineScreenCtrl.h b/Code/Tools/ProjectManager/Source/EngineScreenCtrl.h index b7142ba226..cf0d2a24d0 100644 --- a/Code/Tools/ProjectManager/Source/EngineScreenCtrl.h +++ b/Code/Tools/ProjectManager/Source/EngineScreenCtrl.h @@ -30,6 +30,10 @@ namespace O3DE::ProjectManager bool IsTab() override; bool ContainsScreen(ProjectManagerScreen screen) override; void GoToScreen(ProjectManagerScreen screen) override; + void NotifyCurrentScreen() override; + + public slots: + void TabChanged(int index); QTabWidget* m_tabWidget = nullptr; EngineSettingsScreen* m_engineSettingsScreen = nullptr; diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.cpp index 5d65c740af..bd0a6e9bc3 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.cpp @@ -30,6 +30,7 @@ namespace O3DE::ProjectManager m_layout->setMargin(5); m_layout->setAlignment(Qt::AlignTop); setLayout(m_layout); + setMinimumHeight(400); QHBoxLayout* hLayout = new QHBoxLayout(); @@ -119,6 +120,12 @@ namespace O3DE::ProjectManager setWindowFlags(Qt::FramelessWindowHint | Qt::Dialog); } + CartOverlayWidget::~CartOverlayWidget() + { + // disconnect from all download controller signals + disconnect(m_downloadController, nullptr, this, nullptr); + } + void CartOverlayWidget::CreateGemSection(const QString& singularTitle, const QString& pluralTitle, GetTagIndicesCallback getTagIndices) { QWidget* widget = new QWidget(); @@ -145,7 +152,7 @@ namespace O3DE::ProjectManager } else { - tagContainer->Update(ConvertFromModelIndices(tagIndices)); + tagContainer->Update(GetTagsFromModelIndices(tagIndices)); label->setText(QString("%1 %2").arg(tagIndices.size()).arg(tagIndices.size() == 1 ? singularTitle : pluralTitle)); widget->show(); } @@ -162,13 +169,13 @@ namespace O3DE::ProjectManager void CartOverlayWidget::CreateDownloadSection() { - QWidget* widget = new QWidget(); - widget->setFixedWidth(s_width); - m_layout->addWidget(widget); + m_downloadSectionWidget = new QWidget(); + m_downloadSectionWidget->setFixedWidth(s_width); + m_layout->addWidget(m_downloadSectionWidget); QVBoxLayout* layout = new QVBoxLayout(); layout->setAlignment(Qt::AlignTop); - widget->setLayout(layout); + m_downloadSectionWidget->setLayout(layout); QLabel* titleLabel = new QLabel(); titleLabel->setObjectName("GemCatalogCartOverlaySectionLabel"); @@ -187,93 +194,132 @@ namespace O3DE::ProjectManager QLabel* processingQueueLabel = new QLabel("Processing Queue"); gemDownloadLayout->addWidget(processingQueueLabel); - QWidget* downloadingItemWidget = new QWidget(); - downloadingItemWidget->setObjectName("GemCatalogCartOverlayGemDownloadBG"); - gemDownloadLayout->addWidget(downloadingItemWidget); + m_downloadingListWidget = new QWidget(); + m_downloadingListWidget->setObjectName("GemCatalogCartOverlayGemDownloadBG"); + gemDownloadLayout->addWidget(m_downloadingListWidget); QVBoxLayout* downloadingItemLayout = new QVBoxLayout(); downloadingItemLayout->setAlignment(Qt::AlignTop); - downloadingItemWidget->setLayout(downloadingItemLayout); + m_downloadingListWidget->setLayout(downloadingItemLayout); - auto update = [=](int downloadProgress) + QLabel* downloadsInProgessLabel = new QLabel(""); + downloadsInProgessLabel->setObjectName("NumDownloadsInProgressLabel"); + downloadingItemLayout->addWidget(downloadsInProgessLabel); + + if (m_downloadController->IsDownloadQueueEmpty()) + { + m_downloadSectionWidget->hide(); + } + else { - if (m_downloadController->IsDownloadQueueEmpty()) + // Setup gem download rows for gems that are already in the queue + const AZStd::vector& downloadQueue = m_downloadController->GetDownloadQueue(); + + for (const QString& gemName : downloadQueue) { - widget->hide(); + GemDownloadAdded(gemName); } - else - { - widget->setUpdatesEnabled(false); - // remove items - QLayoutItem* layoutItem = nullptr; - while ((layoutItem = downloadingItemLayout->takeAt(0)) != nullptr) - { - if (layoutItem->layout()) - { - // Gem info row - QLayoutItem* rowLayoutItem = nullptr; - while ((rowLayoutItem = layoutItem->layout()->takeAt(0)) != nullptr) - { - rowLayoutItem->widget()->deleteLater(); - } - layoutItem->layout()->deleteLater(); - } - if (layoutItem->widget()) - { - layoutItem->widget()->deleteLater(); - } - } + } - // Setup gem download rows - const AZStd::vector& downloadQueue = m_downloadController->GetDownloadQueue(); + // connect to download controller data changed + connect(m_downloadController, &DownloadController::GemDownloadAdded, this, &CartOverlayWidget::GemDownloadAdded); + connect(m_downloadController, &DownloadController::GemDownloadRemoved, this, &CartOverlayWidget::GemDownloadRemoved); + connect(m_downloadController, &DownloadController::GemDownloadProgress, this, &CartOverlayWidget::GemDownloadProgress); + connect(m_downloadController, &DownloadController::Done, this, &CartOverlayWidget::GemDownloadComplete); + } - QLabel* downloadsInProgessLabel = new QLabel(""); - downloadsInProgessLabel->setText( - QString("%1 %2").arg(downloadQueue.size()).arg(downloadQueue.size() == 1 ? tr("download in progress...") : tr("downloads in progress..."))); - downloadingItemLayout->addWidget(downloadsInProgessLabel); + void CartOverlayWidget::GemDownloadAdded(const QString& gemName) + { + // Containing widget for the current download item + QWidget* newGemDownloadWidget = new QWidget(); + newGemDownloadWidget->setObjectName(gemName); + QVBoxLayout* downloadingGemLayout = new QVBoxLayout(newGemDownloadWidget); + newGemDownloadWidget->setLayout(downloadingGemLayout); + + // Gem name, progress string, cancel + QHBoxLayout* nameProgressLayout = new QHBoxLayout(newGemDownloadWidget); + TagWidget* newTag = new TagWidget({gemName, gemName}, newGemDownloadWidget); + nameProgressLayout->addWidget(newTag); + QLabel* progress = new QLabel(tr("Queued"), newGemDownloadWidget); + progress->setObjectName("DownloadProgressLabel"); + nameProgressLayout->addWidget(progress); + nameProgressLayout->addStretch(); + QLabel* cancelText = new QLabel(tr("Cancel").arg(gemName), newGemDownloadWidget); + cancelText->setTextInteractionFlags(Qt::LinksAccessibleByMouse); + connect(cancelText, &QLabel::linkActivated, this, &CartOverlayWidget::OnCancelDownloadActivated); + nameProgressLayout->addWidget(cancelText); + downloadingGemLayout->addLayout(nameProgressLayout); + + // Progress bar + QProgressBar* downloadProgessBar = new QProgressBar(newGemDownloadWidget); + downloadProgessBar->setObjectName("DownloadProgressBar"); + downloadingGemLayout->addWidget(downloadProgessBar); + downloadProgessBar->setValue(0); + + m_downloadingListWidget->layout()->addWidget(newGemDownloadWidget); + + const AZStd::vector& downloadQueue = m_downloadController->GetDownloadQueue(); + QLabel* numDownloads = m_downloadingListWidget->findChild("NumDownloadsInProgressLabel"); + numDownloads->setText(QString("%1 %2") + .arg(downloadQueue.size()) + .arg(downloadQueue.size() == 1 ? tr("download in progress...") : tr("downloads in progress..."))); + + m_downloadingListWidget->show(); + } - for (int downloadingGemNumber = 0; downloadingGemNumber < downloadQueue.size(); ++downloadingGemNumber) - { - QHBoxLayout* nameProgressLayout = new QHBoxLayout(); - TagWidget* newTag = new TagWidget(downloadQueue[downloadingGemNumber]); - nameProgressLayout->addWidget(newTag); - QLabel* progress = new QLabel(downloadingGemNumber == 0? QString("%1%").arg(downloadProgress) : tr("Queued")); - nameProgressLayout->addWidget(progress); - QSpacerItem* spacer = new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum); - nameProgressLayout->addSpacerItem(spacer); - QLabel* cancelText = new QLabel(QString("Cancel").arg(downloadQueue[downloadingGemNumber])); - cancelText->setTextInteractionFlags(Qt::LinksAccessibleByMouse); - connect(cancelText, &QLabel::linkActivated, this, &CartOverlayWidget::OnCancelDownloadActivated); - nameProgressLayout->addWidget(cancelText); - downloadingItemLayout->addLayout(nameProgressLayout); - QProgressBar* downloadProgessBar = new QProgressBar(); - downloadingItemLayout->addWidget(downloadProgessBar); - downloadProgessBar->setValue(downloadingGemNumber == 0 ? downloadProgress : 0); - } + void CartOverlayWidget::GemDownloadRemoved(const QString& gemName) + { + QWidget* gemToRemove = m_downloadingListWidget->findChild(gemName); + if (gemToRemove) + { + gemToRemove->deleteLater(); + } - widget->setUpdatesEnabled(true); - widget->show(); - } - }; + if (m_downloadController->IsDownloadQueueEmpty()) + { + m_downloadSectionWidget->hide(); + } + else + { + size_t downloadQueueSize = m_downloadController->GetDownloadQueue().size(); + QLabel* numDownloads = m_downloadingListWidget->findChild("NumDownloadsInProgressLabel"); + numDownloads->setText(QString("%1 %2") + .arg(downloadQueueSize) + .arg(downloadQueueSize == 1 ? tr("download in progress...") : tr("downloads in progress..."))); + } + } - auto downloadEnded = [=](bool /*success*/) + void CartOverlayWidget::GemDownloadProgress(const QString& gemName, int percentage) + { + QWidget* gemToUpdate = m_downloadingListWidget->findChild(gemName); + if (gemToUpdate) { - update(0); // update the list to remove the gem that has finished - }; - // connect to download controller data changed - connect(m_downloadController, &DownloadController::GemDownloadProgress, this, update); - connect(m_downloadController, &DownloadController::Done, this, downloadEnded); - update(0); + QLabel* progressLabel = gemToUpdate->findChild("DownloadProgressLabel"); + if (progressLabel) + { + progressLabel->setText(QString("%1%").arg(percentage)); + } + QProgressBar* progressBar = gemToUpdate->findChild("DownloadProgressBar"); + if (progressBar) + { + progressBar->setValue(percentage); + } + } + } + + void CartOverlayWidget::GemDownloadComplete(const QString& gemName, bool /*success*/) + { + GemDownloadRemoved(gemName); // update the list to remove the gem that has finished } - QStringList CartOverlayWidget::ConvertFromModelIndices(const QVector& gems) const + QVector CartOverlayWidget::GetTagsFromModelIndices(const QVector& gems) const { - QStringList gemNames; - gemNames.reserve(gems.size()); + QVector tags; + tags.reserve(gems.size()); for (const QModelIndex& modelIndex : gems) { - gemNames.push_back(GemModel::GetDisplayName(modelIndex)); + tags.push_back({ GemModel::GetDisplayName(modelIndex), GemModel::GetName(modelIndex) }); } - return gemNames; + return tags; } CartButton::CartButton(GemModel* gemModel, DownloadController* downloadController, QWidget* parent) diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.h index 4d17259840..f3242d6db7 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.h @@ -34,9 +34,16 @@ namespace O3DE::ProjectManager public: CartOverlayWidget(GemModel* gemModel, DownloadController* downloadController, QWidget* parent = nullptr); + ~CartOverlayWidget(); + + public slots: + void GemDownloadAdded(const QString& gemName); + void GemDownloadRemoved(const QString& gemName); + void GemDownloadProgress(const QString& gemName, int percentage); + void GemDownloadComplete(const QString& gemName, bool success); private: - QStringList ConvertFromModelIndices(const QVector& gems) const; + QVector GetTagsFromModelIndices(const QVector& gems) const; using GetTagIndicesCallback = AZStd::function()>; void CreateGemSection(const QString& singularTitle, const QString& pluralTitle, GetTagIndicesCallback getTagIndices); @@ -47,6 +54,9 @@ namespace O3DE::ProjectManager GemModel* m_gemModel = nullptr; DownloadController* m_downloadController = nullptr; + QWidget* m_downloadSectionWidget = nullptr; + QWidget* m_downloadingListWidget = nullptr; + inline constexpr static int s_width = 240; }; diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp index 1a3dc03230..732f4813a2 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp @@ -23,6 +23,7 @@ #include #include #include +#include namespace O3DE::ProjectManager { @@ -32,6 +33,9 @@ namespace O3DE::ProjectManager m_gemModel = new GemModel(this); m_proxyModel = new GemSortFilterProxyModel(m_gemModel, this); + // default to sort by gem name + m_proxyModel->setSortRole(GemModel::RoleName); + QVBoxLayout* vLayout = new QVBoxLayout(); vLayout->setMargin(0); vLayout->setSpacing(0); @@ -45,6 +49,7 @@ namespace O3DE::ProjectManager connect(m_gemModel, &GemModel::gemStatusChanged, this, &GemCatalogScreen::OnGemStatusChanged); connect(m_headerWidget, &GemCatalogHeaderWidget::OpenGemsRepo, this, &GemCatalogScreen::HandleOpenGemRepo); connect(m_headerWidget, &GemCatalogHeaderWidget::AddGem, this, &GemCatalogScreen::OnAddGemClicked); + connect(m_downloadController, &DownloadController::Done, this, &GemCatalogScreen::OnGemDownloadResult); QHBoxLayout* hLayout = new QHBoxLayout(); hLayout->setMargin(0); @@ -54,7 +59,7 @@ namespace O3DE::ProjectManager m_gemInspector = new GemInspector(m_gemModel, this); m_gemInspector->setFixedWidth(240); - connect(m_gemInspector, &GemInspector::TagClicked, this, &GemCatalogScreen::SelectGem); + connect(m_gemInspector, &GemInspector::TagClicked, [=](const Tag& tag) { SelectGem(tag.id); }); QWidget* filterWidget = new QWidget(this); filterWidget->setFixedWidth(240); @@ -82,11 +87,20 @@ namespace O3DE::ProjectManager void GemCatalogScreen::ReinitForProject(const QString& projectPath) { + m_projectPath = projectPath; m_gemModel->Clear(); m_gemsToRegisterWithProject.clear(); + + if (m_filterWidget) + { + // disconnect so we don't update the status filter for every gem we add + disconnect(m_gemModel, &GemModel::dataChanged, m_filterWidget, &GemFilterWidget::ResetGemStatusFilter); + } + FillModel(projectPath); m_proxyModel->ResetFilters(); + m_proxyModel->sort(/*column=*/0); if (m_filterWidget) { @@ -144,9 +158,78 @@ namespace O3DE::ProjectManager { m_gemModel->AddGem(gemInfoResult.GetValue()); m_gemModel->UpdateGemDependencies(); + m_proxyModel->sort(/*column=*/0); + } + } + } + } + + void GemCatalogScreen::Refresh() + { + QHash gemInfoHash; + + // create a hash with the gem name as key + const AZ::Outcome, AZStd::string>& allGemInfosResult = PythonBindingsInterface::Get()->GetAllGemInfos(m_projectPath); + if (allGemInfosResult.IsSuccess()) + { + const QVector& gemInfos = allGemInfosResult.GetValue(); + for (const GemInfo& gemInfo : gemInfos) + { + gemInfoHash.insert(gemInfo.m_name, gemInfo); + } + } + + // add all the gem repos into the hash + const AZ::Outcome, AZStd::string>& allRepoGemInfosResult = PythonBindingsInterface::Get()->GetAllGemRepoGemsInfos(); + if (allRepoGemInfosResult.IsSuccess()) + { + const QVector& allRepoGemInfos = allRepoGemInfosResult.GetValue(); + for (const GemInfo& gemInfo : allRepoGemInfos) + { + if (!gemInfoHash.contains(gemInfo.m_name)) + { + gemInfoHash.insert(gemInfo.m_name, gemInfo); + } + } + } + + // remove gems from the model that no longer exist in the hash and are not project dependencies + int i = 0; + while (i < m_gemModel->rowCount()) + { + QModelIndex index = m_gemModel->index(i,0); + QString gemName = m_gemModel->GetName(index); + const bool gemFound = gemInfoHash.contains(gemName); + if (!gemFound && !m_gemModel->IsAdded(index) && !m_gemModel->IsAddedDependency(index)) + { + m_gemModel->removeRow(i); + } + else + { + if (!gemFound && (m_gemModel->IsAdded(index) || m_gemModel->IsAddedDependency(index))) + { + const QString error = tr("Gem %1 was removed or unregistered, but is still used by the project.").arg(gemName); + AZ_Warning("Project Manager", false, error.toUtf8().constData()); + QMessageBox::warning(this, tr("Gem not found"), error.toUtf8().constData()); } + + gemInfoHash.remove(gemName); + i++; } } + + // add all gems remaining in the hash that were not removed + for(auto iter = gemInfoHash.begin(); iter != gemInfoHash.end(); ++iter) + { + m_gemModel->AddGem(iter.value()); + } + + m_gemModel->UpdateGemDependencies(); + m_proxyModel->sort(/*column=*/0); + + // temporary, until we can refresh filter counts + m_proxyModel->ResetFilters(); + m_filterWidget->ResetAllFilters(); } void GemCatalogScreen::OnGemStatusChanged(const QString& gemName, uint32_t numChangedDependencies) @@ -175,6 +258,7 @@ namespace O3DE::ProjectManager if (added && GemModel::GetDownloadStatus(modelIndex) == GemInfo::DownloadStatus::NotDownloaded) { m_downloadController->AddGemDownload(GemModel::GetName(modelIndex)); + GemModel::SetDownloadStatus(*m_proxyModel, m_proxyModel->mapFromSource(modelIndex), GemInfo::DownloadStatus::Downloading); } } @@ -236,20 +320,22 @@ namespace O3DE::ProjectManager void GemCatalogScreen::FillModel(const QString& projectPath) { - AZ::Outcome, AZStd::string> allGemInfosResult = PythonBindingsInterface::Get()->GetAllGemInfos(projectPath); + m_projectPath = projectPath; + + const AZ::Outcome, AZStd::string>& allGemInfosResult = PythonBindingsInterface::Get()->GetAllGemInfos(projectPath); if (allGemInfosResult.IsSuccess()) { // Add all available gems to the model. - const QVector allGemInfos = allGemInfosResult.GetValue(); + const QVector& allGemInfos = allGemInfosResult.GetValue(); for (const GemInfo& gemInfo : allGemInfos) { m_gemModel->AddGem(gemInfo); } - AZ::Outcome, AZStd::string> allRepoGemInfosResult = PythonBindingsInterface::Get()->GetAllGemRepoGemsInfos(); + const AZ::Outcome, AZStd::string>& allRepoGemInfosResult = PythonBindingsInterface::Get()->GetAllGemRepoGemsInfos(); if (allRepoGemInfosResult.IsSuccess()) { - const QVector allRepoGemInfos = allRepoGemInfosResult.GetValue(); + const QVector& allRepoGemInfos = allRepoGemInfosResult.GetValue(); for (const GemInfo& gemInfo : allRepoGemInfos) { // do not add gems that have already been downloaded @@ -268,10 +354,10 @@ namespace O3DE::ProjectManager m_notificationsEnabled = false; // Gather enabled gems for the given project. - auto enabledGemNamesResult = PythonBindingsInterface::Get()->GetEnabledGemNames(projectPath); + const auto& enabledGemNamesResult = PythonBindingsInterface::Get()->GetEnabledGemNames(projectPath); if (enabledGemNamesResult.IsSuccess()) { - const QVector enabledGemNames = enabledGemNamesResult.GetValue(); + const QVector& enabledGemNames = enabledGemNamesResult.GetValue(); for (const AZStd::string& enabledGemName : enabledGemNames) { const QModelIndex modelIndex = m_gemModel->FindIndexByNameString(enabledGemName.c_str()); @@ -331,12 +417,24 @@ namespace O3DE::ProjectManager for (const QModelIndex& modelIndex : toBeAdded) { - const QString gemPath = GemModel::GetPath(modelIndex); + const QString& gemPath = GemModel::GetPath(modelIndex); + + // make sure any remote gems we added were downloaded successfully + if (GemModel::GetGemOrigin(modelIndex) == GemInfo::Remote && GemModel::GetDownloadStatus(modelIndex) != GemInfo::Downloaded) + { + QMessageBox::critical( + nullptr, "Cannot add gem that isn't downloaded", + tr("Cannot add gem %1 to project because it isn't downloaded yet or failed to download.") + .arg(GemModel::GetDisplayName(modelIndex))); + + return EnableDisableGemsResult::Failed; + } + const AZ::Outcome result = pythonBindings->AddGemToProject(gemPath, projectPath); if (!result.IsSuccess()) { - QMessageBox::critical(nullptr, "Operation failed", - QString("Cannot add gem %1 to project.\n\nError:\n%2").arg(GemModel::GetDisplayName(modelIndex), result.GetError().c_str())); + QMessageBox::critical(nullptr, "Failed to add gem to project", + tr("Cannot add gem %1 to project.

Error:
%2").arg(GemModel::GetDisplayName(modelIndex), result.GetError().c_str())); return EnableDisableGemsResult::Failed; } @@ -354,8 +452,8 @@ namespace O3DE::ProjectManager const AZ::Outcome result = pythonBindings->RemoveGemFromProject(gemPath, projectPath); if (!result.IsSuccess()) { - QMessageBox::critical(nullptr, "Operation failed", - QString("Cannot remove gem %1 from project.\n\nError:\n%2").arg(GemModel::GetDisplayName(modelIndex), result.GetError().c_str())); + QMessageBox::critical(nullptr, "Failed to remove gem from project", + tr("Cannot remove gem %1 from project.

Error:
%2").arg(GemModel::GetDisplayName(modelIndex), result.GetError().c_str())); return EnableDisableGemsResult::Failed; } @@ -366,23 +464,35 @@ namespace O3DE::ProjectManager void GemCatalogScreen::HandleOpenGemRepo() { - QVector gemsToBeAdded = m_gemModel->GatherGemsToBeAdded(true); - QVector gemsToBeRemoved = m_gemModel->GatherGemsToBeRemoved(true); + emit ChangeScreenRequest(ProjectManagerScreen::GemRepos); + } - if (!gemsToBeAdded.empty() || !gemsToBeRemoved.empty()) + void GemCatalogScreen::OnGemDownloadResult(const QString& gemName, bool succeeded) + { + if (succeeded) { - QMessageBox::StandardButton warningResult = QMessageBox::warning( - nullptr, "Pending Changes", - "There are some unsaved changes to the gem selection,
they will be lost if you change screens.
Are you sure?", - QMessageBox::No | QMessageBox::Yes); - - if (warningResult != QMessageBox::Yes) + // refresh the information for downloaded gems + const AZ::Outcome, AZStd::string>& allGemInfosResult = PythonBindingsInterface::Get()->GetAllGemInfos(m_projectPath); + if (allGemInfosResult.IsSuccess()) { - return; + // we should find the gem name now in all gem infos + for (const GemInfo& gemInfo : allGemInfosResult.GetValue()) + { + if (gemInfo.m_name == gemName) + { + QModelIndex index = m_gemModel->FindIndexByNameString(gemName); + if (index.isValid()) + { + m_gemModel->setData(index, GemInfo::Downloaded, GemModel::RoleDownloadStatus); + m_gemModel->setData(index, gemInfo.m_path, GemModel::RolePath); + m_gemModel->setData(index, gemInfo.m_path, GemModel::RoleDirectoryLink); + } + + return; + } + } } } - - emit ChangeScreenRequest(ProjectManagerScreen::GemRepos); } ProjectManagerScreen GemCatalogScreen::GetScreenEnum() diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h index 55fbb1befc..da6d2efa7b 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h @@ -49,6 +49,8 @@ namespace O3DE::ProjectManager void OnGemStatusChanged(const QString& gemName, uint32_t numChangedDependencies); void OnAddGemClicked(); void SelectGem(const QString& gemName); + void OnGemDownloadResult(const QString& gemName, bool succeeded = true); + void Refresh(); protected: void hideEvent(QHideEvent* event) override; @@ -75,5 +77,6 @@ namespace O3DE::ProjectManager DownloadController* m_downloadController = nullptr; bool m_notificationsEnabled = true; QSet m_gemsToRegisterWithProject; + QString m_projectPath = nullptr; }; } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemFilterWidget.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemFilterWidget.cpp index b608445d0f..acea6ce378 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemFilterWidget.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemFilterWidget.cpp @@ -221,7 +221,6 @@ namespace O3DE::ProjectManager ResetGemStatusFilter(); ResetGemOriginFilter(); ResetTypeFilter(); - ResetPlatformFilter(); ResetFeatureFilter(); } diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.cpp index b0b8cca29a..1bcfc6ce9d 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.cpp @@ -106,10 +106,10 @@ namespace O3DE::ProjectManager } // Depending gems - QStringList dependingGems = m_model->GetDependingGemNames(modelIndex); - if (!dependingGems.isEmpty()) + const QVector& dependingGemTags = m_model->GetDependingGemTags(modelIndex); + if (!dependingGemTags.isEmpty()) { - m_dependingGems->Update(tr("Depending Gems"), tr("The following Gems will be automatically enabled with this Gem."), dependingGems); + m_dependingGems->Update(tr("Depending Gems"), tr("The following Gems will be automatically enabled with this Gem."), dependingGemTags); m_dependingGems->show(); } else @@ -120,7 +120,8 @@ namespace O3DE::ProjectManager // Additional information 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))); + const int binarySize = m_model->GetBinarySizeInKB(modelIndex); + m_binarySizeLabel->setText(tr("Binary Size: %1").arg(binarySize ? tr("%1 KB").arg(binarySize) : tr("Unknown"))); m_mainWidget->adjustSize(); m_mainWidget->show(); @@ -222,7 +223,7 @@ namespace O3DE::ProjectManager // Depending gems m_dependingGems = new GemsSubWidget(); - connect(m_dependingGems, &GemsSubWidget::TagClicked, this, [=](const QString& tag){ emit TagClicked(tag); }); + connect(m_dependingGems, &GemsSubWidget::TagClicked, this, [=](const Tag& tag){ emit TagClicked(tag); }); m_mainLayout->addWidget(m_dependingGems); m_mainLayout->addSpacing(20); diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.h index c6548527ab..9a6ad84dea 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.h @@ -44,7 +44,7 @@ namespace O3DE::ProjectManager inline constexpr static const char* s_textColor = "#DDDDDD"; signals: - void TagClicked(const QString& tag); + void TagClicked(const Tag& tag); private slots: void OnSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected); diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.cpp index 3f57aebf71..88c54de0b3 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.cpp @@ -18,6 +18,7 @@ namespace O3DE::ProjectManager : QStandardItemModel(parent) { m_selectionModel = new QItemSelectionModel(this, parent); + connect(this, &QAbstractItemModel::rowsAboutToBeRemoved, this, &GemModel::OnRowsAboutToBeRemoved); } QItemSelectionModel* GemModel::GetSelectionModel() const @@ -64,7 +65,6 @@ namespace O3DE::ProjectManager appendRow(item); const QModelIndex modelIndex = index(rowCount()-1, 0); - m_nameToIndexMap[gemInfo.m_displayName] = modelIndex; m_nameToIndexMap[gemInfo.m_name] = modelIndex; } @@ -177,18 +177,6 @@ namespace O3DE::ProjectManager return {}; } - void GemModel::FindGemDisplayNamesByNameStrings(QStringList& inOutGemNames) - { - for (QString& name : inOutGemNames) - { - QModelIndex modelIndex = FindIndexByNameString(name); - if (modelIndex.isValid()) - { - name = GetDisplayName(modelIndex); - } - } - } - QStringList GemModel::GetDependingGems(const QModelIndex& modelIndex) { return modelIndex.data(RoleDependingGems).toStringList(); @@ -208,16 +196,23 @@ namespace O3DE::ProjectManager } } - QStringList GemModel::GetDependingGemNames(const QModelIndex& modelIndex) + QVector GemModel::GetDependingGemTags(const QModelIndex& modelIndex) { - QStringList result = GetDependingGems(modelIndex); - if (result.isEmpty()) + QVector tags; + + QStringList dependingGemNames = GetDependingGems(modelIndex); + tags.reserve(dependingGemNames.size()); + + for (QString& gemName : dependingGemNames) { - return {}; + const QModelIndex& dependingIndex = FindIndexByNameString(gemName); + if (dependingIndex.isValid()) + { + tags.push_back({ GetDisplayName(dependingIndex), GetName(dependingIndex) }); + } } - FindGemDisplayNamesByNameStrings(result); - return result; + return tags; } QString GemModel::GetVersion(const QModelIndex& modelIndex) @@ -372,6 +367,16 @@ namespace O3DE::ProjectManager gemModel->emit gemStatusChanged(gemName, numChangedDependencies); } + void GemModel::OnRowsAboutToBeRemoved(const QModelIndex& parent, int first, int last) + { + for (int i = first; i <= last; ++i) + { + QModelIndex modelIndex = index(i, 0, parent); + const QString& gemName = GetName(modelIndex); + m_nameToIndexMap.remove(gemName); + } + } + void GemModel::SetIsAddedDependency(QAbstractItemModel& model, const QModelIndex& modelIndex, bool isAdded) { model.setData(modelIndex, isAdded, RoleIsAddedDependency); diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h index 56594ce794..e25a1c7703 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h @@ -10,6 +10,7 @@ #if !defined(Q_MOC_RUN) #include +#include #include #include #include @@ -26,12 +27,39 @@ namespace O3DE::ProjectManager explicit GemModel(QObject* parent = nullptr); QItemSelectionModel* GetSelectionModel() const; + enum UserRole + { + RoleName = Qt::UserRole, + RoleDisplayName, + RoleCreator, + RoleGemOrigin, + RolePlatforms, + RoleSummary, + RoleWasPreviouslyAdded, + RoleWasPreviouslyAddedDependency, + RoleIsAdded, + RoleIsAddedDependency, + RoleDirectoryLink, + RoleDocLink, + RoleDependingGems, + RoleVersion, + RoleLastUpdated, + RoleBinarySize, + RoleFeatures, + RoleTypes, + RolePath, + RoleRequirement, + RoleDownloadStatus, + RoleLicenseText, + RoleLicenseLink + }; + void AddGem(const GemInfo& gemInfo); void Clear(); void UpdateGemDependencies(); QModelIndex FindIndexByNameString(const QString& nameString) const; - QStringList GetDependingGemNames(const QModelIndex& modelIndex); + QVector GetDependingGemTags(const QModelIndex& modelIndex); bool HasDependentGems(const QModelIndex& modelIndex) const; static QString GetName(const QModelIndex& modelIndex); @@ -82,38 +110,13 @@ namespace O3DE::ProjectManager signals: void gemStatusChanged(const QString& gemName, uint32_t numChangedDependencies); + protected slots: + void OnRowsAboutToBeRemoved(const QModelIndex& parent, int first, int last); + private: - void FindGemDisplayNamesByNameStrings(QStringList& inOutGemNames); void GetAllDependingGems(const QModelIndex& modelIndex, QSet& inOutGems); QStringList GetDependingGems(const QModelIndex& modelIndex); - enum UserRole - { - RoleName = Qt::UserRole, - RoleDisplayName, - RoleCreator, - RoleGemOrigin, - RolePlatforms, - RoleSummary, - RoleWasPreviouslyAdded, - RoleWasPreviouslyAddedDependency, - RoleIsAdded, - RoleIsAddedDependency, - RoleDirectoryLink, - RoleDocLink, - RoleDependingGems, - RoleVersion, - RoleLastUpdated, - RoleBinarySize, - RoleFeatures, - RoleTypes, - RolePath, - RoleRequirement, - RoleDownloadStatus, - RoleLicenseText, - RoleLicenseLink - }; - QHash m_nameToIndexMap; QItemSelectionModel* m_selectionModel = nullptr; QHash> m_gemDependencyMap; diff --git a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoInspector.cpp b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoInspector.cpp index 6655aef86d..f816e86733 100644 --- a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoInspector.cpp +++ b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoInspector.cpp @@ -86,7 +86,7 @@ namespace O3DE::ProjectManager } // Included Gems - m_includedGems->Update(tr("Included Gems"), "", m_model->GetIncludedGemNames(modelIndex)); + m_includedGems->Update(tr("Included Gems"), "", m_model->GetIncludedGemTags(modelIndex)); m_mainWidget->adjustSize(); m_mainWidget->show(); diff --git a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoModel.cpp b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoModel.cpp index 7a9617e6c1..6189b6d8bf 100644 --- a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoModel.cpp +++ b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoModel.cpp @@ -103,17 +103,17 @@ namespace O3DE::ProjectManager return modelIndex.data(RoleIncludedGems).toStringList(); } - QStringList GemRepoModel::GetIncludedGemNames(const QModelIndex& modelIndex) + QVector GemRepoModel::GetIncludedGemTags(const QModelIndex& modelIndex) { - QStringList gemNames; - QVector gemInfos = GetIncludedGemInfos(modelIndex); - + QVector tags; + const QVector& gemInfos = GetIncludedGemInfos(modelIndex); + tags.reserve(gemInfos.size()); for (const GemInfo& gemInfo : gemInfos) { - gemNames.append(gemInfo.m_displayName); + tags.append({ gemInfo.m_displayName, gemInfo.m_name }); } - return gemNames; + return tags; } QVector GemRepoModel::GetIncludedGemInfos(const QModelIndex& modelIndex) diff --git a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoModel.h b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoModel.h index f36b66ca48..66fe972a95 100644 --- a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoModel.h +++ b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoModel.h @@ -40,7 +40,7 @@ namespace O3DE::ProjectManager static QString GetPath(const QModelIndex& modelIndex); static QStringList GetIncludedGemPaths(const QModelIndex& modelIndex); - static QStringList GetIncludedGemNames(const QModelIndex& modelIndex); + static QVector GetIncludedGemTags(const QModelIndex& modelIndex); static QVector GetIncludedGemInfos(const QModelIndex& modelIndex); static bool IsEnabled(const QModelIndex& modelIndex); diff --git a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.cpp b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.cpp index 0ddfe41434..794635a3e3 100644 --- a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.cpp +++ b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.cpp @@ -52,6 +52,11 @@ namespace O3DE::ProjectManager Reinit(); } + void GemRepoScreen::NotifyCurrentScreen() + { + Reinit(); + } + void GemRepoScreen::Reinit() { m_gemRepoModel->clear(); @@ -91,6 +96,7 @@ namespace O3DE::ProjectManager if (addGemRepoResult) { Reinit(); + emit OnRefresh(); } else { @@ -116,6 +122,7 @@ namespace O3DE::ProjectManager if (removeGemRepoResult) { Reinit(); + emit OnRefresh(); } else { @@ -130,6 +137,7 @@ namespace O3DE::ProjectManager { bool refreshResult = PythonBindingsInterface::Get()->RefreshAllGemRepos(); Reinit(); + emit OnRefresh(); if (!refreshResult) { @@ -146,6 +154,7 @@ namespace O3DE::ProjectManager if (refreshResult.IsSuccess()) { Reinit(); + emit OnRefresh(); } else { diff --git a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.h b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.h index 46a733362a..eed9a5ec4a 100644 --- a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.h +++ b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.h @@ -28,6 +28,7 @@ namespace O3DE::ProjectManager class GemRepoScreen : public ScreenWidget { + Q_OBJECT public: explicit GemRepoScreen(QWidget* parent = nullptr); ~GemRepoScreen() = default; @@ -37,12 +38,18 @@ namespace O3DE::ProjectManager GemRepoModel* GetGemRepoModel() const { return m_gemRepoModel; } + void NotifyCurrentScreen() override; + + signals: + void OnRefresh(); + public slots: void HandleAddRepoButton(); void HandleRemoveRepoButton(const QModelIndex& modelIndex); void HandleRefreshAllButton(); void HandleRefreshRepoButton(const QModelIndex& modelIndex); + private: void FillModel(); QFrame* CreateNoReposContent(); diff --git a/Code/Tools/ProjectManager/Source/GemsSubWidget.cpp b/Code/Tools/ProjectManager/Source/GemsSubWidget.cpp index 8b7b183008..2572a39db3 100644 --- a/Code/Tools/ProjectManager/Source/GemsSubWidget.cpp +++ b/Code/Tools/ProjectManager/Source/GemsSubWidget.cpp @@ -33,14 +33,14 @@ namespace O3DE::ProjectManager m_layout->addWidget(m_textLabel); m_tagWidget = new TagContainerWidget(); - connect(m_tagWidget, &TagContainerWidget::TagClicked, this, [=](const QString& tag){ emit TagClicked(tag); }); + connect(m_tagWidget, &TagContainerWidget::TagClicked, this, [=](const Tag& tag){ emit TagClicked(tag); }); m_layout->addWidget(m_tagWidget); } - void GemsSubWidget::Update(const QString& title, const QString& text, const QStringList& gemNames) + void GemsSubWidget::Update(const QString& title, const QString& text, const QVector& tags) { m_titleLabel->setText(title); m_textLabel->setText(text); - m_tagWidget->Update(gemNames); + m_tagWidget->Update(tags); } } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemsSubWidget.h b/Code/Tools/ProjectManager/Source/GemsSubWidget.h index a9fabf5e92..5e670b930a 100644 --- a/Code/Tools/ProjectManager/Source/GemsSubWidget.h +++ b/Code/Tools/ProjectManager/Source/GemsSubWidget.h @@ -26,10 +26,10 @@ namespace O3DE::ProjectManager public: GemsSubWidget(QWidget* parent = nullptr); - void Update(const QString& title, const QString& text, const QStringList& gemNames); + void Update(const QString& title, const QString& text, const QVector& tags); signals: - void TagClicked(const QString& tag); + void TagClicked(const Tag& tag); private: QLabel* m_titleLabel = nullptr; diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.cpp b/Code/Tools/ProjectManager/Source/PythonBindings.cpp index 905139a4f2..021066e1c7 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.cpp +++ b/Code/Tools/ProjectManager/Source/PythonBindings.cpp @@ -53,6 +53,8 @@ namespace Platform #define Py_To_String(obj) pybind11::str(obj).cast().c_str() #define Py_To_String_Optional(dict, key, default_string) dict.contains(key) ? Py_To_String(dict[key]) : default_string +#define Py_To_Int(obj) obj.cast() +#define Py_To_Int_Optional(dict, key, default_int) dict.contains(key) ? Py_To_Int(dict[key]) : default_int #define QString_To_Py_String(value) pybind11::str(value.toStdString()) #define QString_To_Py_Path(value) m_pathlib.attr("Path")(value.toStdString()) @@ -705,7 +707,9 @@ namespace O3DE::ProjectManager // optional gemInfo.m_displayName = Py_To_String_Optional(data, "display_name", gemInfo.m_name); gemInfo.m_summary = Py_To_String_Optional(data, "summary", ""); - gemInfo.m_version = ""; + gemInfo.m_version = Py_To_String_Optional(data, "version", gemInfo.m_version); + gemInfo.m_lastUpdatedDate = Py_To_String_Optional(data, "last_updated", gemInfo.m_lastUpdatedDate); + gemInfo.m_binarySizeInKB = Py_To_Int_Optional(data, "binary_size", gemInfo.m_binarySizeInKB); 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", ""); @@ -1175,6 +1179,7 @@ namespace O3DE::ProjectManager QString_To_Py_String(gemName), // gem name pybind11::none(), // destination path false, // skip auto register + false, // force pybind11::cpp_function( [this, gemProgressCallback](int progress) { diff --git a/Code/Tools/ProjectManager/Source/TagWidget.cpp b/Code/Tools/ProjectManager/Source/TagWidget.cpp index 39231ace4b..007f0839d1 100644 --- a/Code/Tools/ProjectManager/Source/TagWidget.cpp +++ b/Code/Tools/ProjectManager/Source/TagWidget.cpp @@ -12,15 +12,16 @@ namespace O3DE::ProjectManager { - TagWidget::TagWidget(const QString& text, QWidget* parent) - : QLabel(text, parent) + TagWidget::TagWidget(const Tag& tag, QWidget* parent) + : QLabel(tag.text, parent) + , m_tag(tag) { setObjectName("TagWidget"); } void TagWidget::mousePressEvent([[maybe_unused]] QMouseEvent* event) { - emit(TagClicked(text())); + emit TagClicked(m_tag); } TagContainerWidget::TagContainerWidget(QWidget* parent) @@ -39,20 +40,34 @@ namespace O3DE::ProjectManager void TagContainerWidget::Update(const QStringList& tags) { - FlowLayout* flowLayout = static_cast(layout()); + Clear(); - // remove old tags - QLayoutItem* layoutItem = nullptr; - while ((layoutItem = layout()->takeAt(0)) != nullptr) + foreach (const QString& tag, tags) { - layoutItem->widget()->deleteLater(); + TagWidget* tagWidget = new TagWidget({tag, tag}); + connect(tagWidget, &TagWidget::TagClicked, this, [=](const Tag& clickedTag){ emit TagClicked(clickedTag); }); + layout()->addWidget(tagWidget); } + } - foreach (const QString& tag, tags) + void TagContainerWidget::Update(const QVector& tags) + { + Clear(); + + foreach (const Tag& tag, tags) { TagWidget* tagWidget = new TagWidget(tag); - connect(tagWidget, &TagWidget::TagClicked, this, [=](const QString& tag){ emit TagClicked(tag); }); - flowLayout->addWidget(tagWidget); + connect(tagWidget, &TagWidget::TagClicked, this, [=](const Tag& clickedTag){ emit TagClicked(clickedTag); }); + layout()->addWidget(tagWidget); + } + } + + void TagContainerWidget::Clear() + { + QLayoutItem* layoutItem = nullptr; + while ((layoutItem = layout()->takeAt(0)) != nullptr) + { + layoutItem->widget()->deleteLater(); } } } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/TagWidget.h b/Code/Tools/ProjectManager/Source/TagWidget.h index 7b4a5b1aaa..fce6eaf863 100644 --- a/Code/Tools/ProjectManager/Source/TagWidget.h +++ b/Code/Tools/ProjectManager/Source/TagWidget.h @@ -10,12 +10,19 @@ #if !defined(Q_MOC_RUN) #include -#include #include +#include +#include #endif namespace O3DE::ProjectManager { + struct Tag + { + QString text; + QString id; + }; + // Single tag class TagWidget : public QLabel @@ -23,14 +30,17 @@ namespace O3DE::ProjectManager Q_OBJECT // AUTOMOC public: - explicit TagWidget(const QString& text, QWidget* parent = nullptr); + explicit TagWidget(const Tag& id, QWidget* parent = nullptr); ~TagWidget() = default; signals: - void TagClicked(const QString& tag); + void TagClicked(const Tag& tag); protected: void mousePressEvent(QMouseEvent* event) override; + + private: + Tag m_tag; }; // Widget containing multiple tags, automatically wrapping based on the size @@ -43,9 +53,13 @@ namespace O3DE::ProjectManager explicit TagContainerWidget(QWidget* parent = nullptr); ~TagContainerWidget() = default; + void Update(const QVector& tags); void Update(const QStringList& tags); signals: - void TagClicked(const QString& tag); + void TagClicked(const Tag& tag); + + private: + void Clear(); }; } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.cpp b/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.cpp index 1c8f9a6931..fb16484961 100644 --- a/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.cpp +++ b/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.cpp @@ -7,6 +7,7 @@ */ #include +#include #include #include #include @@ -39,10 +40,10 @@ namespace O3DE::ProjectManager m_updateSettingsScreen = new UpdateProjectSettingsScreen(); m_gemCatalogScreen = new GemCatalogScreen(); + m_gemRepoScreen = new GemRepoScreen(this); - connect(m_gemCatalogScreen, &ScreenWidget::ChangeScreenRequest, this, [this](ProjectManagerScreen screen){ - emit ChangeScreenRequest(screen); - }); + connect(m_gemCatalogScreen, &ScreenWidget::ChangeScreenRequest, this, &UpdateProjectCtrl::OnChangeScreenRequest); + connect(m_gemRepoScreen, &GemRepoScreen::OnRefresh, m_gemCatalogScreen, &GemCatalogScreen::Refresh); m_stack = new QStackedWidget(this); m_stack->setObjectName("body"); @@ -69,6 +70,7 @@ namespace O3DE::ProjectManager m_stack->addWidget(topBarFrameWidget); m_stack->addWidget(m_gemCatalogScreen); + m_stack->addWidget(m_gemRepoScreen); QDialogButtonBox* backNextButtons = new QDialogButtonBox(); backNextButtons->setObjectName("footer"); @@ -100,6 +102,22 @@ namespace O3DE::ProjectManager // Gather the available gems that will be shown in the gem catalog. m_gemCatalogScreen->ReinitForProject(m_projectInfo.m_path); + + // make sure the gem repo has the latest repo details + m_gemRepoScreen->Reinit(); + } + + void UpdateProjectCtrl::OnChangeScreenRequest(ProjectManagerScreen screen) + { + if (screen == ProjectManagerScreen::GemRepos) + { + m_stack->setCurrentWidget(m_gemRepoScreen); + Update(); + } + else + { + emit ChangeScreenRequest(screen); + } } void UpdateProjectCtrl::HandleGemsButton() @@ -145,6 +163,7 @@ namespace O3DE::ProjectManager QMessageBox::critical(this, tr("Gems downloading"), tr("You must wait for gems to finish downloading before continuing.")); return; } + // Enable or disable the gems that got adjusted in the gem catalog and apply them to the given project. const GemCatalogScreen::EnableDisableGemsResult result = m_gemCatalogScreen->EnableDisableGemsForProject(m_projectInfo.m_path); if (result == GemCatalogScreen::EnableDisableGemsResult::Failed) @@ -181,18 +200,26 @@ namespace O3DE::ProjectManager void UpdateProjectCtrl::Update() { - if (m_stack->currentIndex() == ScreenOrder::Gems) + if (m_stack->currentIndex() == ScreenOrder::GemRepos) + { + m_header->setTitle(QString(tr("Edit Project Settings: \"%1\"")).arg(m_projectInfo.GetProjectDisplayName())); + m_header->setSubTitle(QString(tr("Gem Repositories"))); + m_nextButton->setVisible(false); + } + else if (m_stack->currentIndex() == ScreenOrder::Gems) { m_header->setTitle(QString(tr("Edit Project Settings: \"%1\"")).arg(m_projectInfo.GetProjectDisplayName())); m_header->setSubTitle(QString(tr("Configure Gems"))); m_nextButton->setText(tr("Save")); + m_nextButton->setVisible(true); } else { m_header->setTitle(""); m_header->setSubTitle(QString(tr("Edit Project Settings: \"%1\"")).arg(m_projectInfo.GetProjectDisplayName())); m_nextButton->setText(tr("Save")); + m_nextButton->setVisible(true); } } diff --git a/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.h b/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.h index 3321fad638..ee6fc792f2 100644 --- a/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.h +++ b/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.h @@ -22,9 +22,11 @@ namespace O3DE::ProjectManager QT_FORWARD_DECLARE_CLASS(ScreenHeader) QT_FORWARD_DECLARE_CLASS(UpdateProjectSettingsScreen) QT_FORWARD_DECLARE_CLASS(GemCatalogScreen) + QT_FORWARD_DECLARE_CLASS(GemRepoScreen) class UpdateProjectCtrl : public ScreenWidget { + Q_OBJECT public: explicit UpdateProjectCtrl(QWidget* parent = nullptr); ~UpdateProjectCtrl() = default; @@ -37,6 +39,7 @@ namespace O3DE::ProjectManager void HandleBackButton(); void HandleNextButton(); void HandleGemsButton(); + void OnChangeScreenRequest(ProjectManagerScreen screen); void UpdateCurrentProject(const QString& projectPath); private: @@ -47,13 +50,15 @@ namespace O3DE::ProjectManager enum ScreenOrder { Settings, - Gems + Gems, + GemRepos }; ScreenHeader* m_header = nullptr; QStackedWidget* m_stack = nullptr; UpdateProjectSettingsScreen* m_updateSettingsScreen = nullptr; GemCatalogScreen* m_gemCatalogScreen = nullptr; + GemRepoScreen* m_gemRepoScreen = nullptr; QPushButton* m_backButton = nullptr; QPushButton* m_nextButton = nullptr; diff --git a/Code/Tools/PythonBindingsExample/source/Application.cpp b/Code/Tools/PythonBindingsExample/source/Application.cpp index ab1d1b5acf..cfb28d8e09 100644 --- a/Code/Tools/PythonBindingsExample/source/Application.cpp +++ b/Code/Tools/PythonBindingsExample/source/Application.cpp @@ -39,7 +39,6 @@ namespace PythonBindingsExample AzToolsFramework::EditorPythonConsoleNotificationBus::Handler::BusConnect(); // prepare the Python binding gem(s) - CalculateExecutablePath(); Start(Descriptor()); AZ::SerializeContext* context; diff --git a/Code/Tools/SceneAPI/SceneCore/Tests/Containers/SceneBehaviorTests.cpp b/Code/Tools/SceneAPI/SceneCore/Tests/Containers/SceneBehaviorTests.cpp index 3bf75e2d9b..0478e6cdb2 100644 --- a/Code/Tools/SceneAPI/SceneCore/Tests/Containers/SceneBehaviorTests.cpp +++ b/Code/Tools/SceneAPI/SceneCore/Tests/Containers/SceneBehaviorTests.cpp @@ -368,7 +368,6 @@ namespace AZ::SceneAPI::Containers MOCK_METHOD0(GetSerializeContext, AZ::SerializeContext* ()); MOCK_METHOD0(GetJsonRegistrationContext, AZ::JsonRegistrationContext* ()); MOCK_METHOD0(GetBehaviorContext, AZ::BehaviorContext* ()); - MOCK_CONST_METHOD0(GetAppRoot, const char*()); MOCK_CONST_METHOD0(GetEngineRoot, const char*()); MOCK_CONST_METHOD0(GetExecutableFolder, const char* ()); MOCK_CONST_METHOD1(QueryApplicationType, void(AZ::ApplicationTypeQuery&)); diff --git a/Code/Tools/SerializeContextTools/Converter.cpp b/Code/Tools/SerializeContextTools/Converter.cpp index 6a8bebdbfc..294bf941ac 100644 --- a/Code/Tools/SerializeContextTools/Converter.cpp +++ b/Code/Tools/SerializeContextTools/Converter.cpp @@ -202,8 +202,6 @@ namespace AZ bool skipSystem = commandLine->HasSwitch("skipsystem"); bool isDryRun = commandLine->HasSwitch("dryrun"); - const char* appRoot = const_cast(application).GetAppRoot(); - PathDocumentContainer documents; bool result = true; const AZStd::string& filePath = application.GetConfigFilePath(); @@ -230,7 +228,7 @@ namespace AZ } auto callback = - [&result, skipGems, skipSystem, &configurationName, sourceGameFolder, &appRoot, &documents, &convertSettings, &verifySettings] + [&result, skipGems, skipSystem, &configurationName, sourceGameFolder, &documents, &convertSettings, &verifySettings] (void* classPtr, const Uuid& classId, SerializeContext* context) { if (classId == azrtti_typeid()) @@ -238,7 +236,7 @@ namespace AZ if (!skipSystem) { result = ConvertSystemSettings(documents, *reinterpret_cast(classPtr), - configurationName, sourceGameFolder, appRoot) && result; + configurationName, sourceGameFolder) && result; } // Cleanup the Serialized Element to allow any classes within the element's hierarchy to delete @@ -443,7 +441,7 @@ namespace AZ } bool Converter::ConvertSystemSettings(PathDocumentContainer& documents, const ComponentApplication::Descriptor& descriptor, - const AZStd::string& configurationName, const AZ::IO::PathView& projectFolder, [[maybe_unused]] const AZStd::string& applicationRoot) + const AZStd::string& configurationName, const AZ::IO::PathView& projectFolder) { AZ::IO::FixedMaxPath memoryFilePath{ projectFolder }; memoryFilePath /= "Registry"; diff --git a/Code/Tools/SerializeContextTools/Converter.h b/Code/Tools/SerializeContextTools/Converter.h index 6c8f6c70fb..7d30ca2a3a 100644 --- a/Code/Tools/SerializeContextTools/Converter.h +++ b/Code/Tools/SerializeContextTools/Converter.h @@ -43,7 +43,7 @@ namespace AZ using PathDocumentContainer = AZStd::vector; static bool ConvertSystemSettings(PathDocumentContainer& documents, const ComponentApplication::Descriptor& descriptor, - const AZStd::string& configurationName, const AZ::IO::PathView& projectFolder, const AZStd::string& applicationRoot); + const AZStd::string& configurationName, const AZ::IO::PathView& projectFolder); static bool ConvertSystemComponents(PathDocumentContainer& documents, const Entity& entity, const AZStd::string& configurationName, const AZ::IO::PathView& projectFolder, const JsonSerializerSettings& convertSettings, const JsonDeserializerSettings& verifySettings); diff --git a/Gems/AWSClientAuth/Code/Tests/AWSClientAuthGemMock.h b/Gems/AWSClientAuth/Code/Tests/AWSClientAuthGemMock.h index 1d2e8bad42..19314035c4 100644 --- a/Gems/AWSClientAuth/Code/Tests/AWSClientAuthGemMock.h +++ b/Gems/AWSClientAuth/Code/Tests/AWSClientAuthGemMock.h @@ -608,7 +608,6 @@ namespace AWSClientAuthUnitTest AZ::Entity* FindEntity(const AZ::EntityId&) override { return nullptr; } AZ::BehaviorContext* GetBehaviorContext() override { return nullptr; } const char* GetExecutableFolder() const override { return nullptr; } - const char* GetAppRoot() const override { return nullptr; } const char* GetEngineRoot() const override { return nullptr; } void EnumerateEntities(const EntityCallback& /*callback*/) override {} void QueryApplicationType(AZ::ApplicationTypeQuery& /*appType*/) const override {} diff --git a/Gems/AWSMetrics/Code/Include/Private/MetricsAttribute.h b/Gems/AWSMetrics/Code/Include/Public/MetricsAttribute.h similarity index 100% rename from Gems/AWSMetrics/Code/Include/Private/MetricsAttribute.h rename to Gems/AWSMetrics/Code/Include/Public/MetricsAttribute.h diff --git a/Gems/AWSMetrics/Code/awsmetrics_files.cmake b/Gems/AWSMetrics/Code/awsmetrics_files.cmake index b51235c957..b1a3a647df 100644 --- a/Gems/AWSMetrics/Code/awsmetrics_files.cmake +++ b/Gems/AWSMetrics/Code/awsmetrics_files.cmake @@ -8,6 +8,7 @@ set(FILES Include/Public/AWSMetricsBus.h + Include/Public/MetricsAttribute.h Include/Private/AWSMetricsConstant.h Include/Private/AWSMetricsServiceApi.h Include/Private/AWSMetricsSystemComponent.h @@ -15,7 +16,6 @@ set(FILES Include/Private/DefaultClientIdProvider.h Include/Private/GlobalStatistics.h Include/Private/IdentityProvider.h - Include/Private/MetricsAttribute.h Include/Private/MetricsEvent.h Include/Private/MetricsEventBuilder.h Include/Private/MetricsManager.h diff --git a/Gems/AssetValidation/Code/Source/AssetValidationSystemComponent.cpp b/Gems/AssetValidation/Code/Source/AssetValidationSystemComponent.cpp index 56f28856af..8f8fb48212 100644 --- a/Gems/AssetValidation/Code/Source/AssetValidationSystemComponent.cpp +++ b/Gems/AssetValidation/Code/Source/AssetValidationSystemComponent.cpp @@ -439,13 +439,6 @@ namespace AssetValidation bool GetDefaultSeedListFiles(AZStd::vector& defaultSeedListFiles) { - const char* engineRoot = nullptr; - AzFramework::ApplicationRequests::Bus::BroadcastResult(engineRoot, &AzFramework::ApplicationRequests::GetEngineRoot); - - const char* appRoot = nullptr; - AzFramework::ApplicationRequests::Bus::BroadcastResult(appRoot, &AzFramework::ApplicationRequests::GetAppRoot); - - auto settingsRegistry = AZ::SettingsRegistry::Get(); AZ::SettingsRegistryInterface::FixedValueString gameFolder; auto projectKey = AZ::SettingsRegistryInterface::FixedValueString::format("%s/project_path", AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey); @@ -509,30 +502,28 @@ namespace AssetValidation AZ::Outcome AssetValidationSystemComponent::LoadSeedList(const char* seedPath, AZStd::string& seedListPath) { - AZStd::string absoluteSeedPath = seedPath; + AZ::IO::Path absoluteSeedPath = seedPath; if (AZ::StringFunc::Path::IsRelative(seedPath)) { - const char* appRoot = nullptr; - AzFramework::ApplicationRequests::Bus::BroadcastResult(appRoot, &AzFramework::ApplicationRequests::GetEngineRoot); + AZ::IO::FixedMaxPath engineRoot = AZ::Utils::GetEnginePath(); - if (!appRoot) + if (engineRoot.empty()) { return AZ::Failure(AZStd::string("Couldn't get engine root")); } - absoluteSeedPath = AZStd::string::format("%s/%s", appRoot, seedPath); + absoluteSeedPath = (engineRoot / seedPath).String(); } - AzFramework::StringFunc::Path::Normalize(absoluteSeedPath); AzFramework::AssetSeedList seedList; - if (!AZ::Utils::LoadObjectFromFileInPlace(absoluteSeedPath, seedList)) + if (!AZ::Utils::LoadObjectFromFileInPlace(absoluteSeedPath.Native(), seedList)) { return AZ::Failure(AZStd::string::format("Failed to load seed list %s", absoluteSeedPath.c_str())); } - seedListPath = absoluteSeedPath; + seedListPath = AZStd::move(absoluteSeedPath.Native()); return AZ::Success(seedList); } diff --git a/Gems/AssetValidation/Code/Tests/AssetValidationTestShared.h b/Gems/AssetValidation/Code/Tests/AssetValidationTestShared.h index c4872ab425..8da3ae1b34 100644 --- a/Gems/AssetValidation/Code/Tests/AssetValidationTestShared.h +++ b/Gems/AssetValidation/Code/Tests/AssetValidationTestShared.h @@ -150,13 +150,13 @@ struct AssetValidationTest auto projectPathKey = AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path"; - m_registry.Set(projectPathKey, (AZ::IO::FixedMaxPath(GetEngineRoot()) / "AutomatedTesting").Native()); + m_registry.Set(projectPathKey, (AZ::IO::FixedMaxPath(m_tempDir.GetDirectory()) / "AutomatedTesting").Native()); AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(m_registry); // Set the engine root to the temporary directory and re-update the runtime file paths auto enginePathKey = AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/engine_path"; - m_registry.Set(enginePathKey, GetEngineRoot()); + m_registry.Set(enginePathKey, m_tempDir.GetDirectory()); AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(m_registry); } } @@ -176,11 +176,6 @@ struct AssetValidationTest AZ_Assert(false, "Not implemented"); } - const char* GetEngineRoot() const override - { - return m_tempDir.GetDirectory(); - } - void SetUp() override { using namespace ::testing; diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Tests/ImageProcessing_Test.cpp b/Gems/Atom/Asset/ImageProcessingAtom/Code/Tests/ImageProcessing_Test.cpp index d5b795a1fa..d0029ffc86 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Tests/ImageProcessing_Test.cpp +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Tests/ImageProcessing_Test.cpp @@ -111,7 +111,6 @@ namespace UnitTest AZ::SerializeContext* GetSerializeContext() override { return m_context.get(); } AZ::BehaviorContext* GetBehaviorContext() override { return nullptr; } AZ::JsonRegistrationContext* GetJsonRegistrationContext() override { return m_jsonRegistrationContext.get(); } - const char* GetAppRoot() const override { return nullptr; } const char* GetEngineRoot() const override { return nullptr; } const char* GetExecutableFolder() const override { return nullptr; } void EnumerateEntities(const AZ::ComponentApplicationRequests::EntityCallback& /*callback*/) override {} diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/BackLighting.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/BackLighting.azsli index a4a747d42c..83cf79ed61 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/BackLighting.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/BackLighting.azsli @@ -50,8 +50,13 @@ float3 GetBackLighting(Surface surface, LightingData lightingData, float3 lightI // Thin object mode, using thin-film assumption proposed by Jimenez J. et al, 2010, "Real-Time Realistic Skin Translucency" // http://www.iryoku.com/translucency/downloads/Real-Time-Realistic-Skin-Translucency.pdf - result = shadowRatio ? float3(0.0, 0.0, 0.0) : TransmissionKernel(surface.transmission.thickness * transmissionParams.w, rcp(transmissionParams.xyz)) * - saturate(dot(-surface.normal, dirToLight)) * lightIntensity * shadowRatio; + float litRatio = 1.0 - shadowRatio; + if (litRatio) + { + result = TransmissionKernel(surface.transmission.thickness * transmissionParams.w, rcp(transmissionParams.xyz)) * + saturate(dot(-surface.normal, dirToLight)) * lightIntensity * litRatio; + } + break; } diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/DefaultObjectSrg.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/DefaultObjectSrg.azsli index 10526597cb..9f40e7ab8f 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/DefaultObjectSrg.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/DefaultObjectSrg.azsli @@ -37,6 +37,7 @@ ShaderResourceGroup ObjectSrg : SRG_PerObject float m_padding; bool m_useReflectionProbe; bool m_useParallaxCorrection; + float m_exposure; }; ReflectionProbeData m_reflectionProbeData; diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/Ibl.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/Ibl.azsli index d33a307dfe..3254b8e4ed 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/Ibl.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/Ibl.azsli @@ -85,12 +85,12 @@ void ApplyIBL(Surface surface, inout LightingData lightingData) if(useIbl) { - float iblExposureFactor = pow(2.0, SceneSrg::m_iblExposure); + float globalIblExposure = pow(2.0, SceneSrg::m_iblExposure); if(useDiffuseIbl) { float3 iblDiffuse = GetIblDiffuse(surface.normal, surface.albedo, lightingData.diffuseResponse); - lightingData.diffuseLighting += (iblDiffuse * iblExposureFactor * lightingData.diffuseAmbientOcclusion); + lightingData.diffuseLighting += (iblDiffuse * globalIblExposure * lightingData.diffuseAmbientOcclusion); } if(useSpecularIbl) @@ -116,7 +116,8 @@ void ApplyIBL(Surface surface, inout LightingData lightingData) iblSpecular = iblSpecular * (1.0 - clearCoatResponse) * (1.0 - clearCoatResponse) + clearCoatIblSpecular; } - lightingData.specularLighting += (iblSpecular * iblExposureFactor); + float exposure = ObjectSrg::m_reflectionProbeData.m_useReflectionProbe ? pow(2.0, ObjectSrg::m_reflectionProbeData.m_exposure) : globalIblExposure; + lightingData.specularLighting += (iblSpecular * exposure); } } } diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Skin/SkinObjectSrg.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Skin/SkinObjectSrg.azsli index d0766c295d..99a32629ef 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Skin/SkinObjectSrg.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Skin/SkinObjectSrg.azsli @@ -46,6 +46,7 @@ ShaderResourceGroup ObjectSrg : SRG_PerObject float m_padding; bool m_useReflectionProbe; bool m_useParallaxCorrection; + float m_exposure; }; ReflectionProbeData m_reflectionProbeData; diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionProbeRenderInner.azsl b/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionProbeRenderInner.azsl index a0734caf02..e9333a4694 100644 --- a/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionProbeRenderInner.azsl +++ b/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionProbeRenderInner.azsl @@ -81,7 +81,7 @@ PSOutput MainPS(VSOutput IN, in uint sampleIndex : SV_SampleIndex) } // apply exposure setting - specular *= pow(2.0, SceneSrg::m_iblExposure); + specular *= pow(2.0, ObjectSrg::m_exposure); PSOutput OUT; OUT.m_color = float4(specular, 1.0f); diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionProbeRenderObjectSrg.azsli b/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionProbeRenderObjectSrg.azsli index 8151ed2fd5..366dc691ed 100644 --- a/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionProbeRenderObjectSrg.azsli +++ b/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionProbeRenderObjectSrg.azsli @@ -17,6 +17,7 @@ ShaderResourceGroup ObjectSrg : SRG_PerObject float3 m_outerObbHalfLengths; float3 m_innerObbHalfLengths; bool m_useParallaxCorrection; + float m_exposure; TextureCube m_reflectionCubeMap; float4x4 GetWorldMatrix() diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionProbeRenderOuter.azsl b/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionProbeRenderOuter.azsl index ac97172f1f..138d29398e 100644 --- a/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionProbeRenderOuter.azsl +++ b/Gems/Atom/Feature/Common/Assets/Shaders/Reflections/ReflectionProbeRenderOuter.azsl @@ -104,7 +104,7 @@ PSOutput MainPS(VSOutput IN, in uint sampleIndex : SV_SampleIndex) blendWeight /= max(1.0f, blendWeightAllProbes); // apply exposure setting - specular *= pow(2.0, SceneSrg::m_iblExposure); + specular *= pow(2.0, ObjectSrg::m_exposure); // apply blend weight for additive blending specular *= blendWeight; diff --git a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/ReflectionProbe/ReflectionProbeFeatureProcessor.h b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/ReflectionProbe/ReflectionProbeFeatureProcessor.h index 5efd235a67..ded36f5496 100644 --- a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/ReflectionProbe/ReflectionProbeFeatureProcessor.h +++ b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/ReflectionProbe/ReflectionProbeFeatureProcessor.h @@ -39,6 +39,8 @@ namespace AZ bool IsCubeMapReferenced(const AZStd::string& relativePath) override; bool IsValidProbeHandle(const ReflectionProbeHandle& probe) const override { return (probe.get() != nullptr); } void ShowProbeVisualization(const ReflectionProbeHandle& probe, bool showVisualization) override; + void SetRenderExposure(const ReflectionProbeHandle& probe, float renderExposure) override; + void SetBakeExposure(const ReflectionProbeHandle& probe, float bakeExposure) override; // FeatureProcessor overrides void Activate() override; diff --git a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/ReflectionProbe/ReflectionProbeFeatureProcessorInterface.h b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/ReflectionProbe/ReflectionProbeFeatureProcessorInterface.h index 4eb2130b1b..80c92281ea 100644 --- a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/ReflectionProbe/ReflectionProbeFeatureProcessorInterface.h +++ b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/ReflectionProbe/ReflectionProbeFeatureProcessorInterface.h @@ -50,6 +50,8 @@ namespace AZ virtual bool IsCubeMapReferenced(const AZStd::string& relativePath) = 0; virtual bool IsValidProbeHandle(const ReflectionProbeHandle& probe) const = 0; virtual void ShowProbeVisualization(const ReflectionProbeHandle& probe, bool showVisualization) = 0; + virtual void SetRenderExposure(const ReflectionProbeHandle& probe, float renderExposure) = 0; + virtual void SetBakeExposure(const ReflectionProbeHandle& probe, float bakeExposure) = 0; }; } // namespace Render } // namespace AZ diff --git a/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.cpp index b6c6910fd3..410c80dbdc 100644 --- a/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.cpp @@ -1056,7 +1056,7 @@ namespace AZ // if the shadow is rendering in an EnvironmentCubeMapPass it also needs to be a ReflectiveCubeMap view, // to filter out shadows from objects that are excluded from the cubemap RPI::PassFilter passFilter = RPI::PassFilter::CreateWithPassClass(); - passFilter.SetOwenrScene(GetParentScene()); // only handles passes for this scene + passFilter.SetOwnerScene(GetParentScene()); // only handles passes for this scene RPI::PassSystemInterface::Get()->ForEachPass(passFilter, [&usageFlags]([[maybe_unused]] RPI::Pass* pass) -> RPI::PassFilterExecutionFlow { usageFlags |= RPI::View::UsageReflectiveCubeMap; diff --git a/Gems/Atom/Feature/Common/Code/Source/LuxCore/LuxCoreRenderer.cpp b/Gems/Atom/Feature/Common/Code/Source/LuxCore/LuxCoreRenderer.cpp index c753079d5b..ef3f33d1c3 100644 --- a/Gems/Atom/Feature/Common/Code/Source/LuxCore/LuxCoreRenderer.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/LuxCore/LuxCoreRenderer.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -295,14 +296,12 @@ namespace AZ } // Run luxcoreui.exe - AZStd::string luxCoreExeFullPath; - AzFramework::ApplicationRequests::Bus::BroadcastResult(luxCoreExeFullPath, &AzFramework::ApplicationRequests::GetAppRoot); - luxCoreExeFullPath = luxCoreExeFullPath + AZ_TRAIT_LUXCORE_EXEPATH; - AzFramework::StringFunc::Path::Normalize(luxCoreExeFullPath); + AZ::IO::FixedMaxPath luxCoreExeFullPath = AZ::Utils::GetEnginePath(); + luxCoreExeFullPath /= AZ_TRAIT_LUXCORE_EXEPATH; AZStd::string commandLine = "-o " + AZStd::string(resolvedPath) + "/render.cfg"; - LuxCoreUI::LaunchLuxCoreUI(luxCoreExeFullPath, commandLine); + LuxCoreUI::LaunchLuxCoreUI(luxCoreExeFullPath.String(), commandLine); } } } diff --git a/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp index c7fb19bc5d..99f01ea630 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp @@ -1178,6 +1178,9 @@ namespace AZ AZ::RHI::ShaderInputConstantIndex useParallaxCorrectionConstantIndex = m_shaderResourceGroup->FindShaderInputConstantIndex(Name("m_reflectionProbeData.m_useParallaxCorrection")); AZ_Error("MeshDataInstance", useParallaxCorrectionConstantIndex.IsValid(), "Failed to find ReflectionProbe constant index"); + AZ::RHI::ShaderInputConstantIndex exposureConstantIndex = m_shaderResourceGroup->FindShaderInputConstantIndex(Name("m_reflectionProbeData.m_exposure")); + AZ_Error("MeshDataInstance", exposureConstantIndex.IsValid(), "Failed to find ReflectionProbe constant index"); + // retrieve probe cubemap index Name reflectionCubeMapImageName = Name("m_reflectionProbeCubeMap"); RHI::ShaderInputImageIndex reflectionCubeMapImageIndex = m_shaderResourceGroup->FindShaderInputImageIndex(reflectionCubeMapImageName); @@ -1198,6 +1201,7 @@ namespace AZ m_shaderResourceGroup->SetConstant(innerObbHalfLengthsConstantIndex, reflectionProbes[0]->GetInnerObbWs().GetHalfLengths()); m_shaderResourceGroup->SetConstant(useReflectionProbeConstantIndex, true); m_shaderResourceGroup->SetConstant(useParallaxCorrectionConstantIndex, reflectionProbes[0]->GetUseParallaxCorrection()); + m_shaderResourceGroup->SetConstant(exposureConstantIndex, reflectionProbes[0]->GetRenderExposure()); m_shaderResourceGroup->SetImage(reflectionCubeMapImageIndex, reflectionProbes[0]->GetCubeMapImage()); } diff --git a/Gems/Atom/Feature/Common/Code/Source/PostProcessing/BlendColorGradingLutsPass.cpp b/Gems/Atom/Feature/Common/Code/Source/PostProcessing/BlendColorGradingLutsPass.cpp index f74837bd9a..ca93898d5e 100644 --- a/Gems/Atom/Feature/Common/Code/Source/PostProcessing/BlendColorGradingLutsPass.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/PostProcessing/BlendColorGradingLutsPass.cpp @@ -46,7 +46,11 @@ namespace AZ void BlendColorGradingLutsPass::InitializeShaderVariant() { - AZ_Assert(m_shader != nullptr, "BlendColorGradingLutsPass %s has a null shader when calling InitializeShaderVariant.", GetPathName().GetCStr()); + if (m_shader == nullptr) + { + AZ_Assert(false, "BlendColorGradingLutsPass %s has a null shader when calling InitializeShaderVariant.", GetPathName().GetCStr()); + return; + } // Total variations is MaxBlendLuts plus one for the fallback case that none of the LUTs are found, // and hence zero LUTs are blended resulting in an identity LUT. diff --git a/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbe.cpp b/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbe.cpp index e86d91d387..4ac5782b04 100644 --- a/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbe.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbe.cpp @@ -120,15 +120,17 @@ namespace AZ m_scene->RemoveRenderPipeline(m_environmentCubeMapPipelineId); m_environmentCubeMapPass = nullptr; - // restore exposure - sceneSrg->SetConstant(m_iblExposureConstantIndex, m_previousExposure); + // restore exposures + sceneSrg->SetConstant(m_globalIblExposureConstantIndex, m_previousGlobalIblExposure); + sceneSrg->SetConstant(m_skyBoxExposureConstantIndex, m_previousSkyBoxExposure); m_buildingCubeMap = false; } else { - // set exposure to 0.0 while baking the cubemap - sceneSrg->SetConstant(m_iblExposureConstantIndex, 0.0f); + // set exposures to the user specified value while baking the cubemap + sceneSrg->SetConstant(m_globalIblExposureConstantIndex, m_bakeExposure); + sceneSrg->SetConstant(m_skyBoxExposureConstantIndex, m_bakeExposure); } } @@ -162,6 +164,7 @@ namespace AZ m_renderOuterSrg->SetConstant(m_reflectionRenderData->m_outerObbHalfLengthsRenderConstantIndex, m_outerObbWs.GetHalfLengths()); m_renderOuterSrg->SetConstant(m_reflectionRenderData->m_innerObbHalfLengthsRenderConstantIndex, m_innerObbWs.GetHalfLengths()); m_renderOuterSrg->SetConstant(m_reflectionRenderData->m_useParallaxCorrectionRenderConstantIndex, m_useParallaxCorrection); + m_renderOuterSrg->SetConstant(m_reflectionRenderData->m_exposureConstantIndex, m_renderExposure); m_renderOuterSrg->SetImage(m_reflectionRenderData->m_reflectionCubeMapRenderImageIndex, m_cubeMapImage); m_renderOuterSrg->Compile(); @@ -172,6 +175,7 @@ namespace AZ m_renderInnerSrg->SetConstant(m_reflectionRenderData->m_outerObbHalfLengthsRenderConstantIndex, m_outerObbWs.GetHalfLengths()); m_renderInnerSrg->SetConstant(m_reflectionRenderData->m_innerObbHalfLengthsRenderConstantIndex, m_innerObbWs.GetHalfLengths()); m_renderInnerSrg->SetConstant(m_reflectionRenderData->m_useParallaxCorrectionRenderConstantIndex, m_useParallaxCorrection); + m_renderInnerSrg->SetConstant(m_reflectionRenderData->m_exposureConstantIndex, m_renderExposure); m_renderInnerSrg->SetImage(m_reflectionRenderData->m_reflectionCubeMapRenderImageIndex, m_cubeMapImage); m_renderInnerSrg->Compile(); @@ -303,9 +307,10 @@ namespace AZ const RPI::Ptr& rootPass = environmentCubeMapPipeline->GetRootPass(); rootPass->AddChild(m_environmentCubeMapPass); - // store the current IBL exposure value + // store the current IBL exposure values Data::Instance sceneSrg = m_scene->GetShaderResourceGroup(); - m_previousExposure = sceneSrg->GetConstant(m_iblExposureConstantIndex); + m_previousGlobalIblExposure = sceneSrg->GetConstant(m_globalIblExposureConstantIndex); + m_previousSkyBoxExposure = sceneSrg->GetConstant(m_skyBoxExposureConstantIndex); m_scene->AddRenderPipeline(environmentCubeMapPipeline); } @@ -326,6 +331,17 @@ namespace AZ m_meshFeatureProcessor->SetVisible(m_visualizationMeshHandle, showVisualization); } + void ReflectionProbe::SetRenderExposure(float renderExposure) + { + m_renderExposure = renderExposure; + m_updateSrg = true; + } + + void ReflectionProbe::SetBakeExposure(float bakeExposure) + { + m_bakeExposure = bakeExposure; + } + const RHI::DrawPacket* ReflectionProbe::BuildDrawPacket( const Data::Instance& srg, const RPI::Ptr& pipelineState, diff --git a/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbe.h b/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbe.h index bee304c5b9..17ef54367b 100644 --- a/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbe.h +++ b/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbe.h @@ -61,6 +61,7 @@ namespace AZ RHI::ShaderInputNameIndex m_outerObbHalfLengthsRenderConstantIndex = "m_outerObbHalfLengths"; RHI::ShaderInputNameIndex m_innerObbHalfLengthsRenderConstantIndex = "m_innerObbHalfLengths"; RHI::ShaderInputNameIndex m_useParallaxCorrectionRenderConstantIndex = "m_useParallaxCorrection"; + RHI::ShaderInputNameIndex m_exposureConstantIndex = "m_exposure"; RHI::ShaderInputNameIndex m_reflectionCubeMapRenderImageIndex = "m_reflectionCubeMap"; }; @@ -106,6 +107,14 @@ namespace AZ // enables or disables rendering of the visualization sphere void ShowVisualization(bool showVisualization); + // the exposure to use when rendering meshes with this probe's cubemap + void SetRenderExposure(float renderExposure); + float GetRenderExposure() const { return m_renderExposure; } + + // the exposure to use when baking the probe cubemap + void SetBakeExposure(float bakeExposure); + float GetBakeExposure() const { return m_bakeExposure; } + private: AZ_DISABLE_COPY_MOVE(ReflectionProbe); @@ -157,6 +166,8 @@ namespace AZ RHI::ConstPtr m_blendWeightDrawPacket; RHI::ConstPtr m_renderOuterDrawPacket; RHI::ConstPtr m_renderInnerDrawPacket; + float m_renderExposure = 0.0f; + float m_bakeExposure = 0.0f; bool m_updateSrg = false; const RHI::DrawItemSortKey InvalidSortKey = static_cast(-1); @@ -169,8 +180,10 @@ namespace AZ RPI::Ptr m_environmentCubeMapPass = nullptr; RPI::RenderPipelineId m_environmentCubeMapPipelineId; BuildCubeMapCallback m_callback; - RHI::ShaderInputNameIndex m_iblExposureConstantIndex = "m_iblExposure"; - float m_previousExposure = 0.0f; + RHI::ShaderInputNameIndex m_globalIblExposureConstantIndex = "m_iblExposure"; + RHI::ShaderInputNameIndex m_skyBoxExposureConstantIndex = "m_cubemapExposure"; + float m_previousGlobalIblExposure = 0.0f; + float m_previousSkyBoxExposure = 0.0f; bool m_buildingCubeMap = false; }; diff --git a/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbeFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbeFeatureProcessor.cpp index 52d089ae0d..e9038858ad 100644 --- a/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbeFeatureProcessor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/ReflectionProbe/ReflectionProbeFeatureProcessor.cpp @@ -283,6 +283,18 @@ namespace AZ probe->ShowVisualization(showVisualization); } + void ReflectionProbeFeatureProcessor::SetRenderExposure(const ReflectionProbeHandle& probe, float renderExposure) + { + AZ_Assert(probe.get(), "SetRenderExposure called with an invalid handle"); + probe->SetRenderExposure(renderExposure); + } + + void ReflectionProbeFeatureProcessor::SetBakeExposure(const ReflectionProbeHandle& probe, float bakeExposure) + { + AZ_Assert(probe.get(), "SetBakeExposure called with an invalid handle"); + probe->SetBakeExposure(bakeExposure); + } + void ReflectionProbeFeatureProcessor::FindReflectionProbes(const Vector3& position, ReflectionProbeVector& reflectionProbes) { reflectionProbes.clear(); @@ -431,7 +443,12 @@ namespace AZ { // load shader shader = RPI::LoadCriticalShader(filePath); - AZ_Error("ReflectionProbeFeatureProcessor", shader, "Failed to find asset for shader [%s]", filePath); + + if (shader == nullptr) + { + AZ_Error("ReflectionProbeFeatureProcessor", false, "Failed to find asset for shader [%s]", filePath); + return; + } // store drawlist tag drawListTag = shader->GetDrawListTag(); diff --git a/Gems/Atom/RHI/Code/Include/Atom/RHI/SwapChain.h b/Gems/Atom/RHI/Code/Include/Atom/RHI/SwapChain.h index 97ac3baa90..abad1fb263 100644 --- a/Gems/Atom/RHI/Code/Include/Atom/RHI/SwapChain.h +++ b/Gems/Atom/RHI/Code/Include/Atom/RHI/SwapChain.h @@ -75,6 +75,9 @@ namespace AZ //! Return True if the swap chain prefers exclusive full screen mode and a transition happened, false otherwise. virtual bool SetExclusiveFullScreenState([[maybe_unused]]bool fullScreenState) { return false; } + //! Recreate the swapchain if it becomes invalid during presenting. This should happen at the end of the frame + //! due to images being used as attachments in the frame graph. + virtual void ProcessRecreation() {}; protected: SwapChain(); @@ -98,6 +101,14 @@ namespace AZ ////////////////////////////////////////////////////////////////////////// + //! Shutdown and clear all the images. + void ShutdownImages(); + + //! Initialized all the images. + ResultCode InitImages(); + + //! Flag indicating if swapchain recreation is needed at the end of the frame. + bool m_pendingRecreation = false; private: bool ValidateDescriptor(const SwapChainDescriptor& descriptor) const; diff --git a/Gems/Atom/RHI/Code/Source/RHI/FrameGraphAttachmentDatabase.cpp b/Gems/Atom/RHI/Code/Source/RHI/FrameGraphAttachmentDatabase.cpp index 6bac2b8c7d..388277ff59 100644 --- a/Gems/Atom/RHI/Code/Source/RHI/FrameGraphAttachmentDatabase.cpp +++ b/Gems/Atom/RHI/Code/Source/RHI/FrameGraphAttachmentDatabase.cpp @@ -134,7 +134,6 @@ namespace AZ m_scopeAttachmentLookup.clear(); m_imageAttachments.clear(); m_bufferAttachments.clear(); - m_swapChainAttachments.clear(); m_importedImageAttachments.clear(); m_importedBufferAttachments.clear(); m_transientImageAttachments.clear(); @@ -153,6 +152,13 @@ namespace AZ delete attachment; } m_attachments.clear(); + + for (auto swapchainAttachment : m_swapChainAttachments) + { + swapchainAttachment->GetSwapChain()->ProcessRecreation(); + } + + m_swapChainAttachments.clear(); } ImageDescriptor FrameGraphAttachmentDatabase::GetImageDescriptor(const AttachmentId& attachmentId) const diff --git a/Gems/Atom/RHI/Code/Source/RHI/SwapChain.cpp b/Gems/Atom/RHI/Code/Source/RHI/SwapChain.cpp index ff1f0e69a6..074eedf1b6 100644 --- a/Gems/Atom/RHI/Code/Source/RHI/SwapChain.cpp +++ b/Gems/Atom/RHI/Code/Source/RHI/SwapChain.cpp @@ -58,43 +58,68 @@ namespace AZ // Overwrite descriptor dimensions with the native ones (the ones assigned by the platform) returned by InitInternal. m_descriptor.m_dimensions = nativeDimensions; - m_images.reserve(m_descriptor.m_dimensions.m_imageCount); + resultCode = InitImages(); + } - for (uint32_t imageIdx = 0; imageIdx < m_descriptor.m_dimensions.m_imageCount; ++imageIdx) - { - m_images.emplace_back(RHI::Factory::Get().CreateImage()); - } + return resultCode; + } - InitImageRequest request; + void SwapChain::ShutdownImages() + { + // Shutdown existing set of images. + uint32_t imageSize = aznumeric_cast(m_images.size()); + for (uint32_t imageIdx = 0; imageIdx < imageSize; ++imageIdx) + { + m_images[imageIdx]->Shutdown(); + } - RHI::ImageDescriptor& imageDescriptor = request.m_descriptor; - imageDescriptor.m_dimension = RHI::ImageDimension::Image2D; - imageDescriptor.m_bindFlags = RHI::ImageBindFlags::Color; - imageDescriptor.m_size.m_width = m_descriptor.m_dimensions.m_imageWidth; - imageDescriptor.m_size.m_height = m_descriptor.m_dimensions.m_imageHeight; - imageDescriptor.m_format = m_descriptor.m_dimensions.m_imageFormat; + m_images.clear(); + } - for (uint32_t imageIdx = 0; imageIdx < m_descriptor.m_dimensions.m_imageCount; ++imageIdx) - { - request.m_image = m_images[imageIdx].get(); - request.m_imageIndex = imageIdx; + ResultCode SwapChain::InitImages() + { + ResultCode resultCode = ResultCode::Success; + + m_images.reserve(m_descriptor.m_dimensions.m_imageCount); + + // If the new display mode has more buffers, add them. + for (uint32_t i = 0; i < m_descriptor.m_dimensions.m_imageCount; ++i) + { + m_images.emplace_back(RHI::Factory::Get().CreateImage()); + } + + InitImageRequest request; + + RHI::ImageDescriptor& imageDescriptor = request.m_descriptor; + imageDescriptor.m_dimension = RHI::ImageDimension::Image2D; + imageDescriptor.m_bindFlags = RHI::ImageBindFlags::Color; + imageDescriptor.m_size.m_width = m_descriptor.m_dimensions.m_imageWidth; + imageDescriptor.m_size.m_height = m_descriptor.m_dimensions.m_imageHeight; + imageDescriptor.m_format = m_descriptor.m_dimensions.m_imageFormat; - resultCode = ImagePoolBase::InitImage( - request.m_image, - imageDescriptor, - [this, &request]() + for (uint32_t imageIdx = 0; imageIdx < m_descriptor.m_dimensions.m_imageCount; ++imageIdx) + { + request.m_image = m_images[imageIdx].get(); + request.m_imageIndex = imageIdx; + + resultCode = ImagePoolBase::InitImage( + request.m_image, imageDescriptor, + [this, &request]() { return InitImageInternal(request); }); - if (resultCode != ResultCode::Success) - { - Shutdown(); - break; - } + if (resultCode != ResultCode::Success) + { + AZ_Error("Swapchain", false, "Failed to initialize images."); + Shutdown(); + break; } } + // Reset the current index back to 0 so we match the platform swap chain. + m_currentImageIndex = 0; + return resultCode; } @@ -105,63 +130,15 @@ namespace AZ } ResultCode SwapChain::Resize(const RHI::SwapChainDimensions& dimensions) - { - // Shutdown existing set of images. - for (uint32_t imageIdx = 0; imageIdx < GetImageCount(); ++imageIdx) - { - m_images[imageIdx]->Shutdown(); - } + { + ShutdownImages(); SwapChainDimensions nativeDimensions = dimensions; ResultCode resultCode = ResizeInternal(dimensions, &nativeDimensions); if (resultCode == ResultCode::Success) { m_descriptor.m_dimensions = nativeDimensions; - m_images.reserve(m_descriptor.m_dimensions.m_imageCount); - - // If the new display mode has more buffers, add them. - while (m_images.size() < static_cast(m_descriptor.m_dimensions.m_imageCount)) - { - m_images.emplace_back(RHI::Factory::Get().CreateImage()); - } - - // If it has fewer, trim down. - while (m_images.size() > static_cast(m_descriptor.m_dimensions.m_imageCount)) - { - m_images.pop_back(); - } - - InitImageRequest request; - - RHI::ImageDescriptor& imageDescriptor = request.m_descriptor; - imageDescriptor.m_dimension = RHI::ImageDimension::Image2D; - imageDescriptor.m_bindFlags = RHI::ImageBindFlags::Color; - imageDescriptor.m_size.m_width = m_descriptor.m_dimensions.m_imageWidth; - imageDescriptor.m_size.m_height = m_descriptor.m_dimensions.m_imageHeight; - imageDescriptor.m_format = m_descriptor.m_dimensions.m_imageFormat; - - for (uint32_t imageIdx = 0; imageIdx < GetImageCount(); ++imageIdx) - { - request.m_image = m_images[imageIdx].get(); - request.m_imageIndex = imageIdx; - - resultCode = ImagePoolBase::InitImage( - request.m_image, - imageDescriptor, - [this, &request]() - { - return InitImageInternal(request); - }); - - if (resultCode != ResultCode::Success) - { - Shutdown(); - break; - } - } - - // Reset the current index back to 0 so we match the platform swap chain. - m_currentImageIndex = 0; + resultCode = InitImages(); } return resultCode; @@ -188,7 +165,7 @@ namespace AZ uint32_t SwapChain::GetImageCount() const { - return static_cast(m_images.size()); + return aznumeric_cast(m_images.size()); } uint32_t SwapChain::GetCurrentImageIndex() const @@ -209,8 +186,18 @@ namespace AZ void SwapChain::Present() { AZ_TRACE_METHOD(); - m_currentImageIndex = PresentInternal(); - AZ_Assert(m_currentImageIndex < m_images.size(), "Invalid image index"); + // Due to swapchain recreation, the images are refreshed. + // There is no need to present swapchain for this frame. + const uint32_t imageCount = aznumeric_cast(m_images.size()); + if (imageCount == 0) + { + return; + } + else + { + m_currentImageIndex = PresentInternal(); + AZ_Assert(m_currentImageIndex < imageCount, "Invalid image index"); + } } } } diff --git a/Gems/Atom/RHI/Vulkan/Code/Source/RHI/SwapChain.cpp b/Gems/Atom/RHI/Vulkan/Code/Source/RHI/SwapChain.cpp index bef2b154e1..19c88ec34f 100644 --- a/Gems/Atom/RHI/Vulkan/Code/Source/RHI/SwapChain.cpp +++ b/Gems/Atom/RHI/Vulkan/Code/Source/RHI/SwapChain.cpp @@ -59,6 +59,19 @@ namespace AZ m_swapChainBarrier.m_isValid = true; } + void SwapChain::ProcessRecreation() + { + if (m_pendingRecreation) + { + ShutdownImages(); + InvalidateNativeSwapChain(); + CreateSwapchain(); + InitImages(); + + m_pendingRecreation = false; + } + } + void SwapChain::SetVerticalSyncIntervalInternal(uint32_t previousVsyncInterval) { if (GetDescriptor().m_verticalSyncInterval == 0 || previousVsyncInterval == 0) @@ -231,8 +244,7 @@ namespace AZ // VK_SUBOPTIMAL_KHR is treated as success, but we better update the surface info as well. if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) { - InvalidateNativeSwapChain(); - CreateSwapchain(); + m_pendingRecreation = true; } else { @@ -246,18 +258,16 @@ namespace AZ } }; - m_presentationQueue->QueueCommand(AZStd::move(presentCommand)); - uint32_t acquiredImageIndex = GetCurrentImageIndex(); RHI::ResultCode result = AcquireNewImage(&acquiredImageIndex); if (result == RHI::ResultCode::Fail) { - InvalidateNativeSwapChain(); - CreateSwapchain(); + m_pendingRecreation = true; return 0; } else { + m_presentationQueue->QueueCommand(AZStd::move(presentCommand)); return acquiredImageIndex; } } diff --git a/Gems/Atom/RHI/Vulkan/Code/Source/RHI/SwapChain.h b/Gems/Atom/RHI/Vulkan/Code/Source/RHI/SwapChain.h index ee2ff3c207..68abc97b2d 100644 --- a/Gems/Atom/RHI/Vulkan/Code/Source/RHI/SwapChain.h +++ b/Gems/Atom/RHI/Vulkan/Code/Source/RHI/SwapChain.h @@ -51,6 +51,7 @@ namespace AZ void QueueBarrier(const VkPipelineStageFlags src, const VkPipelineStageFlags dst, const VkImageMemoryBarrier& imageBarrier); + void ProcessRecreation() override; private: SwapChain() = default; diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialSourceData.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialSourceData.h index a67477f061..53d3072370 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialSourceData.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialSourceData.h @@ -31,6 +31,7 @@ namespace AZ static constexpr const char UvGroupName[] = "uvSets"; class MaterialAsset; + class MaterialAssetCreator; //! This is a simple data structure for serializing in/out material source files. class MaterialSourceData final @@ -78,15 +79,33 @@ namespace AZ //! Creates a MaterialAsset from the MaterialSourceData content. //! @param assetId ID for the MaterialAsset - //! @param materialSourceFilePath Indicates the path of the .material file that the MaterialSourceData represents. Used for resolving file-relative paths. + //! @param materialSourceFilePath Indicates the path of the .material file that the MaterialSourceData represents. Used for + //! resolving file-relative paths. //! @param elevateWarnings Indicates whether to treat warnings as errors //! @param includeMaterialPropertyNames Indicates whether to save material property names into the material asset file Outcome> CreateMaterialAsset( Data::AssetId assetId, AZStd::string_view materialSourceFilePath = "", bool elevateWarnings = true, - bool includeMaterialPropertyNames = true - ) const; + bool includeMaterialPropertyNames = true) const; + + //! Creates a MaterialAsset from the MaterialSourceData content. + //! @param assetId ID for the MaterialAsset + //! @param materialSourceFilePath Indicates the path of the .material file that the MaterialSourceData represents. Used for + //! resolving file-relative paths. + //! @param elevateWarnings Indicates whether to treat warnings as errors + //! @param includeMaterialPropertyNames Indicates whether to save material property names into the material asset file + //! @param sourceDependencies if not null, will be populated with a set of all of the loaded material and material type paths + Outcome> CreateMaterialAssetFromSourceData( + Data::AssetId assetId, + AZStd::string_view materialSourceFilePath = "", + bool elevateWarnings = true, + bool includeMaterialPropertyNames = true, + AZStd::unordered_set* sourceDependencies = nullptr) const; + + private: + void ApplyPropertiesToAssetCreator( + AZ::RPI::MaterialAssetCreator& materialAssetCreator, const AZStd::string_view& materialSourceFilePath) const; }; } // namespace RPI } // namespace AZ diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/PassFilter.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/PassFilter.h index c42991725e..b93458113b 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/PassFilter.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/PassFilter.h @@ -56,8 +56,8 @@ namespace AZ OwnerRenderPipeline = AZ_BIT(5) }; - void SetOwenrScene(const Scene* scene); - void SetOwenrRenderPipeline(const RenderPipeline* renderPipeline); + void SetOwnerScene(const Scene* scene); + void SetOwnerRenderPipeline(const RenderPipeline* renderPipeline); void SetPassName(Name passName); void SetTemplateName(Name passTemplateName); void SetPassClass(TypeId passClassTypeId); diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/RPISystem.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/RPISystem.h index 92370c5a82..9138c0b418 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/RPISystem.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/RPISystem.h @@ -97,8 +97,7 @@ namespace AZ // SystemTickBus::OnTick void OnSystemTick() override; - // Fill system time and game time information for simulation or rendering - void FillTickTimeInfo(); + float GetCurrentTime(); // The set of core asset handlers registered by the system. AZStd::vector> m_assetHandlers; @@ -124,7 +123,8 @@ namespace AZ // The job policy used for feature processor's rendering prepare RHI::JobPolicy m_prepareRenderJobPolicy = RHI::JobPolicy::Parallel; - TickTimeInfo m_tickTime; + ScriptTimePoint m_startTime; + float m_currentSimulationTime = 0.0f; RPISystemDescriptor m_descriptor; diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/RenderPipeline.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/RenderPipeline.h index fcf812cc38..6cba0c774f 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/RenderPipeline.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/RenderPipeline.h @@ -32,7 +32,6 @@ namespace AZ namespace RPI { class Scene; - struct TickTimeInfo; class ShaderResourceGroup; class AnyAsset; class WindowContext; @@ -203,7 +202,7 @@ namespace AZ void OnRemovedFromScene(Scene* scene); // Called when this pipeline is about to be rendered - void OnStartFrame(const TickTimeInfo& tick); + void OnStartFrame(float time); // Called when the rendering of current frame is finished. void OnFrameEnd(); diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Scene.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Scene.h index f86383101a..1411a8d996 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Scene.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Scene.h @@ -48,14 +48,6 @@ namespace AZ // Callback function to modify values of a ShaderResourceGroup using ShaderResourceGroupCallback = AZStd::function; - //! A structure for ticks which contains system time and game time. - struct TickTimeInfo - { - float m_currentGameTime; - float m_gameDeltaTime = 0; - }; - - class Scene final : public SceneRequestBus::Handler { @@ -179,12 +171,14 @@ namespace AZ // Cpu simulation which runs all active FeatureProcessor Simulate() functions. // @param jobPolicy if it's JobPolicy::Parallel, the function will spawn a job thread for each FeatureProcessor's simulation. - void Simulate(const TickTimeInfo& tickInfo, RHI::JobPolicy jobPolicy); + // @param simulationTime the number of seconds since the application started + void Simulate(RHI::JobPolicy jobPolicy, float simulationTime); // Collect DrawPackets from FeatureProcessors // @param jobPolicy if it's JobPolicy::Parallel, the function will spawn a job thread for each FeatureProcessor's // PrepareRender. - void PrepareRender(const TickTimeInfo& tickInfo, RHI::JobPolicy jobPolicy); + // @param simulationTime the number of seconds since the application started; this is the same time value that was passed to Simulate() + void PrepareRender(RHI::JobPolicy jobPolicy, float simulationTime); // Function called when the current frame is finished rendering. void OnFrameEnd(); @@ -267,6 +261,7 @@ namespace AZ // Registry which allocates draw filter tag for RenderPipeline RHI::Ptr m_drawFilterTagRegistry; + RHI::ShaderInputConstantIndex m_timeInputIndex; float m_simulationTime; }; diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/AssetCreator.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/AssetCreator.h index abdbe9cdce..79d43d0b8d 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/AssetCreator.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/AssetCreator.h @@ -118,7 +118,7 @@ namespace AZ ResetIssueCounts(); // Because the asset creator can be used multiple times - m_asset = Data::AssetManager::Instance().CreateAsset(assetId, AZ::Data::AssetLoadBehavior::PreLoad); + m_asset = Data::Asset(assetId, aznew AssetDataT, AZ::Data::AssetLoadBehavior::PreLoad); m_beginCalled = true; if (!m_asset) @@ -138,6 +138,7 @@ namespace AZ } else { + Data::AssetManager::Instance().AssignAssetData(m_asset); result = AZStd::move(m_asset); success = true; } diff --git a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/LuaMaterialFunctorSourceData.cpp b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/LuaMaterialFunctorSourceData.cpp index b230953f8c..14ef3bb17d 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/LuaMaterialFunctorSourceData.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/LuaMaterialFunctorSourceData.cpp @@ -137,7 +137,10 @@ namespace AZ } else if (!m_luaSourceFile.empty()) { - auto loadOutcome = RPI::AssetUtils::LoadAsset(materialTypeSourceFilePath, m_luaSourceFile); + // The sub ID for script assets must be explicit. + // LUA source files output a compiled as well as an uncompiled asset, sub Ids of 1 and 2. + auto loadOutcome = + RPI::AssetUtils::LoadAsset(materialTypeSourceFilePath, m_luaSourceFile, ScriptAsset::CompiledAssetSubId); if (!loadOutcome) { AZ_Error("LuaMaterialFunctorSourceData", false, "Could not load script file '%s'", m_luaSourceFile.c_str()); diff --git a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialSourceData.cpp b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialSourceData.cpp index 1467b017d5..d6308ec655 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialSourceData.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialSourceData.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -126,7 +127,8 @@ namespace AZ return changesWereApplied ? ApplyVersionUpdatesResult::UpdatesApplied : ApplyVersionUpdatesResult::NoUpdates; } - Outcome > MaterialSourceData::CreateMaterialAsset(Data::AssetId assetId, AZStd::string_view materialSourceFilePath, bool elevateWarnings, bool includeMaterialPropertyNames) const + Outcome> MaterialSourceData::CreateMaterialAsset( + Data::AssetId assetId, AZStd::string_view materialSourceFilePath, bool elevateWarnings, bool includeMaterialPropertyNames) const { MaterialAssetCreator materialAssetCreator; materialAssetCreator.SetElevateWarnings(elevateWarnings); @@ -172,6 +174,128 @@ namespace AZ materialAssetCreator.Begin(assetId, *parentMaterialAsset.GetValue().Get(), includeMaterialPropertyNames); } + ApplyPropertiesToAssetCreator(materialAssetCreator, materialSourceFilePath); + + Data::Asset material; + if (materialAssetCreator.End(material)) + { + return Success(material); + } + else + { + return Failure(); + } + } + + Outcome> MaterialSourceData::CreateMaterialAssetFromSourceData( + Data::AssetId assetId, + AZStd::string_view materialSourceFilePath, + bool elevateWarnings, + bool includeMaterialPropertyNames, + AZStd::unordered_set* sourceDependencies) const + { + const auto materialTypeSourcePath = AssetUtils::ResolvePathReference(materialSourceFilePath, m_materialType); + const auto materialTypeAssetId = AssetUtils::MakeAssetId(materialTypeSourcePath, 0); + if (!materialTypeAssetId.IsSuccess()) + { + AZ_Error("MaterialSourceData", false, "Failed to create material type asset ID: '%s'.", materialTypeSourcePath.c_str()); + return Failure(); + } + + MaterialTypeSourceData materialTypeSourceData; + if (!AZ::RPI::JsonUtils::LoadObjectFromFile(materialTypeSourcePath, materialTypeSourceData)) + { + AZ_Error("MaterialSourceData", false, "Failed to load MaterialTypeSourceData: '%s'.", materialTypeSourcePath.c_str()); + return Failure(); + } + + materialTypeSourceData.ResolveUvEnums(); + + const auto materialTypeAsset = + materialTypeSourceData.CreateMaterialTypeAsset(materialTypeAssetId.GetValue(), materialTypeSourcePath, elevateWarnings); + if (!materialTypeAsset.IsSuccess()) + { + AZ_Error("MaterialSourceData", false, "Failed to create material type asset from source data: '%s'.", materialTypeSourcePath.c_str()); + return Failure(); + } + + // Track all of the material and material type assets loaded while trying to create a material asset from source data. This will + // be used for evaluating circular dependencies and returned for external monitoring or other use. + AZStd::unordered_set dependencies; + dependencies.insert(materialSourceFilePath); + dependencies.insert(materialTypeSourcePath); + + // Load and build a stack of MaterialSourceData from all of the parent materials in the hierarchy. Properties from the source + // data will be applied in reverse to the asset creator. + AZStd::vector parentSourceDataStack; + + AZStd::string parentSourceRelPath = m_parentMaterial; + AZStd::string parentSourceAbsPath = AssetUtils::ResolvePathReference(materialSourceFilePath, parentSourceRelPath); + while (!parentSourceRelPath.empty()) + { + if (!dependencies.insert(parentSourceAbsPath).second) + { + AZ_Error("MaterialSourceData", false, "Detected circular dependency between materials: '%s' and '%s'.", materialSourceFilePath.data(), parentSourceAbsPath.c_str()); + return Failure(); + } + + MaterialSourceData parentSourceData; + if (!AZ::RPI::JsonUtils::LoadObjectFromFile(parentSourceAbsPath, parentSourceData)) + { + AZ_Error("MaterialSourceData", false, "Failed to load MaterialSourceData for parent material: '%s'.", parentSourceAbsPath.c_str()); + return Failure(); + } + + // Make sure that all materials in the hierarchy share the same material type + const auto parentTypeAssetId = AssetUtils::MakeAssetId(parentSourceAbsPath, parentSourceData.m_materialType, 0); + if (!parentTypeAssetId) + { + AZ_Error("MaterialSourceData", false, "Parent material asset ID wasn't found: '%s'.", parentSourceAbsPath.c_str()); + return Failure(); + } + + if (parentTypeAssetId.GetValue() != materialTypeAssetId.GetValue()) + { + AZ_Error("MaterialSourceData", false, "This material and its parent material do not share the same material type."); + return Failure(); + } + + // Get the location of the next parent material and push the source data onto the stack + parentSourceRelPath = parentSourceData.m_parentMaterial; + parentSourceAbsPath = AssetUtils::ResolvePathReference(parentSourceAbsPath, parentSourceRelPath); + parentSourceDataStack.emplace_back(AZStd::move(parentSourceData)); + } + + // Create the material asset from all the previously loaded source data + MaterialAssetCreator materialAssetCreator; + materialAssetCreator.SetElevateWarnings(elevateWarnings); + materialAssetCreator.Begin(assetId, *materialTypeAsset.GetValue().Get(), includeMaterialPropertyNames); + + while (!parentSourceDataStack.empty()) + { + parentSourceDataStack.back().ApplyPropertiesToAssetCreator(materialAssetCreator, materialSourceFilePath); + parentSourceDataStack.pop_back(); + } + + ApplyPropertiesToAssetCreator(materialAssetCreator, materialSourceFilePath); + + Data::Asset material; + if (materialAssetCreator.End(material)) + { + if (sourceDependencies) + { + sourceDependencies->insert(dependencies.begin(), dependencies.end()); + } + + return Success(material); + } + + return Failure(); + } + + void MaterialSourceData::ApplyPropertiesToAssetCreator( + AZ::RPI::MaterialAssetCreator& materialAssetCreator, const AZStd::string_view& materialSourceFilePath) const + { for (auto& group : m_properties) { for (auto& property : group.second) @@ -183,43 +307,49 @@ namespace AZ } else { - MaterialPropertyIndex propertyIndex = materialAssetCreator.m_materialPropertiesLayout->FindPropertyIndex(propertyId.GetFullName()); + MaterialPropertyIndex propertyIndex = + materialAssetCreator.m_materialPropertiesLayout->FindPropertyIndex(propertyId.GetFullName()); if (propertyIndex.IsValid()) { - const MaterialPropertyDescriptor* propertyDescriptor = materialAssetCreator.m_materialPropertiesLayout->GetPropertyDescriptor(propertyIndex); + const MaterialPropertyDescriptor* propertyDescriptor = + materialAssetCreator.m_materialPropertiesLayout->GetPropertyDescriptor(propertyIndex); switch (propertyDescriptor->GetDataType()) { case MaterialPropertyDataType::Image: - { - Outcome> imageAssetResult = MaterialUtils::GetImageAssetReference(materialSourceFilePath, property.second.m_value.GetValue()); - - if (imageAssetResult.IsSuccess()) - { - auto& imageAsset = imageAssetResult.GetValue(); - // Load referenced images when load material - imageAsset.SetAutoLoadBehavior(Data::AssetLoadBehavior::PreLoad); - materialAssetCreator.SetPropertyValue(propertyId.GetFullName(), imageAsset); - } - else { - materialAssetCreator.ReportError("Material property '%s': Could not find the image '%s'", propertyId.GetFullName().GetCStr(), property.second.m_value.GetValue().data()); + Outcome> imageAssetResult = MaterialUtils::GetImageAssetReference( + materialSourceFilePath, property.second.m_value.GetValue()); + + if (imageAssetResult.IsSuccess()) + { + auto& imageAsset = imageAssetResult.GetValue(); + // Load referenced images when load material + imageAsset.SetAutoLoadBehavior(Data::AssetLoadBehavior::PreLoad); + materialAssetCreator.SetPropertyValue(propertyId.GetFullName(), imageAsset); + } + else + { + materialAssetCreator.ReportError( + "Material property '%s': Could not find the image '%s'", propertyId.GetFullName().GetCStr(), + property.second.m_value.GetValue().data()); + } } - } - break; + break; case MaterialPropertyDataType::Enum: - { - AZ::Name enumName = AZ::Name(property.second.m_value.GetValue()); - uint32_t enumValue = propertyDescriptor->GetEnumValue(enumName); - if (enumValue == MaterialPropertyDescriptor::InvalidEnumValue) { - materialAssetCreator.ReportError("Enum value '%s' couldn't be found in the 'enumValues' list", enumName.GetCStr()); + AZ::Name enumName = AZ::Name(property.second.m_value.GetValue()); + uint32_t enumValue = propertyDescriptor->GetEnumValue(enumName); + if (enumValue == MaterialPropertyDescriptor::InvalidEnumValue) + { + materialAssetCreator.ReportError( + "Enum value '%s' couldn't be found in the 'enumValues' list", enumName.GetCStr()); + } + else + { + materialAssetCreator.SetPropertyValue(propertyId.GetFullName(), enumValue); + } } - else - { - materialAssetCreator.SetPropertyValue(propertyId.GetFullName(), enumValue); - } - } - break; + break; default: materialAssetCreator.SetPropertyValue(propertyId.GetFullName(), property.second.m_value); break; @@ -227,21 +357,12 @@ namespace AZ } else { - materialAssetCreator.ReportWarning("Can not find property id '%s' in MaterialPropertyLayout", propertyId.GetFullName().GetStringView().data()); + materialAssetCreator.ReportWarning( + "Can not find property id '%s' in MaterialPropertyLayout", propertyId.GetFullName().GetStringView().data()); } } } } - - Data::Asset material; - if (materialAssetCreator.End(material)) - { - return Success(material); - } - else - { - return Failure(); - } } } // namespace RPI diff --git a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp index 08f57c7cd3..396ba71e14 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp @@ -393,11 +393,12 @@ namespace AZ for (const ShaderVariantReferenceData& shaderRef : m_shaderCollection) { const auto& shaderFile = shaderRef.m_shaderFilePath; - const auto& shaderAsset = AssetUtils::LoadAsset(materialTypeSourceFilePath, shaderFile, 0); + auto shaderAssetResult = AssetUtils::LoadAsset(materialTypeSourceFilePath, shaderFile, 0); - if (shaderAsset) + if (shaderAssetResult) { - auto optionsLayout = shaderAsset.GetValue()->GetShaderOptionGroupLayout(); + auto shaderAsset = shaderAssetResult.GetValue(); + auto optionsLayout = shaderAsset->GetShaderOptionGroupLayout(); ShaderOptionGroup options{ optionsLayout }; for (auto& iter : shaderRef.m_shaderOptionValues) { @@ -408,12 +409,11 @@ namespace AZ } materialTypeAssetCreator.AddShader( - shaderAsset.GetValue(), options.GetShaderVariantId(), - shaderRef.m_shaderTag.IsEmpty() ? Uuid::CreateRandom().ToString() : shaderRef.m_shaderTag - ); + shaderAsset, options.GetShaderVariantId(), + shaderRef.m_shaderTag.IsEmpty() ? Uuid::CreateRandom().ToString() : shaderRef.m_shaderTag); // Gather UV names - const ShaderInputContract& shaderInputContract = shaderAsset.GetValue()->GetInputContract(); + const ShaderInputContract& shaderInputContract = shaderAsset->GetInputContract(); for (const ShaderInputContract::StreamChannelInfo& channel : shaderInputContract.m_streamChannels) { const RHI::ShaderSemantic& semantic = channel.m_semantic; @@ -493,15 +493,19 @@ namespace AZ { case MaterialPropertyDataType::Image: { - Outcome> imageAssetResult = MaterialUtils::GetImageAssetReference(materialTypeSourceFilePath, property.m_value.GetValue()); + auto imageAssetResult = MaterialUtils::GetImageAssetReference( + materialTypeSourceFilePath, property.m_value.GetValue()); - if (imageAssetResult.IsSuccess()) + if (imageAssetResult) { - materialTypeAssetCreator.SetPropertyValue(propertyId.GetFullName(), imageAssetResult.GetValue()); + auto imageAsset = imageAssetResult.GetValue(); + materialTypeAssetCreator.SetPropertyValue(propertyId.GetFullName(), imageAsset); } else { - materialTypeAssetCreator.ReportError("Material property '%s': Could not find the image '%s'", propertyId.GetFullName().GetCStr(), property.m_value.GetValue().data()); + materialTypeAssetCreator.ReportError( + "Material property '%s': Could not find the image '%s'", propertyId.GetFullName().GetCStr(), + property.m_value.GetValue().data()); } } break; diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/FullscreenTrianglePass.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/FullscreenTrianglePass.cpp index 40dce7d138..828966f377 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/FullscreenTrianglePass.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/FullscreenTrianglePass.cpp @@ -136,6 +136,12 @@ namespace AZ RHI::DrawLinear draw = RHI::DrawLinear(); draw.m_vertexCount = 3; + if (m_shader == nullptr) + { + AZ_Error("PassSystem", false, "[FullscreenTrianglePass]: Shader not loaded!"); + return; + } + RHI::PipelineStateDescriptorForDraw pipelineStateDescriptor; // [GFX TODO][ATOM-872] The pass should be able to drive the shader variant diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/PassFilter.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/PassFilter.cpp index d9e458c615..d172abd81f 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/PassFilter.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/PassFilter.cpp @@ -90,13 +90,13 @@ namespace AZ return filter; } - void PassFilter::SetOwenrScene(const Scene* scene) + void PassFilter::SetOwnerScene(const Scene* scene) { m_ownerScene = scene; UpdateFilterOptions(); } - void PassFilter::SetOwenrRenderPipeline(const RenderPipeline* renderPipeline) + void PassFilter::SetOwnerRenderPipeline(const RenderPipeline* renderPipeline) { m_ownerRenderPipeline = renderPipeline; UpdateFilterOptions(); diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/RPISystem.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/RPISystem.cpp index 943966c13b..2b32503838 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/RPISystem.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/RPISystem.cpp @@ -268,21 +268,23 @@ namespace AZ AssetInitBus::Broadcast(&AssetInitBus::Events::PostLoadInit); - // Update tick time info - FillTickTimeInfo(); + m_currentSimulationTime = GetCurrentTime(); for (auto& scene : m_scenes) { - scene->Simulate(m_tickTime, m_simulationJobPolicy); + scene->Simulate(m_simulationJobPolicy, m_currentSimulationTime); } } - void RPISystem::FillTickTimeInfo() + float RPISystem::GetCurrentTime() { - AZ::TickRequestBus::BroadcastResult(m_tickTime.m_gameDeltaTime, &AZ::TickRequestBus::Events::GetTickDeltaTime); - ScriptTimePoint currentTime; - AZ::TickRequestBus::BroadcastResult(currentTime, &AZ::TickRequestBus::Events::GetTimeAtCurrentTick); - m_tickTime.m_currentGameTime = static_cast(currentTime.GetSeconds()); + ScriptTimePoint timeAtCurrentTick; + AZ::TickRequestBus::BroadcastResult(timeAtCurrentTick, &AZ::TickRequestBus::Events::GetTimeAtCurrentTick); + + // We subtract the start time to maximize precision of the time value, since we will be converting it to a float. + double currentTime = timeAtCurrentTick.GetSeconds() - m_startTime.GetSeconds(); + + return aznumeric_cast(currentTime); } void RPISystem::RenderTick() @@ -301,7 +303,7 @@ namespace AZ // [GFX TODO] We may parallel scenes' prepare render. for (auto& scenePtr : m_scenes) { - scenePtr->PrepareRender(m_tickTime, m_prepareRenderJobPolicy); + scenePtr->PrepareRender(m_prepareRenderJobPolicy, m_currentSimulationTime); } m_rhiSystem.FrameUpdate( diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/RenderPipeline.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/RenderPipeline.cpp index 6378a249a4..8d4303f187 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/RenderPipeline.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/RenderPipeline.cpp @@ -375,7 +375,7 @@ namespace AZ m_scene->RemoveRenderPipeline(m_nameId); } - void RenderPipeline::OnStartFrame([[maybe_unused]] const TickTimeInfo& tick) + void RenderPipeline::OnStartFrame([[maybe_unused]] float time) { AZ_PROFILE_SCOPE(RPI, "RenderPipeline: OnStartFrame"); diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Scene.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Scene.cpp index 41fefda656..b27e3518e1 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Scene.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Scene.cpp @@ -44,6 +44,9 @@ namespace AZ { auto shaderAsset = RPISystemInterface::Get()->GetCommonShaderAssetForSrgs(); scene->m_srg = ShaderResourceGroup::Create(shaderAsset, sceneSrgLayout->GetName()); + + // Set value for constants defined in SceneTimeSrg.azsli + scene->m_timeInputIndex = scene->m_srg->FindShaderInputConstantIndex(Name{ "m_time" }); } scene->m_name = sceneDescriptor.m_nameId; @@ -410,11 +413,11 @@ namespace AZ //[GFX TODO]: the completion job should start here } - void Scene::Simulate([[maybe_unused]] const TickTimeInfo& tickInfo, RHI::JobPolicy jobPolicy) + void Scene::Simulate(RHI::JobPolicy jobPolicy, float simulationTime) { AZ_PROFILE_SCOPE(RPI, "Scene: Simulate"); - m_simulationTime = tickInfo.m_currentGameTime; + m_simulationTime = simulationTime; // If previous simulation job wasn't done, wait for it to finish. if (m_taskGraphActive) @@ -483,11 +486,9 @@ namespace AZ { if (m_srg) { - // Set value for constants defined in SceneTimeSrg.azsli - RHI::ShaderInputConstantIndex timeIndex = m_srg->FindShaderInputConstantIndex(Name{ "m_time" }); - if (timeIndex.IsValid()) + if (m_timeInputIndex.IsValid()) { - m_srg->SetConstant(timeIndex, m_simulationTime); + m_srg->SetConstant(m_timeInputIndex, m_simulationTime); } // signal any handlers to update values for their partial scene srg @@ -620,7 +621,7 @@ namespace AZ WaitAndCleanCompletionJob(finalizeDrawListsCompletion); } - void Scene::PrepareRender(const TickTimeInfo& tickInfo, RHI::JobPolicy jobPolicy) + void Scene::PrepareRender(RHI::JobPolicy jobPolicy, float simulationTime) { AZ_PROFILE_SCOPE(RPI, "Scene: PrepareRender"); @@ -644,7 +645,7 @@ namespace AZ if (pipeline->NeedsRender()) { activePipelines.push_back(pipeline); - pipeline->OnStartFrame(tickInfo); + pipeline->OnStartFrame(simulationTime); } } } diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialTypeAsset.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialTypeAsset.cpp index 76634201eb..48654d7769 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialTypeAsset.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialTypeAsset.cpp @@ -188,6 +188,10 @@ namespace AZ void MaterialTypeAsset::SetReady() { m_status = AssetStatus::Ready; + + // If this was created dynamically using MaterialTypeAssetCreator (which is what calls SetReady()), + // we need to connect to the AssetBus for reloads. + PostLoadInit(); } bool MaterialTypeAsset::PostLoadInit() diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/ShaderCollection.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/ShaderCollection.cpp index 87076891dd..16813cb6b7 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/ShaderCollection.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/ShaderCollection.cpp @@ -126,8 +126,8 @@ namespace AZ } ShaderCollection::Item::Item() + : m_renderStatesOverlay(RHI::GetInvalidRenderStates()) { - m_renderStatesOverlay = RHI::GetInvalidRenderStates(); } ShaderCollection::Item& ShaderCollection::operator[](size_t i) @@ -156,7 +156,8 @@ namespace AZ } ShaderCollection::Item::Item(const Data::Asset& shaderAsset, const AZ::Name& shaderTag, ShaderVariantId variantId) - : m_shaderAsset(shaderAsset) + : m_renderStatesOverlay(RHI::GetInvalidRenderStates()) + , m_shaderAsset(shaderAsset) , m_shaderVariantId(variantId) , m_shaderTag(shaderTag) , m_shaderOptionGroup(shaderAsset->GetShaderOptionGroupLayout(), variantId) @@ -164,7 +165,8 @@ namespace AZ } ShaderCollection::Item::Item(Data::Asset&& shaderAsset, const AZ::Name& shaderTag, ShaderVariantId variantId) - : m_shaderAsset(AZStd::move(shaderAsset)) + : m_renderStatesOverlay(RHI::GetInvalidRenderStates()) + , m_shaderAsset(AZStd::move(shaderAsset)) , m_shaderVariantId(variantId) , m_shaderTag(shaderTag) , m_shaderOptionGroup(shaderAsset->GetShaderOptionGroupLayout(), variantId) diff --git a/Gems/Atom/RPI/Code/Tests.Builders/BuilderTestFixture.h b/Gems/Atom/RPI/Code/Tests.Builders/BuilderTestFixture.h index 31c1bc6715..02e69e732c 100644 --- a/Gems/Atom/RPI/Code/Tests.Builders/BuilderTestFixture.h +++ b/Gems/Atom/RPI/Code/Tests.Builders/BuilderTestFixture.h @@ -46,7 +46,6 @@ namespace UnitTest bool DeleteEntity(const AZ::EntityId&) override { return false; } AZ::Entity* FindEntity(const AZ::EntityId&) override { return nullptr; } AZ::BehaviorContext* GetBehaviorContext() override { return nullptr; } - const char* GetAppRoot() const override { return nullptr; } const char* GetEngineRoot() const override { return nullptr; } const char* GetExecutableFolder() const override { return nullptr; } void EnumerateEntities(const EntityCallback& /*callback*/) override {} diff --git a/Gems/Atom/RPI/Code/Tests/Common/AssetManagerTestFixture.h b/Gems/Atom/RPI/Code/Tests/Common/AssetManagerTestFixture.h index ee3ad94d4f..fb88e62617 100644 --- a/Gems/Atom/RPI/Code/Tests/Common/AssetManagerTestFixture.h +++ b/Gems/Atom/RPI/Code/Tests/Common/AssetManagerTestFixture.h @@ -44,7 +44,6 @@ namespace UnitTest AZ::Entity* FindEntity(const AZ::EntityId&) override { return nullptr; } AZ::BehaviorContext* GetBehaviorContext() override { return nullptr; } AZ::JsonRegistrationContext* GetJsonRegistrationContext() override { return nullptr; } - const char* GetAppRoot() const override { return nullptr; } const char* GetEngineRoot() const override { return nullptr; } const char* GetExecutableFolder() const override { return nullptr; } void EnumerateEntities(const EntityCallback& /*callback*/) override {} diff --git a/Gems/Atom/TestData/TestData/Materials/SkinTestCases/002_wrinkle_regression_test.material b/Gems/Atom/TestData/TestData/Materials/SkinTestCases/002_wrinkle_regression_test.material index 400044d29f..78f597b14f 100644 --- a/Gems/Atom/TestData/TestData/Materials/SkinTestCases/002_wrinkle_regression_test.material +++ b/Gems/Atom/TestData/TestData/Materials/SkinTestCases/002_wrinkle_regression_test.material @@ -33,7 +33,7 @@ }, "subsurfaceScattering": { "enableSubsurfaceScattering": true, - "influenceMap": "Objects/Lucy/Lucy_thickness.tif", + "influenceMap": "TestData/Textures/checker8x8_gray_512.png", "scatterDistance": 15.0, "subsurfaceScatterFactor": 0.4300000071525574, "thicknessMap": "Objects/Lucy/Lucy_thickness.tif", @@ -47,8 +47,7 @@ 0.3182879388332367, 0.16388189792633058, 1.0 - ], - "useInfluenceMap": false + ] }, "wrinkleLayers": { "baseColorMap1": "TestData/Textures/cc0/Lava004_1K_Color.jpg", @@ -61,4 +60,4 @@ "normalMap2": "TestData/Textures/TextureHaven/4k_castle_brick_02_red/4k_castle_brick_02_red_normal.png" } } -} +} \ No newline at end of file diff --git a/Gems/Atom/TestData/TestData/Materials/StandardPbrTestCases/015_SubsurfaceScattering_Transmission_Thin.material b/Gems/Atom/TestData/TestData/Materials/StandardPbrTestCases/015_SubsurfaceScattering_Transmission_Thin.material new file mode 100644 index 0000000000..ee9bb1f4a5 --- /dev/null +++ b/Gems/Atom/TestData/TestData/Materials/StandardPbrTestCases/015_SubsurfaceScattering_Transmission_Thin.material @@ -0,0 +1,29 @@ +{ + "description": "", + "parentMaterial": "", + "materialType": "Materials/Types/EnhancedPBR.materialtype", + "materialTypeVersion": 4, + "properties": { + "baseColor": { + "color": [ + 0.027664607390761375, + 0.1926604062318802, + 0.013916227966547012, + 1.0 + ] + }, + "general": { + "doubleSided": true + }, + "subsurfaceScattering": { + "thickness": 0.20000000298023224, + "transmissionMode": "ThinObject", + "transmissionTint": [ + 0.009140154346823692, + 0.19806210696697235, + 0.01095597818493843, + 1.0 + ] + } + } +} \ No newline at end of file diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Document/AtomToolsDocumentSystemComponent.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Document/AtomToolsDocumentSystemComponent.cpp index 5652e9fe23..00de2a7c4c 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Document/AtomToolsDocumentSystemComponent.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Document/AtomToolsDocumentSystemComponent.cpp @@ -159,7 +159,7 @@ namespace AtomToolsFramework void AtomToolsDocumentSystemComponent::OnDocumentExternallyModified(const AZ::Uuid& documentId) { - m_documentIdsToReopen.insert(documentId); + m_documentIdsWithExternalChanges.insert(documentId); if (!AZ::TickBus::Handler::BusIsConnected()) { AZ::TickBus::Handler::BusConnect(); @@ -168,7 +168,7 @@ namespace AtomToolsFramework void AtomToolsDocumentSystemComponent::OnDocumentDependencyModified(const AZ::Uuid& documentId) { - m_documentIdsToReopen.insert(documentId); + m_documentIdsWithDependencyChanges.insert(documentId); if (!AZ::TickBus::Handler::BusIsConnected()) { AZ::TickBus::Handler::BusConnect(); @@ -177,7 +177,7 @@ namespace AtomToolsFramework void AtomToolsDocumentSystemComponent::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time) { - for (const AZ::Uuid& documentId : m_documentIdsToReopen) + for (const AZ::Uuid& documentId : m_documentIdsWithExternalChanges) { AZStd::string documentPath; AtomToolsDocumentRequestBus::EventResult(documentPath, documentId, &AtomToolsDocumentRequestBus::Events::GetAbsolutePath); @@ -191,6 +191,8 @@ namespace AtomToolsFramework continue; } + m_documentIdsWithDependencyChanges.erase(documentId); + AtomToolsFramework::TraceRecorder traceRecorder(m_maxMessageBoxLineCount); bool openResult = false; @@ -204,7 +206,7 @@ namespace AtomToolsFramework } } - for (const AZ::Uuid& documentId : m_documentIdsToReopen) + for (const AZ::Uuid& documentId : m_documentIdsWithDependencyChanges) { AZStd::string documentPath; AtomToolsDocumentRequestBus::EventResult(documentPath, documentId, &AtomToolsDocumentRequestBus::Events::GetAbsolutePath); @@ -231,8 +233,8 @@ namespace AtomToolsFramework } } - m_documentIdsToReopen.clear(); - m_documentIdsToReopen.clear(); + m_documentIdsWithDependencyChanges.clear(); + m_documentIdsWithExternalChanges.clear(); AZ::TickBus::Handler::BusDisconnect(); } diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Document/AtomToolsDocumentSystemComponent.h b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Document/AtomToolsDocumentSystemComponent.h index 9c556a07e7..a0f5eb085d 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Document/AtomToolsDocumentSystemComponent.h +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Document/AtomToolsDocumentSystemComponent.h @@ -85,8 +85,8 @@ namespace AtomToolsFramework AZStd::intrusive_ptr m_settings; AZStd::function m_documentCreator; AZStd::unordered_map> m_documentMap; - AZStd::unordered_set m_documentIdsToRebuild; - AZStd::unordered_set m_documentIdsToReopen; + AZStd::unordered_set m_documentIdsWithExternalChanges; + AZStd::unordered_set m_documentIdsWithDependencyChanges; const size_t m_maxMessageBoxLineCount = 15; }; } // namespace AtomToolsFramework diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Inspector/InspectorPropertyGroupWidget.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Inspector/InspectorPropertyGroupWidget.cpp index 6b9aee17f7..af71954737 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Inspector/InspectorPropertyGroupWidget.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Inspector/InspectorPropertyGroupWidget.cpp @@ -43,7 +43,7 @@ namespace AtomToolsFramework m_propertyEditor->Setup(context, instanceNotificationHandler, false); m_propertyEditor->AddInstance(instance, instanceClassId, nullptr, instanceToCompare); m_propertyEditor->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); - m_propertyEditor->InvalidateAll(); + m_propertyEditor->QueueInvalidation(AzToolsFramework::PropertyModificationRefreshLevel::Refresh_EntireTree); m_layout->addWidget(m_propertyEditor); setLayout(m_layout); diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Util/Util.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Util/Util.cpp index be112345a9..d3bce34af3 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Util/Util.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Util/Util.cpp @@ -133,29 +133,12 @@ namespace AtomToolsFramework bool LaunchTool(const QString& baseName, const QString& extension, const QStringList& arguments) { - const char* engineRoot = nullptr; - AzFramework::ApplicationRequests::Bus::BroadcastResult(engineRoot, &AzFramework::ApplicationRequests::GetEngineRoot); - AZ_Assert(engineRoot != nullptr, "AzFramework::ApplicationRequests::GetEngineRoot failed"); + AZ::IO::FixedMaxPath engineRoot = AZ::Utils::GetEnginePath(); + AZ_Assert(!engineRoot.empty(), "Cannot query Engine Path"); - char binFolderName[AZ_MAX_PATH_LEN] = {}; - AZ::Utils::GetExecutablePathReturnType ret = AZ::Utils::GetExecutablePath(binFolderName, AZ_MAX_PATH_LEN); + AZ::IO::FixedMaxPath launchPath = AZ::IO::FixedMaxPath(AZ::Utils::GetExecutableDirectory()) + / (baseName + extension).toUtf8().constData(); - // If it contains the filename, zero out the last path separator character... - if (ret.m_pathIncludesFilename) - { - char* lastSlash = strrchr(binFolderName, AZ_CORRECT_FILESYSTEM_SEPARATOR); - if (lastSlash) - { - *lastSlash = '\0'; - } - } - - const QString path = QString("%1%2%3%4") - .arg(binFolderName) - .arg(AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING) - .arg(baseName) - .arg(extension); - - return QProcess::startDetached(path, arguments, engineRoot); + return QProcess::startDetached(launchPath.c_str(), arguments, engineRoot.c_str()); } } diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp b/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp index 98af749261..ee58cbea3e 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp @@ -567,26 +567,26 @@ namespace MaterialEditor } } - void MaterialDocument::SourceFileChanged(AZStd::string relativePath, AZStd::string scanFolder, AZ::Uuid sourceUUID) + void MaterialDocument::SourceFileChanged(AZStd::string relativePath, AZStd::string scanFolder, [[maybe_unused]] AZ::Uuid sourceUUID) { - if (m_sourceAssetId.m_guid == sourceUUID) + auto sourcePath = AZ::RPI::AssetUtils::ResolvePathReference(scanFolder, relativePath); + + if (m_absolutePath == sourcePath) { // ignore notifications caused by saving the open document if (!m_saveTriggeredInternally) { AZ_TracePrintf("MaterialDocument", "Material document changed externally: '%s'.\n", m_absolutePath.c_str()); - AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentExternallyModified, m_id); + AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast( + &AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentExternallyModified, m_id); } m_saveTriggeredInternally = false; } - } - - void MaterialDocument::OnAssetReloaded(AZ::Data::Asset asset) - { - if (m_dependentAssetIds.find(asset->GetId()) != m_dependentAssetIds.end()) + else if (m_sourceDependencies.find(sourcePath) != m_sourceDependencies.end()) { AZ_TracePrintf("MaterialDocument", "Material document dependency changed: '%s'.\n", m_absolutePath.c_str()); - AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentDependencyModified, m_id); + AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast( + &AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentDependencyModified, m_id); } } @@ -655,7 +655,6 @@ namespace MaterialEditor return false; } - m_sourceAssetId = sourceAssetInfo.m_assetId; m_relativePath = sourceAssetInfo.m_relativePath; if (!AzFramework::StringFunc::Path::Normalize(m_relativePath)) { @@ -722,14 +721,15 @@ namespace MaterialEditor // we can create the asset dynamically from the source data. // Long term, the material document should not be concerned with assets at all. The viewport window should be the // only thing concerned with assets or instances. - auto createResult = m_materialSourceData.CreateMaterialAsset(Uuid::CreateRandom(), m_absolutePath, true); - if (!createResult) + auto materialAssetResult = + m_materialSourceData.CreateMaterialAssetFromSourceData(Uuid::CreateRandom(), m_absolutePath, true, true, &m_sourceDependencies); + if (!materialAssetResult) { AZ_Error("MaterialDocument", false, "Material asset could not be created from source data: '%s'.", m_absolutePath.c_str()); return false; } - m_materialAsset = createResult.GetValue(); + m_materialAsset = materialAssetResult.GetValue(); if (!m_materialAsset.IsReady()) { AZ_Error("MaterialDocument", false, "Material asset is not ready: '%s'.", m_absolutePath.c_str()); @@ -743,28 +743,35 @@ namespace MaterialEditor return false; } - // track material type asset to notify when dependencies change - m_dependentAssetIds.insert(materialTypeAsset->GetId()); - AZ::Data::AssetBus::MultiHandler::BusConnect(materialTypeAsset->GetId()); - AZStd::array_view parentPropertyValues = materialTypeAsset->GetDefaultPropertyValues(); AZ::Data::Asset parentMaterialAsset; if (!m_materialSourceData.m_parentMaterial.empty()) { - // There is a parent for this material - auto parentMaterialResult = AssetUtils::LoadAsset(m_absolutePath, m_materialSourceData.m_parentMaterial); - if (!parentMaterialResult) + AZ::RPI::MaterialSourceData parentMaterialSourceData; + const auto parentMaterialFilePath = AssetUtils::ResolvePathReference(m_absolutePath, m_materialSourceData.m_parentMaterial); + if (!AZ::RPI::JsonUtils::LoadObjectFromFile(parentMaterialFilePath, parentMaterialSourceData)) { - AZ_Error("MaterialDocument", false, "Parent material asset could not be loaded: '%s'.", m_materialSourceData.m_parentMaterial.c_str()); + AZ_Error("MaterialDocument", false, "Material parent source data could not be loaded for: '%s'.", parentMaterialFilePath.c_str()); return false; } - parentMaterialAsset = parentMaterialResult.GetValue(); - parentPropertyValues = parentMaterialAsset->GetPropertyValues(); + const auto parentMaterialAssetIdResult = AssetUtils::MakeAssetId(parentMaterialFilePath, 0); + if (!parentMaterialAssetIdResult) + { + AZ_Error("MaterialDocument", false, "Material parent asset ID could not be created: '%s'.", parentMaterialFilePath.c_str()); + return false; + } - // track parent material asset to notify when dependencies change - m_dependentAssetIds.insert(parentMaterialAsset->GetId()); - AZ::Data::AssetBus::MultiHandler::BusConnect(parentMaterialAsset->GetId()); + auto parentMaterialAssetResult = parentMaterialSourceData.CreateMaterialAssetFromSourceData( + parentMaterialAssetIdResult.GetValue(), parentMaterialFilePath, true, true); + if (!parentMaterialAssetResult) + { + AZ_Error("MaterialDocument", false, "Material parent asset could not be created from source data: '%s'.", parentMaterialFilePath.c_str()); + return false; + } + + parentMaterialAsset = parentMaterialAssetResult.GetValue(); + parentPropertyValues = parentMaterialAsset->GetPropertyValues(); } // Creating a material from a material asset will fail if a texture is referenced but not loaded @@ -913,15 +920,13 @@ namespace MaterialEditor void MaterialDocument::Clear() { AZ::TickBus::Handler::BusDisconnect(); - AZ::Data::AssetBus::MultiHandler::BusDisconnect(); AzToolsFramework::AssetSystemBus::Handler::BusDisconnect(); m_materialAsset = {}; m_materialInstance = {}; m_absolutePath.clear(); m_relativePath.clear(); - m_sourceAssetId = {}; - m_dependentAssetIds.clear(); + m_sourceDependencies.clear(); m_saveTriggeredInternally = {}; m_compilePending = {}; m_properties.clear(); diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.h b/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.h index 03997a2a91..452111f99a 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.h +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.h @@ -29,7 +29,6 @@ namespace MaterialEditor : public AtomToolsFramework::AtomToolsDocument , public MaterialDocumentRequestBus::Handler , private AZ::TickBus::Handler - , private AZ::Data::AssetBus::MultiHandler , private AzToolsFramework::AssetSystemBus::Handler { public: @@ -105,11 +104,6 @@ namespace MaterialEditor void SourceFileChanged(AZStd::string relativePath, AZStd::string scanFolder, AZ::Uuid sourceUUID) override; ////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////// - // AZ::Data::AssetBus::Router overrides... - void OnAssetReloaded(AZ::Data::Asset asset) override; - ////////////////////////////////////////////////////////////////////////// - bool SavePropertiesToSourceData(AZ::RPI::MaterialSourceData& sourceData, PropertyFilterFunction propertyFilter) const; bool OpenInternal(AZStd::string_view loadPath); @@ -137,11 +131,8 @@ namespace MaterialEditor // Material instance being edited AZ::Data::Instance m_materialInstance; - // Asset used to open document - AZ::Data::AssetId m_sourceAssetId; - // Set of assets that can trigger a document reload - AZStd::unordered_set m_dependentAssetIds; + AZStd::unordered_set m_sourceDependencies; // Track if document saved itself last to skip external modification notification bool m_saveTriggeredInternally = false; diff --git a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiPassTree.inl b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiPassTree.inl index 55e457926a..5a8aaf7ded 100644 --- a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiPassTree.inl +++ b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiPassTree.inl @@ -19,11 +19,11 @@ #include -#include #include #include #include +#include #ifndef SCRIPTABLE_IMGUI #define Scriptable_ImGui ImGui @@ -334,11 +334,10 @@ namespace AZ::Render if (m_engineRoot.empty()) { - const char* engineRoot = nullptr; - AzFramework::ApplicationRequests::Bus::BroadcastResult(engineRoot, &AzFramework::ApplicationRequests::GetEngineRoot); - if (engineRoot) + AZ::IO::FixedMaxPathString engineRoot = AZ::Utils::GetEnginePath(); + if (!engineRoot.empty()) { - m_engineRoot = AZStd::string(engineRoot); + m_engineRoot = AZStd::string_view(engineRoot); } } diff --git a/Gems/AtomContent/ReferenceMaterials/Tools/Launch_Cmd.bat b/Gems/AtomContent/ReferenceMaterials/Tools/Launch_Cmd.bat index d100c9ddc7..0b94be5bea 100644 --- a/Gems/AtomContent/ReferenceMaterials/Tools/Launch_Cmd.bat +++ b/Gems/AtomContent/ReferenceMaterials/Tools/Launch_Cmd.bat @@ -1,4 +1,6 @@ @echo off +:: Keep changes local +SETLOCAL enableDelayedExpansion REM REM Copyright (c) Contributors to the Open 3D Engine Project @@ -13,7 +15,7 @@ REM :: Puts you in the CMD within the dev environment :: Set up window -TITLE O3DE Asset Gem Cmd +TITLE O3DE DCC Scripting Interface Cmd :: Use obvious color to prevent confusion (Grey with Yellow Text) COLOR 8E @@ -21,15 +23,12 @@ COLOR 8E cd %~dp0 PUSHD %~dp0 -:: Keep changes local -SETLOCAL enableDelayedExpansion - CALL %~dp0\Project_Env.bat echo. echo _____________________________________________________________________ echo. -echo ~ O3DE Asset Gem CMD ... +echo ~ O3DE %O3DE_PROJECT% Asset Gem CMD ... echo _____________________________________________________________________ echo. diff --git a/Gems/AtomContent/ReferenceMaterials/Tools/Launch_Maya.bat b/Gems/AtomContent/ReferenceMaterials/Tools/Launch_Maya.bat index b9a6b399f3..0af25905a0 100644 --- a/Gems/AtomContent/ReferenceMaterials/Tools/Launch_Maya.bat +++ b/Gems/AtomContent/ReferenceMaterials/Tools/Launch_Maya.bat @@ -1,6 +1,3 @@ -:: Launches maya wityh a bunch of local hooks for Lumberyard -:: ToDo: move all of this to a .json data driven boostrapping system - @echo off REM @@ -37,7 +34,7 @@ echo DCCSI_MAYA_VERSION = %DCCSI_MAYA_VERSION% IF EXIST "%~dp0Project_Env.bat" CALL %~dp0Project_Env.bat echo ________________________________ -echo Launching Maya %DCCSI_MAYA_VERSION% for Lumberyard... +echo Launching Maya %DCCSI_MAYA_VERSION% for O3DE: %O3DE_PROJECT%... :::: Set Maya native project acess to this project ::set MAYA_PROJECT=%LY_PROJECT% @@ -47,8 +44,10 @@ echo Launching Maya %DCCSI_MAYA_VERSION% for Lumberyard... Set MAYA_VP2_DEVICE_OVERRIDE = VirtualDeviceDx11 :: Default to the right version of Maya if we can detect it... and launch -IF EXIST "%MAYA_LOCATION%\bin\Maya.exe" ( - start "" "%MAYA_LOCATION%\bin\Maya.exe" %* +echo MAYA_BIN_PATH = %MAYA_BIN_PATH% + +IF EXIST "%MAYA_BIN_PATH%\Maya.exe" ( + start "" "%MAYA_BIN_PATH%\Maya.exe" %* ) ELSE ( Where maya.exe 2> NUL IF ERRORLEVEL 1 ( diff --git a/Gems/AtomContent/ReferenceMaterials/Tools/Project_Env.bat b/Gems/AtomContent/ReferenceMaterials/Tools/Project_Env.bat index 6e2c8b5914..ddf934d206 100644 --- a/Gems/AtomContent/ReferenceMaterials/Tools/Project_Env.bat +++ b/Gems/AtomContent/ReferenceMaterials/Tools/Project_Env.bat @@ -29,23 +29,23 @@ PUSHD %~dp0 set ABS_PATH=%~dp0 :: project name as a str tag -IF "%LY_PROJECT_NAME%"=="" ( - for %%I in ("%~dp0.") do for %%J in ("%%~dpI.") do set LY_PROJECT_NAME=%%~nxJ +IF "%O3DE_PROJECT%"=="" ( + for %%I in ("%~dp0.") do for %%J in ("%%~dpI.") do set O3DE_PROJECT=%%~nxJ ) echo. echo _____________________________________________________________________ echo. -echo ~ Setting up O3DE %LY_PROJECT_NAME% Environment ... +echo ~ Setting up O3DE %O3DE_PROJECT% Environment ... echo _____________________________________________________________________ echo. -echo LY_PROJECT_NAME = %LY_PROJECT_NAME% +echo O3DE_PROJECT = %O3DE_PROJECT% :: if the user has set up a custom env call it :: this should allow the user to locally -:: set env hooks like LY_DEV or LY_PROJECT +:: set env hooks like O3DE_DEV or O3DE_PROJECT_PATH IF EXIST "%~dp0User_Env.bat" CALL %~dp0User_Env.bat -echo LY_DEV = %LY_DEV% +echo O3DE_DEV = %O3DE_DEV% :: Constant Vars (Global) :: global debug flag (propogates) @@ -74,23 +74,20 @@ echo DCCSI_LOGLEVEL = %DCCSI_LOGLEVEL% IF "%DCCSI_MAYA_VERSION%"=="" (set DCCSI_MAYA_VERSION=2020) echo DCCSI_MAYA_VERSION = %DCCSI_MAYA_VERSION% -:: LY_PROJECT is ideally treated as a full path in the env launchers +:: O3DE_PROJECT_PATH is ideally treated as a full path in the env launchers :: do to changes in o3de, external engine/project/gem folder structures, etc. -IF "%LY_PROJECT%"=="" ( - for %%i in ("%~dp0..") do set "LY_PROJECT=%%~fi" +IF "%O3DE_PROJECT_PATH%"=="" ( + for %%i in ("%~dp0..") do set "O3DE_PROJECT_PATH=%%~fi" ) -echo LY_PROJECT = %LY_PROJECT% +echo O3DE_PROJECT_PATH = %O3DE_PROJECT_PATH% -:: this is here for archaic reasons, WILL DEPRECATE -IF "%LY_PROJECT_PATH%"=="" (set LY_PROJECT_PATH=%LY_PROJECT%) -echo LY_PROJECT_PATH = %LY_PROJECT_PATH% +:: Change to root O3DE dev dir +IF "%O3DE_DEV%"=="" echo ~ You must set O3DE_DEV in a User_Env.bat to match your local engine repo! +IF "%O3DE_DEV%"=="" echo ~ Using default O3DE_DEV=C:\Depot\o3de-engine +IF "%O3DE_DEV%"=="" (set O3DE_DEV=C:\Depot\o3de-engine) +echo O3DE_DEV = %O3DE_DEV% -:: Change to root Lumberyard dev dir -:: You must set this in a User_Env.bat to match youe engine repo location! -IF "%LY_DEV%"=="" (set LY_DEV=C:\Depot\o3de-engine) -echo LY_DEV = %LY_DEV% - -CALL %LY_DEV%\Gems\AtomLyIntegration\TechnicalArt\DccScriptingInterface\Launchers\Windows\Env_Maya.bat +CALL %O3DE_DEV%\Gems\AtomLyIntegration\TechnicalArt\DccScriptingInterface\Tools\Dev\Windows\Env_Maya.bat :: Restore original directory popd diff --git a/Gems/AtomContent/Sponza/Tools/Launch_Cmd.bat b/Gems/AtomContent/Sponza/Tools/Launch_Cmd.bat index 99c2c12c51..0b94be5bea 100644 --- a/Gems/AtomContent/Sponza/Tools/Launch_Cmd.bat +++ b/Gems/AtomContent/Sponza/Tools/Launch_Cmd.bat @@ -1,4 +1,6 @@ @echo off +:: Keep changes local +SETLOCAL enableDelayedExpansion REM REM Copyright (c) Contributors to the Open 3D Engine Project @@ -21,15 +23,12 @@ COLOR 8E cd %~dp0 PUSHD %~dp0 -:: Keep changes local -SETLOCAL enableDelayedExpansion - CALL %~dp0\Project_Env.bat echo. echo _____________________________________________________________________ echo. -echo ~ LY DCC Scripting Interface CMD ... +echo ~ O3DE %O3DE_PROJECT% Asset Gem CMD ... echo _____________________________________________________________________ echo. diff --git a/Gems/AtomContent/Sponza/Tools/Launch_Maya.bat b/Gems/AtomContent/Sponza/Tools/Launch_Maya.bat index d774adf79b..0af25905a0 100644 --- a/Gems/AtomContent/Sponza/Tools/Launch_Maya.bat +++ b/Gems/AtomContent/Sponza/Tools/Launch_Maya.bat @@ -34,7 +34,7 @@ echo DCCSI_MAYA_VERSION = %DCCSI_MAYA_VERSION% IF EXIST "%~dp0Project_Env.bat" CALL %~dp0Project_Env.bat echo ________________________________ -echo Launching Maya %DCCSI_MAYA_VERSION% for Lumberyard... +echo Launching Maya %DCCSI_MAYA_VERSION% for O3DE: %O3DE_PROJECT%... :::: Set Maya native project acess to this project ::set MAYA_PROJECT=%LY_PROJECT% @@ -44,8 +44,10 @@ echo Launching Maya %DCCSI_MAYA_VERSION% for Lumberyard... Set MAYA_VP2_DEVICE_OVERRIDE = VirtualDeviceDx11 :: Default to the right version of Maya if we can detect it... and launch -IF EXIST "%MAYA_LOCATION%\bin\Maya.exe" ( - start "" "%MAYA_LOCATION%\bin\Maya.exe" %* +echo MAYA_BIN_PATH = %MAYA_BIN_PATH% + +IF EXIST "%MAYA_BIN_PATH%\Maya.exe" ( + start "" "%MAYA_BIN_PATH%\Maya.exe" %* ) ELSE ( Where maya.exe 2> NUL IF ERRORLEVEL 1 ( diff --git a/Gems/AtomContent/Sponza/Tools/Project_Env.bat b/Gems/AtomContent/Sponza/Tools/Project_Env.bat index 6e2c8b5914..ddf934d206 100644 --- a/Gems/AtomContent/Sponza/Tools/Project_Env.bat +++ b/Gems/AtomContent/Sponza/Tools/Project_Env.bat @@ -29,23 +29,23 @@ PUSHD %~dp0 set ABS_PATH=%~dp0 :: project name as a str tag -IF "%LY_PROJECT_NAME%"=="" ( - for %%I in ("%~dp0.") do for %%J in ("%%~dpI.") do set LY_PROJECT_NAME=%%~nxJ +IF "%O3DE_PROJECT%"=="" ( + for %%I in ("%~dp0.") do for %%J in ("%%~dpI.") do set O3DE_PROJECT=%%~nxJ ) echo. echo _____________________________________________________________________ echo. -echo ~ Setting up O3DE %LY_PROJECT_NAME% Environment ... +echo ~ Setting up O3DE %O3DE_PROJECT% Environment ... echo _____________________________________________________________________ echo. -echo LY_PROJECT_NAME = %LY_PROJECT_NAME% +echo O3DE_PROJECT = %O3DE_PROJECT% :: if the user has set up a custom env call it :: this should allow the user to locally -:: set env hooks like LY_DEV or LY_PROJECT +:: set env hooks like O3DE_DEV or O3DE_PROJECT_PATH IF EXIST "%~dp0User_Env.bat" CALL %~dp0User_Env.bat -echo LY_DEV = %LY_DEV% +echo O3DE_DEV = %O3DE_DEV% :: Constant Vars (Global) :: global debug flag (propogates) @@ -74,23 +74,20 @@ echo DCCSI_LOGLEVEL = %DCCSI_LOGLEVEL% IF "%DCCSI_MAYA_VERSION%"=="" (set DCCSI_MAYA_VERSION=2020) echo DCCSI_MAYA_VERSION = %DCCSI_MAYA_VERSION% -:: LY_PROJECT is ideally treated as a full path in the env launchers +:: O3DE_PROJECT_PATH is ideally treated as a full path in the env launchers :: do to changes in o3de, external engine/project/gem folder structures, etc. -IF "%LY_PROJECT%"=="" ( - for %%i in ("%~dp0..") do set "LY_PROJECT=%%~fi" +IF "%O3DE_PROJECT_PATH%"=="" ( + for %%i in ("%~dp0..") do set "O3DE_PROJECT_PATH=%%~fi" ) -echo LY_PROJECT = %LY_PROJECT% +echo O3DE_PROJECT_PATH = %O3DE_PROJECT_PATH% -:: this is here for archaic reasons, WILL DEPRECATE -IF "%LY_PROJECT_PATH%"=="" (set LY_PROJECT_PATH=%LY_PROJECT%) -echo LY_PROJECT_PATH = %LY_PROJECT_PATH% +:: Change to root O3DE dev dir +IF "%O3DE_DEV%"=="" echo ~ You must set O3DE_DEV in a User_Env.bat to match your local engine repo! +IF "%O3DE_DEV%"=="" echo ~ Using default O3DE_DEV=C:\Depot\o3de-engine +IF "%O3DE_DEV%"=="" (set O3DE_DEV=C:\Depot\o3de-engine) +echo O3DE_DEV = %O3DE_DEV% -:: Change to root Lumberyard dev dir -:: You must set this in a User_Env.bat to match youe engine repo location! -IF "%LY_DEV%"=="" (set LY_DEV=C:\Depot\o3de-engine) -echo LY_DEV = %LY_DEV% - -CALL %LY_DEV%\Gems\AtomLyIntegration\TechnicalArt\DccScriptingInterface\Launchers\Windows\Env_Maya.bat +CALL %O3DE_DEV%\Gems\AtomLyIntegration\TechnicalArt\DccScriptingInterface\Tools\Dev\Windows\Env_Maya.bat :: Restore original directory popd diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/CMakeLists.txt b/Gems/AtomLyIntegration/CommonFeatures/Code/CMakeLists.txt index 95331a2f3f..7b685ece13 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/CMakeLists.txt +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/CMakeLists.txt @@ -111,6 +111,7 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS) RUNTIME_DEPENDENCIES Gem::Atom_RPI.Editor Gem::Atom_Feature_Common.Editor + Gem::AtomToolsFramework.Editor Legacy::EditorCommon ) diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ReflectionProbe/EditorReflectionProbeComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ReflectionProbe/EditorReflectionProbeComponent.cpp index ae5c930096..578c6e9300 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ReflectionProbe/EditorReflectionProbeComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ReflectionProbe/EditorReflectionProbeComponent.cpp @@ -40,6 +40,7 @@ namespace AZ ->Field("bakedCubeMapQualityLevel", &EditorReflectionProbeComponent::m_bakedCubeMapQualityLevel) ->Field("bakedCubeMapRelativePath", &EditorReflectionProbeComponent::m_bakedCubeMapRelativePath) ->Field("authoredCubeMapAsset", &EditorReflectionProbeComponent::m_authoredCubeMapAsset) + ->Field("bakeExposure", &EditorReflectionProbeComponent::m_bakeExposure) ; if (AZ::EditContext* editContext = serializeContext->GetEditContext()) @@ -62,6 +63,13 @@ namespace AZ ->Attribute(AZ::Edit::Attributes::ButtonText, "Bake Reflection Probe") ->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorReflectionProbeComponent::BakeReflectionProbe) ->Attribute(AZ::Edit::Attributes::Visibility, &EditorReflectionProbeComponent::GetBakedCubemapVisibilitySetting) + ->DataElement(AZ::Edit::UIHandlers::Slider, &EditorReflectionProbeComponent::m_bakeExposure, "Bake Exposure", "Exposure to use when baking the cubemap") + ->Attribute(AZ::Edit::Attributes::SoftMin, -16.0f) + ->Attribute(AZ::Edit::Attributes::SoftMax, 16.0f) + ->Attribute(AZ::Edit::Attributes::Min, -20.0f) + ->Attribute(AZ::Edit::Attributes::Max, 20.0f) + ->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorReflectionProbeComponent::OnBakeExposureChanged) + ->Attribute(AZ::Edit::Attributes::Visibility, &EditorReflectionProbeComponent::GetBakedCubemapVisibilitySetting) ->ClassElement(AZ::Edit::ClassElements::Group, "Cubemap") ->Attribute(AZ::Edit::Attributes::AutoExpand, true) ->DataElement(AZ::Edit::UIHandlers::Default, &EditorReflectionProbeComponent::m_useBakedCubemap, "Use Baked Cubemap", "Selects between a cubemap that captures the environment at location in the scene or a preauthored cubemap") @@ -111,6 +119,11 @@ namespace AZ ->Attribute(AZ::Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly) ->DataElement(AZ::Edit::UIHandlers::CheckBox, &ReflectionProbeComponentConfig::m_showVisualization, "Show Visualization", "Show the reflection probe visualization sphere") ->Attribute(AZ::Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly) + ->DataElement(AZ::Edit::UIHandlers::Slider, &ReflectionProbeComponentConfig::m_renderExposure, "Exposure", "Exposure to use when rendering meshes with the cubemap") + ->Attribute(AZ::Edit::Attributes::SoftMin, -5.0f) + ->Attribute(AZ::Edit::Attributes::SoftMax, 5.0f) + ->Attribute(AZ::Edit::Attributes::Min, -20.0f) + ->Attribute(AZ::Edit::Attributes::Max, 20.0f) ; } } @@ -275,6 +288,13 @@ namespace AZ return AZ::Edit::PropertyRefreshLevels::None; } + AZ::u32 EditorReflectionProbeComponent::OnBakeExposureChanged() + { + m_controller.SetBakeExposure(m_bakeExposure); + + return AZ::Edit::PropertyRefreshLevels::None; + } + AZ::u32 EditorReflectionProbeComponent::GetBakedCubemapVisibilitySetting() { // controls specific to baked cubemaps call this to determine their visibility diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ReflectionProbe/EditorReflectionProbeComponent.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ReflectionProbe/EditorReflectionProbeComponent.h index 441da19e78..3cd017fd18 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ReflectionProbe/EditorReflectionProbeComponent.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ReflectionProbe/EditorReflectionProbeComponent.h @@ -55,6 +55,7 @@ namespace AZ // change notifications AZ::u32 OnUseBakedCubemapChanged(); AZ::u32 OnAuthoredCubemapChanged(); + AZ::u32 OnBakeExposureChanged(); // retrieves visibility for baked or authored cubemap controls AZ::u32 GetBakedCubemapVisibilitySetting(); @@ -77,6 +78,7 @@ namespace AZ AZStd::string m_bakedCubeMapRelativePath; Data::Asset m_bakedCubeMapAsset; Data::Asset m_authoredCubeMapAsset; + float m_bakeExposure = 0.0f; // flag indicating if a cubemap bake is currently in progress AZStd::atomic_bool m_bakeInProgress = false; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ReflectionProbe/ReflectionProbeComponentController.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ReflectionProbe/ReflectionProbeComponentController.cpp index 4022dfda9b..017f6c9cdf 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ReflectionProbe/ReflectionProbeComponentController.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ReflectionProbe/ReflectionProbeComponentController.cpp @@ -35,7 +35,7 @@ namespace AZ if (auto* serializeContext = azrtti_cast(context)) { serializeContext->Class() - ->Version(0) + ->Version(1) ->Field("OuterHeight", &ReflectionProbeComponentConfig::m_outerHeight) ->Field("OuterLength", &ReflectionProbeComponentConfig::m_outerLength) ->Field("OuterWidth", &ReflectionProbeComponentConfig::m_outerWidth) @@ -49,7 +49,9 @@ namespace AZ ->Field("AuthoredCubeMapAsset", &ReflectionProbeComponentConfig::m_authoredCubeMapAsset) ->Field("EntityId", &ReflectionProbeComponentConfig::m_entityId) ->Field("UseParallaxCorrection", &ReflectionProbeComponentConfig::m_useParallaxCorrection) - ->Field("ShowVisualization", &ReflectionProbeComponentConfig::m_showVisualization); + ->Field("ShowVisualization", &ReflectionProbeComponentConfig::m_showVisualization) + ->Field("RenderExposure", &ReflectionProbeComponentConfig::m_renderExposure) + ->Field("BakeExposure", &ReflectionProbeComponentConfig::m_bakeExposure); } } @@ -157,6 +159,9 @@ namespace AZ cubeMapAsset.QueueLoad(); Data::AssetBus::MultiHandler::BusConnect(cubeMapAsset.GetId()); } + + // set cubemap render exposure + m_featureProcessor->SetRenderExposure(m_handle, m_configuration.m_renderExposure); } void ReflectionProbeComponentController::Deactivate() @@ -284,6 +289,16 @@ namespace AZ m_configuration.m_innerHeight = AZStd::min(m_configuration.m_innerHeight, m_configuration.m_outerHeight); } + void ReflectionProbeComponentController::SetBakeExposure(float bakeExposure) + { + if (!m_featureProcessor) + { + return; + } + + m_featureProcessor->SetBakeExposure(m_handle, bakeExposure); + } + void ReflectionProbeComponentController::BakeReflectionProbe(BuildCubeMapCallback callback, const AZStd::string& relativePath) { if (!m_featureProcessor) diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ReflectionProbe/ReflectionProbeComponentController.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ReflectionProbe/ReflectionProbeComponentController.h index 18e13f023b..ad7d9f7f53 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ReflectionProbe/ReflectionProbeComponentController.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ReflectionProbe/ReflectionProbeComponentController.h @@ -68,6 +68,9 @@ namespace AZ Data::Asset m_bakedCubeMapAsset; Data::Asset m_authoredCubeMapAsset; AZ::u64 m_entityId{ EntityId::InvalidEntityId }; + + float m_renderExposure = 0.0f; + float m_bakeExposure = 0.0f; }; class ReflectionProbeComponentController final @@ -99,6 +102,9 @@ namespace AZ // returns the outer extent Aabb for this reflection AZ::Aabb GetAabb() const; + // set the exposure to use when baking the cubemap + void SetBakeExposure(float bakeExposure); + // initiate the reflection probe bake, invokes callback when complete void BakeReflectionProbe(BuildCubeMapCallback callback, const AZStd::string& relativePath); diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Env_Maya.bat b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Env_Maya.bat index 5e3c600124..0ed46e9400 100644 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Env_Maya.bat +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Env_Maya.bat @@ -46,7 +46,7 @@ echo DCCSI_PY_VERSION_RELEASE = %DCCSI_PY_VERSION_RELEASE% echo DCCSI_MAYA_VERSION = %DCCSI_MAYA_VERSION% :::: Set Maya native project acess to this project -IF "%MAYA_PROJECT%"=="" (set MAYA_PROJECT=%O3DE_PROJECT%) +IF "%MAYA_PROJECT%"=="" (set MAYA_PROJECT=%O3DE_PROJECT_PATH%) echo MAYA_PROJECT = %MAYA_PROJECT% :: maya sdk path diff --git a/Gems/AudioSystem/Code/Source/Editor/ImplementationManager.cpp b/Gems/AudioSystem/Code/Source/Editor/ImplementationManager.cpp index bee5c1dd51..f339ff8bb3 100644 --- a/Gems/AudioSystem/Code/Source/Editor/ImplementationManager.cpp +++ b/Gems/AudioSystem/Code/Source/Editor/ImplementationManager.cpp @@ -9,8 +9,6 @@ #include -#include - #include #include #include @@ -32,10 +30,6 @@ bool CImplementationManager::LoadImplementation() // release the loaded implementation (if any) Release(); - const char* engineRoot = nullptr; - AzFramework::ApplicationRequests::Bus::BroadcastResult(engineRoot, &AzFramework::ApplicationRequests::GetEngineRoot); - AZ_Assert(engineRoot != nullptr, "Unable to communicate with AzFramework::ApplicationRequests::Bus"); - AudioControlsEditor::EditorImplPluginEventBus::Broadcast(&AudioControlsEditor::EditorImplPluginEventBus::Events::InitializeEditorImplPlugin); } else diff --git a/Gems/EditorPythonBindings/Code/Source/PythonSystemComponent.cpp b/Gems/EditorPythonBindings/Code/Source/PythonSystemComponent.cpp index 876ef19803..fe9156013f 100644 --- a/Gems/EditorPythonBindings/Code/Source/PythonSystemComponent.cpp +++ b/Gems/EditorPythonBindings/Code/Source/PythonSystemComponent.cpp @@ -597,11 +597,10 @@ namespace EditorPythonBindings { AZStd::unordered_set pyPackageSites(pythonPathStack.begin(), pythonPathStack.end()); - const char* engineRoot = nullptr; - AzFramework::ApplicationRequests::Bus::BroadcastResult(engineRoot, &AzFramework::ApplicationRequests::GetEngineRoot); + AZ::IO::FixedMaxPath engineRoot = AZ::Utils::GetEnginePath(); // set PYTHON_HOME - AZStd::string pyBasePath = Platform::GetPythonHomePath(PY_PACKAGE, engineRoot); + AZStd::string pyBasePath = Platform::GetPythonHomePath(PY_PACKAGE, engineRoot.c_str()); if (!AZ::IO::SystemFile::Exists(pyBasePath.c_str())) { AZ_Warning("python", false, "Python home path must exist! path:%s", pyBasePath.c_str()); diff --git a/Gems/EditorPythonBindings/Code/Tests/PythonTestingUtility.h b/Gems/EditorPythonBindings/Code/Tests/PythonTestingUtility.h index 143863b4c8..e2b3d1eadd 100644 --- a/Gems/EditorPythonBindings/Code/Tests/PythonTestingUtility.h +++ b/Gems/EditorPythonBindings/Code/Tests/PythonTestingUtility.h @@ -135,10 +135,6 @@ namespace UnitTest void NormalizePath(AZStd::string& ) override {} void NormalizePathKeepCase(AZStd::string& ) override {} void CalculateBranchTokenForEngineRoot(AZStd::string& ) const override {} - // Gets the engine root path for testing - const char* GetEngineRoot() const override { return m_engineRoot.c_str(); } - // Retrieves the app root path for testing - const char* GetAppRoot() const override { return m_engineRoot.c_str(); } AZ::ComponentApplication m_app; AZStd::unique_ptr m_fileIOHelper; diff --git a/Gems/LmbrCentral/Code/Tests/Builders/SliceBuilderTests.cpp b/Gems/LmbrCentral/Code/Tests/Builders/SliceBuilderTests.cpp index 73ef7143c8..0965991dfa 100644 --- a/Gems/LmbrCentral/Code/Tests/Builders/SliceBuilderTests.cpp +++ b/Gems/LmbrCentral/Code/Tests/Builders/SliceBuilderTests.cpp @@ -311,7 +311,6 @@ namespace UnitTest SerializeContext* GetSerializeContext() override { return m_serializeContext; } BehaviorContext* GetBehaviorContext() override { return nullptr; } JsonRegistrationContext* GetJsonRegistrationContext() override { return nullptr; } - const char* GetAppRoot() const override { return nullptr; } const char* GetEngineRoot() const override { return nullptr; } const char* GetExecutableFolder() const override { return nullptr; } void EnumerateEntities(const EntityCallback& /*callback*/) override {} diff --git a/Gems/LyShine/Code/Editor/Animation/UiAnimViewDialog.cpp b/Gems/LyShine/Code/Editor/Animation/UiAnimViewDialog.cpp index e6c2dda74c..9ad9a50aa5 100644 --- a/Gems/LyShine/Code/Editor/Animation/UiAnimViewDialog.cpp +++ b/Gems/LyShine/Code/Editor/Animation/UiAnimViewDialog.cpp @@ -257,6 +257,7 @@ BOOL CUiAnimViewDialog::OnInitDialog() m_wndSplitter->addWidget(m_wndDopeSheet); m_wndSplitter->setStretchFactor(0, 1); m_wndSplitter->setStretchFactor(1, 10); + m_wndSplitter->setChildrenCollapsible(false); l->addWidget(m_wndSplitter); w->setLayout(l); setCentralWidget(w); @@ -283,6 +284,11 @@ BOOL CUiAnimViewDialog::OnInitDialog() m_wndCurveEditorDock->setVisible(false); m_wndCurveEditorDock->setEnabled(false); + // In order to prevent the track editor view from collapsing and becoming invisible, we use the + // minimum size of the curve editor for the track editor as well. Since both editors use the same + // view widget in the UI animation editor when not in 'Both' mode, the sizes can be identical. + m_wndDopeSheet->setMinimumSize(m_wndCurveEditor->minimumSizeHint()); + InitSequences(); m_lazyInitDone = false; diff --git a/Gems/Multiplayer/Code/Tests/CommonBenchmarkSetup.h b/Gems/Multiplayer/Code/Tests/CommonBenchmarkSetup.h index 3c3d77e011..238e652b52 100644 --- a/Gems/Multiplayer/Code/Tests/CommonBenchmarkSetup.h +++ b/Gems/Multiplayer/Code/Tests/CommonBenchmarkSetup.h @@ -41,7 +41,6 @@ namespace Multiplayer AZ::SerializeContext* GetSerializeContext() override { return {}; } AZ::BehaviorContext* GetBehaviorContext() override { return {}; } AZ::JsonRegistrationContext* GetJsonRegistrationContext() override { return {}; } - const char* GetAppRoot() const override { return {}; } const char* GetEngineRoot() const override { return {}; } const char* GetExecutableFolder() const override { return {}; } void QueryApplicationType([[maybe_unused]] AZ::ApplicationTypeQuery& appType) const override {} diff --git a/Gems/Multiplayer/Code/Tests/MockInterfaces.h b/Gems/Multiplayer/Code/Tests/MockInterfaces.h index 8cebf280b9..8fd32bd79b 100644 --- a/Gems/Multiplayer/Code/Tests/MockInterfaces.h +++ b/Gems/Multiplayer/Code/Tests/MockInterfaces.h @@ -146,7 +146,6 @@ namespace UnitTest MOCK_METHOD0(GetSerializeContext, AZ::SerializeContext* ()); MOCK_METHOD0(GetBehaviorContext, AZ::BehaviorContext* ()); MOCK_METHOD0(GetJsonRegistrationContext, AZ::JsonRegistrationContext* ()); - MOCK_CONST_METHOD0(GetAppRoot, const char* ()); MOCK_CONST_METHOD0(GetEngineRoot, const char* ()); MOCK_CONST_METHOD0(GetExecutableFolder, const char* ()); MOCK_METHOD0(GetDrillerManager, AZ::Debug::DrillerManager* ()); diff --git a/Gems/PhysX/Code/Mocks/PhysX/MockPhysXHeightfieldProviderComponent.h b/Gems/PhysX/Code/Mocks/PhysX/MockPhysXHeightfieldProviderComponent.h index 7e500881c0..d462c30158 100644 --- a/Gems/PhysX/Code/Mocks/PhysX/MockPhysXHeightfieldProviderComponent.h +++ b/Gems/PhysX/Code/Mocks/PhysX/MockPhysXHeightfieldProviderComponent.h @@ -69,6 +69,10 @@ namespace UnitTest MOCK_CONST_METHOD1(UpdateHeights, AZStd::vector(const AZ::Aabb& dirtyRegion)); MOCK_CONST_METHOD1(UpdateHeightsAndMaterials, AZStd::vector(const AZ::Aabb& dirtyRegion)); MOCK_CONST_METHOD0(GetHeightfieldAabb, AZ::Aabb()); + MOCK_CONST_METHOD0(GetHeightfieldMinHeight, float()); + MOCK_CONST_METHOD0(GetHeightfieldMaxHeight, float()); + MOCK_CONST_METHOD0(GetHeightfieldGridColumns, int32_t()); + MOCK_CONST_METHOD0(GetHeightfieldGridRows, int32_t()); }; } // namespace UnitTest diff --git a/Gems/PhysX/Code/Source/Debug/PhysXDebug.cpp b/Gems/PhysX/Code/Source/Debug/PhysXDebug.cpp index bc9265a92a..fb8e61289d 100644 --- a/Gems/PhysX/Code/Source/Debug/PhysXDebug.cpp +++ b/Gems/PhysX/Code/Source/Debug/PhysXDebug.cpp @@ -13,7 +13,7 @@ #include #include #include -#include +#include namespace PhysX { @@ -108,8 +108,7 @@ namespace PhysX AzFramework::StringFunc::Append(filename, m_config.m_pvdConfigurationData.m_fileName.c_str()); AzFramework::StringFunc::Append(filename, ".pxd2"); - AZStd::string rootDirectory; - AZ::ComponentApplicationBus::BroadcastResult(rootDirectory, &AZ::ComponentApplicationRequests::GetAppRoot); + AZStd::string rootDirectory{ AZStd::string_view(AZ::Utils::GetEnginePath()) }; // Create the full filepath. AZStd::string safeFilePath; diff --git a/Gems/ScriptCanvas/Code/Tests/ScriptCanvasBuilderTests.cpp b/Gems/ScriptCanvas/Code/Tests/ScriptCanvasBuilderTests.cpp index cba14feac6..33982bbcd7 100644 --- a/Gems/ScriptCanvas/Code/Tests/ScriptCanvasBuilderTests.cpp +++ b/Gems/ScriptCanvas/Code/Tests/ScriptCanvasBuilderTests.cpp @@ -89,7 +89,6 @@ protected: AZ::SerializeContext* GetSerializeContext() override { return m_serializeContext; } AZ::BehaviorContext* GetBehaviorContext() override { return nullptr; } AZ::JsonRegistrationContext* GetJsonRegistrationContext() override { return nullptr; } - const char* GetAppRoot() const override { return nullptr; } const char* GetEngineRoot() const override { return nullptr; } const char* GetExecutableFolder() const override { return nullptr; } void EnumerateEntities(const AZ::ComponentApplicationRequests::EntityCallback& /*callback*/) override {} diff --git a/Gems/ScriptCanvasTesting/Code/Source/Framework/ScriptCanvasTestFixture.h b/Gems/ScriptCanvasTesting/Code/Source/Framework/ScriptCanvasTestFixture.h index f45b74d4f1..6e38f71eec 100644 --- a/Gems/ScriptCanvasTesting/Code/Source/Framework/ScriptCanvasTestFixture.h +++ b/Gems/ScriptCanvasTesting/Code/Source/Framework/ScriptCanvasTestFixture.h @@ -91,14 +91,6 @@ namespace ScriptCanvasTests AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance(); AZ_Assert(fileIO, "SC unit tests require filehandling"); - if (!fileIO->GetAlias("@engroot@")) - { - const char* engineRoot = nullptr; - AzFramework::ApplicationRequests::Bus::BroadcastResult(engineRoot, &AzFramework::ApplicationRequests::GetEngineRoot); - AZ_Assert(engineRoot, "null engine root"); - fileIO->SetAlias("@engroot@", engineRoot); - } - s_setupSucceeded = fileIO->GetAlias("@engroot@") != nullptr; AZ::TickBus::AllowFunctionQueuing(true); diff --git a/Gems/Terrain/Assets/Shaders/Terrain/TerrainCommon.azsli b/Gems/Terrain/Assets/Shaders/Terrain/TerrainCommon.azsli index cd680f721a..52d0e0a6cc 100644 --- a/Gems/Terrain/Assets/Shaders/Terrain/TerrainCommon.azsli +++ b/Gems/Terrain/Assets/Shaders/Terrain/TerrainCommon.azsli @@ -56,6 +56,7 @@ ShaderResourceGroup ObjectSrg : SRG_PerObject float m_padding; bool m_useReflectionProbe; bool m_useParallaxCorrection; + float m_exposure; }; ReflectionProbeData m_reflectionProbeData; diff --git a/Gems/Terrain/Code/Source/Components/TerrainPhysicsColliderComponent.cpp b/Gems/Terrain/Code/Source/Components/TerrainPhysicsColliderComponent.cpp index e9c235c1bd..6c76e90fd0 100644 --- a/Gems/Terrain/Code/Source/Components/TerrainPhysicsColliderComponent.cpp +++ b/Gems/Terrain/Code/Source/Components/TerrainPhysicsColliderComponent.cpp @@ -185,12 +185,28 @@ namespace Terrain void TerrainPhysicsColliderComponent::GetHeightfieldHeightBounds(float& minHeightBounds, float& maxHeightBounds) const { - AZ::Aabb heightfieldAabb = GetHeightfieldAabb(); + const AZ::Aabb heightfieldAabb = GetHeightfieldAabb(); // Because our terrain heights are relative to the center of the bounding box, the min and max allowable heights are also // relative to the center. They are also clamped to the size of the bounding box. - minHeightBounds = -(heightfieldAabb.GetZExtent() / 2.0f); maxHeightBounds = heightfieldAabb.GetZExtent() / 2.0f; + minHeightBounds = -maxHeightBounds; + } + + float TerrainPhysicsColliderComponent::GetHeightfieldMinHeight() const + { + float minHeightBounds{ 0.0f }; + float maxHeightBounds{ 0.0f }; + GetHeightfieldHeightBounds(minHeightBounds, maxHeightBounds); + return minHeightBounds; + } + + float TerrainPhysicsColliderComponent::GetHeightfieldMaxHeight() const + { + float minHeightBounds{ 0.0f }; + float maxHeightBounds{ 0.0f }; + GetHeightfieldHeightBounds(minHeightBounds, maxHeightBounds); + return maxHeightBounds; } AZ::Transform TerrainPhysicsColliderComponent::GetHeightfieldTransform() const @@ -199,9 +215,7 @@ namespace Terrain AZ::Vector3 translate; AZ::TransformBus::EventResult(translate, GetEntityId(), &AZ::TransformBus::Events::GetWorldTranslation); - AZ::Transform transform = AZ::Transform::CreateTranslation(translate); - - return transform; + return AZ::Transform::CreateTranslation(translate); } void TerrainPhysicsColliderComponent::GenerateHeightsInBounds(AZStd::vector& heights) const @@ -298,6 +312,24 @@ namespace Terrain numRows = aznumeric_cast((bounds.GetMax().GetY() - bounds.GetMin().GetY()) / gridResolution.GetY()); } + int32_t TerrainPhysicsColliderComponent::GetHeightfieldGridColumns() const + { + int32_t numColumns{ 0 }; + int32_t numRows{ 0 }; + + GetHeightfieldGridSize(numColumns, numRows); + return numColumns; + } + + int32_t TerrainPhysicsColliderComponent::GetHeightfieldGridRows() const + { + int32_t numColumns{ 0 }; + int32_t numRows{ 0 }; + + GetHeightfieldGridSize(numColumns, numRows); + return numRows; + } + AZStd::vector TerrainPhysicsColliderComponent::GetMaterialList() const { return AZStd::vector(); diff --git a/Gems/Terrain/Code/Source/Components/TerrainPhysicsColliderComponent.h b/Gems/Terrain/Code/Source/Components/TerrainPhysicsColliderComponent.h index e268223689..6462909c89 100644 --- a/Gems/Terrain/Code/Source/Components/TerrainPhysicsColliderComponent.h +++ b/Gems/Terrain/Code/Source/Components/TerrainPhysicsColliderComponent.h @@ -58,7 +58,11 @@ namespace Terrain // HeightfieldProviderRequestsBus AZ::Vector2 GetHeightfieldGridSpacing() const override; void GetHeightfieldGridSize(int32_t& numColumns, int32_t& numRows) const override; + int32_t GetHeightfieldGridColumns() const override; + int32_t GetHeightfieldGridRows() const override; void GetHeightfieldHeightBounds(float& minHeightBounds, float& maxHeightBounds) const override; + float GetHeightfieldMinHeight() const override; + float GetHeightfieldMaxHeight() const override; AZ::Aabb GetHeightfieldAabb() const override; AZ::Transform GetHeightfieldTransform() const override; AZStd::vector GetMaterialList() const override; diff --git a/Gems/Terrain/Code/Source/TerrainRenderer/Components/TerrainSurfaceMaterialsListComponent.cpp b/Gems/Terrain/Code/Source/TerrainRenderer/Components/TerrainSurfaceMaterialsListComponent.cpp index 271919070c..1dbcbd1120 100644 --- a/Gems/Terrain/Code/Source/TerrainRenderer/Components/TerrainSurfaceMaterialsListComponent.cpp +++ b/Gems/Terrain/Code/Source/TerrainRenderer/Components/TerrainSurfaceMaterialsListComponent.cpp @@ -74,7 +74,7 @@ namespace Terrain ->DataElement( AZ::Edit::UIHandlers::Default, &TerrainSurfaceMaterialsListConfig::m_surfaceMaterials, - "Gradient to Material Mappings", "Maps surfaces to materials."); + "Material Mappings", "Maps surfaces to materials."); } } } @@ -123,7 +123,7 @@ namespace Terrain { surfaceMaterialMapping.m_active = false; surfaceMaterialMapping.m_materialAsset.QueueLoad(); - AZ::Data::AssetBus::Handler::BusConnect(surfaceMaterialMapping.m_materialAsset.GetId()); + AZ::Data::AssetBus::MultiHandler::BusConnect(surfaceMaterialMapping.m_materialAsset.GetId()); } } } @@ -136,7 +136,7 @@ namespace Terrain { if (surfaceMaterialMapping.m_materialAsset.GetId().IsValid()) { - AZ::Data::AssetBus::Handler::BusDisconnect(surfaceMaterialMapping.m_materialAsset.GetId()); + AZ::Data::AssetBus::MultiHandler::BusDisconnect(surfaceMaterialMapping.m_materialAsset.GetId()); surfaceMaterialMapping.m_materialAsset.Release(); surfaceMaterialMapping.m_materialInstance.reset(); surfaceMaterialMapping.m_activeMaterialAssetId = AZ::Data::AssetId(); @@ -202,7 +202,7 @@ namespace Terrain // Don't disconnect from the AssetBus if this material is mapped more than once. if (CountMaterialIDInstances(surfaceMaterialMapping.m_activeMaterialAssetId) == 1) { - AZ::Data::AssetBus::Handler::BusDisconnect(surfaceMaterialMapping.m_activeMaterialAssetId); + AZ::Data::AssetBus::MultiHandler::BusDisconnect(surfaceMaterialMapping.m_activeMaterialAssetId); } surfaceMaterialMapping.m_activeMaterialAssetId = AZ::Data::AssetId(); @@ -238,7 +238,7 @@ namespace Terrain // All materials have been deactivated, stop listening for requests and notifications. m_cachedAabb = AZ::Aabb::CreateNull(); LmbrCentral::ShapeComponentNotificationsBus::Handler::BusDisconnect(); - TerrainAreaMaterialRequestBus::Handler::BusConnect(GetEntityId()); + TerrainAreaMaterialRequestBus::Handler::BusDisconnect(); } } diff --git a/Gems/Terrain/Code/Source/TerrainRenderer/Components/TerrainSurfaceMaterialsListComponent.h b/Gems/Terrain/Code/Source/TerrainRenderer/Components/TerrainSurfaceMaterialsListComponent.h index 7c36033c41..72eee5f68e 100644 --- a/Gems/Terrain/Code/Source/TerrainRenderer/Components/TerrainSurfaceMaterialsListComponent.h +++ b/Gems/Terrain/Code/Source/TerrainRenderer/Components/TerrainSurfaceMaterialsListComponent.h @@ -53,7 +53,7 @@ namespace Terrain class TerrainSurfaceMaterialsListComponent : public AZ::Component , private TerrainAreaMaterialRequestBus::Handler - , private AZ::Data::AssetBus::Handler + , private AZ::Data::AssetBus::MultiHandler , private LmbrCentral::ShapeComponentNotificationsBus::Handler { public: diff --git a/Gems/Terrain/Code/Tests/LayerSpawnerTests.cpp b/Gems/Terrain/Code/Tests/LayerSpawnerTests.cpp index 3778ba860f..9de441eecf 100644 --- a/Gems/Terrain/Code/Tests/LayerSpawnerTests.cpp +++ b/Gems/Terrain/Code/Tests/LayerSpawnerTests.cpp @@ -6,15 +6,13 @@ * */ +#include + #include #include #include -#include - #include -#include -#include #include #include @@ -23,21 +21,12 @@ using ::testing::NiceMock; using ::testing::AtLeast; using ::testing::_; -using ::testing::NiceMock; -using ::testing::AtLeast; -using ::testing::_; - class LayerSpawnerComponentTest : public ::testing::Test { protected: AZ::ComponentApplication m_app; - AZStd::unique_ptr m_entity; - Terrain::TerrainLayerSpawnerComponent* m_layerSpawnerComponent; - UnitTest::MockAxisAlignedBoxShapeComponent* m_shapeComponent; - AZStd::unique_ptr> m_terrainSystem; - void SetUp() override { AZ::ComponentApplication::Descriptor appDesc; @@ -50,78 +39,86 @@ protected: void TearDown() override { - m_entity.reset(); - m_terrainSystem.reset(); m_app.Destroy(); } - void CreateEntity() + AZStd::unique_ptr CreateEntity() { - m_entity = AZStd::make_unique(); - m_entity->Init(); + auto entity = AZStd::make_unique(); + entity->Init(); - ASSERT_TRUE(m_entity); - } - - void AddLayerSpawnerAndShapeComponentToEntity() - { - AddLayerSpawnerAndShapeComponentToEntity(Terrain::TerrainLayerSpawnerConfig()); + return entity; } - void AddLayerSpawnerAndShapeComponentToEntity(const Terrain::TerrainLayerSpawnerConfig& config) + Terrain::TerrainLayerSpawnerComponent* AddLayerSpawnerToEntity(AZ::Entity* entity, const Terrain::TerrainLayerSpawnerConfig& config) { - m_layerSpawnerComponent = m_entity->CreateComponent(config); - m_app.RegisterComponentDescriptor(m_layerSpawnerComponent->CreateDescriptor()); + auto layerSpawnerComponent = entity->CreateComponent(config); + m_app.RegisterComponentDescriptor(layerSpawnerComponent->CreateDescriptor()); - m_shapeComponent = m_entity->CreateComponent(); - m_app.RegisterComponentDescriptor(m_shapeComponent->CreateDescriptor()); - - ASSERT_TRUE(m_layerSpawnerComponent); - ASSERT_TRUE(m_shapeComponent); + return layerSpawnerComponent; } - void CreateMockTerrainSystem() + UnitTest::MockAxisAlignedBoxShapeComponent* AddShapeComponentToEntity(AZ::Entity* entity) { - m_terrainSystem = AZStd::make_unique>(); + UnitTest::MockAxisAlignedBoxShapeComponent* shapeComponent = entity->CreateComponent(); + m_app.RegisterComponentDescriptor(shapeComponent->CreateDescriptor()); + + return shapeComponent; } }; -TEST_F(LayerSpawnerComponentTest, ActivatEntityActivateSuccess) +TEST_F(LayerSpawnerComponentTest, ActivateEntityWithoutShapeFails) +{ + auto entity = CreateEntity(); + + AddLayerSpawnerToEntity(entity.get(), Terrain::TerrainLayerSpawnerConfig()); + + const AZ::Entity::DependencySortOutcome sortOutcome = entity->EvaluateDependenciesGetDetails(); + EXPECT_FALSE(sortOutcome.IsSuccess()); + + entity.reset(); +} + +TEST_F(LayerSpawnerComponentTest, ActivateEntityActivateSuccess) { - CreateEntity(); - AddLayerSpawnerAndShapeComponentToEntity(); + auto entity = CreateEntity(); + + AddLayerSpawnerToEntity(entity.get(), Terrain::TerrainLayerSpawnerConfig()); + AddShapeComponentToEntity(entity.get()); - m_entity->Activate(); - EXPECT_EQ(m_entity->GetState(), AZ::Entity::State::Active); - - m_entity->Deactivate(); + entity->Activate(); + EXPECT_EQ(entity->GetState(), AZ::Entity::State::Active); + + entity.reset(); } TEST_F(LayerSpawnerComponentTest, LayerSpawnerDefaultValuesCorrect) { - CreateEntity(); - AddLayerSpawnerAndShapeComponentToEntity(); + auto entity = CreateEntity(); + AddLayerSpawnerToEntity(entity.get(), Terrain::TerrainLayerSpawnerConfig()); + AddShapeComponentToEntity(entity.get()); - m_entity->Activate(); + entity->Activate(); AZ::u32 priority = 999, layer = 999; - Terrain::TerrainSpawnerRequestBus::Event(m_entity->GetId(), &Terrain::TerrainSpawnerRequestBus::Events::GetPriority, layer, priority); + Terrain::TerrainSpawnerRequestBus::Event(entity->GetId(), &Terrain::TerrainSpawnerRequestBus::Events::GetPriority, layer, priority); EXPECT_EQ(0, priority); EXPECT_EQ(1, layer); bool useGroundPlane = false; - Terrain::TerrainSpawnerRequestBus::EventResult(useGroundPlane, m_entity->GetId(), &Terrain::TerrainSpawnerRequestBus::Events::GetUseGroundPlane); + Terrain::TerrainSpawnerRequestBus::EventResult( + useGroundPlane, entity->GetId(), &Terrain::TerrainSpawnerRequestBus::Events::GetUseGroundPlane); EXPECT_TRUE(useGroundPlane); - m_entity->Deactivate(); + entity.reset(); } TEST_F(LayerSpawnerComponentTest, LayerSpawnerConfigValuesCorrect) { - CreateEntity(); + auto entity = CreateEntity(); constexpr static AZ::u32 testPriority = 15; constexpr static AZ::u32 testLayer = 0; @@ -131,12 +128,13 @@ TEST_F(LayerSpawnerComponentTest, LayerSpawnerConfigValuesCorrect) config.m_priority = testPriority; config.m_useGroundPlane = false; - AddLayerSpawnerAndShapeComponentToEntity(config); + AddLayerSpawnerToEntity(entity.get(), config); + AddShapeComponentToEntity(entity.get()); - m_entity->Activate(); + entity->Activate(); AZ::u32 priority = 999, layer = 999; - Terrain::TerrainSpawnerRequestBus::Event(m_entity->GetId(), &Terrain::TerrainSpawnerRequestBus::Events::GetPriority, layer, priority); + Terrain::TerrainSpawnerRequestBus::Event(entity->GetId(), &Terrain::TerrainSpawnerRequestBus::Events::GetPriority, layer, priority); EXPECT_EQ(testPriority, priority); EXPECT_EQ(testLayer, layer); @@ -144,82 +142,86 @@ TEST_F(LayerSpawnerComponentTest, LayerSpawnerConfigValuesCorrect) bool useGroundPlane = true; Terrain::TerrainSpawnerRequestBus::EventResult( - useGroundPlane, m_entity->GetId(), &Terrain::TerrainSpawnerRequestBus::Events::GetUseGroundPlane); + useGroundPlane, entity->GetId(), &Terrain::TerrainSpawnerRequestBus::Events::GetUseGroundPlane); EXPECT_FALSE(useGroundPlane); - m_entity->Deactivate(); + entity.reset(); } TEST_F(LayerSpawnerComponentTest, LayerSpawnerRegisterAreaUpdatesTerrainSystem) { - CreateEntity(); + auto entity = CreateEntity(); - CreateMockTerrainSystem(); + NiceMock terrainSystem; // The Activate call should register the area. - EXPECT_CALL(*m_terrainSystem, RegisterArea(_)).Times(1); + EXPECT_CALL(terrainSystem, RegisterArea(_)).Times(1); - AddLayerSpawnerAndShapeComponentToEntity(); + AddLayerSpawnerToEntity(entity.get(), Terrain::TerrainLayerSpawnerConfig()); + AddShapeComponentToEntity(entity.get()); - m_entity->Activate(); + entity->Activate(); - m_entity->Deactivate(); + entity.reset(); } TEST_F(LayerSpawnerComponentTest, LayerSpawnerUnregisterAreaUpdatesTerrainSystem) { - CreateEntity(); + auto entity = CreateEntity(); - CreateMockTerrainSystem(); + NiceMock terrainSystem; // The Deactivate call should unregister the area. - EXPECT_CALL(*m_terrainSystem, UnregisterArea(_)).Times(1); + EXPECT_CALL(terrainSystem, UnregisterArea(_)).Times(1); - AddLayerSpawnerAndShapeComponentToEntity(); + AddLayerSpawnerToEntity(entity.get(), Terrain::TerrainLayerSpawnerConfig()); + AddShapeComponentToEntity(entity.get()); - m_entity->Activate(); + entity->Activate(); - m_entity->Deactivate(); + entity.reset(); } TEST_F(LayerSpawnerComponentTest, LayerSpawnerTransformChangedUpdatesTerrainSystem) { - CreateEntity(); + auto entity = CreateEntity(); - CreateMockTerrainSystem(); + NiceMock terrainSystem; // The TransformChanged call should refresh the area. - EXPECT_CALL(*m_terrainSystem, RefreshArea(_, _)).Times(1); + EXPECT_CALL(terrainSystem, RefreshArea(_, _)).Times(1); - AddLayerSpawnerAndShapeComponentToEntity(); + AddLayerSpawnerToEntity(entity.get(), Terrain::TerrainLayerSpawnerConfig()); + AddShapeComponentToEntity(entity.get()); - m_entity->Activate(); + entity->Activate(); // The component gets transform change notifications via the shape bus. LmbrCentral::ShapeComponentNotificationsBus::Event( - m_entity->GetId(), &LmbrCentral::ShapeComponentNotificationsBus::Events::OnShapeChanged, + entity->GetId(), &LmbrCentral::ShapeComponentNotificationsBus::Events::OnShapeChanged, LmbrCentral::ShapeComponentNotifications::ShapeChangeReasons::TransformChanged); - m_entity->Deactivate(); + entity.reset(); } TEST_F(LayerSpawnerComponentTest, LayerSpawnerShapeChangedUpdatesTerrainSystem) { - CreateEntity(); + auto entity = CreateEntity(); - CreateMockTerrainSystem(); + NiceMock terrainSystem; // The ShapeChanged call should refresh the area. - EXPECT_CALL(*m_terrainSystem, RefreshArea(_, _)).Times(1); + EXPECT_CALL(terrainSystem, RefreshArea(_, _)).Times(1); - AddLayerSpawnerAndShapeComponentToEntity(); + AddLayerSpawnerToEntity(entity.get(), Terrain::TerrainLayerSpawnerConfig()); + AddShapeComponentToEntity(entity.get()); - m_entity->Activate(); + entity->Activate(); - LmbrCentral::ShapeComponentNotificationsBus::Event( - m_entity->GetId(), &LmbrCentral::ShapeComponentNotificationsBus::Events::OnShapeChanged, + LmbrCentral::ShapeComponentNotificationsBus::Event( + entity->GetId(), &LmbrCentral::ShapeComponentNotificationsBus::Events::OnShapeChanged, LmbrCentral::ShapeComponentNotifications::ShapeChangeReasons::ShapeChanged); - m_entity->Deactivate(); + entity.reset(); } diff --git a/Registry/Platform/Mac/bootstrap_overrides.setreg b/Registry/Platform/Mac/bootstrap_overrides.setreg new file mode 100644 index 0000000000..4e1ca76724 --- /dev/null +++ b/Registry/Platform/Mac/bootstrap_overrides.setreg @@ -0,0 +1,12 @@ +{ + "Amazon": { + "AzCore": { + "Bootstrap": { + // The first time an application is launched on MacOS, each + // dynamic library is inspected by the OS before being loaded. + // This can take a while on some Macs. + "launch_ap_timeout": 300 + } + } + } +} diff --git a/Templates/PythonToolGem/Template/Code/CMakeLists.txt b/Templates/PythonToolGem/Template/Code/CMakeLists.txt index a6044e717b..b90f2d7703 100644 --- a/Templates/PythonToolGem/Template/Code/CMakeLists.txt +++ b/Templates/PythonToolGem/Template/Code/CMakeLists.txt @@ -53,6 +53,8 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS) BUILD_DEPENDENCIES PUBLIC Gem::${Name}.Editor.Static + RUNTIME_DEPENDENCIES + Gem::QtForPython.Editor ) # By default, we will specify that the above target ${Name} would be used by diff --git a/Templates/PythonToolGem/Template/gem.json b/Templates/PythonToolGem/Template/gem.json index d4ff637bee..84f5b65a3e 100644 --- a/Templates/PythonToolGem/Template/gem.json +++ b/Templates/PythonToolGem/Template/gem.json @@ -13,5 +13,8 @@ "${Name}" ], "icon_path": "preview.png", - "requirements": "" + "requirements": "", + "dependencies": [ + "QtForPython" + ] } diff --git a/Tools/LyTestTools/ly_test_tools/o3de/editor_test.py b/Tools/LyTestTools/ly_test_tools/o3de/editor_test.py index 781977cf33..392838d180 100644 --- a/Tools/LyTestTools/ly_test_tools/o3de/editor_test.py +++ b/Tools/LyTestTools/ly_test_tools/o3de/editor_test.py @@ -634,6 +634,7 @@ class EditorTestSuite(): else: test_result = Result.Fail.create(test_spec, output, editor_log_content) except WaitTimeoutError: + output = editor.get_output() editor.kill() editor_log_content = editor_utils.retrieve_editor_log_content(run_id, log_name, workspace) test_result = Result.Timeout.create(test_spec, output, test_spec.timeout, editor_log_content) diff --git a/cmake/Projects.cmake b/cmake/Projects.cmake index 2c42533e35..13161c9e0f 100644 --- a/cmake/Projects.cmake +++ b/cmake/Projects.cmake @@ -183,7 +183,9 @@ if("${CMAKE_INSTALL_CONFIG_NAME}" MATCHES "^([Rr][Ee][Ll][Ee][Aa][Ss][Ee])$") 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}) + if (artifacts_to_remove) + file(REMOVE ${artifacts_to_remove}) + endif() endif() ]=]) diff --git a/scripts/build/Jenkins/Jenkinsfile b/scripts/build/Jenkins/Jenkinsfile index 1388366661..beb4a21620 100644 --- a/scripts/build/Jenkins/Jenkinsfile +++ b/scripts/build/Jenkins/Jenkinsfile @@ -16,7 +16,7 @@ EMPTY_JSON = readJSON text: '{}' ENGINE_REPOSITORY_NAME = 'o3de' // Branches with build snapshots -BUILD_SNAPSHOTS = ['development', 'stabilization/2106'] +BUILD_SNAPSHOTS = ['development', 'stabilization/2110'] // Build snapshots with empty snapshot (for use with 'SNAPSHOT' pipeline paramater) BUILD_SNAPSHOTS_WITH_EMPTY = BUILD_SNAPSHOTS + '' @@ -102,6 +102,10 @@ def IsJobEnabled(branchName, buildTypeMap, pipelineName, platformName) { } } +def IsAPLogUpload(branchName, jobName) { + return !IsPullRequest(branchName) && jobName.toLowerCase().contains('asset') && env.AP_LOGS_S3_BUCKET +} + def GetRunningPipelineName(JENKINS_JOB_NAME) { // If the job name has an underscore def job_parts = JENKINS_JOB_NAME.tokenize('/')[0].tokenize('_') @@ -267,7 +271,7 @@ def CheckoutRepo(boolean disableSubmodules = false) { palRm('commitdate') } -def HandleDriveMount(String snapshot, String repositoryName, String projectName, String pipeline, String branchName, String platform, String buildType, String workspace, boolean recreateVolume = false) { +def HandleDriveMount(String snapshot, String repositoryName, String projectName, String pipeline, String branchName, String platform, String buildType, String workspace, boolean recreateVolume = false) { unstash name: 'incremental_build_script' def pythonCmd = '' @@ -277,9 +281,7 @@ def HandleDriveMount(String snapshot, String repositoryName, String projectName, if(recreateVolume) { palSh("${pythonCmd} ${INCREMENTAL_BUILD_SCRIPT_PATH} --action delete --repository_name ${repositoryName} --project ${projectName} --pipeline ${pipeline} --branch ${branchName} --platform ${platform} --build_type ${buildType}", 'Deleting volume', winSlashReplacement=false) } - timeout(5) { - palSh("${pythonCmd} ${INCREMENTAL_BUILD_SCRIPT_PATH} --action mount --snapshot ${snapshot} --repository_name ${repositoryName} --project ${projectName} --pipeline ${pipeline} --branch ${branchName} --platform ${platform} --build_type ${buildType}", 'Mounting volume', winSlashReplacement=false) - } + palSh("${pythonCmd} ${INCREMENTAL_BUILD_SCRIPT_PATH} --action mount --snapshot ${snapshot} --repository_name ${repositoryName} --project ${projectName} --pipeline ${pipeline} --branch ${branchName} --platform ${platform} --build_type ${buildType}", 'Mounting volume', winSlashReplacement=false) if(env.IS_UNIX) { sh label: 'Setting volume\'s ownership', @@ -431,6 +433,27 @@ def ExportTestScreenshots(Map options, String branchName, String platformName, S } } +def UploadAPLogs(Map options, String branchName, String platformName, String jobName, String workspace, Map params) { + dir("${workspace}/${ENGINE_REPOSITORY_NAME}") { + projects = params.CMAKE_LY_PROJECTS.split(",") + projects.each{ project -> + def apLogsPath = "${project}/user/log" + def s3UploadScriptPath = "scripts/build/tools/upload_to_s3.py" + if(env.IS_UNIX) { + pythonPath = "${options.PYTHON_DIR}/python.sh" + } + else { + pythonPath = "${options.PYTHON_DIR}/python.cmd" + } + def command = "${pythonPath} -u ${s3UploadScriptPath} --base_dir ${apLogsPath} " + + "--file_regex \".*\" --bucket ${env.AP_LOGS_S3_BUCKET} " + + "--search_subdirectories True --key_prefix ${env.JENKINS_JOB_NAME}/${branchName}/${env.BUILD_NUMBER}/${platformName}/${jobName} " + + '--extra_args {\\"ACL\\":\\"bucket-owner-full-control\\"}' + palSh(command, "Uploading AP logs for job ${jobName} for branch ${branchName}", false) + } + } + } + def PostBuildCommonSteps(String workspace, boolean mount = true) { echo 'Starting post-build common steps...' @@ -494,6 +517,14 @@ def CreateExportTestScreenshotsStage(Map pipelineConfig, String branchName, Stri } } +def CreateUploadAPLogsStage(Map pipelineConfig, String branchName, String platformName, String jobName, String workspace, Map params) { + return { + stage("${jobName}_upload_ap_logs") { + UploadAPLogs(pipelineConfig, branchName, platformName, jobName, workspace, params) + } + } +} + def CreateTeardownStage(Map environmentVars) { return { stage('Teardown') { @@ -518,9 +549,11 @@ def CreateSingleNode(Map pipelineConfig, def platform, def build_job, Map envVar CreateSetupStage(pipelineConfig, snapshot, repositoryName, projectName, pipelineName, branchName, platform.key, build_job.key, envVars, onlyMountEBSVolume).call() if(build_job.value.steps) { //this is a pipe with many steps so create all the build stages + pipelineEnvVars = GetBuildEnvVars(platform.value.PIPELINE_ENV ?: EMPTY_JSON, build_job.value.PIPELINE_ENV ?: EMPTY_JSON, pipelineName) build_job.value.steps.each { build_step -> build_job_name = build_step - envVars = GetBuildEnvVars(platform.value.PIPELINE_ENV ?: EMPTY_JSON, platform.value.build_types[build_step].PIPELINE_ENV ?: EMPTY_JSON, pipelineName) + // This addition of maps makes it that the right operand will override entries if they overlap with the left operand + envVars = pipelineEnvVars + GetBuildEnvVars(platform.value.PIPELINE_ENV ?: EMPTY_JSON, platform.value.build_types[build_step].PIPELINE_ENV ?: EMPTY_JSON, pipelineName) try { CreateBuildStage(pipelineConfig, platform.key, build_step, envVars).call() } @@ -543,6 +576,9 @@ def CreateSingleNode(Map pipelineConfig, def platform, def build_job, Map envVar error "Node disconnected during build: ${e}" // Error raised to retry stage on a new node } } + if (IsAPLogUpload(branchName, build_job_name)) { + CreateUploadAPLogsStage(pipelineConfig, branchName, platform.key, build_job_name, envVars['WORKSPACE'], platform.value.build_types[build_job_name].PARAMETERS).call() + } // All other errors will be raised outside the retry block currentResult = envVars['ON_FAILURE_MARK'] ?: 'FAILURE' currentException = e.toString() @@ -770,6 +806,7 @@ try { platform.value.build_types.each { build_job -> if (IsJobEnabled(branchName, build_job, pipelineName, platform.key)) { // User can filter jobs, jobs are tagged by pipeline def envVars = GetBuildEnvVars(platform.value.PIPELINE_ENV ?: EMPTY_JSON, build_job.value.PIPELINE_ENV ?: EMPTY_JSON, pipelineName) + envVars['JENKINS_JOB_NAME'] = env.JOB_NAME // Save original Jenkins job name to JENKINS_JOB_NAME envVars['JOB_NAME'] = "${branchName}_${platform.key}_${build_job.key}" // backwards compatibility, some scripts rely on this someBuildHappened = true diff --git a/scripts/o3de/o3de/download.py b/scripts/o3de/o3de/download.py index 98f5d051a4..9e576d64b5 100644 --- a/scripts/o3de/o3de/download.py +++ b/scripts/o3de/o3de/download.py @@ -20,6 +20,7 @@ import sys import urllib.parse import urllib.request import zipfile +from datetime import datetime from o3de import manifest, repo, utils, validation, register @@ -88,10 +89,9 @@ def get_downloadable(engine_name: str = None, search_func = lambda manifest_json_data: repo.search_repo(manifest_json_data, engine_name, project_name, gem_name, template_name) return repo.search_o3de_object(manifest_json, o3de_object_uris, search_func) - def download_o3de_object(object_name: str, default_folder_name: str, dest_path: str or pathlib.Path, object_type: str, downloadable_kwarg_key, skip_auto_register: bool, - download_progress_callback = None) -> int: + force_overwrite: bool, download_progress_callback = None) -> int: download_path = manifest.get_o3de_cache_folder() / default_folder_name / object_name download_path.mkdir(parents=True, exist_ok=True) @@ -125,8 +125,15 @@ def download_o3de_object(object_name: str, default_folder_name: str, dest_path: logger.error(f'Destination path cannot be empty.') return 1 if dest_path.exists(): - logger.error(f'Destination path {dest_path} already exists.') - return 1 + if not force_overwrite: + logger.error(f'Destination path {dest_path} already exists.') + return 1 + else: + try: + shutil.rmtree(dest_path) + except OSError: + logger.error(f'Could not remove existing destination path {dest_path}.') + return 1 dest_path.mkdir(exist_ok=True) @@ -149,38 +156,119 @@ def download_o3de_object(object_name: str, default_folder_name: str, dest_path: def download_engine(engine_name: str, dest_path: str or pathlib.Path, skip_auto_register: bool, + force_overwrite: bool, download_progress_callback = None) -> int: - return download_o3de_object(engine_name, 'engines', dest_path, 'engine', 'engine_name', skip_auto_register, download_progress_callback) + return download_o3de_object(engine_name, + 'engines', + dest_path, + 'engine', + 'engine_name', + skip_auto_register, + force_overwrite, + download_progress_callback) def download_project(project_name: str, dest_path: str or pathlib.Path, skip_auto_register: bool, + force_overwrite: bool, download_progress_callback = None) -> int: - return download_o3de_object(project_name, 'projects', dest_path, 'project', 'project_name', skip_auto_register, download_progress_callback) + return download_o3de_object(project_name, + 'projects', + dest_path, + 'project', + 'project_name', + skip_auto_register, + force_overwrite, + download_progress_callback) def download_gem(gem_name: str, dest_path: str or pathlib.Path, skip_auto_register: bool, + force_overwrite: bool, download_progress_callback = None) -> int: - return download_o3de_object(gem_name, 'gems', dest_path, 'gem', 'gem_name', skip_auto_register, download_progress_callback) + return download_o3de_object(gem_name, + 'gems', + dest_path, + 'gem', + 'gem_name', + skip_auto_register, + force_overwrite, + download_progress_callback) def download_template(template_name: str, dest_path: str or pathlib.Path, skip_auto_register: bool, + force_overwrite: bool, download_progress_callback = None) -> int: - return download_o3de_object(template_name, 'templates', dest_path, 'template', 'template_name', skip_auto_register, download_progress_callback) + return download_o3de_object(template_name, + 'templates', + dest_path, + 'template', + 'template_name', + skip_auto_register, + force_overwrite, + download_progress_callback) def download_restricted(restricted_name: str, dest_path: str or pathlib.Path, skip_auto_register: bool, + force_overwrite: bool, download_progress_callback = None) -> int: - return download_o3de_object(restricted_name, 'restricted', dest_path, 'restricted', 'restricted_name', skip_auto_register, download_progress_callback) + return download_o3de_object(restricted_name, + 'restricted', + dest_path, + 'restricted', + 'restricted_name', + skip_auto_register, + force_overwrite, + download_progress_callback) + +def is_o3de_object_update_available(object_name: str, downloadable_kwarg_key, local_last_updated: str) -> bool: + downloadable_object_data = get_downloadable(**{downloadable_kwarg_key : object_name}) + if not downloadable_object_data: + logger.error(f'Downloadable o3de object {object_name} not found.') + return False + try: + repo_copy_updated_string = downloadable_object_data['last_updated'] + except KeyError: + logger.warn(f'last_updated field not found for {object_name}.') + return False + + try: + local_last_updated_time = datetime.fromisoformat(local_last_updated) + except ValueError: + logger.warn(f'last_updated field has incorrect format for local copy of {downloadable_kwarg_key} {object_name}.') + # Possible that an earlier version did not have this field so still want to check against cached downloadable version + local_last_updated_time = datetime.min + + try: + repo_copy_updated_date = datetime.fromisoformat(repo_copy_updated_string) + except ValueError: + logger.error(f'last_updated field in incorrect format for repository copy of {downloadable_kwarg_key} {object_name}.') + return False + + return repo_copy_updated_date > local_last_updated_time + +def is_o3de_engine_update_available(engine_name: str, local_last_updated: str): + return is_o3de_object_update_available(engine_name, 'engine_name', local_last_updated) + +def is_o3de_project_update_available(project_name: str, local_last_updated: str): + return is_o3de_object_update_available(project_name, 'project_name', local_last_updated) + +def is_o3de_gem_update_available(gem_name: str, local_last_updated: str): + return is_o3de_object_update_available(gem_name, 'gem_name', local_last_updated) + +def is_o3de_template_update_available(template_name: str, local_last_updated: str): + return is_o3de_object_update_available(template_name, 'template_name', local_last_updated) + +def is_o3de_restricted_update_available(restricted_name: str, local_last_updated: str): + return is_o3de_object_update_available(restricted_name, 'restricted_name', local_last_updated) def _run_download(args: argparse) -> int: if args.override_home_folder: @@ -189,19 +277,23 @@ def _run_download(args: argparse) -> int: if args.engine_name: return download_engine(args.engine_name, args.dest_path, - args.skip_auto_register) + args.skip_auto_register, + args.force) elif args.project_name: return download_project(args.project_name, args.dest_path, - args.skip_auto_register) + args.skip_auto_register, + args.force) elif args.gem_name: return download_gem(args.gem_name, args.dest_path, - args.skip_auto_register) + args.skip_auto_register, + args.force) elif args.template_name: return download_template(args.template_name, args.dest_path, - args.skip_auto_register) + args.skip_auto_register, + args.force) return 1 @@ -230,6 +322,9 @@ def add_parser_args(parser): parser.add_argument('-sar', '--skip-auto-register', action='store_true', required=False, default=False, help = 'Skip the automatic registration of new object download') + parser.add_argument('-f', '--force', action='store_true', required=False, + default=False, + help = 'Force overwrite the current object') parser.add_argument('-ohf', '--override-home-folder', type=str, required=False, help='By default the home folder is the user folder, override it to this folder.') diff --git a/scripts/o3de/o3de/utils.py b/scripts/o3de/o3de/utils.py index 8663502a3f..9872c0779b 100755 --- a/scripts/o3de/o3de/utils.py +++ b/scripts/o3de/o3de/utils.py @@ -117,15 +117,23 @@ def backup_folder(folder: str or pathlib.Path) -> None: if backup_folder_name.is_dir(): renamed = True -def download_file(parsed_uri, download_path: pathlib.Path, download_progress_callback = None) -> int: +def download_file(parsed_uri, download_path: pathlib.Path, force_overwrite, download_progress_callback = None) -> int: """ :param parsed_uri: uniform resource identifier to zip file to download :param download_path: location path on disk to download file :download_progress_callback: callback called with the download progress as a percentage, returns true to request to cancel the download """ if download_path.is_file(): - logger.warn(f'File already downloaded to {download_path}.') - elif parsed_uri.scheme in ['http', 'https', 'ftp', 'ftps']: + if not force_overwrite: + logger.warn(f'File already downloaded to {download_path}.') + else: + try: + os.unlink(download_path) + except OSError: + logger.error(f'Could not remove existing download path {download_path}.') + return 1 + + if parsed_uri.scheme in ['http', 'https', 'ftp', 'ftps']: with urllib.request.urlopen(parsed_uri.geturl()) as s: download_file_size = 0 try: diff --git a/system_windows_pc.cfg b/system_windows_pc.cfg index aa53ed9323..e34c35e581 100644 --- a/system_windows_pc.cfg +++ b/system_windows_pc.cfg @@ -15,3 +15,4 @@ r_ShadersAllowCompilation = 1 -- Localization Settings sys_localization_format=0 +log_RemoteConsoleAllowedAddresses=127.0.0.1