diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_PostFXLayerAdded.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_PostFXLayerAdded.py index a81864f379..efef4c0a43 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_PostFXLayerAdded.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_PostFXLayerAdded.py @@ -70,8 +70,6 @@ def AtomEditorComponents_postfx_layer_AddedToEntity(): :return: None """ - import os - import azlmbr.legacy.general as general from editor_python_test_tools.editor_entity_utils import EditorEntity diff --git a/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/editor_entity_utils.py b/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/editor_entity_utils.py index 844f8de903..9e857ed8bc 100644 --- a/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/editor_entity_utils.py +++ b/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/editor_entity_utils.py @@ -107,6 +107,19 @@ class EditorComponent: return type_ids + +def convert_to_azvector3(xyz) -> azlmbr.math.Vector3: + """ + Converts a vector3-like element into a azlmbr.math.Vector3 + """ + if isinstance(xyz, Tuple) or isinstance(xyz, List): + assert len(xyz) == 3, ValueError("vector must be a 3 element list/tuple or azlmbr.math.Vector3") + return math.Vector3(float(xyz[0]), float(xyz[1]), float(xyz[2])) + elif isinstance(xyz, type(math.Vector3())): + return xyz + else: + raise ValueError("vector must be a 3 element list/tuple or azlmbr.math.Vector3") + class EditorEntity: """ Entity class is used to create and interact with Editor Entities. @@ -183,15 +196,6 @@ class EditorEntity: :return: EditorEntity class object """ - def convert_to_azvector3(xyz) -> math.Vector3: - if isinstance(xyz, Tuple) or isinstance(xyz, List): - assert len(xyz) == 3, ValueError("vector must be a 3 element list/tuple or azlmbr.math.Vector3") - return math.Vector3(*xyz) - elif isinstance(xyz, type(math.Vector3())): - return xyz - else: - raise ValueError("vector must be a 3 element list/tuple or azlmbr.math.Vector3") - if parent_id is None: parent_id = azlmbr.entity.EntityId() @@ -206,7 +210,7 @@ class EditorEntity: return entity # Methods - def set_name(self, entity_name: str): + def set_name(self, entity_name: str) -> None: """ Given entity_name, sets name to Entity :param: entity_name: Name of the entity to set @@ -324,7 +328,7 @@ class EditorEntity: self.start_status = status return status - def set_start_status(self, desired_start_status: str): + def set_start_status(self, desired_start_status: str) -> None: """ Set an entity as active/inactive at beginning of runtime or it is editor-only, given its entity id and the start status then return set success @@ -382,18 +386,75 @@ class EditorEntity: """ return editor.EditorEntityInfoRequestBus(bus.Event, "IsVisible", self.id) + # World Transform Functions + def get_world_translation(self) -> azlmbr.math.Vector3: + """ + Gets the world translation of the entity + """ + return azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", self.id) + + def set_world_translation(self, new_translation) -> None: + """ + Sets the new world translation of the current entity + """ + new_translation = convert_to_azvector3(new_translation) + azlmbr.components.TransformBus(azlmbr.bus.Event, "SetWorldTranslation", self.id, new_translation) + + def get_world_rotation(self) -> azlmbr.math.Quaternion: + """ + Gets the world rotation of the entity + """ + return azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldRotation", self.id) + + def set_world_rotation(self, new_rotation): + """ + Sets the new world rotation of the current entity + """ + new_rotation = convert_to_azvector3(new_rotation) + azlmbr.components.TransformBus(azlmbr.bus.Event, "SetWorldRotation", self.id, new_rotation) + + # Local Transform Functions + def get_local_uniform_scale(self) -> float: + """ + Gets the local uniform scale of the entity + """ + return azlmbr.components.TransformBus(azlmbr.bus.Event, "GetLocalUniformScale", self.id) + def set_local_uniform_scale(self, scale_float) -> None: """ - Sets the "SetLocalUniformScale" value on the entity. + Sets the local uniform scale value(relative to the parent) on the entity. :param scale_float: value for "SetLocalUniformScale" to set to. :return: None """ azlmbr.components.TransformBus(azlmbr.bus.Event, "SetLocalUniformScale", self.id, scale_float) - def set_local_rotation(self, vector3_rotation) -> None: + def get_local_rotation(self) -> azlmbr.math.Quaternion: """ - Sets the "SetLocalRotation" value on the entity. + Gets the local rotation of the entity + """ + return azlmbr.components.TransformBus(azlmbr.bus.Event, "GetLocalRotation", self.id) + + def set_local_rotation(self, new_rotation) -> None: + """ + Sets the set the local rotation(relative to the parent) of the current entity. :param vector3_rotation: The math.Vector3 value to use for rotation on the entity (uses radians). :return: None """ - azlmbr.components.TransformBus(azlmbr.bus.Event, "SetLocalRotation", self.id, vector3_rotation) + new_rotation = convert_to_azvector3(new_rotation) + azlmbr.components.TransformBus(azlmbr.bus.Event, "SetLocalRotation", self.id, new_rotation) + + def get_local_translation(self) -> azlmbr.math.Vector3: + """ + Gets the local translation of the current entity. + :return: The math.Vector3 value of the local translation. + """ + return azlmbr.components.TransformBus(azlmbr.bus.Event, "GetLocalTranslation", self.id) + + def set_local_translation(self, new_translation) -> None: + """ + Sets the local translation(relative to the parent) of the current entity. + :param vector3_translation: The math.Vector3 value to use for translation on the entity. + :return: None + """ + new_translation = convert_to_azvector3(new_translation) + azlmbr.components.TransformBus(azlmbr.bus.Event, "SetLocalTranslation", self.id, new_translation) diff --git a/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/prefab_utils.py b/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/prefab_utils.py index 10a6ab1ef4..76c2a42c0a 100644 --- a/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/prefab_utils.py +++ b/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/prefab_utils.py @@ -138,6 +138,14 @@ class PrefabInstance: self.container_entity = reparented_container_entity current_instance_prefab.instances.add(self) + def get_direct_child_entities(self): + """ + Returns the entities only contained in the current prefab instance. + This function does not return entities contained in other child instances + """ + return self.container_entity.get_children() + + # This is a helper class which contains some of the useful information about a prefab template. class Prefab: diff --git a/AutomatedTesting/Gem/PythonTests/Physics/TestSuite_Main_Optimized.py b/AutomatedTesting/Gem/PythonTests/Physics/TestSuite_Main_Optimized.py index 63d3b58249..3d668a2085 100644 --- a/AutomatedTesting/Gem/PythonTests/Physics/TestSuite_Main_Optimized.py +++ b/AutomatedTesting/Gem/PythonTests/Physics/TestSuite_Main_Optimized.py @@ -53,6 +53,27 @@ class EditorSingleTest_WithFileOverrides(EditorSingleTest): for f in original_file_list: fm._restore_file(f, file_list[f]) +@pytest.mark.xfail(reason="Optimized tests are experimental, we will enable xfail and monitor them temporarily.") +@pytest.mark.SUITE_main +@pytest.mark.parametrize("launcher_platform", ['windows_editor']) +@pytest.mark.parametrize("project", ["AutomatedTesting"]) +class TestAutomationWithPrefabSystemEnabled(EditorTestSuite): + + global_extra_cmdline_args = ['-BatchMode', '-autotest_mode', + 'extra_cmdline_args=["--regset=/Amazon/Preferences/EnablePrefabSystem=true"]'] + + @staticmethod + def get_number_parallel_editors(): + return 16 + + class C4982801_PhysXColliderShape_CanBeSelected(EditorSharedTest): + from .tests.collider import Collider_BoxShapeEditing as test_module + + class C4982800_PhysXColliderShape_CanBeSelected(EditorSharedTest): + from .tests.collider import Collider_SphereShapeEditing as test_module + + class C4982802_PhysXColliderShape_CanBeSelected(EditorSharedTest): + from .tests.collider import Collider_CapsuleShapeEditing as test_module @pytest.mark.xfail(reason="Optimized tests are experimental, we will enable xfail and monitor them temporarily.") @pytest.mark.SUITE_main @@ -286,15 +307,6 @@ class TestAutomation(EditorTestSuite): class C19723164_ShapeCollider_WontCrashEditor(EditorSharedTest): from .tests.shape_collider import ShapeCollider_LargeNumberOfShapeCollidersWontCrashEditor as test_module - class C4982800_PhysXColliderShape_CanBeSelected(EditorSharedTest): - from .tests.collider import Collider_SphereShapeEditting as test_module - - class C4982801_PhysXColliderShape_CanBeSelected(EditorSharedTest): - from .tests.collider import Collider_BoxShapeEditting as test_module - - class C4982802_PhysXColliderShape_CanBeSelected(EditorSharedTest): - from .tests.collider import Collider_CapsuleShapeEditting as test_module - class C12905528_ForceRegion_WithNonTriggerCollider(EditorSharedTest): from .tests.force_region import ForceRegion_WithNonTriggerColliderWarning as test_module # Fixme: expected_lines = ["[Warning] (PhysX Force Region) - Please ensure collider component marked as trigger exists in entity"] diff --git a/AutomatedTesting/Gem/PythonTests/Physics/TestSuite_Periodic.py b/AutomatedTesting/Gem/PythonTests/Physics/TestSuite_Periodic.py index e5dd9adbb9..55e51dd2f8 100755 --- a/AutomatedTesting/Gem/PythonTests/Physics/TestSuite_Periodic.py +++ b/AutomatedTesting/Gem/PythonTests/Physics/TestSuite_Periodic.py @@ -401,19 +401,22 @@ class TestAutomation(TestAutomationBase): self._run_test(request, workspace, editor, test_module) @revert_physics_config - def test_Collider_SphereShapeEditting(self, request, workspace, editor, launcher_platform): - from .tests.collider import Collider_SphereShapeEditting as test_module - self._run_test(request, workspace, editor, test_module) + def test_Collider_SphereShapeEditing(self, request, workspace, editor, launcher_platform): + from .tests.collider import Collider_SphereShapeEditing as test_module + self._run_test(request, workspace, editor, test_module, + extra_cmdline_args=["--regset=/Amazon/Preferences/EnablePrefabSystem=true"]) @revert_physics_config - def test_Collider_BoxShapeEditting(self, request, workspace, editor, launcher_platform): - from .tests.collider import Collider_BoxShapeEditting as test_module - self._run_test(request, workspace, editor, test_module) + def test_Collider_BoxShapeEditing(self, request, workspace, editor, launcher_platform): + from .tests.collider import Collider_BoxShapeEditing as test_module + self._run_test(request, workspace, editor, test_module, + extra_cmdline_args=["--regset=/Amazon/Preferences/EnablePrefabSystem=true"]) @revert_physics_config - def test_Collider_CapsuleShapeEditting(self, request, workspace, editor, launcher_platform): - from .tests.collider import Collider_CapsuleShapeEditting as test_module - self._run_test(request, workspace, editor, test_module) + def test_Collider_CapsuleShapeEditing(self, request, workspace, editor, launcher_platform): + from .tests.collider import Collider_CapsuleShapeEditing as test_module + self._run_test(request, workspace, editor, test_module, + extra_cmdline_args=["--regset=/Amazon/Preferences/EnablePrefabSystem=true"]) def test_ForceRegion_WithNonTriggerColliderWarning(self, request, workspace, editor, launcher_platform): from .tests.force_region import ForceRegion_WithNonTriggerColliderWarning as test_module diff --git a/AutomatedTesting/Gem/PythonTests/Physics/tests/collider/Collider_BoxShapeEditting.py b/AutomatedTesting/Gem/PythonTests/Physics/tests/collider/Collider_BoxShapeEditing.py similarity index 97% rename from AutomatedTesting/Gem/PythonTests/Physics/tests/collider/Collider_BoxShapeEditting.py rename to AutomatedTesting/Gem/PythonTests/Physics/tests/collider/Collider_BoxShapeEditing.py index 68ff0b4edc..a6730c8559 100644 --- a/AutomatedTesting/Gem/PythonTests/Physics/tests/collider/Collider_BoxShapeEditting.py +++ b/AutomatedTesting/Gem/PythonTests/Physics/tests/collider/Collider_BoxShapeEditing.py @@ -19,7 +19,7 @@ class Tests(): # fmt: on -def Collider_BoxShapeEditting(): +def Collider_BoxShapeEditing(): """ Summary: Adding PhysX Collider and Shape components to test entity, then attempting to modify the shape's dimensions @@ -73,7 +73,7 @@ def Collider_BoxShapeEditting(): helper.init_idle() # 1) Load the empty level - helper.open_level("Physics", "Base") + helper.open_level("", "Base") # 2) Create the test entity test_entity = Entity.create_editor_entity("Test Entity") @@ -102,4 +102,4 @@ def Collider_BoxShapeEditting(): if __name__ == "__main__": from editor_python_test_tools.utils import Report - Report.start_test(Collider_BoxShapeEditting) + Report.start_test(Collider_BoxShapeEditing) diff --git a/AutomatedTesting/Gem/PythonTests/Physics/tests/collider/Collider_CapsuleShapeEditting.py b/AutomatedTesting/Gem/PythonTests/Physics/tests/collider/Collider_CapsuleShapeEditing.py similarity index 97% rename from AutomatedTesting/Gem/PythonTests/Physics/tests/collider/Collider_CapsuleShapeEditting.py rename to AutomatedTesting/Gem/PythonTests/Physics/tests/collider/Collider_CapsuleShapeEditing.py index 7df12c68f0..12435cc54a 100644 --- a/AutomatedTesting/Gem/PythonTests/Physics/tests/collider/Collider_CapsuleShapeEditting.py +++ b/AutomatedTesting/Gem/PythonTests/Physics/tests/collider/Collider_CapsuleShapeEditing.py @@ -19,7 +19,7 @@ class Tests(): # fmt: on -def Collider_CapsuleShapeEditting(): +def Collider_CapsuleShapeEditing(): """ Summary: Adding PhysX Collider and Shape components to test entity, then attempting to modify the shape's dimensions @@ -74,7 +74,7 @@ def Collider_CapsuleShapeEditting(): helper.init_idle() # 1) Load the empty level - helper.open_level("Physics", "Base") + helper.open_level("", "Base") # 2) Create the test entity test_entity = Entity.create_editor_entity("Test Entity") @@ -102,4 +102,4 @@ def Collider_CapsuleShapeEditting(): if __name__ == "__main__": from editor_python_test_tools.utils import Report - Report.start_test(Collider_CapsuleShapeEditting) + Report.start_test(Collider_CapsuleShapeEditing) diff --git a/AutomatedTesting/Gem/PythonTests/Physics/tests/collider/Collider_SphereShapeEditting.py b/AutomatedTesting/Gem/PythonTests/Physics/tests/collider/Collider_SphereShapeEditing.py similarity index 96% rename from AutomatedTesting/Gem/PythonTests/Physics/tests/collider/Collider_SphereShapeEditting.py rename to AutomatedTesting/Gem/PythonTests/Physics/tests/collider/Collider_SphereShapeEditing.py index bffd041d92..ef91235411 100644 --- a/AutomatedTesting/Gem/PythonTests/Physics/tests/collider/Collider_SphereShapeEditting.py +++ b/AutomatedTesting/Gem/PythonTests/Physics/tests/collider/Collider_SphereShapeEditing.py @@ -19,7 +19,7 @@ class Tests(): # fmt: on -def Collider_SphereShapeEditting(): +def Collider_SphereShapeEditing(): """ Summary: Adding PhysX Collider and Shape components to test entity, then attempting to modify the shape's dimensions @@ -57,7 +57,7 @@ def Collider_SphereShapeEditting(): helper.init_idle() # 1) Load the empty level - helper.open_level("Physics", "Base") + helper.open_level("", "Base") # 2) Create the test entity test_entity = Entity.create_editor_entity("Test Entity") @@ -90,4 +90,4 @@ def Collider_SphereShapeEditting(): if __name__ == "__main__": from editor_python_test_tools.utils import Report - Report.start_test(Collider_SphereShapeEditting) + Report.start_test(Collider_SphereShapeEditing) diff --git a/AutomatedTesting/Gem/PythonTests/Prefab/TestSuite_Main.py b/AutomatedTesting/Gem/PythonTests/Prefab/TestSuite_Main.py index 5337f0669c..479915752f 100644 --- a/AutomatedTesting/Gem/PythonTests/Prefab/TestSuite_Main.py +++ b/AutomatedTesting/Gem/PythonTests/Prefab/TestSuite_Main.py @@ -55,3 +55,11 @@ class TestAutomation(TestAutomationBase): def test_PrefabBasicWorkflow_CreateAndDuplicatePrefab(self, request, workspace, editor, launcher_platform): from .tests import PrefabBasicWorkflow_CreateAndDuplicatePrefab as test_module self._run_prefab_test(request, workspace, editor, test_module) + + def test_PrefabComplexWorflow_CreatePrefabOfChildEntity(self, request, workspace, editor, launcher_platform): + from .tests import PrefabComplexWorflow_CreatePrefabOfChildEntity as test_module + self._run_prefab_test(request, workspace, editor, test_module, autotest_mode=False) + + def test_PrefabComplexWorflow_CreatePrefabInsidePrefab(self, request, workspace, editor, launcher_platform): + from .tests import PrefabComplexWorflow_CreatePrefabInsidePrefab as test_module + self._run_prefab_test(request, workspace, editor, test_module, autotest_mode=False) diff --git a/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabComplexWorflow_CreatePrefabInsidePrefab.py b/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabComplexWorflow_CreatePrefabInsidePrefab.py new file mode 100644 index 0000000000..e14fc96449 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabComplexWorflow_CreatePrefabInsidePrefab.py @@ -0,0 +1,57 @@ +""" +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 +""" + +def PrefabComplexWorflow_CreatePrefabInsidePrefab(): + """ + Test description: + - Creates an entity with a physx collider + - Creates a prefab "Outer_prefab" and an instance based of that entity + - Creates a prefab "Inner_prefab" inside "Outer_prefab" based the entity contained inside of it + Checks that the entity is correctly handlded by the prefab system checking the name and that it contains the physx collider + """ + + from editor_python_test_tools.editor_entity_utils import EditorEntity + from editor_python_test_tools.prefab_utils import Prefab + + import PrefabTestUtils as prefab_test_utils + + prefab_test_utils.open_base_tests_level() + + # Creates a new Entity at the root level + # Asserts if creation didn't succeed + entity = EditorEntity.create_editor_entity_at((100.0, 100.0, 100.0), name = "TestEntity") + assert entity.id.IsValid(), "Couldn't create entity" + entity.add_component("PhysX Collider") + assert entity.has_component("PhysX Collider"), "Attempted to add a PhysX Collider but no physx collider collider was found afterwards" + + # Create a prefab based on that entity + outer_prefab, outer_instance = Prefab.create_prefab([entity], "Outer_prefab") + # The test should be now inside the outer prefab instance. + entity = outer_instance.get_direct_child_entities()[0] + # We track if that is the same entity by checking the name and if it still contains the component that we created before + assert entity.get_name() == "TestEntity", f"Entity name inside outer_prefab doesn't match the original name, original:'TestEntity' current:'{entity.get_name()}'" + assert entity.has_component("PhysX Collider"), "Entity name inside outer_prefab doesn't have the collider component it should" + + # Now, create another prefab, based on the entity that is inside outer_prefab + inner_prefab, inner_instance = Prefab.create_prefab([entity], "Inner_prefab") + # The test entity should now be inside the inner prefab instance + entity = inner_instance.get_direct_child_entities()[0] + # We track if that is the same entity by checking the name and if it still contains the component that we created before + assert entity.get_name() == "TestEntity", f"Entity name inside inner_prefab doesn't match the original name, original:'TestEntity' current:'{entity.get_name()}'" + assert entity.has_component("PhysX Collider"), "Entity name inside inner_prefab doesn't have the collider component it should" + + # Verify hierarchy of entities: + # Outer_prefab + # |- Inner_prefab + # | |- TestEntity + assert entity.get_parent_id() == inner_instance.container_entity.id + assert inner_instance.container_entity.get_parent_id() == outer_instance.container_entity.id + + +if __name__ == "__main__": + from editor_python_test_tools.utils import Report + Report.start_test(PrefabComplexWorflow_CreatePrefabInsidePrefab) diff --git a/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabComplexWorflow_CreatePrefabOfChildEntity.py b/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabComplexWorflow_CreatePrefabOfChildEntity.py new file mode 100644 index 0000000000..dec44d52be --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabComplexWorflow_CreatePrefabOfChildEntity.py @@ -0,0 +1,52 @@ +""" +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 +""" + +def PrefabComplexWorflow_CreatePrefabOfChildEntity(): + """ + Test description: + - Creates two entities, parent and child. Child entity has Parent entity as its parent. + - Creates a prefab of the child entity. + Test is successful if the new instanced prefab of the child has the parent entity id + """ + + CAR_PREFAB_FILE_NAME = 'car_prefab' + + from editor_python_test_tools.editor_entity_utils import EditorEntity + from editor_python_test_tools.prefab_utils import Prefab + + import PrefabTestUtils as prefab_test_utils + + prefab_test_utils.open_base_tests_level() + + # Creates a new Entity at the root level + # Asserts if creation didn't succeed + parent_entity = EditorEntity.create_editor_entity_at((100.0, 100.0, 100.0)) + assert parent_entity.id.IsValid(), "Couldn't create parent entity" + + child_entity = EditorEntity.create_editor_entity(parent_id=parent_entity.id) + assert child_entity.id.IsValid(), "Couldn't create child entity" + assert child_entity.get_world_translation().IsClose(parent_entity.get_world_translation()), f"Child entity position{child_entity.get_world_translation().ToString()}" \ + f" is not located at the same position as the parent{parent_entity.get_world_translation().ToString()}" + + # Asserts if prefab creation doesn't succeed + child_prefab, child_instance = Prefab.create_prefab([child_entity], CAR_PREFAB_FILE_NAME) + child_entity_on_child_instance = child_instance.get_direct_child_entities()[0] + assert child_instance.container_entity.get_parent_id().IsValid(), "Newly instanced entity has no parent" + assert child_instance.container_entity.get_parent_id() == parent_entity.id, "Newly instanced entity parent does not match the expected parent" + assert child_instance.container_entity.get_world_translation().IsClose(parent_entity.get_world_translation()), "Newly instanced entity position is not located at the same position as the parent" + # Move the parent position, it should update the child position + parent_entity.set_world_translation((200.0, 200.0, 200.0)) + child_instance_translation = child_instance.container_entity.get_world_translation() + assert child_instance_translation.IsClose(azlmbr.math.Vector3(200.0, 200.0, 200.0)), f"Instance position position{child_instance_translation.ToString()} didn't get updated" \ + f" to the same position as the parent{parent_entity.get_world_translation().ToString()}" + child_translation = child_entity_on_child_instance.get_world_translation() + assert child_translation.IsClose(azlmbr.math.Vector3(200.0, 200.0, 200.0)), f"Entity position{child_translation.ToString()} of the instance didn't get updated" \ + f" to the same position as the parent{parent_entity.get_world_translation().ToString()}" + +if __name__ == "__main__": + from editor_python_test_tools.utils import Report + Report.start_test(PrefabComplexWorflow_CreatePrefabOfChildEntity) diff --git a/AutomatedTesting/Gem/PythonTests/PythonAssetBuilder/mock_asset_builder.py b/AutomatedTesting/Gem/PythonTests/PythonAssetBuilder/mock_asset_builder.py index 443a420b61..a6e8b97b63 100644 --- a/AutomatedTesting/Gem/PythonTests/PythonAssetBuilder/mock_asset_builder.py +++ b/AutomatedTesting/Gem/PythonTests/PythonAssetBuilder/mock_asset_builder.py @@ -23,7 +23,7 @@ def create_jobs(request): jobDescriptorList = [] for platformInfo in request.enabledPlatforms: jobDesc = azlmbr.asset.builder.JobDescriptor() - jobDesc.jobKey = jobKeyName + jobDesc.jobKey = f'{jobKeyName}-{platformInfo.identifier}' jobDesc.set_platform_identifier(platformInfo.identifier) jobDescriptorList.append(jobDesc) @@ -38,7 +38,7 @@ def on_create_jobs(args): return create_jobs(request) except: log_exception_traceback() - # returing back a default CreateJobsResponse() records an asset error + # returning back a default CreateJobsResponse() records an asset error return azlmbr.asset.builder.CreateJobsResponse() def process_file(request): @@ -58,6 +58,7 @@ def process_file(request): fileOutput = open(tempFilename, "w") fileOutput.write('{}') fileOutput.close() + print(f'Wrote mock asset file: {tempFilename}') # generate a product asset file entry subId = binascii.crc32(mockFilename.encode()) diff --git a/AutomatedTesting/Gem/PythonTests/editor_test_testing/TestSuite_Main.py b/AutomatedTesting/Gem/PythonTests/editor_test_testing/TestSuite_Main.py index 15ba6690a1..50b89ab138 100644 --- a/AutomatedTesting/Gem/PythonTests/editor_test_testing/TestSuite_Main.py +++ b/AutomatedTesting/Gem/PythonTests/editor_test_testing/TestSuite_Main.py @@ -15,6 +15,7 @@ import sys import importlib import re +import ly_test_tools from ly_test_tools import LAUNCHERS sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -25,8 +26,15 @@ import ly_test_tools.environment.process_utils as process_utils import argparse, sys -@pytest.mark.SUITE_main -@pytest.mark.parametrize("launcher_platform", ['windows_editor']) +def get_editor_launcher_platform(): + if ly_test_tools.WINDOWS: + return "windows_editor" + elif ly_test_tools.LINUX: + return "linux_editor" + else: + return None + +@pytest.mark.parametrize("launcher_platform", [get_editor_launcher_platform()]) @pytest.mark.parametrize("project", ["AutomatedTesting"]) class TestEditorTest: @@ -69,7 +77,7 @@ class TestEditorTest: from ly_test_tools.o3de.editor_test import EditorSingleTest, EditorSharedTest, EditorTestSuite @pytest.mark.SUITE_main - @pytest.mark.parametrize("launcher_platform", ['windows_editor']) + @pytest.mark.parametrize("launcher_platform", [{get_editor_launcher_platform()}]) @pytest.mark.parametrize("project", ["AutomatedTesting"]) class TestAutomation(EditorTestSuite): class test_single(EditorSingleTest): @@ -123,7 +131,7 @@ class TestEditorTest: from ly_test_tools.o3de.editor_test import EditorSingleTest, EditorSharedTest, EditorTestSuite @pytest.mark.SUITE_main - @pytest.mark.parametrize("launcher_platform", ['windows_editor']) + @pytest.mark.parametrize("launcher_platform", [{get_editor_launcher_platform()}]) @pytest.mark.parametrize("project", ["AutomatedTesting"]) class TestAutomation(EditorTestSuite): {module_class_code} diff --git a/AutomatedTesting/Levels/Base/Base.prefab b/AutomatedTesting/Levels/Base/Base.prefab new file mode 100644 index 0000000000..98495663b7 --- /dev/null +++ b/AutomatedTesting/Levels/Base/Base.prefab @@ -0,0 +1,53 @@ +{ + "ContainerEntity": { + "Id": "ContainerEntity", + "Name": "Base", + "Components": { + "Component_[10182366347512475253]": { + "$type": "EditorPrefabComponent", + "Id": 10182366347512475253 + }, + "Component_[12917798267488243668]": { + "$type": "EditorPendingCompositionComponent", + "Id": 12917798267488243668 + }, + "Component_[3261249813163778338]": { + "$type": "EditorOnlyEntityComponent", + "Id": 3261249813163778338 + }, + "Component_[3837204912784440039]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 3837204912784440039 + }, + "Component_[4272963378099646759]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 4272963378099646759, + "Parent Entity": "" + }, + "Component_[4848458548047175816]": { + "$type": "EditorVisibilityComponent", + "Id": 4848458548047175816 + }, + "Component_[5787060997243919943]": { + "$type": "EditorInspectorComponent", + "Id": 5787060997243919943 + }, + "Component_[7804170251266531779]": { + "$type": "EditorLockComponent", + "Id": 7804170251266531779 + }, + "Component_[7874177159288365422]": { + "$type": "EditorEntitySortComponent", + "Id": 7874177159288365422 + }, + "Component_[8018146290632383969]": { + "$type": "EditorEntityIconComponent", + "Id": 8018146290632383969 + }, + "Component_[8452360690590857075]": { + "$type": "SelectionComponent", + "Id": 8452360690590857075 + } + } + } +} \ No newline at end of file diff --git a/Code/Editor/CVarMenu.h b/Code/Editor/CVarMenu.h index efcc8e8caf..5195bd99d7 100644 --- a/Code/Editor/CVarMenu.h +++ b/Code/Editor/CVarMenu.h @@ -16,9 +16,12 @@ #include #include +struct ICVar; + class CVarMenu : public QMenu { + Q_OBJECT public: // CVar that can be toggled on and off struct CVarToggle diff --git a/Code/Editor/ConfigGroup.cpp b/Code/Editor/ConfigGroup.cpp index 74fe6f7b5c..42236e43dd 100644 --- a/Code/Editor/ConfigGroup.cpp +++ b/Code/Editor/ConfigGroup.cpp @@ -19,10 +19,9 @@ namespace Config CConfigGroup::~CConfigGroup() { - for (TConfigVariables::const_iterator it = m_vars.begin(); - it != m_vars.end(); ++it) + for (IConfigVar* var : m_vars) { - delete (*it); + delete var; } } @@ -31,17 +30,15 @@ namespace Config m_vars.push_back(var); } - uint32 CConfigGroup::GetVarCount() + AZ::u32 CConfigGroup::GetVarCount() { - return static_cast(m_vars.size()); + return aznumeric_cast(m_vars.size()); } IConfigVar* CConfigGroup::GetVar(const char* szName) { - for (TConfigVariables::const_iterator it = m_vars.begin(); - it != m_vars.end(); ++it) + for (IConfigVar* var : m_vars) { - IConfigVar* var = (*it); if (0 == _stricmp(szName, var->GetName().c_str())) { return var; @@ -53,20 +50,19 @@ namespace Config const IConfigVar* CConfigGroup::GetVar(const char* szName) const { - for (TConfigVariables::const_iterator it = m_vars.begin(); - it != m_vars.end(); ++it) + for (const IConfigVar* var : m_vars) { - IConfigVar* var = (*it); if (0 == _stricmp(szName, var->GetName().c_str())) { return var; } + } return nullptr; } - IConfigVar* CConfigGroup::GetVar(uint index) + IConfigVar* CConfigGroup::GetVar(AZ::u32 index) { if (index < m_vars.size()) { @@ -76,7 +72,7 @@ namespace Config return nullptr; } - const IConfigVar* CConfigGroup::GetVar(uint index) const + const IConfigVar* CConfigGroup::GetVar(AZ::u32 index) const { if (index < m_vars.size()) { @@ -89,114 +85,110 @@ namespace Config void CConfigGroup::SaveToXML(XmlNodeRef node) { // save only values that don't have default values - for (TConfigVariables::const_iterator it = m_vars.begin(); - it != m_vars.end(); ++it) + for (const IConfigVar* var : m_vars) { - IConfigVar* var = (*it); - if (!var->IsFlagSet(IConfigVar::eFlag_DoNotSave)) + if (var->IsFlagSet(IConfigVar::eFlag_DoNotSave) || var->IsDefault()) { - if (!var->IsDefault()) - { - const char* szName = var->GetName().c_str(); + continue; + } - switch (var->GetType()) - { - case IConfigVar::eType_BOOL: - { - bool currentValue = false; - var->Get(¤tValue); - node->setAttr(szName, currentValue); - break; - } + const char* szName = var->GetName().c_str(); - case IConfigVar::eType_INT: - { - int currentValue = 0; - var->Get(¤tValue); - node->setAttr(szName, currentValue); - break; - } + switch (var->GetType()) + { + case IConfigVar::eType_BOOL: + { + bool currentValue = false; + var->Get(¤tValue); + node->setAttr(szName, currentValue); + break; + } - case IConfigVar::eType_FLOAT: - { - float currentValue = 0; - var->Get(¤tValue); - node->setAttr(szName, currentValue); - break; - } + case IConfigVar::eType_INT: + { + int currentValue = 0; + var->Get(¤tValue); + node->setAttr(szName, currentValue); + break; + } - case IConfigVar::eType_STRING: - { - AZStd::string currentValue; - var->Get(¤tValue); - node->setAttr(szName, currentValue.c_str()); - break; - } - } - } + case IConfigVar::eType_FLOAT: + { + float currentValue = 0; + var->Get(¤tValue); + node->setAttr(szName, currentValue); + break; + } + + case IConfigVar::eType_STRING: + { + AZStd::string currentValue; + var->Get(¤tValue); + node->setAttr(szName, currentValue.c_str()); + break; + } } } } void CConfigGroup::LoadFromXML(XmlNodeRef node) { - // save only values that don't have default values - for (TConfigVariables::const_iterator it = m_vars.begin(); - it != m_vars.end(); ++it) + // load values that are save-able + for (IConfigVar* var : m_vars) { - IConfigVar* var = (*it); - if (!var->IsFlagSet(IConfigVar::eFlag_DoNotSave)) + if (var->IsFlagSet(IConfigVar::eFlag_DoNotSave)) { - const char* szName = var->GetName().c_str(); + continue; + } + const char* szName = var->GetName().c_str(); - switch (var->GetType()) + switch (var->GetType()) + { + case IConfigVar::eType_BOOL: + { + bool currentValue = false; + var->GetDefault(¤tValue); + if (node->getAttr(szName, currentValue)) { - case IConfigVar::eType_BOOL: - { - bool currentValue = false; - var->GetDefault(¤tValue); - if (node->getAttr(szName, currentValue)) - { - var->Set(¤tValue); - } - break; + var->Set(¤tValue); } + break; + } - case IConfigVar::eType_INT: + case IConfigVar::eType_INT: + { + int currentValue = 0; + var->GetDefault(¤tValue); + if (node->getAttr(szName, currentValue)) { - int currentValue = 0; - var->GetDefault(¤tValue); - if (node->getAttr(szName, currentValue)) - { - var->Set(¤tValue); - } - break; + var->Set(¤tValue); } + break; + } - case IConfigVar::eType_FLOAT: + case IConfigVar::eType_FLOAT: + { + float currentValue = 0; + var->GetDefault(¤tValue); + if (node->getAttr(szName, currentValue)) { - float currentValue = 0; - var->GetDefault(¤tValue); - if (node->getAttr(szName, currentValue)) - { - var->Set(¤tValue); - } - break; + var->Set(¤tValue); } + break; + } - case IConfigVar::eType_STRING: + case IConfigVar::eType_STRING: + { + AZStd::string currentValue; + var->GetDefault(¤tValue); + QString readValue(currentValue.c_str()); + if (node->getAttr(szName, readValue)) { - AZStd::string currentValue; - var->GetDefault(¤tValue); - QString readValue(currentValue.c_str()); - if (node->getAttr(szName, readValue)) - { - currentValue = readValue.toUtf8().data(); - var->Set(¤tValue); - } - break; - } + currentValue = readValue.toUtf8().data(); + var->Set(¤tValue); } + break; + } } } } diff --git a/Code/Editor/ConfigGroup.h b/Code/Editor/ConfigGroup.h index 769a29ba8a..004725e32c 100644 --- a/Code/Editor/ConfigGroup.h +++ b/Code/Editor/ConfigGroup.h @@ -8,8 +8,12 @@ #pragma once -#ifndef CRYINCLUDE_EDITOR_CONFIGGROUP_H -#define CRYINCLUDE_EDITOR_CONFIGGROUP_H +#include +#include +#include + +struct ICVar; +class XmlNodeRef; namespace Config { @@ -32,7 +36,7 @@ namespace Config eFlag_DoNotSave = 1 << 2, }; - IConfigVar(const char* szName, const char* szDescription, EType varType, uint8 flags) + IConfigVar(const char* szName, const char* szDescription, EType varType, AZ::u8 flags) : m_name(szName) , m_description(szDescription) , m_type(varType) @@ -42,22 +46,22 @@ namespace Config virtual ~IConfigVar() = default; - ILINE EType GetType() const + AZ_FORCE_INLINE EType GetType() const { return m_type; } - ILINE const AZStd::string& GetName() const + AZ_FORCE_INLINE const AZStd::string& GetName() const { return m_name; } - ILINE const AZStd::string& GetDescription() const + AZ_FORCE_INLINE const AZStd::string& GetDescription() const { return m_description; } - ILINE bool IsFlagSet(EFlags flag) const + AZ_FORCE_INLINE bool IsFlagSet(EFlags flag) const { return 0 != (m_flags & flag); } @@ -68,73 +72,28 @@ namespace Config virtual void GetDefault(void* outPtr) const = 0; virtual void Reset() = 0; - static EType TranslateType(const bool&) { return eType_BOOL; } - static EType TranslateType(const int&) { return eType_INT; } - static EType TranslateType(const float&) { return eType_FLOAT; } - static EType TranslateType(const AZStd::string&) { return eType_STRING; } + static constexpr EType TranslateType(const bool&) { return eType_BOOL; } + static constexpr EType TranslateType(const int&) { return eType_INT; } + static constexpr EType TranslateType(const float&) { return eType_FLOAT; } + static constexpr EType TranslateType(const AZStd::string&) { return eType_STRING; } protected: EType m_type; - uint8 m_flags; + AZ::u8 m_flags; AZStd::string m_name; AZStd::string m_description; void* m_ptr; ICVar* m_pCVar; }; - // Typed wrapper for config variable - template - class TConfigVar - : public IConfigVar - { - private: - T m_default; - - public: - TConfigVar(const char* szName, const char* szDescription, uint8 flags, T& ptr, const T& defaultValue) - : IConfigVar(szName, szDescription, IConfigVar::TranslateType(ptr), flags) - , m_default(defaultValue) - { - m_ptr = &ptr; - - // reset to default value on initializations - ptr = defaultValue; - } - - virtual void Get(void* outPtr) const - { - *reinterpret_cast(outPtr) = *reinterpret_cast(m_ptr); - } - - virtual void Set(const void* ptr) - { - *reinterpret_cast(m_ptr) = *reinterpret_cast(ptr); - } - - virtual void Reset() - { - *reinterpret_cast(m_ptr) = m_default; - } - - virtual void GetDefault(void* outPtr) const - { - *reinterpret_cast(outPtr) = m_default; - } - - virtual bool IsDefault() const - { - return *reinterpret_cast(m_ptr) == m_default; - } - }; - // Group of configuration variables with optional mapping to CVars class CConfigGroup { private: - typedef std::vector TConfigVariables; + using TConfigVariables = AZStd::vector ; TConfigVariables m_vars; - typedef std::vector TConsoleVariables; + using TConsoleVariables = AZStd::vector; TConsoleVariables m_consoleVars; public: @@ -142,20 +101,13 @@ namespace Config virtual ~CConfigGroup(); void AddVar(IConfigVar* var); - uint32 GetVarCount(); + AZ::u32 GetVarCount(); IConfigVar* GetVar(const char* szName); - IConfigVar* GetVar(uint index); + IConfigVar* GetVar(AZ::u32 index); const IConfigVar* GetVar(const char* szName) const; - const IConfigVar* GetVar(uint index) const; + const IConfigVar* GetVar(AZ::u32 index) const; void SaveToXML(XmlNodeRef node); void LoadFromXML(XmlNodeRef node); - - template - void AddVar(const char* szName, const char* szDescription, T& var, const T& defaultValue, uint8 flags = 0) - { - AddVar(new TConfigVar(szName, szDescription, flags, var, defaultValue)); - } }; }; -#endif // CRYINCLUDE_EDITOR_CONFIGGROUP_H diff --git a/Code/Editor/Controls/ReflectedPropertyControl/PropertyMiscCtrl.h b/Code/Editor/Controls/ReflectedPropertyControl/PropertyMiscCtrl.h index 5ec24b679d..849e44cedd 100644 --- a/Code/Editor/Controls/ReflectedPropertyControl/PropertyMiscCtrl.h +++ b/Code/Editor/Controls/ReflectedPropertyControl/PropertyMiscCtrl.h @@ -6,8 +6,6 @@ * */ -#ifndef CRYINCLUDE_EDITOR_UTILS_PROPERTYMISCCTRL_H -#define CRYINCLUDE_EDITOR_UTILS_PROPERTYMISCCTRL_H #pragma once #if !defined(Q_MOC_RUN) @@ -53,6 +51,7 @@ private: class UserPopupWidgetHandler : public QObject, public AzToolsFramework::PropertyHandler < CReflectedVarUser, UserPropertyEditor> { + Q_OBJECT public: AZ_CLASS_ALLOCATOR(UserPopupWidgetHandler, AZ::SystemAllocator, 0); bool IsDefaultHandler() const override { return false; } @@ -67,6 +66,7 @@ public: class FloatCurveHandler : public QObject, public AzToolsFramework::PropertyHandler < CReflectedVarSpline, CSplineCtrl> { + Q_OBJECT public: AZ_CLASS_ALLOCATOR(FloatCurveHandler, AZ::SystemAllocator, 0); bool IsDefaultHandler() const override { return false; } @@ -80,5 +80,3 @@ public: void OnSplineChange(CSplineCtrl*); }; - -#endif // CRYINCLUDE_EDITOR_UTILS_PROPERTYMISCCTRL_H diff --git a/Code/Editor/Controls/ReflectedPropertyControl/PropertyResourceCtrl.cpp b/Code/Editor/Controls/ReflectedPropertyControl/PropertyResourceCtrl.cpp index c5ccc599d6..d26e978ae8 100644 --- a/Code/Editor/Controls/ReflectedPropertyControl/PropertyResourceCtrl.cpp +++ b/Code/Editor/Controls/ReflectedPropertyControl/PropertyResourceCtrl.cpp @@ -58,17 +58,9 @@ private: void OnClicked() override { QString tempValue(""); - QString ext(""); - if (m_path.isEmpty() == false) + if (!m_path.isEmpty() && !Path::GetExt(m_path).isEmpty()) { - if (Path::GetExt(m_path) == "") - { - tempValue = ""; - } - else - { - tempValue = m_path; - } + tempValue = m_path; } AssetSelectionModel selection; diff --git a/Code/Editor/Controls/ReflectedPropertyControl/PropertyResourceCtrl.h b/Code/Editor/Controls/ReflectedPropertyControl/PropertyResourceCtrl.h index fa30eba034..087ee9f1db 100644 --- a/Code/Editor/Controls/ReflectedPropertyControl/PropertyResourceCtrl.h +++ b/Code/Editor/Controls/ReflectedPropertyControl/PropertyResourceCtrl.h @@ -99,6 +99,7 @@ class FileResourceSelectorWidgetHandler : QObject , public AzToolsFramework::PropertyHandler < CReflectedVarResource, FileResourceSelectorWidget > { + Q_OBJECT public: AZ_CLASS_ALLOCATOR(FileResourceSelectorWidgetHandler, AZ::SystemAllocator, 0); diff --git a/Code/Editor/Controls/ReflectedPropertyControl/ReflectedVarWrapper.cpp b/Code/Editor/Controls/ReflectedPropertyControl/ReflectedVarWrapper.cpp index b3f1b35461..2320938f08 100644 --- a/Code/Editor/Controls/ReflectedPropertyControl/ReflectedVarWrapper.cpp +++ b/Code/Editor/Controls/ReflectedPropertyControl/ReflectedVarWrapper.cpp @@ -446,41 +446,54 @@ void ReflectedVarUserAdapter::SetVariable(IVariable *pVariable) m_reflectedVar.reset(new CReflectedVarUser( pVariable->GetHumanName().toUtf8().data())); } -void ReflectedVarUserAdapter::SyncReflectedVarToIVar(IVariable *pVariable) +void ReflectedVarUserAdapter::SyncReflectedVarToIVar(IVariable* pVariable) { QString value; pVariable->Get(value); m_reflectedVar->m_value = value.toUtf8().data(); - //extract the list of custom items from the IVariable user data - IVariable::IGetCustomItems* pGetCustomItems = static_cast (pVariable->GetUserData().value()); - if (pGetCustomItems != nullptr) - { - std::vector items; - QString dlgTitle; - // call the user supplied callback to fill-in items and get dialog title - bool bShowIt = pGetCustomItems->GetItems(pVariable, items, dlgTitle); - if (bShowIt) // if func didn't veto, show the dialog - { - m_reflectedVar->m_enableEdit = true; - m_reflectedVar->m_useTree = pGetCustomItems->UseTree(); - m_reflectedVar->m_treeSeparator = pGetCustomItems->GetTreeSeparator(); - m_reflectedVar->m_dialogTitle = dlgTitle.toUtf8().data(); - m_reflectedVar->m_itemNames.resize(items.size()); - m_reflectedVar->m_itemDescriptions.resize(items.size()); - - QByteArray ba; - int i = -1; - std::generate(m_reflectedVar->m_itemNames.begin(), m_reflectedVar->m_itemNames.end(), [&items, &i, &ba]() { ++i; ba = items[i].name.toUtf8(); return ba.data(); }); - i = -1; - std::generate(m_reflectedVar->m_itemDescriptions.begin(), m_reflectedVar->m_itemDescriptions.end(), [&items, &i, &ba]() { ++i; ba = items[i].desc.toUtf8(); return ba.data(); }); - - } - } - else + // extract the list of custom items from the IVariable user data + IVariable::IGetCustomItems* pGetCustomItems = static_cast(pVariable->GetUserData().value()); + if (pGetCustomItems == nullptr) { m_reflectedVar->m_enableEdit = false; + return; } + + std::vector items; + QString dlgTitle; + // call the user supplied callback to fill-in items and get dialog title + bool bShowIt = pGetCustomItems->GetItems(pVariable, items, dlgTitle); + if (!bShowIt) // if func vetoed it, don't show the dialog + { + return; + } + m_reflectedVar->m_enableEdit = true; + m_reflectedVar->m_useTree = pGetCustomItems->UseTree(); + m_reflectedVar->m_treeSeparator = pGetCustomItems->GetTreeSeparator(); + m_reflectedVar->m_dialogTitle = dlgTitle.toUtf8().data(); + m_reflectedVar->m_itemNames.resize(items.size()); + m_reflectedVar->m_itemDescriptions.resize(items.size()); + + QByteArray ba; + int i = -1; + AZStd::generate( + m_reflectedVar->m_itemNames.begin(), m_reflectedVar->m_itemNames.end(), + [&items, &i, &ba]() + { + ++i; + ba = items[i].name.toUtf8(); + return ba.data(); + }); + i = -1; + AZStd::generate( + m_reflectedVar->m_itemDescriptions.begin(), m_reflectedVar->m_itemDescriptions.end(), + [&items, &i, &ba]() + { + ++i; + ba = items[i].desc.toUtf8(); + return ba.data(); + }); } void ReflectedVarUserAdapter::SyncIVarToReflectedVar(IVariable *pVariable) diff --git a/Code/Editor/Controls/TimelineCtrl.cpp b/Code/Editor/Controls/TimelineCtrl.cpp index aa30c326ac..3890b46b2b 100644 --- a/Code/Editor/Controls/TimelineCtrl.cpp +++ b/Code/Editor/Controls/TimelineCtrl.cpp @@ -126,7 +126,6 @@ void TimelineWidget::DrawTicks(QPainter* painter) const QPen pOldPen = painter->pen(); const QPen ltgray(QColor(110, 110, 110)); - const QPen black(palette().color(QPalette::Normal, QPalette::Text)); const QPen redpen(QColor(255, 0, 255)); // Draw time ticks every tick step seconds. @@ -598,7 +597,6 @@ void TimelineWidget::DrawSecondTicks(QPainter* painter) { const QPen ltgray(QColor(110, 110, 110)); const QPen black(palette().color(QPalette::Normal, QPalette::Text)); - const QPen redpen(QColor(255, 0, 255)); for (int gx = m_grid.firstGridLine.x(); gx < m_grid.firstGridLine.x() + m_grid.numGridLines.x() + 1; gx++) { diff --git a/Code/Editor/CryEdit.cpp b/Code/Editor/CryEdit.cpp index e4138da932..0277abe60d 100644 --- a/Code/Editor/CryEdit.cpp +++ b/Code/Editor/CryEdit.cpp @@ -4137,7 +4137,15 @@ extern "C" int AZ_DLL_EXPORT CryEditMain(int argc, char* argv[]) AzQtComponents::Utilities::HandleDpiAwareness(AzQtComponents::Utilities::SystemDpiAware); Editor::EditorQtApplication* app = Editor::EditorQtApplication::newInstance(argc, argv); - if (app->arguments().contains("-autotest_mode")) + QStringList qArgs = app->arguments(); + const bool is_automated_test = AZStd::any_of(qArgs.begin(), qArgs.end(), + [](const QString& elem) + { + return elem.endsWith("autotest_mode") || elem.endsWith("runpythontest"); + } + ); + + if (is_automated_test) { // Nullroute all stdout to null for automated tests, this way we make sure // that the test result output is not polluted with unrelated output data. diff --git a/Code/Editor/CryEdit.h b/Code/Editor/CryEdit.h index f68cfdc33d..53ee8f1905 100644 --- a/Code/Editor/CryEdit.h +++ b/Code/Editor/CryEdit.h @@ -431,6 +431,7 @@ public: class CCrySingleDocTemplate : public QObject { + Q_OBJECT private: explicit CCrySingleDocTemplate(const QMetaObject* pDocClass) : QObject() diff --git a/Code/Editor/CustomResolutionDlg.h b/Code/Editor/CustomResolutionDlg.h index 5dd9acaae8..e1b8035c65 100644 --- a/Code/Editor/CustomResolutionDlg.h +++ b/Code/Editor/CustomResolutionDlg.h @@ -12,8 +12,6 @@ // Notice : Refer to ViewportTitleDlg.cpp for a use case. -#ifndef CRYINCLUDE_EDITOR_CUSTOMRESOLUTIONDLG_H -#define CRYINCLUDE_EDITOR_CUSTOMRESOLUTIONDLG_H #pragma once #if !defined(Q_MOC_RUN) @@ -28,6 +26,7 @@ namespace Ui class CCustomResolutionDlg : public QDialog { + Q_OBJECT public: CCustomResolutionDlg(int w, int h, QWidget* pParent = nullptr); ~CCustomResolutionDlg(); @@ -42,5 +41,3 @@ protected: QScopedPointer m_ui; }; - -#endif // CRYINCLUDE_EDITOR_CUSTOMRESOLUTIONDLG_H diff --git a/Code/Editor/CustomizeKeyboardDialog.cpp b/Code/Editor/CustomizeKeyboardDialog.cpp index ce09f8d878..280d6c5323 100644 --- a/Code/Editor/CustomizeKeyboardDialog.cpp +++ b/Code/Editor/CustomizeKeyboardDialog.cpp @@ -211,9 +211,9 @@ public: void Reset(QAction& action) { - emit beginResetModel(); + beginResetModel(); m_action = &action; - emit endResetModel(); + endResetModel(); } private: @@ -266,7 +266,7 @@ QStringList CustomizeKeyboardDialog::BuildModels(QWidget* parent) categories.append(category); QMenu* menu = menuAction->menu(); - m_menuActions[category] = GetAllActionsForMenu(menu, QStringLiteral("")); + m_menuActions[category] = GetAllActionsForMenu(menu, QString()); } return categories; diff --git a/Code/Editor/ErrorDialog.cpp b/Code/Editor/ErrorDialog.cpp index 0a45b2bf89..e43df75947 100644 --- a/Code/Editor/ErrorDialog.cpp +++ b/Code/Editor/ErrorDialog.cpp @@ -25,9 +25,9 @@ namespace SandboxEditor connect(m_ui->okButton, &QPushButton::clicked, this, &ErrorDialog::OnOK); connect( m_ui->messages, - SIGNAL(itemSelectionChanged()), + &QTreeWidget::itemSelectionChanged, this, - SLOT(MessageSelectionChanged())); + &ErrorDialog::MessageSelectionChanged); } ErrorDialog::~ErrorDialog() diff --git a/Code/Editor/KeyboardCustomizationSettings.cpp b/Code/Editor/KeyboardCustomizationSettings.cpp index c4f9133f66..81d9375850 100644 --- a/Code/Editor/KeyboardCustomizationSettings.cpp +++ b/Code/Editor/KeyboardCustomizationSettings.cpp @@ -240,7 +240,7 @@ QJsonObject KeyboardCustomizationSettings::ExportGroup() void KeyboardCustomizationSettings::ImportFromFile(QWidget* parent) { - QString fileName = QFileDialog::getOpenFileName(parent, QObject::tr("Export Keyboard Shortcuts"), QStringLiteral(""), QObject::tr("Keyboard Settings (*.keys)")); + QString fileName = QFileDialog::getOpenFileName(parent, QObject::tr("Export Keyboard Shortcuts"), QString(), QObject::tr("Keyboard Settings (*.keys)")); if (fileName.isEmpty()) { return; diff --git a/Code/Editor/MainStatusBar.cpp b/Code/Editor/MainStatusBar.cpp index f955035bd8..acc7f664df 100644 --- a/Code/Editor/MainStatusBar.cpp +++ b/Code/Editor/MainStatusBar.cpp @@ -419,7 +419,7 @@ void MemoryStatusItem::updateStatus() GeneralStatusItem::GeneralStatusItem(QString name, MainStatusBar* parent) : StatusBarItem(name, parent) { - connect(parent, SIGNAL(messageChanged(QString)), this, SLOT(update())); + connect(parent, &MainStatusBar::messageChanged, this, [this](const QString&) { update(); }); } QString GeneralStatusItem::CurrentText() const diff --git a/Code/Editor/NewLevelDialog.cpp b/Code/Editor/NewLevelDialog.cpp index 29445cef2b..c773acdb6f 100644 --- a/Code/Editor/NewLevelDialog.cpp +++ b/Code/Editor/NewLevelDialog.cpp @@ -100,9 +100,10 @@ CNewLevelDialog::CNewLevelDialog(QWidget* pParent /*=nullptr*/) m_level = ""; // First of all, keyboard focus is related to widget tab order, and the default tab order is based on the order in which // widgets are constructed. Therefore, creating more widgets changes the keyboard focus. That is why setFocus() is called last. - // Secondly, using singleShot() allows setFocus() slot of the QLineEdit instance to be invoked right after the event system - // is ready to do so. Therefore, it is better to use singleShot() than directly call setFocus(). - QTimer::singleShot(0, ui->LEVEL, SLOT(OnStartup())); + // in OnStartup() + // Secondly, using singleShot() allows OnStartup() slot of the QLineEdit instance to be invoked right after the event system + // is ready to do so. Therefore, it is better to use singleShot() than directly call OnStartup(). + QTimer::singleShot(0, this, &CNewLevelDialog::OnStartup); ReloadLevelFolder(); } diff --git a/Code/Editor/Plugins/ComponentEntityEditorPlugin/UI/AssetCatalogModel.cpp b/Code/Editor/Plugins/ComponentEntityEditorPlugin/UI/AssetCatalogModel.cpp index d0091f968e..df8d6db4a8 100644 --- a/Code/Editor/Plugins/ComponentEntityEditorPlugin/UI/AssetCatalogModel.cpp +++ b/Code/Editor/Plugins/ComponentEntityEditorPlugin/UI/AssetCatalogModel.cpp @@ -197,48 +197,46 @@ AssetCatalogModel::~AssetCatalogModel() AzFramework::AssetCatalogEventBus::Handler::BusDisconnect(); } -AZ::Data::AssetType AssetCatalogModel::GetAssetType(QString filename) const +AZ::Data::AssetType AssetCatalogModel::GetAssetType(const QString &filename) const { - AZ::Data::AssetType returnType = AZ::Uuid::CreateNull(); // Compare file extensions with the map created from the asset database. int dotIndex = filename.lastIndexOf('.'); - if (dotIndex >= 0) + if (dotIndex < 0) { - QString extension = filename.mid(dotIndex); - for (auto pair : m_extensionToAssetType) - { - QString qExtensions = pair.first.c_str(); - if (qExtensions.indexOf(extension) >= 0) - { - if (pair.second.size() > 1) - { - // There are multiple types with this extension. Check each handler to see if they can handle this data type. - AZStd::string azFilename = filename.toStdString().c_str(); - EBUS_EVENT(AzFramework::ApplicationRequests::Bus, MakePathAssetRootRelative, azFilename); - AZ::Data::AssetId assetId; - EBUS_EVENT_RESULT(assetId, AZ::Data::AssetCatalogRequestBus, GetAssetIdByPath, azFilename.c_str(), AZ::Data::s_invalidAssetType, false); + return AZ::Uuid::CreateNull(); + } - for (AZ::Uuid type : pair.second) - { - const AZ::Data::AssetHandler* handler = AZ::Data::AssetManager::Instance().GetHandler(type); - if (handler && handler->CanHandleAsset(assetId)) - { - returnType = type; - break; - } - } - } - else - { - returnType = pair.second[0]; - break; - } + QStringRef extension = filename.midRef(dotIndex); + for (const auto& pair : m_extensionToAssetType) + { + QString qExtensions = pair.first.c_str(); + if (qExtensions.indexOf(extension) < 0 || pair.second.empty()) + { + continue; + } + if (pair.second.size() == 1) + { + return pair.second[0]; + } + + // There are multiple types with this extension. Search for a handler that can handle this data type. + AZStd::string azFilename = filename.toStdString().c_str(); + EBUS_EVENT(AzFramework::ApplicationRequests::Bus, MakePathAssetRootRelative, azFilename); + AZ::Data::AssetId assetId; + EBUS_EVENT_RESULT(assetId, AZ::Data::AssetCatalogRequestBus, GetAssetIdByPath, azFilename.c_str(), AZ::Data::s_invalidAssetType, false); + + for (const AZ::Uuid& type : pair.second) + { + const AZ::Data::AssetHandler* handler = AZ::Data::AssetManager::Instance().GetHandler(type); + if (handler && handler->CanHandleAsset(assetId)) + { + return type; } } } - return returnType; + return AZ::Uuid::CreateNull(); } QStandardItem* AssetCatalogModel::GetPath(QString& path, bool createIfNeeded, QStandardItem* parent) @@ -419,7 +417,7 @@ AssetCatalogEntry* AssetCatalogModel::AddAsset(QString assetPath, AZ::Data::Asse // icons' memory being reclaimed and crashing the Editor. QSize size = fileIcon.actualSize(QSize(16, 16)); QIcon deepCopy = fileIcon.pixmap(size).copy(0, 0, size.width(), size.height()); - + if (!fileIcon.isNull()) { m_assetTypeToIcon[assetType] = deepCopy; diff --git a/Code/Editor/Plugins/ComponentEntityEditorPlugin/UI/AssetCatalogModel.h b/Code/Editor/Plugins/ComponentEntityEditorPlugin/UI/AssetCatalogModel.h index c9143bb259..1dffa81d67 100644 --- a/Code/Editor/Plugins/ComponentEntityEditorPlugin/UI/AssetCatalogModel.h +++ b/Code/Editor/Plugins/ComponentEntityEditorPlugin/UI/AssetCatalogModel.h @@ -110,7 +110,7 @@ protected: void SetFilterRegExp(const AZStd::string& filterType, const QRegExp& regExp); void ClearFilterRegExp(const AZStd::string& filterType = AZStd::string()); - AZ::Data::AssetType GetAssetType(QString filename) const; + AZ::Data::AssetType GetAssetType(const QString &filename) const; QStandardItem* GetPath(QString& path, bool createIfNeeded, QStandardItem* parent = nullptr); void ApplyFilter(QStandardItem* parent); diff --git a/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp b/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp index dabf02e260..df8db79db0 100644 --- a/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp +++ b/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp @@ -215,11 +215,6 @@ namespace AZ m_oldProjectPath = newProjectPath; // Merge the project.json file into settings registry under ProjectSettingsRootKey path. - AZ::IO::FixedMaxPath projectMetadataFile{ AZ::SettingsRegistryMergeUtils::FindEngineRoot(m_registry) / newProjectPath }; - projectMetadataFile /= "project.json"; - m_registry.MergeSettingsFile(projectMetadataFile.Native(), - AZ::SettingsRegistryInterface::Format::JsonMergePatch, AZ::SettingsRegistryMergeUtils::ProjectSettingsRootKey); - // Update all the runtime file paths based on the new "project_path" value. AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(m_registry); } diff --git a/Code/Framework/AzCore/AzCore/Component/TransformBus.h b/Code/Framework/AzCore/AzCore/Component/TransformBus.h index 1af77da51b..f8e30147da 100644 --- a/Code/Framework/AzCore/AzCore/Component/TransformBus.h +++ b/Code/Framework/AzCore/AzCore/Component/TransformBus.h @@ -168,6 +168,11 @@ namespace AZ //! Rotation modifiers //! @{ + //! Set the world rotation matrix using the composition of rotations around + //! the principle axes in the order of z-axis first and y-axis and then x-axis. + //! @param eulerRadianAngles A Vector3 denoting radian angles of the rotations around each principle axis. + virtual void SetWorldRotation([[maybe_unused]] const AZ::Vector3& eulerAnglesRadian) {} + //! Sets the entity's rotation in the world in quaternion notation. //! The origin of the axes is the entity's position in world space. //! @param quaternion A quaternion that represents the rotation to use for the entity. diff --git a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.cpp b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.cpp index 36f66312d8..5458a3fadf 100644 --- a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.cpp +++ b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.cpp @@ -634,12 +634,18 @@ namespace AZ::SettingsRegistryMergeUtils } // Project name - if it was set via merging project.json use that value, otherwise use the project path's folder name. - auto projectNameKey = - AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::ProjectSettingsRootKey) + constexpr auto projectNameKey = + FixedValueString(AZ::SettingsRegistryMergeUtils::ProjectSettingsRootKey) + "/project_name"; - AZ::SettingsRegistryInterface::FixedValueString projectName; - if (!registry.Get(projectName, projectNameKey)) + // Read the project name from the project.json file if it exists + if (AZ::IO::FixedMaxPath projectJsonPath = normalizedProjectPath / "project.json"; + AZ::IO::SystemFile::Exists(projectJsonPath.c_str())) + { + registry.MergeSettingsFile(projectJsonPath.Native(), + AZ::SettingsRegistryInterface::Format::JsonMergePatch, AZ::SettingsRegistryMergeUtils::ProjectSettingsRootKey); + } + if (FixedValueString projectName; !registry.Get(projectName, projectNameKey)) { projectName = path.Filename().Native(); registry.Set(projectNameKey, projectName); diff --git a/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.cpp b/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.cpp index 68c4b00243..bc109b73c2 100644 --- a/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.cpp +++ b/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.cpp @@ -323,6 +323,13 @@ namespace AzFramework return localZ; } + void TransformComponent::SetWorldRotation(const AZ::Vector3& eulerAnglesRadian) + { + AZ::Transform newWorldTransform = m_worldTM; + newWorldTransform.SetRotation(AZ::Quaternion::CreateFromEulerAnglesRadians(eulerAnglesRadian)); + SetWorldTM(newWorldTransform); + } + void TransformComponent::SetWorldRotationQuaternion(const AZ::Quaternion& quaternion) { AZ::Transform newWorldTransform = m_worldTM; diff --git a/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.h b/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.h index ac282b4d49..2375f28ed1 100644 --- a/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.h +++ b/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.h @@ -108,6 +108,7 @@ namespace AzFramework float GetLocalZ() override; // Rotation modifiers + void SetWorldRotation(const AZ::Vector3& eulerAnglesRadian) override; void SetWorldRotationQuaternion(const AZ::Quaternion& quaternion) override; AZ::Vector3 GetWorldRotation() override; diff --git a/Code/Framework/AzFramework/AzFramework/Matchmaking/IMatchmakingRequests.h b/Code/Framework/AzFramework/AzFramework/Matchmaking/IMatchmakingRequests.h index c657e0edc7..ccf9acdf8b 100644 --- a/Code/Framework/AzFramework/AzFramework/Matchmaking/IMatchmakingRequests.h +++ b/Code/Framework/AzFramework/AzFramework/Matchmaking/IMatchmakingRequests.h @@ -24,17 +24,17 @@ namespace AzFramework IMatchmakingRequests() = default; virtual ~IMatchmakingRequests() = default; - // Registers a player's acceptance or rejection of a proposed matchmaking. - // @param acceptMatchRequest The request of AcceptMatch operation + //! Registers a player's acceptance or rejection of a proposed matchmaking. + //! @param acceptMatchRequest The request of AcceptMatch operation virtual void AcceptMatch(const AcceptMatchRequest& acceptMatchRequest) = 0; - // Create a game match for a group of players. - // @param startMatchmakingRequest The request of StartMatchmaking operation - // @return A unique identifier for a matchmaking ticket + //! Create a game match for a group of players. + //! @param startMatchmakingRequest The request of StartMatchmaking operation + //! @return A unique identifier for a matchmaking ticket virtual AZStd::string StartMatchmaking(const StartMatchmakingRequest& startMatchmakingRequest) = 0; - // Cancels a matchmaking ticket that is currently being processed. - // @param stopMatchmakingRequest The request of StopMatchmaking operation + //! Cancels a matchmaking ticket that is currently being processed. + //! @param stopMatchmakingRequest The request of StopMatchmaking operation virtual void StopMatchmaking(const StopMatchmakingRequest& stopMatchmakingRequest) = 0; }; @@ -48,16 +48,16 @@ namespace AzFramework IMatchmakingAsyncRequests() = default; virtual ~IMatchmakingAsyncRequests() = default; - // AcceptMatch Async - // @param acceptMatchRequest The request of AcceptMatch operation + //! AcceptMatch Async + //! @param acceptMatchRequest The request of AcceptMatch operation virtual void AcceptMatchAsync(const AcceptMatchRequest& acceptMatchRequest) = 0; - // StartMatchmaking Async - // @param startMatchmakingRequest The request of StartMatchmaking operation + //! StartMatchmaking Async + //! @param startMatchmakingRequest The request of StartMatchmaking operation virtual void StartMatchmakingAsync(const StartMatchmakingRequest& startMatchmakingRequest) = 0; - // StopMatchmaking Async - // @param stopMatchmakingRequest The request of StopMatchmaking operation + //! StopMatchmaking Async + //! @param stopMatchmakingRequest The request of StopMatchmaking operation virtual void StopMatchmakingAsync(const StopMatchmakingRequest& stopMatchmakingRequest) = 0; }; @@ -76,14 +76,14 @@ namespace AzFramework static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; ////////////////////////////////////////////////////////////////////////// - // OnAcceptMatchAsyncComplete is fired once AcceptMatchAsync completes + //! OnAcceptMatchAsyncComplete is fired once AcceptMatchAsync completes virtual void OnAcceptMatchAsyncComplete() = 0; - // OnStartMatchmakingAsyncComplete is fired once StartMatchmakingAsync completes - // @param matchmakingTicketId The unique identifier for the matchmaking ticket + //! OnStartMatchmakingAsyncComplete is fired once StartMatchmakingAsync completes + //! @param matchmakingTicketId The unique identifier for the matchmaking ticket virtual void OnStartMatchmakingAsyncComplete(const AZStd::string& matchmakingTicketId) = 0; - // OnStopMatchmakingAsyncComplete is fired once StopMatchmakingAsync completes + //! OnStopMatchmakingAsyncComplete is fired once StopMatchmakingAsync completes virtual void OnStopMatchmakingAsyncComplete() = 0; }; using MatchmakingAsyncRequestNotificationBus = AZ::EBus; diff --git a/Code/Framework/AzFramework/AzFramework/Matchmaking/MatchmakingNotifications.h b/Code/Framework/AzFramework/AzFramework/Matchmaking/MatchmakingNotifications.h index aa19b94b4a..0ec52c7215 100644 --- a/Code/Framework/AzFramework/AzFramework/Matchmaking/MatchmakingNotifications.h +++ b/Code/Framework/AzFramework/AzFramework/Matchmaking/MatchmakingNotifications.h @@ -29,17 +29,17 @@ namespace AzFramework static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; ////////////////////////////////////////////////////////////////////////// - // OnMatchAcceptance is fired when match is found and pending on acceptance - // Use this notification to accept found match + //! OnMatchAcceptance is fired when match is found and pending on acceptance + //! Use this notification to accept found match virtual void OnMatchAcceptance() = 0; - // OnMatchComplete is fired when match is complete + //! OnMatchComplete is fired when match is complete virtual void OnMatchComplete() = 0; - // OnMatchError is fired when match is processed with error + //! OnMatchError is fired when match is processed with error virtual void OnMatchError() = 0; - // OnMatchFailure is fired when match is failed to complete + //! OnMatchFailure is fired when match is failed to complete virtual void OnMatchFailure() = 0; }; using MatchmakingNotificationBus = AZ::EBus; diff --git a/Code/Framework/AzFramework/AzFramework/Matchmaking/MatchmakingRequests.h b/Code/Framework/AzFramework/AzFramework/Matchmaking/MatchmakingRequests.h index 9169a83588..5f5dcc0643 100644 --- a/Code/Framework/AzFramework/AzFramework/Matchmaking/MatchmakingRequests.h +++ b/Code/Framework/AzFramework/AzFramework/Matchmaking/MatchmakingRequests.h @@ -29,11 +29,11 @@ namespace AzFramework AcceptMatchRequest() = default; virtual ~AcceptMatchRequest() = default; - // Player response to accept or reject match + //! Player response to accept or reject match bool m_acceptMatch; - // A list of unique identifiers for players delivering the response + //! A list of unique identifiers for players delivering the response AZStd::vector m_playerIds; - // A unique identifier for a matchmaking ticket + //! A unique identifier for a matchmaking ticket AZStd::string m_ticketId; }; @@ -47,7 +47,7 @@ namespace AzFramework StartMatchmakingRequest() = default; virtual ~StartMatchmakingRequest() = default; - // A unique identifier for a matchmaking ticket + //! A unique identifier for a matchmaking ticket AZStd::string m_ticketId; }; @@ -61,7 +61,7 @@ namespace AzFramework StopMatchmakingRequest() = default; virtual ~StopMatchmakingRequest() = default; - // A unique identifier for a matchmaking ticket + //! A unique identifier for a matchmaking ticket AZStd::string m_ticketId; }; } // namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/Physics/Configuration/JointConfiguration.cpp b/Code/Framework/AzFramework/AzFramework/Physics/Configuration/JointConfiguration.cpp index d2f74510e5..0a0e1bb8e3 100644 --- a/Code/Framework/AzFramework/AzFramework/Physics/Configuration/JointConfiguration.cpp +++ b/Code/Framework/AzFramework/AzFramework/Physics/Configuration/JointConfiguration.cpp @@ -10,6 +10,7 @@ #include #include +#include namespace AzPhysics { @@ -28,6 +29,71 @@ namespace AzPhysics ->Field("ChildLocalPosition", &JointConfiguration::m_childLocalPosition) ->Field("StartSimulationEnabled", &JointConfiguration::m_startSimulationEnabled) ; + + if (auto* editContext = serializeContext->GetEditContext()) + { + editContext->Class("Joint Configuration", "Joint configuration.") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::AutoExpand, true) + ->DataElement(AZ::Edit::UIHandlers::Default, &JointConfiguration::m_parentLocalRotation, + "Parent local rotation", "Parent joint frame relative to parent body.") + ->Attribute(AZ::Edit::Attributes::Visibility, &JointConfiguration::GetParentLocalRotationVisibility) + ->DataElement(AZ::Edit::UIHandlers::Default, &JointConfiguration::m_parentLocalPosition, + "Parent local position", "Joint position relative to parent body.") + ->Attribute(AZ::Edit::Attributes::Visibility, &JointConfiguration::GetParentLocalPositionVisibility) + ->DataElement(AZ::Edit::UIHandlers::Default, &JointConfiguration::m_childLocalRotation, + "Child local rotation", "Child joint frame relative to child body.") + ->Attribute(AZ::Edit::Attributes::Visibility, &JointConfiguration::GetChildLocalRotationVisibility) + ->DataElement(AZ::Edit::UIHandlers::Default, &JointConfiguration::m_childLocalPosition, + "Child local position", "Joint position relative to child body.") + ->Attribute(AZ::Edit::Attributes::Visibility, &JointConfiguration::GetChildLocalPositionVisibility) + ->DataElement(AZ::Edit::UIHandlers::Default, &JointConfiguration::m_startSimulationEnabled, + "Start simulation enabled", "When active, the joint will be enabled when the simulation begins.") + ->Attribute(AZ::Edit::Attributes::Visibility, &JointConfiguration::GetStartSimulationEnabledVisibility) + ; + } } } -} + + AZ::Crc32 JointConfiguration::GetPropertyVisibility(JointConfiguration::PropertyVisibility property) const + { + return (m_propertyVisibilityFlags & property) != 0 ? AZ::Edit::PropertyVisibility::Show : AZ::Edit::PropertyVisibility::Hide; + } + + void JointConfiguration::SetPropertyVisibility(JointConfiguration::PropertyVisibility property, bool isVisible) + { + if (isVisible) + { + m_propertyVisibilityFlags |= property; + } + else + { + m_propertyVisibilityFlags &= ~property; + } + } + + AZ::Crc32 JointConfiguration::GetParentLocalRotationVisibility() const + { + return GetPropertyVisibility(JointConfiguration::PropertyVisibility::ParentLocalRotation); + } + + AZ::Crc32 JointConfiguration::GetParentLocalPositionVisibility() const + { + return GetPropertyVisibility(JointConfiguration::PropertyVisibility::ParentLocalPosition); + } + + AZ::Crc32 JointConfiguration::GetChildLocalRotationVisibility() const + { + return GetPropertyVisibility(JointConfiguration::PropertyVisibility::ChildLocalRotation); + } + + AZ::Crc32 JointConfiguration::GetChildLocalPositionVisibility() const + { + return GetPropertyVisibility(JointConfiguration::PropertyVisibility::ChildLocalPosition); + } + + AZ::Crc32 JointConfiguration::GetStartSimulationEnabledVisibility() const + { + return GetPropertyVisibility(JointConfiguration::PropertyVisibility::StartSimulationEnabled); + } +} // namespace AzPhysics diff --git a/Code/Framework/AzFramework/AzFramework/Physics/Configuration/JointConfiguration.h b/Code/Framework/AzFramework/AzFramework/Physics/Configuration/JointConfiguration.h index ff9bfb5bea..2a246692d7 100644 --- a/Code/Framework/AzFramework/AzFramework/Physics/Configuration/JointConfiguration.h +++ b/Code/Framework/AzFramework/AzFramework/Physics/Configuration/JointConfiguration.h @@ -31,6 +31,25 @@ namespace AzPhysics JointConfiguration() = default; virtual ~JointConfiguration() = default; + // Visibility helpers for use in the Editor when reflected. + enum PropertyVisibility : AZ::u8 + { + ParentLocalRotation = 1 << 0, //!< Whether the parent local rotation is visible. + ParentLocalPosition = 1 << 1, //!< Whether the parent local position is visible. + ChildLocalRotation = 1 << 2, //!< Whether the child local rotation is visible. + ChildLocalPosition = 1 << 3, //!< Whether the child local position is visible. + StartSimulationEnabled = 1 << 4 //!< Whether the start simulation enabled setting is visible. + }; + + AZ::Crc32 GetPropertyVisibility(PropertyVisibility property) const; + void SetPropertyVisibility(PropertyVisibility property, bool isVisible); + + AZ::Crc32 GetParentLocalRotationVisibility() const; + AZ::Crc32 GetParentLocalPositionVisibility() const; + AZ::Crc32 GetChildLocalRotationVisibility() const; + AZ::Crc32 GetChildLocalPositionVisibility() const; + AZ::Crc32 GetStartSimulationEnabledVisibility() const; + // Entity/object association. void* m_customUserData = nullptr; @@ -40,8 +59,11 @@ namespace AzPhysics AZ::Quaternion m_childLocalRotation = AZ::Quaternion::CreateIdentity(); ///< Child joint frame relative to child body. AZ::Vector3 m_childLocalPosition = AZ::Vector3::CreateZero(); ///< Joint position relative to child body. bool m_startSimulationEnabled = true; - + // For debugging/tracking purposes only. AZStd::string m_debugName; + + // Default all visibility settings to invisible, since most joint configurations don't need to display these. + AZ::u8 m_propertyVisibilityFlags = 0; }; } diff --git a/Code/Framework/AzFramework/AzFramework/Session/ISessionHandlingRequests.h b/Code/Framework/AzFramework/AzFramework/Session/ISessionHandlingRequests.h index 065d6bb9d5..188ea7b994 100644 --- a/Code/Framework/AzFramework/AzFramework/Session/ISessionHandlingRequests.h +++ b/Code/Framework/AzFramework/AzFramework/Session/ISessionHandlingRequests.h @@ -18,16 +18,16 @@ namespace AzFramework //! The properties for handling join session request. struct SessionConnectionConfig { - // A unique identifier for registered player in session. + //! A unique identifier for registered player in session. AZStd::string m_playerSessionId; - // The DNS identifier assigned to the instance that is running the session. + //! The DNS identifier assigned to the instance that is running the session. AZStd::string m_dnsName; - // The IP address of the session. + //! The IP address of the session. AZStd::string m_ipAddress; - // The port number for the session. + //! The port number for the session. uint16_t m_port = 0; }; @@ -35,10 +35,10 @@ namespace AzFramework //! The properties for handling player connect/disconnect struct PlayerConnectionConfig { - // A unique identifier for player connection. + //! A unique identifier for player connection. uint32_t m_playerConnectionId = 0; - // A unique identifier for registered player in session. + //! A unique identifier for registered player in session. AZStd::string m_playerSessionId; }; @@ -51,12 +51,12 @@ namespace AzFramework ISessionHandlingClientRequests() = default; virtual ~ISessionHandlingClientRequests() = default; - // Request the player join session - // @param sessionConnectionConfig The required properties to handle the player join session process - // @return The result of player join session process + //! Request the player join session + //! @param sessionConnectionConfig The required properties to handle the player join session process + //! @return The result of player join session process virtual bool RequestPlayerJoinSession(const SessionConnectionConfig& sessionConnectionConfig) = 0; - // Request the connected player leave session + //! Request the connected player leave session virtual void RequestPlayerLeaveSession() = 0; }; @@ -69,26 +69,26 @@ namespace AzFramework ISessionHandlingProviderRequests() = default; virtual ~ISessionHandlingProviderRequests() = default; - // Handle the destroy session process + //! Handle the destroy session process virtual void HandleDestroySession() = 0; - // Validate the player join session process - // @param playerConnectionConfig The required properties to validate the player join session process - // @return The result of player join session validation + //! Validate the player join session process + //! @param playerConnectionConfig The required properties to validate the player join session process + //! @return The result of player join session validation virtual bool ValidatePlayerJoinSession(const PlayerConnectionConfig& playerConnectionConfig) = 0; - // Handle the player leave session process - // @param playerConnectionConfig The required properties to handle the player leave session process + //! Handle the player leave session process + //! @param playerConnectionConfig The required properties to handle the player leave session process virtual void HandlePlayerLeaveSession(const PlayerConnectionConfig& playerConnectionConfig) = 0; - // Retrieves the file location of a pem-encoded TLS certificate for Client to Server communication - // @return If successful, returns the file location of TLS certificate file; if not successful, returns - // empty string. + //! Retrieves the file location of a pem-encoded TLS certificate for Client to Server communication + //! @return If successful, returns the file location of TLS certificate file; if not successful, returns + //! empty string. virtual AZ::IO::Path GetExternalSessionCertificate() = 0; - // Retrieves the file location of a pem-encoded TLS certificate for Server to Server communication - // @return If successful, returns the file location of TLS certificate file; if not successful, returns - // empty string. + //! Retrieves the file location of a pem-encoded TLS certificate for Server to Server communication + //! @return If successful, returns the file location of TLS certificate file; if not successful, returns + //! empty string. virtual AZ::IO::Path GetInternalSessionCertificate() = 0; }; } // namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/Session/ISessionRequests.h b/Code/Framework/AzFramework/AzFramework/Session/ISessionRequests.h index bdc3fe1444..8b985a3187 100644 --- a/Code/Framework/AzFramework/AzFramework/Session/ISessionRequests.h +++ b/Code/Framework/AzFramework/AzFramework/Session/ISessionRequests.h @@ -25,22 +25,22 @@ namespace AzFramework ISessionRequests() = default; virtual ~ISessionRequests() = default; - // Create a session for players to find and join. - // @param createSessionRequest The request of CreateSession operation - // @return The request id if session creation request succeeds; empty if it fails + //! Create a session for players to find and join. + //! @param createSessionRequest The request of CreateSession operation + //! @return The request id if session creation request succeeds; empty if it fails virtual AZStd::string CreateSession(const CreateSessionRequest& createSessionRequest) = 0; - // Retrieve all active sessions that match the given search criteria and sorted in specific order. - // @param searchSessionsRequest The request of SearchSessions operation - // @return The response of SearchSessions operation + //! Retrieve all active sessions that match the given search criteria and sorted in specific order. + //! @param searchSessionsRequest The request of SearchSessions operation + //! @return The response of SearchSessions operation virtual SearchSessionsResponse SearchSessions(const SearchSessionsRequest& searchSessionsRequest) const = 0; - // Reserve an open player slot in a session, and perform connection from client to server. - // @param joinSessionRequest The request of JoinSession operation - // @return True if joining session succeeds; False otherwise + //! Reserve an open player slot in a session, and perform connection from client to server. + //! @param joinSessionRequest The request of JoinSession operation + //! @return True if joining session succeeds; False otherwise virtual bool JoinSession(const JoinSessionRequest& joinSessionRequest) = 0; - // Disconnect player from session. + //! Disconnect player from session. virtual void LeaveSession() = 0; }; @@ -54,19 +54,19 @@ namespace AzFramework ISessionAsyncRequests() = default; virtual ~ISessionAsyncRequests() = default; - // CreateSession Async - // @param createSessionRequest The request of CreateSession operation + //! CreateSession Async + //! @param createSessionRequest The request of CreateSession operation virtual void CreateSessionAsync(const CreateSessionRequest& createSessionRequest) = 0; - // SearchSessions Async - // @param searchSessionsRequest The request of SearchSessions operation + //! SearchSessions Async + //! @param searchSessionsRequest The request of SearchSessions operation virtual void SearchSessionsAsync(const SearchSessionsRequest& searchSessionsRequest) const = 0; - // JoinSession Async - // @param joinSessionRequest The request of JoinSession operation + //! JoinSession Async + //! @param joinSessionRequest The request of JoinSession operation virtual void JoinSessionAsync(const JoinSessionRequest& joinSessionRequest) = 0; - // LeaveSession Async + //! LeaveSession Async virtual void LeaveSessionAsync() = 0; }; @@ -85,19 +85,19 @@ namespace AzFramework static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; ////////////////////////////////////////////////////////////////////////// - // OnCreateSessionAsyncComplete is fired once CreateSessionAsync completes - // @param createSessionResponse The request id if session creation request succeeds; empty if it fails + //! OnCreateSessionAsyncComplete is fired once CreateSessionAsync completes + //! @param createSessionResponse The request id if session creation request succeeds; empty if it fails virtual void OnCreateSessionAsyncComplete(const AZStd::string& createSessionReponse) = 0; - // OnSearchSessionsAsyncComplete is fired once SearchSessionsAsync completes - // @param searchSessionsResponse The response of SearchSessions call + //! OnSearchSessionsAsyncComplete is fired once SearchSessionsAsync completes + //! @param searchSessionsResponse The response of SearchSessions call virtual void OnSearchSessionsAsyncComplete(const SearchSessionsResponse& searchSessionsResponse) = 0; - // OnJoinSessionAsyncComplete is fired once JoinSessionAsync completes - // @param joinSessionsResponse True if joining session succeeds; False otherwise + //! OnJoinSessionAsyncComplete is fired once JoinSessionAsync completes + //! @param joinSessionsResponse True if joining session succeeds; False otherwise virtual void OnJoinSessionAsyncComplete(bool joinSessionsResponse) = 0; - // OnLeaveSessionAsyncComplete is fired once LeaveSessionAsync completes + //! OnLeaveSessionAsyncComplete is fired once LeaveSessionAsync completes virtual void OnLeaveSessionAsyncComplete() = 0; }; using SessionAsyncRequestNotificationBus = AZ::EBus; diff --git a/Code/Framework/AzFramework/AzFramework/Session/SessionConfig.h b/Code/Framework/AzFramework/AzFramework/Session/SessionConfig.h index 45e40c2f29..f951cf58fa 100644 --- a/Code/Framework/AzFramework/AzFramework/Session/SessionConfig.h +++ b/Code/Framework/AzFramework/AzFramework/Session/SessionConfig.h @@ -24,46 +24,46 @@ namespace AzFramework SessionConfig() = default; virtual ~SessionConfig() = default; - // A time stamp indicating when this session was created. Format is a number expressed in Unix time as milliseconds. + //! A time stamp indicating when this session was created. Format is a number expressed in Unix time as milliseconds. uint64_t m_creationTime = 0; - // A time stamp indicating when this data object was terminated. Same format as creation time. + //! A time stamp indicating when this data object was terminated. Same format as creation time. uint64_t m_terminationTime = 0; - // A unique identifier for a player or entity creating the session. + //! A unique identifier for a player or entity creating the session. AZStd::string m_creatorId; - // A collection of custom properties for a session. + //! A collection of custom properties for a session. AZStd::unordered_map m_sessionProperties; - // The matchmaking process information that was used to create the session. + //! The matchmaking process information that was used to create the session. AZStd::string m_matchmakingData; - // A unique identifier for the session. + //! A unique identifier for the session. AZStd::string m_sessionId; - // A descriptive label that is associated with a session. + //! A descriptive label that is associated with a session. AZStd::string m_sessionName; - // The DNS identifier assigned to the instance that is running the session. + //! The DNS identifier assigned to the instance that is running the session. AZStd::string m_dnsName; - // The IP address of the session. + //! The IP address of the session. AZStd::string m_ipAddress; - // The port number for the session. + //! The port number for the session. uint16_t m_port = 0; - // The maximum number of players that can be connected simultaneously to the session. + //! The maximum number of players that can be connected simultaneously to the session. uint64_t m_maxPlayer = 0; - // Number of players currently in the session. + //! Number of players currently in the session. uint64_t m_currentPlayer = 0; - // Current status of the session. + //! Current status of the session. AZStd::string m_status; - // Provides additional information about session status. + //! Provides additional information about session status. AZStd::string m_statusReason; }; } // namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/Session/SessionNotifications.h b/Code/Framework/AzFramework/AzFramework/Session/SessionNotifications.h index 2213343aa2..cf1382aeba 100644 --- a/Code/Framework/AzFramework/AzFramework/Session/SessionNotifications.h +++ b/Code/Framework/AzFramework/AzFramework/Session/SessionNotifications.h @@ -29,42 +29,42 @@ namespace AzFramework static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; ////////////////////////////////////////////////////////////////////////// - // OnSessionHealthCheck is fired in health check process - // Use this notification to perform any custom health check - // @return True if OnSessionHealthCheck succeeds, false otherwise + //! OnSessionHealthCheck is fired in health check process + //! Use this notification to perform any custom health check + //! @return True if OnSessionHealthCheck succeeds, false otherwise virtual bool OnSessionHealthCheck() = 0; - // OnCreateSessionBegin is fired at the beginning of session creation process - // Use this notification to perform any necessary configuration or initialization before - // creating session - // @param sessionConfig The properties to describe a session - // @return True if OnCreateSessionBegin succeeds, false otherwise + //! OnCreateSessionBegin is fired at the beginning of session creation process + //! Use this notification to perform any necessary configuration or initialization before + //! creating session + //! @param sessionConfig The properties to describe a session + //! @return True if OnCreateSessionBegin succeeds, false otherwise virtual bool OnCreateSessionBegin(const SessionConfig& sessionConfig) = 0; - // OnCreateSessionEnd is fired at the end of session creation process - // Use this notification to perform any follow-up operation after session is created and active + //! OnCreateSessionEnd is fired at the end of session creation process + //! Use this notification to perform any follow-up operation after session is created and active virtual void OnCreateSessionEnd() = 0; - // OnDestroySessionBegin is fired at the beginning of session termination process - // Use this notification to perform any cleanup operation before destroying session, - // like gracefully disconnect players, cleanup data, etc. - // @return True if OnDestroySessionBegin succeeds, false otherwise + //! OnDestroySessionBegin is fired at the beginning of session termination process + //! Use this notification to perform any cleanup operation before destroying session, + //! like gracefully disconnect players, cleanup data, etc. + //! @return True if OnDestroySessionBegin succeeds, false otherwise virtual bool OnDestroySessionBegin() = 0; - // OnDestroySessionEnd is fired at the end of session termination process - // Use this notification to perform any follow-up operation after session is destroyed, - // like shutdown application process, etc. + //! OnDestroySessionEnd is fired at the end of session termination process + //! Use this notification to perform any follow-up operation after session is destroyed, + //! like shutdown application process, etc. virtual void OnDestroySessionEnd() = 0; - // OnUpdateSessionBegin is fired at the beginning of session update process - // Use this notification to perform any configuration or initialization to handle - // the session settings changing - // @param sessionConfig The properties to describe a session - // @param updateReason The reason for session update + //! OnUpdateSessionBegin is fired at the beginning of session update process + //! Use this notification to perform any configuration or initialization to handle + //! the session settings changing + //! @param sessionConfig The properties to describe a session + //! @param updateReason The reason for session update virtual void OnUpdateSessionBegin(const SessionConfig& sessionConfig, const AZStd::string& updateReason) = 0; - // OnUpdateSessionBegin is fired at the end of session update process - // Use this notification to perform any follow-up operations after session is updated + //! OnUpdateSessionBegin is fired at the end of session update process + //! Use this notification to perform any follow-up operations after session is updated virtual void OnUpdateSessionEnd() = 0; }; using SessionNotificationBus = AZ::EBus; diff --git a/Code/Framework/AzFramework/AzFramework/Session/SessionRequests.h b/Code/Framework/AzFramework/AzFramework/Session/SessionRequests.h index 1ae018e1ef..d806e6fefa 100644 --- a/Code/Framework/AzFramework/AzFramework/Session/SessionRequests.h +++ b/Code/Framework/AzFramework/AzFramework/Session/SessionRequests.h @@ -31,16 +31,16 @@ namespace AzFramework CreateSessionRequest() = default; virtual ~CreateSessionRequest() = default; - // A unique identifier for a player or entity creating the session. + //! A unique identifier for a player or entity creating the session. AZStd::string m_creatorId; - // A collection of custom properties for a session. + //! A collection of custom properties for a session. AZStd::unordered_map m_sessionProperties; - // A descriptive label that is associated with a session. + //! A descriptive label that is associated with a session. AZStd::string m_sessionName; - // The maximum number of players that can be connected simultaneously to the session. + //! The maximum number of players that can be connected simultaneously to the session. uint64_t m_maxPlayer = 0; }; @@ -54,17 +54,17 @@ namespace AzFramework SearchSessionsRequest() = default; virtual ~SearchSessionsRequest() = default; - // String containing the search criteria for the session search. If no filter expression is included, the request returns results - // for all active sessions. + //! String containing the search criteria for the session search. If no filter expression is included, the request returns results + //! for all active sessions. AZStd::string m_filterExpression; - // Instructions on how to sort the search results. If no sort expression is included, the request returns results in random order. + //! Instructions on how to sort the search results. If no sort expression is included, the request returns results in random order. AZStd::string m_sortExpression; - // The maximum number of results to return. + //! The maximum number of results to return. uint8_t m_maxResult = 0; - // A token that indicates the start of the next sequential page of results. + //! A token that indicates the start of the next sequential page of results. AZStd::string m_nextToken; }; @@ -78,10 +78,10 @@ namespace AzFramework SearchSessionsResponse() = default; virtual ~SearchSessionsResponse() = default; - // A collection of sessions that match the search criteria and sorted in specific order. + //! A collection of sessions that match the search criteria and sorted in specific order. AZStd::vector m_sessionConfigs; - // A token that indicates the start of the next sequential page of results. + //! A token that indicates the start of the next sequential page of results. AZStd::string m_nextToken; }; @@ -95,13 +95,13 @@ namespace AzFramework JoinSessionRequest() = default; virtual ~JoinSessionRequest() = default; - // A unique identifier for the session. + //! A unique identifier for the session. AZStd::string m_sessionId; - // A unique identifier for a player. Player IDs are developer-defined. + //! A unique identifier for a player. Player IDs are developer-defined. AZStd::string m_playerId; - // Developer-defined information related to a player. + //! Developer-defined information related to a player. AZStd::string m_playerData; }; } // namespace AzFramework diff --git a/Code/Framework/AzGameFramework/AzGameFramework/Application/GameApplication.cpp b/Code/Framework/AzGameFramework/AzGameFramework/Application/GameApplication.cpp index 0cce93d751..6957844452 100644 --- a/Code/Framework/AzGameFramework/AzGameFramework/Application/GameApplication.cpp +++ b/Code/Framework/AzGameFramework/AzGameFramework/Application/GameApplication.cpp @@ -82,7 +82,7 @@ namespace AzGameFramework // Used the lowercase the platform name since the bootstrap.game...setreg is being loaded // from the asset cache root where all the files are in lowercased from regardless of the filesystem case-sensitivity - static constexpr char filename[] = "bootstrap.game." AZ_BUILD_CONFIGURATION_TYPE "." AZ_TRAIT_OS_PLATFORM_CODENAME_LOWER ".setreg"; + static constexpr char filename[] = "bootstrap.game." AZ_BUILD_CONFIGURATION_TYPE ".setreg"; AZ::IO::FixedMaxPath cacheRootPath; if (registry.Get(cacheRootPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_CacheRootFolder)) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyAssetCtrl.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyAssetCtrl.cpp index 7f3b9e61fd..24b2c7466e 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyAssetCtrl.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyAssetCtrl.cpp @@ -1387,7 +1387,10 @@ namespace AzToolsFramework QDataStream stream(&pixmapBytes, QIODevice::ReadOnly); QPixmap pixmap; stream >> pixmap; - GUI->SetBrowseButtonIcon(pixmap); + if (!pixmap.isNull()) + { + GUI->SetBrowseButtonIcon(pixmap); + } } } } @@ -1417,6 +1420,8 @@ namespace AzToolsFramework } else if (attrib == AZ_CRC_CE("ThumbnailIcon")) { + GUI->SetCustomThumbnailEnabled(false); + AZStd::string iconPath; if (attrValue->Read(iconPath) && !iconPath.empty()) { @@ -1434,8 +1439,11 @@ namespace AzToolsFramework QDataStream stream(&pixmapBytes, QIODevice::ReadOnly); QPixmap pixmap; stream >> pixmap; - GUI->SetCustomThumbnailEnabled(true); - GUI->SetCustomThumbnailPixmap(pixmap); + if (!pixmap.isNull()) + { + GUI->SetCustomThumbnailEnabled(true); + GUI->SetCustomThumbnailPixmap(pixmap); + } } } } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/ThumbnailPropertyCtrl.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/ThumbnailPropertyCtrl.cpp index d8ddee6b76..0bbb898196 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/ThumbnailPropertyCtrl.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/ThumbnailPropertyCtrl.cpp @@ -67,16 +67,9 @@ namespace AzToolsFramework void ThumbnailPropertyCtrl::SetThumbnailKey(Thumbnailer::SharedThumbnailKey key, const char* contextName) { - if (m_customThumbnailEnabled) - { - ClearThumbnail(); - } - else - { - m_key = key; - m_thumbnail->SetThumbnailKey(m_key, contextName); - m_thumbnailEnlarged->SetThumbnailKey(m_key, contextName); - } + m_key = key; + m_thumbnail->SetThumbnailKey(m_key, contextName); + m_thumbnailEnlarged->SetThumbnailKey(m_key, contextName); UpdateVisibility(); } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportUi/ViewportUiDisplay.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportUi/ViewportUiDisplay.cpp index 5155de3087..0f79e3535d 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportUi/ViewportUiDisplay.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportUi/ViewportUiDisplay.cpp @@ -301,6 +301,7 @@ namespace AzToolsFramework::ViewportUi::Internal HighlightBorderSize + ViewportUiOverlayMargin, HighlightBorderSize + ViewportUiOverlayMargin); m_componentModeBorderText.setVisible(true); m_componentModeBorderText.setText(borderTitle.c_str()); + UpdateUiOverlayGeometry(); } void ViewportUiDisplay::RemoveViewportBorder() diff --git a/Code/Legacy/CrySystem/CrySystem_precompiled.h b/Code/Legacy/CrySystem/CrySystem_precompiled.h index faa924931f..3ececa6514 100644 --- a/Code/Legacy/CrySystem/CrySystem_precompiled.h +++ b/Code/Legacy/CrySystem/CrySystem_precompiled.h @@ -60,13 +60,6 @@ #include -#if defined(WIN32) || defined(WIN64) || defined(APPLE) || defined(LINUX) -#if defined(DEDICATED_SERVER) -// enable/disable map load slicing functionality from the build -#define MAP_LOADING_SLICING -#endif -#endif - #ifdef WIN32 #include #include diff --git a/Code/Legacy/CrySystem/ViewSystem/ViewSystem.cpp b/Code/Legacy/CrySystem/ViewSystem/ViewSystem.cpp index e83676827f..d7e5c081c0 100644 --- a/Code/Legacy/CrySystem/ViewSystem/ViewSystem.cpp +++ b/Code/Legacy/CrySystem/ViewSystem/ViewSystem.cpp @@ -115,17 +115,20 @@ CViewSystem::CViewSystem(ISystem* pSystem) , m_useDeferredViewSystemUpdate(false) , m_bControlsAudioListeners(true) { -#if !defined(_RELEASE) && !defined(DEDICATED_SERVER) - if (!s_debugCamera) +#if !defined(_RELEASE) + if (!gEnv->IsDedicated()) { - s_debugCamera = new DebugCamera; - } + if (!s_debugCamera) + { + s_debugCamera = new DebugCamera; + } - REGISTER_COMMAND("debugCameraToggle", ToggleDebugCamera, VF_DEV_ONLY, "Toggle the debug camera.\n"); - REGISTER_COMMAND("debugCameraInvertY", ToggleDebugCameraInvertY, VF_DEV_ONLY, "Toggle debug camera Y-axis inversion.\n"); - REGISTER_COMMAND("debugCameraMove", DebugCameraMove, VF_DEV_ONLY, "Move the debug camera the specified distance (x y z).\n"); - gEnv->pConsole->CreateKeyBind("ctrl_keyboard_key_punctuation_backslash", "debugCameraToggle"); - gEnv->pConsole->CreateKeyBind("alt_keyboard_key_punctuation_backslash", "debugCameraInvertY"); + REGISTER_COMMAND("debugCameraToggle", ToggleDebugCamera, VF_DEV_ONLY, "Toggle the debug camera.\n"); + REGISTER_COMMAND("debugCameraInvertY", ToggleDebugCameraInvertY, VF_DEV_ONLY, "Toggle debug camera Y-axis inversion.\n"); + REGISTER_COMMAND("debugCameraMove", DebugCameraMove, VF_DEV_ONLY, "Move the debug camera the specified distance (x y z).\n"); + gEnv->pConsole->CreateKeyBind("ctrl_keyboard_key_punctuation_backslash", "debugCameraToggle"); + gEnv->pConsole->CreateKeyBind("alt_keyboard_key_punctuation_backslash", "debugCameraInvertY"); + } #endif REGISTER_CVAR2("cl_camera_noise", &m_fCameraNoise, -1, 0, @@ -167,6 +170,21 @@ CViewSystem::~CViewSystem() { m_pSystem->GetILevelSystem()->RemoveListener(this); } + +#if !defined(_RELEASE) + if (!gEnv->IsDedicated()) + { + UNREGISTER_COMMAND("debugCameraToggle"); + UNREGISTER_COMMAND("debugCameraInvertY"); + UNREGISTER_COMMAND("debugCameraMove"); + + if (s_debugCamera) + { + delete s_debugCamera; + s_debugCamera = nullptr; + } + } +#endif } //------------------------------------------------------------------------ diff --git a/Code/Tools/AssetBundler/CMakeLists.txt b/Code/Tools/AssetBundler/CMakeLists.txt index d613b35ce5..dcc62595c9 100644 --- a/Code/Tools/AssetBundler/CMakeLists.txt +++ b/Code/Tools/AssetBundler/CMakeLists.txt @@ -47,6 +47,10 @@ ly_add_target( AZ::AssetBundlerBatch.Static ) +# Adds a specialized .setreg to identify gems enabled in the active project. +# This associates the AssetBundlerBatch target with the .Builders gem variants. +ly_set_gem_variant_to_load(TARGETS AssetBundlerBatch VARIANTS Builders) + # AssetBundler - Qt GUI Application ly_add_target( NAME AssetBundler ${PAL_TRAIT_BUILD_ASSETBUNDLER_APPLICATION_TYPE} @@ -73,6 +77,10 @@ ly_add_target( ${additional_dependencies} ) +# Adds a specialized .setreg to identify gems enabled in the active project. +# This associates the AssetBundler target with the .Builders gem variants. +ly_set_gem_variant_to_load(TARGETS AssetBundler VARIANTS Builders) + if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) ly_add_target( diff --git a/Code/Tools/AssetBundler/source/utils/applicationManager.cpp b/Code/Tools/AssetBundler/source/utils/applicationManager.cpp index 0742035c1a..0388570fdd 100644 --- a/Code/Tools/AssetBundler/source/utils/applicationManager.cpp +++ b/Code/Tools/AssetBundler/source/utils/applicationManager.cpp @@ -54,7 +54,12 @@ namespace AssetBundler bool ApplicationManager::Init() { AZ::Debug::TraceMessageBus::Handler::BusConnect(); - Start(AzFramework::Application::Descriptor()); + + ComponentApplication::StartupParameters startupParameters; + // The AssetBundler does not need to load gems + startupParameters.m_loadDynamicModules = false; + Start(AzFramework::Application::Descriptor(), startupParameters); + AZ::SerializeContext* context; EBUS_EVENT_RESULT(context, AZ::ComponentApplicationBus, GetSerializeContext); AZ_Assert(context, "No serialize context"); diff --git a/Code/Tools/AssetBundler/tests/applicationManagerTests.cpp b/Code/Tools/AssetBundler/tests/applicationManagerTests.cpp index 35d3a5da1f..0d915dcc49 100644 --- a/Code/Tools/AssetBundler/tests/applicationManagerTests.cpp +++ b/Code/Tools/AssetBundler/tests/applicationManagerTests.cpp @@ -71,7 +71,11 @@ namespace AssetBundler m_data->m_applicationManager.reset(aznew MockApplicationManagerTest(0, 0)); - m_data->m_applicationManager->Start(AzFramework::Application::Descriptor()); + + AZ::ComponentApplication::StartupParameters startupParameters; + // The AssetBundler does not need to load gems + startupParameters.m_loadDynamicModules = false; + m_data->m_applicationManager->Start(AzFramework::Application::Descriptor(), startupParameters); // Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is // shared across the whole engine, if multiple tests are run in parallel, the saving could cause a crash diff --git a/Code/Tools/AssetProcessor/native/InternalBuilders/SettingsRegistryBuilder.cpp b/Code/Tools/AssetProcessor/native/InternalBuilders/SettingsRegistryBuilder.cpp index c65ab24aed..6f15fa5ea2 100644 --- a/Code/Tools/AssetProcessor/native/InternalBuilders/SettingsRegistryBuilder.cpp +++ b/Code/Tools/AssetProcessor/native/InternalBuilders/SettingsRegistryBuilder.cpp @@ -259,6 +259,11 @@ namespace AssetProcessor scratchBuffer.reserve(512 * 1024); // Reserve 512kb to avoid repeatedly resizing the buffer; AZStd::fixed_vector platformCodes; AzFramework::PlatformHelper::AppendPlatformCodeNames(platformCodes, request.m_platformInfo.m_identifier); + AZ_Assert(platformCodes.size() <= 1, "A one-to-one mapping of asset type platform identifier" + " to platform codename is required in the SettingsRegistryBuilder." + " The bootstrap.game is now only produced per build configuration and doesn't take into account" + " different platforms names"); + const AZStd::string& assetPlatformIdentifier = request.m_jobDescription.GetPlatformIdentifier(); // Determines the suffix that will be used for the launcher based on processing server vs non-server assets const char* launcherType = assetPlatformIdentifier != AzFramework::PlatformHelper::GetPlatformName(AzFramework::PlatformId::SERVER) @@ -293,9 +298,10 @@ namespace AssetProcessor outputBuffer.Reserve(512 * 1024); // Reserve 512kb to avoid repeatedly resizing the buffer; SettingsExporter exporter(outputBuffer, excludes); - for (AZStd::string_view platform : platformCodes) + if (!platformCodes.empty()) { - AZ::u32 productSubID = static_cast(AZStd::hash{}(platform)); // Deliberately ignoring half the bits. + AZStd::string_view platform = platformCodes.front(); + constexpr AZ::u32 productSubID = 0; for (size_t i = 0; i < AZStd::size(specializations); ++i) { const AZ::SettingsRegistryInterface::Specializations& specialization = specializations[i]; @@ -337,7 +343,7 @@ namespace AssetProcessor // The purpose of this section is to copy the Gem's SourcePaths from the Global Settings Registry // the local SettingsRegistry. The reason this is needed is so that the call to // `MergeSettingsToRegistry_GemRegistries` below is able to locate each gem's "/Registry" folder - // that will be merged into the bootstrap.game...setreg file + // that will be merged into the bootstrap.game..setreg file // This is used by the GameLauncher applications to read from a single merged .setreg file // containing the settings needed to run a game/simulation without have access to the source code base registry AZStd::vector gemInfos; @@ -408,8 +414,6 @@ namespace AssetProcessor } outputPath += specialization.GetSpecialization(0); // Append configuration - outputPath += '.'; - outputPath += platform; outputPath += ".setreg"; AZ::IO::SystemFile file; diff --git a/Code/Tools/AssetProcessor/native/tests/AssetProcessorTest.h b/Code/Tools/AssetProcessor/native/tests/AssetProcessorTest.h index 258268c182..df40683027 100644 --- a/Code/Tools/AssetProcessor/native/tests/AssetProcessorTest.h +++ b/Code/Tools/AssetProcessor/native/tests/AssetProcessorTest.h @@ -25,7 +25,7 @@ namespace AssetProcessor : public ::testing::Test { protected: - UnitTestUtils::AssertAbsorber* m_errorAbsorber; + AZStd::unique_ptr m_errorAbsorber{}; FileStatePassthrough m_fileStateCache; void SetUp() override @@ -40,7 +40,7 @@ namespace AssetProcessor m_ownsSysAllocator = true; AZ::AllocatorInstance::Create(); } - m_errorAbsorber = new UnitTestUtils::AssertAbsorber(); + m_errorAbsorber = AZStd::make_unique(); m_application = AZStd::make_unique(); @@ -60,8 +60,8 @@ namespace AssetProcessor AssetUtilities::ResetAssetRoot(); m_application.reset(); - delete m_errorAbsorber; - m_errorAbsorber = nullptr; + m_errorAbsorber.reset(); + if (m_ownsSysAllocator) { AZ::AllocatorInstance::Destroy(); diff --git a/Code/Tools/AssetProcessor/native/tests/assetmanager/AssetProcessorManagerTest.cpp b/Code/Tools/AssetProcessor/native/tests/assetmanager/AssetProcessorManagerTest.cpp index 6c19213414..0b02b168dc 100644 --- a/Code/Tools/AssetProcessor/native/tests/assetmanager/AssetProcessorManagerTest.cpp +++ b/Code/Tools/AssetProcessor/native/tests/assetmanager/AssetProcessorManagerTest.cpp @@ -4447,7 +4447,9 @@ AssetBuilderSDK::AssetBuilderDesc MockBuilderInfoHandler::CreateBuilderDesc(cons void FingerprintTest::SetUp() { + AZ_Printf("FingerprintTest", "SetUp start"); AssetProcessorManagerTest::SetUp(); + AZ_Printf("FingerprintTest", "SetUp self"); // We don't want the mock application manager to provide builder descriptors, mockBuilderInfoHandler will provide our own m_mockApplicationManager->BusDisconnect(); @@ -4466,18 +4468,23 @@ void FingerprintTest::SetUp() }); ASSERT_TRUE(UnitTestUtils::CreateDummyFile(m_absolutePath, "")); + AZ_Printf("FingerprintTest", "SetUp end"); } void FingerprintTest::TearDown() { + AZ_Printf("FingerprintTest", "TearDown start"); m_jobResults = AZStd::vector{}; m_mockBuilderInfoHandler = {}; + AZ_Printf("FingerprintTest", "TearDown parent"); AssetProcessorManagerTest::TearDown(); + AZ_Printf("FingerprintTest", "TearDown end"); } void FingerprintTest::RunFingerprintTest(QString builderFingerprint, QString jobFingerprint, bool expectedResult) { + AZ_Printf("FingerprintTest", "Fingerprint Test Start"); m_mockBuilderInfoHandler.m_builderDesc.m_analysisFingerprint = builderFingerprint.toUtf8().data(); m_mockBuilderInfoHandler.m_jobFingerprint = jobFingerprint; QMetaObject::invokeMethod(m_assetProcessorManager.get(), "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, m_absolutePath)); @@ -4486,6 +4493,7 @@ void FingerprintTest::RunFingerprintTest(QString builderFingerprint, QString job ASSERT_EQ(m_mockBuilderInfoHandler.m_createJobsCount, 1); ASSERT_EQ(m_jobResults.size(), 1); ASSERT_EQ(m_jobResults[0].m_autoFail, expectedResult); + AZ_Printf("FingerprintTest", "Fingerprint Test End"); } TEST_F(FingerprintTest, FingerprintChecking_JobFingerprint_NoBuilderFingerprint) diff --git a/Code/Tools/AssetProcessor/native/unittests/UnitTestRunner.h b/Code/Tools/AssetProcessor/native/unittests/UnitTestRunner.h index c281b3050a..0c4357f84a 100644 --- a/Code/Tools/AssetProcessor/native/unittests/UnitTestRunner.h +++ b/Code/Tools/AssetProcessor/native/unittests/UnitTestRunner.h @@ -21,6 +21,7 @@ #endif #include +#include //! These macros can be used for checking your unit tests, //! you can check AssetScannerUnitTest.cpp for usage @@ -155,6 +156,7 @@ namespace UnitTestUtils bool OnPreWarning([[maybe_unused]] const char* window, [[maybe_unused]] const char* fileName, [[maybe_unused]] int line, [[maybe_unused]] const char* func, [[maybe_unused]] const char* message) override { + UnitTest::ColoredPrintf(UnitTest::COLOR_YELLOW, message); ++m_numWarningsAbsorbed; if (m_debugMessages) { @@ -165,6 +167,7 @@ namespace UnitTestUtils bool OnPreAssert([[maybe_unused]] const char* fileName, [[maybe_unused]] int line, [[maybe_unused]] const char* func, [[maybe_unused]] const char* message) override { + UnitTest::ColoredPrintf(UnitTest::COLOR_YELLOW, message); ++m_numAssertsAbsorbed; if (m_debugMessages) { @@ -175,6 +178,7 @@ namespace UnitTestUtils bool OnPreError([[maybe_unused]] const char* window, [[maybe_unused]] const char* fileName, [[maybe_unused]] int line, [[maybe_unused]] const char* func, [[maybe_unused]] const char* message) override { + UnitTest::ColoredPrintf(UnitTest::COLOR_YELLOW, message); ++m_numErrorsAbsorbed; if (m_debugMessages) { @@ -183,8 +187,9 @@ namespace UnitTestUtils return true; // I handled this, do not forward it } - bool OnPrintf(const char* /*window*/, const char* /*message*/) override + bool OnPrintf(const char* /*window*/, const char* message) override { + UnitTest::ColoredPrintf(UnitTest::COLOR_YELLOW, message); ++m_numMessagesAbsorbed; return true; } diff --git a/Code/Tools/ProjectManager/Platform/Windows/ProjectBuilderWorker_windows.cpp b/Code/Tools/ProjectManager/Platform/Windows/ProjectBuilderWorker_windows.cpp index 075c6de774..b6b37b222d 100644 --- a/Code/Tools/ProjectManager/Platform/Windows/ProjectBuilderWorker_windows.cpp +++ b/Code/Tools/ProjectManager/Platform/Windows/ProjectBuilderWorker_windows.cpp @@ -21,8 +21,7 @@ namespace O3DE::ProjectManager return AZ::Success(QStringList{ ProjectCMakeCommand, "-B", targetBuildPath, "-S", m_projectInfo.m_path, - QString("-DLY_3RDPARTY_PATH=").append(thirdPartyPath), - "-DLY_UNITY_BUILD=ON" } ); + QString("-DLY_3RDPARTY_PATH=").append(thirdPartyPath) } ); } AZ::Outcome ProjectBuilderWorker::ConstructCmakeBuildCommandArguments() const diff --git a/Code/Tools/ProjectManager/Resources/ProjectManager.qss b/Code/Tools/ProjectManager/Resources/ProjectManager.qss index fecdc67920..d3ec066be7 100644 --- a/Code/Tools/ProjectManager/Resources/ProjectManager.qss +++ b/Code/Tools/ProjectManager/Resources/ProjectManager.qss @@ -242,11 +242,15 @@ QTabBar::tab:focus { /************** Project Settings **************/ #projectSettings { - margin-top:42px; + margin-top:30px; +} + +#projectPreviewLabel { + margin: 10px 0 5px 0; } #projectTemplate { - margin: 55px 0 0 50px; + margin: 25px 0 0 50px; } #projectTemplateLabel { font-size:16px; diff --git a/Code/Tools/ProjectManager/Source/EngineSettingsScreen.cpp b/Code/Tools/ProjectManager/Source/EngineSettingsScreen.cpp index dec1c9a257..c7df00f423 100644 --- a/Code/Tools/ProjectManager/Source/EngineSettingsScreen.cpp +++ b/Code/Tools/ProjectManager/Source/EngineSettingsScreen.cpp @@ -11,19 +11,28 @@ #include #include #include +#include #include #include #include #include +#include namespace O3DE::ProjectManager { EngineSettingsScreen::EngineSettingsScreen(QWidget* parent) : ScreenWidget(parent) { - auto* layout = new QVBoxLayout(); + QScrollArea* scrollArea = new QScrollArea(this); + scrollArea->setWidgetResizable(true); + + QWidget* scrollWidget = new QWidget(this); + scrollArea->setWidget(scrollWidget); + + QVBoxLayout* layout = new QVBoxLayout(scrollWidget); layout->setAlignment(Qt::AlignTop); + scrollWidget->setLayout(layout); setObjectName("engineSettingsScreen"); @@ -39,9 +48,18 @@ namespace O3DE::ProjectManager formTitleLabel->setObjectName("formTitleLabel"); layout->addWidget(formTitleLabel); - m_engineVersion = new FormLineEditWidget(tr("Engine Version"), engineInfo.m_version, this); - m_engineVersion->lineEdit()->setReadOnly(true); - layout->addWidget(m_engineVersion); + FormLineEditWidget* engineName = new FormLineEditWidget(tr("Engine Name"), engineInfo.m_name, this); + engineName->lineEdit()->setReadOnly(true); + layout->addWidget(engineName); + + FormLineEditWidget* engineVersion = new FormLineEditWidget(tr("Engine Version"), engineInfo.m_version, this); + engineVersion->lineEdit()->setReadOnly(true); + layout->addWidget(engineVersion); + + FormBrowseEditWidget* engineFolder = new FormBrowseEditWidget(tr("Engine Folder"), engineInfo.m_path, this); + engineFolder->lineEdit()->setReadOnly(true); + connect( engineFolder, &FormBrowseEditWidget::OnBrowse, [engineInfo]{ AzQtComponents::ShowFileOnDesktop(engineInfo.m_path); }); + layout->addWidget(engineFolder); m_thirdParty = new FormFolderBrowseEditWidget(tr("3rd Party Software Folder"), engineInfo.m_thirdPartyPath, this); m_thirdParty->lineEdit()->setValidator(new PathValidator(PathValidator::PathMode::ExistingFolder, this)); @@ -71,7 +89,11 @@ namespace O3DE::ProjectManager connect(m_defaultProjectTemplates->lineEdit(), &QLineEdit::textChanged, this, &EngineSettingsScreen::OnTextChanged); layout->addWidget(m_defaultProjectTemplates); - setLayout(layout); + QVBoxLayout* mainLayout = new QVBoxLayout(); + mainLayout->setAlignment(Qt::AlignTop); + mainLayout->setMargin(0); + mainLayout->addWidget(scrollArea); + setLayout(mainLayout); } ProjectManagerScreen EngineSettingsScreen::GetScreenEnum() diff --git a/Code/Tools/ProjectManager/Source/EngineSettingsScreen.h b/Code/Tools/ProjectManager/Source/EngineSettingsScreen.h index 2f16400405..1efabd4b5e 100644 --- a/Code/Tools/ProjectManager/Source/EngineSettingsScreen.h +++ b/Code/Tools/ProjectManager/Source/EngineSettingsScreen.h @@ -29,7 +29,6 @@ namespace O3DE::ProjectManager void OnTextChanged(); private: - FormLineEditWidget* m_engineVersion; FormBrowseEditWidget* m_thirdParty; FormBrowseEditWidget* m_defaultProjects; FormBrowseEditWidget* m_defaultGems; diff --git a/Code/Tools/ProjectManager/Source/FormBrowseEditWidget.cpp b/Code/Tools/ProjectManager/Source/FormBrowseEditWidget.cpp index 8cd28fcbb4..fe101cf37a 100644 --- a/Code/Tools/ProjectManager/Source/FormBrowseEditWidget.cpp +++ b/Code/Tools/ProjectManager/Source/FormBrowseEditWidget.cpp @@ -20,7 +20,8 @@ namespace O3DE::ProjectManager setObjectName("formBrowseEditWidget"); QPushButton* browseButton = new QPushButton(this); - connect(browseButton, &QPushButton::pressed, this, &FormBrowseEditWidget::HandleBrowseButton); + connect( browseButton, &QPushButton::pressed, [this]{ emit OnBrowse(); }); + connect( this, &FormBrowseEditWidget::OnBrowse, this, &FormBrowseEditWidget::HandleBrowseButton); m_frameLayout->addWidget(browseButton); } @@ -34,7 +35,7 @@ namespace O3DE::ProjectManager int key = event->key(); if (key == Qt::Key_Return || key == Qt::Key_Enter) { - HandleBrowseButton(); + emit OnBrowse(); } } diff --git a/Code/Tools/ProjectManager/Source/FormBrowseEditWidget.h b/Code/Tools/ProjectManager/Source/FormBrowseEditWidget.h index a1f6948ce9..179fe03253 100644 --- a/Code/Tools/ProjectManager/Source/FormBrowseEditWidget.h +++ b/Code/Tools/ProjectManager/Source/FormBrowseEditWidget.h @@ -24,10 +24,13 @@ namespace O3DE::ProjectManager explicit FormBrowseEditWidget(const QString& labelText = "", QWidget* parent = nullptr); ~FormBrowseEditWidget() = default; + signals: + void OnBrowse(); + protected: void keyPressEvent(QKeyEvent* event) override; protected slots: - virtual void HandleBrowseButton() = 0; + virtual void HandleBrowseButton() {}; }; } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp index a22f41d054..98121d7cd2 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp @@ -80,7 +80,7 @@ namespace O3DE::ProjectManager void GemCatalogScreen::ReinitForProject(const QString& projectPath) { - m_gemModel->clear(); + m_gemModel->Clear(); m_gemsToRegisterWithProject.clear(); FillModel(projectPath); @@ -145,10 +145,11 @@ namespace O3DE::ProjectManager } } - void GemCatalogScreen::OnGemStatusChanged(const QModelIndex& modelIndex, uint32_t numChangedDependencies) + void GemCatalogScreen::OnGemStatusChanged(const QString& gemName, uint32_t numChangedDependencies) { if (m_notificationsEnabled) { + QModelIndex modelIndex = m_gemModel->FindIndexByNameString(gemName); bool added = GemModel::IsAdded(modelIndex); bool dependency = GemModel::IsAddedDependency(modelIndex); @@ -233,7 +234,11 @@ namespace O3DE::ProjectManager const QVector allRepoGemInfos = allRepoGemInfosResult.GetValue(); for (const GemInfo& gemInfo : allRepoGemInfos) { - m_gemModel->AddGem(gemInfo); + // do not add gems that have already been downloaded + if (!m_gemModel->FindIndexByNameString(gemInfo.m_name).isValid()) + { + m_gemModel->AddGem(gemInfo); + } } } else @@ -257,7 +262,8 @@ namespace O3DE::ProjectManager GemModel::SetWasPreviouslyAdded(*m_gemModel, modelIndex, true); GemModel::SetIsAdded(*m_gemModel, modelIndex, true); } - else + // ${Name} is a special name used in templates and is not really an error + else if (enabledGemName != "${Name}") { AZ_Warning("ProjectManager::GemCatalog", false, "Cannot find entry for gem with name '%s'. The CMake target name probably does not match the specified name in the gem.json.", diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h index 1ade87af0c..1b34019d1a 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h @@ -46,7 +46,7 @@ namespace O3DE::ProjectManager DownloadController* GetDownloadController() const { return m_downloadController; } public slots: - void OnGemStatusChanged(const QModelIndex& modelIndex, uint32_t numChangedDependencies); + void OnGemStatusChanged(const QString& gemName, uint32_t numChangedDependencies); void OnAddGemClicked(); protected: diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.cpp index acdef483ae..fb228c0b4a 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.cpp @@ -27,6 +27,14 @@ namespace O3DE::ProjectManager void GemModel::AddGem(const GemInfo& gemInfo) { + if (FindIndexByNameString(gemInfo.m_name).isValid()) + { + // do not add gems with duplicate names + // this can happen by mistake or when a gem repo has a gem with the same name as a local gem + AZ_TracePrintf("GemModel", "Ignoring duplicate gem: %s", gemInfo.m_name.toUtf8().constData()); + return; + } + QStandardItem* item = new QStandardItem(); item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); @@ -60,6 +68,7 @@ namespace O3DE::ProjectManager void GemModel::Clear() { clear(); + m_nameToIndexMap.clear(); } void GemModel::UpdateGemDependencies() @@ -276,9 +285,11 @@ namespace O3DE::ProjectManager void GemModel::SetIsAdded(QAbstractItemModel& model, const QModelIndex& modelIndex, bool isAdded) { + // get the gemName first, because the modelIndex data change after adding because of filters + QString gemName = modelIndex.data(RoleName).toString(); model.setData(modelIndex, isAdded, RoleIsAdded); - UpdateDependencies(model, modelIndex); + UpdateDependencies(model, gemName, isAdded); } bool GemModel::HasDependentGems(const QModelIndex& modelIndex) const @@ -294,15 +305,17 @@ namespace O3DE::ProjectManager return false; } - void GemModel::UpdateDependencies(QAbstractItemModel& model, const QModelIndex& modelIndex) + void GemModel::UpdateDependencies(QAbstractItemModel& model, const QString& gemName, bool isAdded) { GemModel* gemModel = GetSourceModel(&model); AZ_Assert(gemModel, "Failed to obtain GemModel"); + QModelIndex modelIndex = gemModel->FindIndexByNameString(gemName); + QVector dependencies = gemModel->GatherGemDependencies(modelIndex); uint32_t numChangedDependencies = 0; - if (IsAdded(modelIndex)) + if (isAdded) { for (const QModelIndex& dependency : dependencies) { @@ -324,7 +337,7 @@ namespace O3DE::ProjectManager bool hasDependentGems = gemModel->HasDependentGems(modelIndex); if (IsAddedDependency(modelIndex) != hasDependentGems) { - SetIsAddedDependency(model, modelIndex, hasDependentGems); + SetIsAddedDependency(*gemModel, modelIndex, hasDependentGems); } for (const QModelIndex& dependency : dependencies) @@ -343,7 +356,7 @@ namespace O3DE::ProjectManager } } - gemModel->emit gemStatusChanged(modelIndex, numChangedDependencies); + gemModel->emit gemStatusChanged(gemName, numChangedDependencies); } void GemModel::SetIsAddedDependency(QAbstractItemModel& model, const QModelIndex& modelIndex, bool isAdded) diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h index 938543eb39..35231cc105 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h @@ -64,7 +64,7 @@ namespace O3DE::ProjectManager static bool NeedsToBeAdded(const QModelIndex& modelIndex, bool includeDependencies = false); static bool NeedsToBeRemoved(const QModelIndex& modelIndex, bool includeDependencies = false); static bool HasRequirement(const QModelIndex& modelIndex); - static void UpdateDependencies(QAbstractItemModel& model, const QModelIndex& modelIndex); + static void UpdateDependencies(QAbstractItemModel& model, const QString& gemName, bool isAdded); static void SetDownloadStatus(QAbstractItemModel& model, const QModelIndex& modelIndex, GemInfo::DownloadStatus status); bool DoGemsToBeAddedHaveRequirements() const; @@ -78,7 +78,7 @@ namespace O3DE::ProjectManager int TotalAddedGems(bool includeDependencies = false) const; signals: - void gemStatusChanged(const QModelIndex& modelIndex, uint32_t numChangedDependencies); + void gemStatusChanged(const QString& gemName, uint32_t numChangedDependencies); private: void FindGemDisplayNamesByNameStrings(QStringList& inOutGemNames); diff --git a/Code/Tools/ProjectManager/Source/UpdateProjectSettingsScreen.cpp b/Code/Tools/ProjectManager/Source/UpdateProjectSettingsScreen.cpp index 6f7f7e1bed..3bfc07c5b0 100644 --- a/Code/Tools/ProjectManager/Source/UpdateProjectSettingsScreen.cpp +++ b/Code/Tools/ProjectManager/Source/UpdateProjectSettingsScreen.cpp @@ -36,6 +36,7 @@ namespace O3DE::ProjectManager QLabel* projectPreviewLabel = new QLabel(tr("Select an image (PNG). Minimum %1 x %2 pixels.") .arg(QString::number(ProjectPreviewImageWidth), QString::number(ProjectPreviewImageHeight))); + projectPreviewLabel->setObjectName("projectPreviewLabel"); previewExtrasLayout->addWidget(projectPreviewLabel); m_projectPreviewImage = new QLabel(this); diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftCreateSessionOnQueueRequest.h b/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftCreateSessionOnQueueRequest.h index a9b06c5bc9..24dfce1dfa 100644 --- a/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftCreateSessionOnQueueRequest.h +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftCreateSessionOnQueueRequest.h @@ -25,11 +25,11 @@ namespace AWSGameLift AWSGameLiftCreateSessionOnQueueRequest() = default; virtual ~AWSGameLiftCreateSessionOnQueueRequest() = default; - // Name of the queue to use to place the new game session. You can use either the queue name or ARN value. + //! Name of the queue to use to place the new game session. You can use either the queue name or ARN value. AZStd::string m_queueName; - // A unique identifier to assign to the new game session placement. This value is developer-defined. - // The value must be unique across all Regions and cannot be reused unless you are resubmitting a canceled or timed-out placement request. + //! A unique identifier to assign to the new game session placement. This value is developer-defined. + //! The value must be unique across all Regions and cannot be reused unless you are resubmitting a canceled or timed-out placement request. AZStd::string m_placementId; }; } // namespace AWSGameLift diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftCreateSessionRequest.h b/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftCreateSessionRequest.h index 4fab16ca70..97ffd0b321 100644 --- a/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftCreateSessionRequest.h +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftCreateSessionRequest.h @@ -25,14 +25,14 @@ namespace AWSGameLift AWSGameLiftCreateSessionRequest() = default; virtual ~AWSGameLiftCreateSessionRequest() = default; - // A unique identifier for the alias associated with the fleet to create a game session in. + //! A unique identifier for the alias associated with the fleet to create a game session in. AZStd::string m_aliasId; - // A unique identifier for the fleet to create a game session in. + //! A unique identifier for the fleet to create a game session in. AZStd::string m_fleetId; - // Custom string that uniquely identifies the new game session request. - // This is useful for ensuring that game session requests with the same idempotency token are processed only once. + //! Custom string that uniquely identifies the new game session request. + //! This is useful for ensuring that game session requests with the same idempotency token are processed only once. AZStd::string m_idempotencyToken; }; } // namespace AWSGameLift diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftSearchSessionsRequest.h b/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftSearchSessionsRequest.h index bfc2dc630e..02f3638998 100644 --- a/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftSearchSessionsRequest.h +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftSearchSessionsRequest.h @@ -25,13 +25,13 @@ namespace AWSGameLift AWSGameLiftSearchSessionsRequest() = default; virtual ~AWSGameLiftSearchSessionsRequest() = default; - // A unique identifier for the alias associated with the fleet to search for active game sessions. + //! A unique identifier for the alias associated with the fleet to search for active game sessions. AZStd::string m_aliasId; - // A unique identifier for the fleet to search for active game sessions. + //! A unique identifier for the fleet to search for active game sessions. AZStd::string m_fleetId; - // A fleet location to search for game sessions. + //! A fleet location to search for game sessions. AZStd::string m_location; }; } // namespace AWSGameLift diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftStartMatchmakingRequest.h b/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftStartMatchmakingRequest.h index ec3719c6d3..b2060032e8 100644 --- a/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftStartMatchmakingRequest.h +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftStartMatchmakingRequest.h @@ -30,9 +30,10 @@ namespace AWSGameLift AWSGameLiftStartMatchmakingRequest() = default; virtual ~AWSGameLiftStartMatchmakingRequest() = default; - // Name of the matchmaking configuration to use for this request + //! Name of the matchmaking configuration to use for this request AZStd::string m_configurationName; - // Information on each player to be matched + + //! Information on each player to be matched AZStd::vector m_players; }; } // namespace AWSGameLift diff --git a/Gems/Atom/Bootstrap/Code/Source/BootstrapSystemComponent.cpp b/Gems/Atom/Bootstrap/Code/Source/BootstrapSystemComponent.cpp index 3f5761270a..ed3241b312 100644 --- a/Gems/Atom/Bootstrap/Code/Source/BootstrapSystemComponent.cpp +++ b/Gems/Atom/Bootstrap/Code/Source/BootstrapSystemComponent.cpp @@ -259,6 +259,7 @@ namespace AZ // Create and register a scene with all available feature processors RPI::SceneDescriptor sceneDesc; + sceneDesc.m_nameId = AZ::Name("Main"); AZ::RPI::ScenePtr atomScene = RPI::Scene::CreateScene(sceneDesc); atomScene->EnableAllFeatureProcessors(); atomScene->Activate(); diff --git a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Material/MaterialAssignment.h b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Material/MaterialAssignment.h index 40555bae00..21d9ec1bba 100644 --- a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Material/MaterialAssignment.h +++ b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Material/MaterialAssignment.h @@ -78,5 +78,10 @@ namespace AZ //! Find an assignment id corresponding to the lod and label substring filters MaterialAssignmentId FindMaterialAssignmentIdInModel( const Data::Instance& model, const MaterialAssignmentLodIndex lodFilter, const AZStd::string& labelFilter); + + //! Special case handling to convert script values to supported types + AZ::RPI::MaterialPropertyValue ConvertMaterialPropertyValueFromScript( + const AZ::RPI::MaterialPropertyDescriptor* propertyDescriptor, const AZStd::any& value); + } // namespace Render } // namespace AZ diff --git a/Gems/Atom/Feature/Common/Code/Source/FrameCaptureSystemComponent.cpp b/Gems/Atom/Feature/Common/Code/Source/FrameCaptureSystemComponent.cpp index ed2d8a1d9f..a9bb7271ab 100644 --- a/Gems/Atom/Feature/Common/Code/Source/FrameCaptureSystemComponent.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/FrameCaptureSystemComponent.cpp @@ -12,7 +12,7 @@ #include #include -#include +#include #include #include diff --git a/Gems/Atom/Feature/Common/Code/Source/LuxCore/LuxCoreTexture.cpp b/Gems/Atom/Feature/Common/Code/Source/LuxCore/LuxCoreTexture.cpp index aa906a06f7..1fd8f0d91c 100644 --- a/Gems/Atom/Feature/Common/Code/Source/LuxCore/LuxCoreTexture.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/LuxCore/LuxCoreTexture.cpp @@ -32,7 +32,7 @@ namespace AZ { if (m_rtPipeline) { - AZ::RPI::RPISystemInterface::Get()->GetDefaultScene()->RemoveRenderPipeline(m_rtPipeline->GetId()); + m_rtPipeline->RemoveFromScene(); m_rtPipeline = nullptr; } @@ -111,8 +111,12 @@ namespace AZ parentPass->SetSourceTexture(m_texture, RHI::Format::R8G8B8A8_UNORM); break; } - - AZ::RPI::RPISystemInterface::Get()->GetDefaultScene()->AddRenderPipeline(m_rtPipeline); + + const auto mainScene = AZ::RPI::RPISystemInterface::Get()->GetSceneByName(AZ::Name("RPI")); + if (mainScene) + { + mainScene->AddRenderPipeline(m_rtPipeline); + } } bool LuxCoreTexture::IsIBLTexture() diff --git a/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignment.cpp b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignment.cpp index c79ceb39ba..d99ce211bf 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignment.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignment.cpp @@ -143,28 +143,34 @@ namespace AZ { bool wasRenamed = false; Name newName; - RPI::MaterialPropertyIndex materialPropertyIndex = m_materialInstance->FindPropertyIndex(propertyPair.first, &wasRenamed, &newName); + RPI::MaterialPropertyIndex materialPropertyIndex = + m_materialInstance->FindPropertyIndex(propertyPair.first, &wasRenamed, &newName); - // FindPropertyIndex will have already reported a message about what the old and new names are. Here we just add some extra info to help the user resolve it. - AZ_Warning("MaterialAssignment", !wasRenamed, + // FindPropertyIndex will have already reported a message about what the old and new names are. Here we just add + // some extra info to help the user resolve it. + AZ_Warning( + "MaterialAssignment", !wasRenamed, "Consider running \"Apply Automatic Property Updates\" to use the latest property names.", - propertyPair.first.GetCStr(), - newName.GetCStr()); + propertyPair.first.GetCStr(), newName.GetCStr()); if (wasRenamed && m_propertyOverrides.find(newName) != m_propertyOverrides.end()) { materialPropertyIndex.Reset(); - - AZ_Warning("MaterialAssignment", false, - "Material property '%s' has been renamed to '%s', and a property override exists for both. The one with the old name will be ignored.", - propertyPair.first.GetCStr(), - newName.GetCStr()); + + AZ_Warning( + "MaterialAssignment", false, + "Material property '%s' has been renamed to '%s', and a property override exists for both. The one with " + "the old name will be ignored.", + propertyPair.first.GetCStr(), newName.GetCStr()); } if (!materialPropertyIndex.IsNull()) { + const auto propertyDescriptor = + m_materialInstance->GetMaterialPropertiesLayout()->GetPropertyDescriptor(materialPropertyIndex); + m_materialInstance->SetPropertyValue( - materialPropertyIndex, AZ::RPI::MaterialPropertyValue::FromAny(propertyPair.second)); + materialPropertyIndex, ConvertMaterialPropertyValueFromScript(propertyDescriptor, propertyPair.second)); } } } @@ -284,5 +290,58 @@ namespace AZ return MaterialAssignmentId(); } + + template + AZ::RPI::MaterialPropertyValue ConvertMaterialPropertyValueNumericType(const AZStd::any& value) + { + if (value.is()) + { + return aznumeric_cast(AZStd::any_cast(value)); + } + if (value.is()) + { + return aznumeric_cast(AZStd::any_cast(value)); + } + if (value.is()) + { + return aznumeric_cast(AZStd::any_cast(value)); + } + if (value.is()) + { + return aznumeric_cast(AZStd::any_cast(value)); + } + + return AZ::RPI::MaterialPropertyValue::FromAny(value); + } + + AZ::RPI::MaterialPropertyValue ConvertMaterialPropertyValueFromScript( + const AZ::RPI::MaterialPropertyDescriptor* propertyDescriptor, const AZStd::any& value) + { + switch (propertyDescriptor->GetDataType()) + { + case AZ::RPI::MaterialPropertyDataType::Enum: + if (value.is()) + { + return propertyDescriptor->GetEnumValue(AZStd::any_cast(value)); + } + if (value.is()) + { + return propertyDescriptor->GetEnumValue(AZ::Name(AZStd::any_cast(value))); + } + return ConvertMaterialPropertyValueNumericType(value); + case AZ::RPI::MaterialPropertyDataType::Int: + return ConvertMaterialPropertyValueNumericType(value); + case AZ::RPI::MaterialPropertyDataType::UInt: + return ConvertMaterialPropertyValueNumericType(value); + case AZ::RPI::MaterialPropertyDataType::Float: + return ConvertMaterialPropertyValueNumericType(value); + case AZ::RPI::MaterialPropertyDataType::Bool: + return ConvertMaterialPropertyValueNumericType(value); + default: + break; + } + + return AZ::RPI::MaterialPropertyValue::FromAny(value); + } } // namespace Render } // namespace AZ diff --git a/Gems/Atom/RHI/DX12/Code/CMakeLists.txt b/Gems/Atom/RHI/DX12/Code/CMakeLists.txt index b913ad58bf..975b838ffa 100644 --- a/Gems/Atom/RHI/DX12/Code/CMakeLists.txt +++ b/Gems/Atom/RHI/DX12/Code/CMakeLists.txt @@ -107,7 +107,7 @@ ly_add_target( Gem::Atom_RHI.Reflect Gem::Atom_RHI_DX12.Reflect 3rdParty::d3dx12 - ${AFTERMATH_BUILD_DEPENDENCY} + ${AFTERMATH_BUILD_DEPENDENCY} COMPILE_DEFINITIONS PRIVATE ${USE_NSIGHT_AFTERMATH_DEFINE} @@ -128,7 +128,6 @@ ly_add_target( BUILD_DEPENDENCIES PRIVATE AZ::AzCore - Gem::Atom_RHI.Reflect Gem::Atom_RHI.Public Gem::Atom_RHI_DX12.Reflect diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/Pass.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/Pass.h index e7b9825ecb..be7ac9e7a4 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/Pass.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/Pass.h @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -59,6 +60,7 @@ namespace AZ struct PassRequest; struct PassValidationResults; class AttachmentReadback; + class ImageAttachmentCopy; using SortedPipelineViewTags = AZStd::set; using PassesByDrawList = AZStd::map; @@ -94,6 +96,8 @@ namespace AZ { AZ_RPI_PASS(Pass); + friend class ImageAttachmentPreviewPass; + public: using ChildPassIndex = RHI::Handle; @@ -369,6 +373,9 @@ namespace AZ void UpdateReadbackAttachment(FramePrepareParams params, bool beforeAddScopes); + // Setup ImageAttachmentCopy + void UpdateAttachmentCopy(FramePrepareParams params); + // --- Protected Members --- const Name PassNameThis{"This"}; @@ -466,6 +473,9 @@ namespace AZ AZStd::shared_ptr m_attachmentReadback; PassAttachmentReadbackOption m_readbackOption; + // For image attachment preview + AZStd::weak_ptr m_attachmentCopy; + private: // Return the Timestamp result of this pass virtual TimestampResult GetTimestampResultInternal() const; diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/PassLibrary.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/PassLibrary.h index 66c3c205ab..f79da54636 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/PassLibrary.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/PassLibrary.h @@ -77,6 +77,16 @@ namespace AZ const AZStd::shared_ptr GetPassTemplate(const Name& name) const; const AZStd::vector& GetPassesForTemplate(const Name& templateName) const; + //! Removes a PassTemplate by name, only if the following two conditions are met: + //! 1- The template was NOT created from an Asset. This means the template will be erasable + //! only if it was created at runtime with C++. + //! 2- The are no instantiated Passes referencing such template. + //! If the template exists but both conditions are not met then the function will assert. + //! If a template with the given name doesn't exist the function does nothing. + //! This function should be used judiciously, and under rare circumstances. For example, + //! Applications that iteratively create and need to delete templates at runtime. + void RemovePassTemplate(const Name& name); + //! Removes a pass from both it's associated template (if it has one) and from the pass name mapping void RemovePassFromLibrary(Pass* pass); diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/PassSystem.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/PassSystem.h index 8390b0f7e2..6412bf6166 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/PassSystem.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/PassSystem.h @@ -94,6 +94,7 @@ namespace AZ bool HasPassesForTemplateName(const Name& templateName) const override; bool AddPassTemplate(const Name& name, const AZStd::shared_ptr& passTemplate) override; const AZStd::shared_ptr GetPassTemplate(const Name& name) const override; + void RemovePassTemplate(const Name& name) override; void RemovePassFromLibrary(Pass* pass) override; void RegisterPass(Pass* pass) override; void UnregisterPass(Pass* pass) override; diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/PassSystemInterface.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/PassSystemInterface.h index 7f944df88f..b91a31cfd5 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/PassSystemInterface.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/PassSystemInterface.h @@ -199,6 +199,9 @@ namespace AZ //! Retrieves a PassTemplate from the library virtual const AZStd::shared_ptr GetPassTemplate(const Name& name) const = 0; + //! See remarks in PassLibrary.h for the function with this name. + virtual void RemovePassTemplate(const Name& name) = 0; + //! Removes all references to the given pass from the pass library virtual void RemovePassFromLibrary(Pass* pass) = 0; diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/RenderPass.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/RenderPass.h index 5fb90d044e..ec51e34897 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/RenderPass.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/RenderPass.h @@ -13,9 +13,7 @@ #include #include -#include #include -#include #include namespace AZ @@ -29,7 +27,6 @@ namespace AZ namespace RPI { - class ImageAttachmentCopy; class RenderPass; class Query; @@ -41,8 +38,6 @@ namespace AZ { AZ_RPI_PASS(RenderPass); - friend class ImageAttachmentPreviewPass; - using ScopeQuery = AZStd::array, static_cast(ScopeQueryType::Count)>; public: @@ -143,8 +138,6 @@ namespace AZ // Readback the results from the ScopeQueries void ReadbackScopeQueryResults(); - AZStd::weak_ptr m_attachmentCopy; - // Readback results from the Timestamp queries TimestampResult m_timestampResult; // Readback results from the PipelineStatistics queries diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/Specific/ImageAttachmentPreviewPass.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/Specific/ImageAttachmentPreviewPass.h index 2e2b14a699..8ee7a44b6f 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/Specific/ImageAttachmentPreviewPass.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/Specific/ImageAttachmentPreviewPass.h @@ -78,7 +78,7 @@ namespace AZ ~ImageAttachmentPreviewPass(); //! Preview the PassAttachment of a pass' PassAttachmentBinding - void PreviewImageAttachmentForPass(RenderPass* pass, const PassAttachment* passAttachment); + void PreviewImageAttachmentForPass(Pass* pass, const PassAttachment* passAttachment); //! Set the output color attachment for this pass void SetOutputColorAttachment(RHI::Ptr outputImageAttachment); 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 4914e4b6fe..92370c5a82 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/RPISystem.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/RPISystem.h @@ -70,7 +70,8 @@ namespace AZ void InitializeSystemAssets() override; void RegisterScene(ScenePtr scene) override; void UnregisterScene(ScenePtr scene) override; - ScenePtr GetScene(const SceneId& sceneId) const override; + Scene* GetScene(const SceneId& sceneId) const override; + Scene* GetSceneByName(const AZ::Name& name) const override; ScenePtr GetDefaultScene() const override; RenderPipelinePtr GetRenderPipelineForWindow(AzFramework::NativeWindowHandle windowHandle) override; Data::Asset GetCommonShaderAssetForSrgs() const override; diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/RPISystemInterface.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/RPISystemInterface.h index fe81596bd7..3f30d498cf 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/RPISystemInterface.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/RPISystemInterface.h @@ -13,6 +13,7 @@ #include +#include #include namespace AZ @@ -46,11 +47,14 @@ namespace AZ //! Unregister a scene from RPISystem. The scene won't be simulated or rendered. virtual void UnregisterScene(ScenePtr scene) = 0; - // [GFX TODO] to be removed when we have scene setup in AZ Core - virtual ScenePtr GetDefaultScene() const = 0; - + //! Deprecated. Use GetSceneByName(name), GetSceneForEntityContextId(entityContextId) or Scene::GetSceneForEntityId(AZ::EntityId entityId) instead + AZ_DEPRECATED(virtual ScenePtr GetDefaultScene() const = 0;, "This method has been deprecated. Please use GetSceneByName(name), GetSceneForEntityContextId(entityContextId) or Scene::GetSceneForEntityId(AZ::EntityId entityId) instead."); + //! Get scene by using scene id. - virtual ScenePtr GetScene(const SceneId& sceneId) const = 0; + virtual Scene* GetScene(const SceneId& sceneId) const = 0; + + //! Get scene by using scene name. + virtual Scene* GetSceneByName(const AZ::Name& name) const = 0; //! Get the render pipeline created for a window virtual RenderPipelinePtr GetRenderPipelineForWindow(AzFramework::NativeWindowHandle windowHandle) = 0; 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 fc81368331..f86383101a 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Scene.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Scene.h @@ -80,6 +80,9 @@ namespace AZ //! Gets the RPI::Scene for a given entityContextId. //! May return nullptr if there is no RPI::Scene created for that entityContext. static Scene* GetSceneForEntityContextId(AzFramework::EntityContextId entityContextId); + + //! Gets the RPI::Scene for a given entityId. + static Scene* GetSceneForEntityId(AZ::EntityId entityId); ~Scene(); @@ -135,6 +138,8 @@ namespace AZ const SceneId& GetId() const; + AZ::Name GetName() const; + //! Set default pipeline by render pipeline ID. //! It returns true if the default render pipeline was set from the input ID. //! If the specified render pipeline doesn't exist in this scene then it won't do anything and returns false. @@ -245,6 +250,9 @@ namespace AZ // The uuid to identify this scene. SceneId m_id; + // Scene's name which is set at initialization. Can be empty + AZ::Name m_name; + bool m_activated = false; bool m_taskGraphActive = false; // update during tick, to ensure it only changes on frame boundaries @@ -286,13 +294,10 @@ namespace AZ template FeatureProcessorType* Scene::GetFeatureProcessorForEntity(AZ::EntityId entityId) { - // Find the entity context for the entity ID. - AzFramework::EntityContextId entityContextId = AzFramework::EntityContextId::CreateNull(); - AzFramework::EntityIdContextQueryBus::EventResult(entityContextId, entityId, &AzFramework::EntityIdContextQueryBus::Events::GetOwningContextId); - - if (!entityContextId.IsNull()) + RPI::Scene* renderScene = GetSceneForEntityId(entityId); + if (renderScene) { - return GetFeatureProcessorForEntityContextId(entityContextId); + return renderScene->GetFeatureProcessor(); } return nullptr; }; diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/System/SceneDescriptor.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/System/SceneDescriptor.h index 2072568d59..eb7a2b6cb7 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/System/SceneDescriptor.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/System/SceneDescriptor.h @@ -9,6 +9,7 @@ #pragma once #include +#include #include #include @@ -25,6 +26,9 @@ namespace AZ //! List of feature processors which the scene will initially enable. AZStd::vector m_featureProcessorNames; + + //! A name used as scene id. It can be used to search a registered scene via RPISystemInterface::GetScene() + AZ::Name m_nameId; }; } // namespace RPI } // namespace AZ diff --git a/Gems/Atom/RPI/Code/Source/RPI.Builders/Pass/PassBuilder.cpp b/Gems/Atom/RPI/Code/Source/RPI.Builders/Pass/PassBuilder.cpp index d5e243c687..6a0b10633e 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Builders/Pass/PassBuilder.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Builders/Pass/PassBuilder.cpp @@ -10,8 +10,8 @@ #include #include - #include +#include #include #include @@ -33,11 +33,27 @@ namespace AZ static const char* PassAssetExtension = "pass"; } + namespace PassBuilderNamespace + { + enum PassDependencies + { + Shader, + AttachmentImage, + Count + }; + + static const AZStd::tuple DependencyExtensionJobKeyTable[PassDependencies::Count] = + { + {".shader", "Shader Asset"}, + {".attimage", "Any Asset Builder"} + }; + } + void PassBuilder::RegisterBuilder() { AssetBuilderSDK::AssetBuilderDesc builder; builder.m_name = PassBuilderJobKey; - builder.m_version = 13; // antonmic: making .pass files declare dependency on shaders they reference + builder.m_version = 14; // making .pass files emit product dependencies for the shaders they reference so they are picked up by the asset bundler builder.m_busId = azrtti_typeid(); builder.m_createJobFunction = AZStd::bind(&PassBuilder::CreateJobs, this, AZStd::placeholders::_1, AZStd::placeholders::_2); builder.m_processJobFunction = AZStd::bind(&PassBuilder::ProcessJob, this, AZStd::placeholders::_1, AZStd::placeholders::_2); @@ -104,8 +120,27 @@ namespace AZ } } + bool SetJobKeyForExtension(const AZStd::string& filePath, FindPassReferenceAssetParams& params) + { + AZStd::string extension; + StringFunc::Path::GetExtension(filePath.c_str(), extension); + for (const auto& [dependencyExtension, jobKey] : PassBuilderNamespace::DependencyExtensionJobKeyTable) + { + if (extension == dependencyExtension) + { + params.jobKey = jobKey; + return true; + } + } + + AZ_Error(PassBuilderName, false, "PassBuilder found a dependency with extension '%s', but does not know the corresponding job key. Add the job key for that extension to SetJobKeyForExtension in PassBuilder.cpp", extension.c_str()); + params.jobKey = "Unknown"; + return false; + } + // Helper function to find all assetId's and object references - bool FindReferencedAssets(FindPassReferenceAssetParams& params, AssetBuilderSDK::JobDescriptor* job) + bool FindReferencedAssets( + FindPassReferenceAssetParams& params, AssetBuilderSDK::JobDescriptor* job, AZStd::vector* productDependencies) { SerializeContext::ErrorHandler errorLogger; errorLogger.Reset(); @@ -129,8 +164,8 @@ namespace AZ if (job != nullptr) // Create Job Phase { params.dependencySourceFile = path; - bool dependencyAddedSuccessfully = AddDependency(params, job); - success = dependencyAddedSuccessfully && success; + success &= SetJobKeyForExtension(path, params); + success &= AddDependency(params, job); } else // Process Job Phase { @@ -139,6 +174,9 @@ namespace AZ if (assetIdOutcome) { assetReference->m_assetId = assetIdOutcome.GetValue(); + productDependencies->push_back( + AssetBuilderSDK::ProductDependency{assetReference->m_assetId, AZ::Data::ProductDependencyInfo::CreateFlags(Data::AssetLoadBehavior::NoLoad)} + ); } else { @@ -223,9 +261,9 @@ namespace AZ params.passAssetSourceFile = request.m_sourceFile; params.passAssetUuid = passAssetUuid; params.serializeContext = serializeContext; - params.jobKey = "Shader Asset"; + params.jobKey = "Unknown"; - if (!FindReferencedAssets(params, &job)) + if (!FindReferencedAssets(params, &job, nullptr)) { return; } @@ -287,9 +325,10 @@ namespace AZ params.passAssetSourceFile = request.m_sourceFile; params.passAssetUuid = passAssetUuid; params.serializeContext = serializeContext; - params.jobKey = "Shader Asset"; + params.jobKey = "Unknown"; - if (!FindReferencedAssets(params, nullptr)) + AZStd::vector productDependencies; + if (!FindReferencedAssets(params, nullptr, &productDependencies)) { return; } @@ -313,6 +352,7 @@ namespace AZ // --- Save output product(s) to response --- AssetBuilderSDK::JobProduct jobProduct(destPath, PassAsset::RTTI_Type(), 0); + jobProduct.m_dependencies = productDependencies; jobProduct.m_dependenciesHandled = true; response.m_outputProducts.push_back(jobProduct); response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success; diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Culling.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Culling.cpp index 348f0aa57a..ac6b10694e 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Culling.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Culling.cpp @@ -699,9 +699,7 @@ namespace AZ m_parentScene = parentScene; AZ_Assert(m_visScene == nullptr, "IVisibilityScene already created for this RPI::Scene"); - char sceneIdBuf[40] = ""; - m_parentScene->GetId().ToString(sceneIdBuf); - AZ::Name visSceneName(AZStd::string::format("RenderCullScene[%s]", sceneIdBuf)); + AZ::Name visSceneName(AZStd::string::format("RenderCullScene[%s]", m_parentScene->GetName().GetCStr())); m_visScene = AZ::Interface::Get()->CreateVisibilityScene(visSceneName); #ifdef AZ_CULL_DEBUG_ENABLED diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/Pass.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/Pass.cpp index 3c1de28d6a..d04a35a10b 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/Pass.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/Pass.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -1215,6 +1216,12 @@ namespace AZ m_queueState = PassQueueState::NoQueue; InitializeInternal(); + + // Need to recreate the dest attachment because the source attachment might be changed + if (!m_attachmentCopy.expired()) + { + m_attachmentCopy.lock()->InvalidateDestImage(); + } m_state = PassState::Initialized; } @@ -1301,6 +1308,9 @@ namespace AZ // readback attachment with output state UpdateReadbackAttachment(params, false); + // update attachment copy for preview + UpdateAttachmentCopy(params); + UpdateConnectedOutputBindings(); } @@ -1489,6 +1499,14 @@ namespace AZ } } + void Pass::UpdateAttachmentCopy(FramePrepareParams params) + { + if (!m_attachmentCopy.expired()) + { + m_attachmentCopy.lock()->FrameBegin(params); + } + } + bool Pass::IsTimestampQueryEnabled() const { return m_flags.m_timestampQueryEnabled; diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/PassLibrary.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/PassLibrary.cpp index 6a6f5f3ff9..13b2fa391f 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/PassLibrary.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/PassLibrary.cpp @@ -236,6 +236,19 @@ namespace AZ return true; } + void PassLibrary::RemovePassTemplate(const Name& name) + { + auto itr = m_templateEntries.find(name); + if (itr != m_templateEntries.end()) + { + AZ_Assert(itr->second.m_passes.empty(), "Can not delete PassTemplate '%s' because there are %zu Passes referencing it", + name.GetCStr(), itr->second.m_passes.size()); + AZ_Assert(!itr->second.m_mappingAssetId.IsValid(), "Can not delete PassTemplate '%s' because it was created from an asset", + name.GetCStr()); + m_templateEntries.erase(itr); + } + } + void PassLibrary::RemovePassFromLibrary(Pass* pass) { if (m_isShuttingDown) diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/PassSystem.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/PassSystem.cpp index 7f2948c13a..39d0e879e1 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/PassSystem.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/PassSystem.cpp @@ -466,6 +466,11 @@ namespace AZ return m_passLibrary.GetPassTemplate(name); } + void PassSystem::RemovePassTemplate(const Name& name) + { + m_passLibrary.RemovePassTemplate(name); + } + void PassSystem::RemovePassFromLibrary(Pass* pass) { m_passLibrary.RemovePassFromLibrary(pass); diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/RenderPass.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/RenderPass.cpp index b8115dff10..8353762c0f 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/RenderPass.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/RenderPass.cpp @@ -177,12 +177,6 @@ namespace AZ } } } - - // Need to recreate the dest attachment because the source attachment might be changed - if (!m_attachmentCopy.expired()) - { - m_attachmentCopy.lock()->InvalidateDestImage(); - } } void RenderPass::FrameBeginInternal(FramePrepareParams params) @@ -196,11 +190,7 @@ namespace AZ // Read back the ScopeQueries submitted from previous frames ReadbackScopeQueryResults(); - - if (!m_attachmentCopy.expired()) - { - m_attachmentCopy.lock()->FrameBegin(params); - } + CollectSrgs(); PassSystemInterface::Get()->IncrementFrameRenderPassCount(); diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/Specific/ImageAttachmentPreviewPass.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/Specific/ImageAttachmentPreviewPass.cpp index 74d52fd64d..fc1ea2ce1b 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/Specific/ImageAttachmentPreviewPass.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/Specific/ImageAttachmentPreviewPass.cpp @@ -14,7 +14,7 @@ #include #include #include -#include +#include #include #include #include @@ -131,7 +131,7 @@ namespace AZ Data::AssetBus::Handler::BusDisconnect(); } - void ImageAttachmentPreviewPass::PreviewImageAttachmentForPass(RenderPass* pass, const PassAttachment* passAttachment) + void ImageAttachmentPreviewPass::PreviewImageAttachmentForPass(Pass* pass, const PassAttachment* passAttachment) { if (passAttachment->GetAttachmentType() != RHI::AttachmentType::Image) { diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/RPISystem.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/RPISystem.cpp index eb74a81e3e..943966c13b 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/RPISystem.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/RPISystem.cpp @@ -159,6 +159,11 @@ namespace AZ AZ_Assert(false, "Scene was already registered"); return; } + else if (!scene->GetName().IsEmpty() && scene->GetName() == sceneItem->GetName()) + { + // only report a warning if there is a scene with duplicated name + AZ_Warning("RPISystem", false, "There is a registered scene with same name [%s]", scene->GetName().GetCStr()); + } } m_scenes.push_back(scene); @@ -177,11 +182,35 @@ namespace AZ AZ_Assert(false, "Can't unregister scene which wasn't registered"); } - ScenePtr RPISystem::GetScene(const SceneId& sceneId) const + Scene* RPISystem::GetScene(const SceneId& sceneId) const { for (const auto& scene : m_scenes) { if (scene->GetId() == sceneId) + { + return scene.get(); + } + } + return nullptr; + } + + Scene* RPISystem::GetSceneByName(const AZ::Name& name) const + { + for (const auto& scene : m_scenes) + { + if (scene->GetName() == name) + { + return scene.get(); + } + } + return nullptr; + } + + ScenePtr RPISystem::GetDefaultScene() const + { + for (const auto& scene : m_scenes) + { + if (scene->GetName() == AZ::Name("Main")) { return scene; } @@ -189,16 +218,6 @@ namespace AZ return nullptr; } - ScenePtr RPISystem::GetDefaultScene() const - { - if (m_scenes.size() > 0) - { - return m_scenes[0]; - } - return nullptr; - } - - RenderPipelinePtr RPISystem::GetRenderPipelineForWindow(AzFramework::NativeWindowHandle windowHandle) { RenderPipelinePtr renderPipeline; diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/RPIUtils.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/RPIUtils.cpp index e70885daa1..fda8f967da 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/RPIUtils.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/RPIUtils.cpp @@ -119,7 +119,12 @@ namespace AZ AzFramework::AssetSystem::AssetStatus status = AzFramework::AssetSystem::AssetStatus_Unknown; AzFramework::AssetSystemRequestBus::BroadcastResult( status, &AzFramework::AssetSystemRequestBus::Events::CompileAssetSync, path); - AZ_Error("RPIUtils", status == AzFramework::AssetSystem::AssetStatus_Compiled, "Could not compile image at '%s'", path.data()); + + // When running with no Asset Processor (for example in release), CompileAssetSync will return AssetStatus_Unknown. + AZ_Error( + "RPIUtils", + status == AzFramework::AssetSystem::AssetStatus_Compiled || status == AzFramework::AssetSystem::AssetStatus_Unknown, + "Could not compile image at '%s'", path.data()); Data::AssetId streamingImageAssetId; Data::AssetCatalogRequestBus::BroadcastResult( diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Scene.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Scene.cpp index c5d82c6f29..41fefda656 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Scene.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Scene.cpp @@ -45,7 +45,9 @@ namespace AZ auto shaderAsset = RPISystemInterface::Get()->GetCommonShaderAssetForSrgs(); scene->m_srg = ShaderResourceGroup::Create(shaderAsset, sceneSrgLayout->GetName()); } - + + scene->m_name = sceneDescriptor.m_nameId; + return ScenePtr(scene); } @@ -83,10 +85,23 @@ namespace AZ return nullptr; } + Scene* Scene::GetSceneForEntityId(AZ::EntityId entityId) + { + // Find the entity context for the entity ID. + AzFramework::EntityContextId entityContextId = AzFramework::EntityContextId::CreateNull(); + AzFramework::EntityIdContextQueryBus::EventResult(entityContextId, entityId, &AzFramework::EntityIdContextQueryBus::Events::GetOwningContextId); + + if (!entityContextId.IsNull()) + { + return GetSceneForEntityContextId(entityContextId); + } + return nullptr; + } + Scene::Scene() { - m_id = Uuid::CreateRandom(); + m_id = AZ::Uuid::CreateRandom(); m_cullingScene = aznew CullingScene(); SceneRequestBus::Handler::BusConnect(m_id); m_drawFilterTagRegistry = RHI::DrawFilterTagRegistry::Create(); @@ -299,7 +314,6 @@ namespace AZ // Force to update the lookup table since adding render pipeline would effect any pipeline states created before pass system tick RebuildPipelineStatesLookup(); - AZ_Assert(!m_id.IsNull(), "RPI::Scene needs to have a valid uuid."); SceneNotificationBus::Event(m_id, &SceneNotification::OnRenderPipelineAdded, pipeline); } @@ -785,6 +799,11 @@ namespace AZ { return m_id; } + + AZ::Name Scene::GetName() const + { + return m_name; + } bool Scene::SetDefaultRenderPipeline(const RenderPipelineId& pipelineId) { diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/PreviewRenderer/PreviewRenderer.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/PreviewRenderer/PreviewRenderer.cpp index 2b39a87623..27d468e64b 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/PreviewRenderer/PreviewRenderer.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/PreviewRenderer/PreviewRenderer.cpp @@ -43,6 +43,7 @@ namespace AtomToolsFramework &PreviewerFeatureProcessorProviderBus::Handler::GetRequiredFeatureProcessors, featureProcessors); AZ::RPI::SceneDescriptor sceneDesc; + sceneDesc.m_nameId = AZ::Name("PreviewRenderer"); sceneDesc.m_featureProcessorNames.assign(featureProcessors.begin(), featureProcessors.end()); m_scene = AZ::RPI::Scene::CreateScene(sceneDesc); diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/InputController/MaterialEditorViewportInputController.cpp b/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/InputController/MaterialEditorViewportInputController.cpp index 932e2e7436..1784405ffa 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/InputController/MaterialEditorViewportInputController.cpp +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/InputController/MaterialEditorViewportInputController.cpp @@ -279,9 +279,9 @@ namespace MaterialEditor // reset environment AZ::Transform iblTransform = AZ::Transform::CreateIdentity(); AZ::TransformBus::Event(m_iblEntityId, &AZ::TransformBus::Events::SetLocalTM, iblTransform); + const AZ::Matrix4x4 rotationMatrix = AZ::Matrix4x4::CreateIdentity(); - AZ::RPI::ScenePtr scene = AZ::RPI::RPISystemInterface::Get()->GetDefaultScene(); - auto skyBoxFeatureProcessorInterface = scene->GetFeatureProcessor(); + auto skyBoxFeatureProcessorInterface = AZ::RPI::Scene::GetFeatureProcessorForEntity(m_iblEntityId); skyBoxFeatureProcessorInterface->SetCubemapRotationMatrix(rotationMatrix); if (m_behavior) diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/InputController/RotateEnvironmentBehavior.cpp b/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/InputController/RotateEnvironmentBehavior.cpp index 21d7dee51b..17fb3f3e52 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/InputController/RotateEnvironmentBehavior.cpp +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/InputController/RotateEnvironmentBehavior.cpp @@ -25,8 +25,7 @@ namespace MaterialEditor m_iblEntityId, &MaterialEditorViewportInputControllerRequestBus::Handler::GetIblEntityId); AZ_Assert(m_iblEntityId.IsValid(), "Failed to find m_iblEntityId"); - AZ::RPI::ScenePtr scene = AZ::RPI::RPISystemInterface::Get()->GetDefaultScene(); - m_skyBoxFeatureProcessorInterface = scene->GetFeatureProcessor(); + m_skyBoxFeatureProcessorInterface = AZ::RPI::Scene::GetFeatureProcessorForEntity(m_iblEntityId); } void RotateEnvironmentBehavior::TickInternal(float x, float y, float z) diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/MaterialViewportRenderer.cpp b/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/MaterialViewportRenderer.cpp index 6d6fd377e3..4ad87492e3 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/MaterialViewportRenderer.cpp +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/MaterialViewportRenderer.cpp @@ -67,6 +67,7 @@ namespace MaterialEditor // Create and register a scene with all available feature processors AZ::RPI::SceneDescriptor sceneDesc; + sceneDesc.m_nameId = AZ::Name("MaterialViewport"); m_scene = AZ::RPI::Scene::CreateScene(sceneDesc); m_scene->EnableAllFeatureProcessors(); diff --git a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiGpuProfiler.h b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiGpuProfiler.h index 1bed2b5715..7413390357 100644 --- a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiGpuProfiler.h +++ b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiGpuProfiler.h @@ -249,7 +249,7 @@ namespace AZ // Controls how often the timestamp data is refreshed RefreshType m_refreshType = RefreshType::Realtime; - AZStd::sys_time_t m_lastUpdateTimeMicroSecond; + AZStd::sys_time_t m_lastUpdateTimeMicroSecond = 0; }; diff --git a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiGpuProfiler.inl b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiGpuProfiler.inl index 80dcace2df..405848aa35 100644 --- a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiGpuProfiler.inl +++ b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiGpuProfiler.inl @@ -651,7 +651,7 @@ namespace AZ if (m_refreshType == RefreshType::OncePerSecond) { auto now = AZStd::GetTimeNowMicroSecond(); - if (m_lastUpdateTimeMicroSecond == 0 || now - m_lastUpdateTimeMicroSecond > 1000000) + if (now - m_lastUpdateTimeMicroSecond > 1000000) { needEnable = true; m_lastUpdateTimeMicroSecond = now; diff --git a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiPassTree.h b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiPassTree.h index 12e9776ae2..0e942bca58 100644 --- a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiPassTree.h +++ b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiPassTree.h @@ -39,6 +39,8 @@ namespace AZ bool m_showAttachments = false; AZ::RPI::Pass* m_selectedPass = nullptr; + AZ::RPI::Pass* m_lastSelectedPass = nullptr; + AZ::Name m_selectedPassPath; AZ::RHI::AttachmentId m_attachmentId; AZ::Name m_slotName; bool m_selectedChanged = false; diff --git a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiPassTree.inl b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiPassTree.inl index fa649ed47b..55e457926a 100644 --- a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiPassTree.inl +++ b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiPassTree.inl @@ -31,7 +31,7 @@ namespace AZ::Render { - inline AZ::RPI::PassAttachment* FindPassAttachment(AZ::RPI::RenderPass* pass, AZ::RHI::AttachmentId attachmentId) + inline AZ::RPI::PassAttachment* FindPassAttachment(AZ::RPI::Pass* pass, AZ::RHI::AttachmentId attachmentId) { for (auto& binding : pass->GetAttachmentBindings()) { @@ -47,6 +47,10 @@ namespace AZ::Render { using namespace AZ; + // always set m_selectedPass to empty and use m_selectedPassPath to find it when render the pass tree + m_selectedPass = nullptr; + bool needSaveAttachment = false; + ImGui::SetNextWindowSize(ImVec2(200.f, 200.f), ImGuiCond_FirstUseEver); if (ImGui::Begin("PassTree View", &draw, ImGuiWindowFlags_None)) { @@ -83,60 +87,16 @@ namespace AZ::Render if (Scriptable_ImGui::Button("Save Attachment")) { - m_attachmentReadbackInfo = ""; - if (!m_readback) - { - m_readback = AZStd::make_shared(AZ::RHI::ScopeId{ "AttachmentReadback" }); - m_readback->SetCallback(AZStd::bind(&ImGuiPassTree::ReadbackCallback, this, AZStd::placeholders::_1)); - } - - if (m_selectedPass && !m_slotName.IsEmpty()) - { - bool readbackResult = m_selectedPass->ReadbackAttachment(m_readback, m_slotName); - if (!readbackResult) - { - AZ_Error("ImGuiPassTree", false, "Failed to readback attachment from pass [%s] slot [%s]", m_selectedPass->GetName().GetCStr(), m_slotName.GetCStr()); - } - } + needSaveAttachment = true; } ImGui::TextWrapped("%s", m_attachmentReadbackInfo.c_str()); } - if (m_previewAttachment && m_selectedChanged) - { - m_selectedChanged = false; - if (!m_attachmentId.IsEmpty() && m_selectedPass) - { - AZ::RPI::RenderPass* renderPass = azrtti_cast(m_selectedPass); - if (renderPass) - { - if (!m_previewPass->GetParent()) - { - RPI::PassSystemInterface::Get()->GetRootPass()->AddChild(m_previewPass); - } - AZ::RPI::PassAttachment* attachment = FindPassAttachment(renderPass, m_attachmentId); - if (attachment) - { - // Reset output attachment to empty so the preview will use pass's owner render pipeline's output - m_previewPass->SetOutputColorAttachment(nullptr); - m_previewPass->PreviewImageAttachmentForPass(renderPass, attachment); - } - } - else - { - m_previewPass->ClearPreviewAttachment(); - if (m_previewPass->GetParent()) - { - m_previewPass->QueueForRemoval(); - } - } - } - } - ImGui::End(); // Draw the hierarchical view + // It will assign m_seletedPass if there is a pass matches m_seletedPassPath ImGui::SetNextWindowPos(ImVec2(300, 60), ImGuiCond_FirstUseEver); ImGui::SetNextWindowSize(ImVec2(300, 500), ImGuiCond_FirstUseEver); if (ImGui::Begin("PassTree", nullptr, ImGuiWindowFlags_None)) @@ -144,6 +104,63 @@ namespace AZ::Render DrawTreeView(rootPass); } ImGui::End(); + + // It's possible that the pass pointer changed but selected pass path wasn't changed + if (m_selectedPass != m_lastSelectedPass) + { + m_selectedChanged = true; + if (m_selectedPass == nullptr) + { + m_selectedPassPath = AZ::Name{}; + } + } + m_lastSelectedPass = m_selectedPass; + + if (m_previewAttachment && m_selectedChanged) + { + m_selectedChanged = false; + if (!m_attachmentId.IsEmpty() && m_selectedPass) + { + if (!m_previewPass->GetParent()) + { + RPI::PassSystemInterface::Get()->GetRootPass()->AddChild(m_previewPass); + } + AZ::RPI::PassAttachment* attachment = FindPassAttachment(m_selectedPass, m_attachmentId); + if (attachment) + { + // Reset output attachment to empty so the preview will use pass's owner render pipeline's output + m_previewPass->SetOutputColorAttachment(nullptr); + m_previewPass->PreviewImageAttachmentForPass(m_selectedPass, attachment); + } + } + else + { + m_previewPass->ClearPreviewAttachment(); + if (m_previewPass->GetParent()) + { + m_previewPass->QueueForRemoval(); + } + } + } + + if (needSaveAttachment) + { + m_attachmentReadbackInfo = ""; + if (!m_readback) + { + m_readback = AZStd::make_shared(AZ::RHI::ScopeId{ "AttachmentReadback" }); + m_readback->SetCallback(AZStd::bind(&ImGuiPassTree::ReadbackCallback, this, AZStd::placeholders::_1)); + } + + if (m_selectedPass && !m_slotName.IsEmpty()) + { + bool readbackResult = m_selectedPass->ReadbackAttachment(m_readback, m_slotName); + if (!readbackResult) + { + AZ_Error("ImGuiPassTree", false, "Failed to readback attachment from pass [%s] slot [%s]", m_selectedPass->GetName().GetCStr(), m_slotName.GetCStr()); + } + } + } } inline void ImGuiPassTree::DrawPassAttachments(AZ::RPI::Pass* pass) @@ -202,6 +219,7 @@ namespace AZ::Render if (Scriptable_ImGui::Selectable(label.c_str(), m_attachmentId == binding.m_attachment->GetAttachmentId())) { + m_selectedPassPath = pass->GetPathName(); m_selectedPass = pass; m_attachmentId = binding.m_attachment->GetAttachmentId(); m_slotName = binding.m_name; @@ -232,9 +250,9 @@ namespace AZ::Render if (!m_showAttachments) { // Only draw the leaf pass as selectable if we are not showing attachments as its children - if (Scriptable_ImGui::Selectable(pass->GetName().GetCStr(), m_selectedPass == pass)) + if (Scriptable_ImGui::Selectable(pass->GetName().GetCStr(), m_selectedPassPath == pass->GetPathName())) { - m_selectedPass = pass; + m_selectedPassPath = pass->GetPathName(); m_attachmentId = AZ::RHI::AttachmentId{}; m_slotName = AZ::Name{}; m_selectedChanged = true; @@ -244,13 +262,13 @@ namespace AZ::Render { // Draw the pass as a tree node which has attachments as its children ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick | ImGuiTreeNodeFlags_DefaultOpen - | ((m_selectedPass == pass) ? ImGuiTreeNodeFlags_Selected : 0); + | ((m_selectedPassPath == pass->GetPathName()) ? ImGuiTreeNodeFlags_Selected : 0); bool nodeOpen = Scriptable_ImGui::TreeNodeEx(pass->GetName().GetCStr(), flags); if (ImGui::IsItemClicked()) { - m_selectedPass = pass; + m_selectedPassPath = pass->GetPathName(); m_attachmentId = AZ::RHI::AttachmentId{}; m_slotName = AZ::Name{}; m_selectedChanged = true; @@ -259,7 +277,6 @@ namespace AZ::Render if (nodeOpen) { DrawPassAttachments(pass); - Scriptable_ImGui::TreePop(); } } @@ -268,13 +285,13 @@ namespace AZ::Render { // For a ParentPasse, draw it as a tree node ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick | ImGuiTreeNodeFlags_DefaultOpen - | ((m_selectedPass == pass) ? ImGuiTreeNodeFlags_Selected : 0); + | ((m_selectedPassPath == pass->GetPathName()) ? ImGuiTreeNodeFlags_Selected : 0); bool nodeOpen = ImGui::TreeNodeEx(pass->GetName().GetCStr(), flags); if (ImGui::IsItemClicked()) { - m_selectedPass = pass; + m_selectedPassPath = pass->GetPathName(); m_attachmentId = AZ::RHI::AttachmentId{}; m_slotName = AZ::Name{}; m_selectedChanged = true; @@ -282,7 +299,10 @@ namespace AZ::Render if (nodeOpen) { - DrawPassAttachments(pass); + if (m_showAttachments) + { + DrawPassAttachments(pass); + } for (const auto& child : asParent->GetChildren()) { DrawTreeView(child.get()); @@ -296,6 +316,12 @@ namespace AZ::Render { ImGui::PopStyleColor(); } + + // set m_selectedPass if pass path matches + if (pass->GetPathName() == m_selectedPassPath) + { + m_selectedPass = pass; + } } inline void ImGuiPassTree::ReadbackCallback(const AZ::RPI::AttachmentReadback::ReadbackResult& readbackResult) @@ -364,7 +390,9 @@ namespace AZ::Render m_previewAttachment = false; m_showAttachments = false; + m_selectedPassPath = AZ::Name{}; m_selectedPass = nullptr; + m_lastSelectedPass = nullptr; m_attachmentId = AZ::RHI::AttachmentId{}; m_slotName = AZ::Name{}; m_selectedChanged = false; diff --git a/Gems/AtomLyIntegration/AtomBridge/Code/Source/AtomBridgeSystemComponent.cpp b/Gems/AtomLyIntegration/AtomBridge/Code/Source/AtomBridgeSystemComponent.cpp index b439ac475c..d822b16486 100644 --- a/Gems/AtomLyIntegration/AtomBridge/Code/Source/AtomBridgeSystemComponent.cpp +++ b/Gems/AtomLyIntegration/AtomBridge/Code/Source/AtomBridgeSystemComponent.cpp @@ -108,7 +108,7 @@ namespace AZ { m_dynamicDrawManager.reset(); AZ::RPI::ViewportContextManagerNotificationsBus::Handler::BusDisconnect(); - RPI::Scene* scene = RPI::RPISystemInterface::Get()->GetDefaultScene().get(); + RPI::Scene* scene = AZ::RPI::Scene::GetSceneForEntityContextId(m_entityContextId); // Check if scene is emptry since scene might be released already when running AtomSampleViewer if (scene) { @@ -157,9 +157,9 @@ namespace AZ void AtomBridgeSystemComponent::OnBootstrapSceneReady(AZ::RPI::Scene* bootstrapScene) { - AZ_UNUSED(bootstrapScene); // Make default AtomDebugDisplayViewportInterface - AZStd::shared_ptr mainEntityDebugDisplay = AZStd::make_shared(AzFramework::g_defaultSceneEntityDebugDisplayId); + AZStd::shared_ptr mainEntityDebugDisplay = + AZStd::make_shared(AzFramework::g_defaultSceneEntityDebugDisplayId, bootstrapScene); m_activeViewportsList[AzFramework::g_defaultSceneEntityDebugDisplayId] = mainEntityDebugDisplay; } diff --git a/Gems/AtomLyIntegration/AtomBridge/Code/Source/AtomDebugDisplayViewportInterface.cpp b/Gems/AtomLyIntegration/AtomBridge/Code/Source/AtomDebugDisplayViewportInterface.cpp index 39d8933863..14f1c5aa0d 100644 --- a/Gems/AtomLyIntegration/AtomBridge/Code/Source/AtomDebugDisplayViewportInterface.cpp +++ b/Gems/AtomLyIntegration/AtomBridge/Code/Source/AtomDebugDisplayViewportInterface.cpp @@ -256,12 +256,11 @@ namespace AZ::AtomBridge viewportContextPtr->ConnectSceneChangedHandler(m_sceneChangeHandler); } - AtomDebugDisplayViewportInterface::AtomDebugDisplayViewportInterface(uint32_t defaultInstanceAddress) + AtomDebugDisplayViewportInterface::AtomDebugDisplayViewportInterface(uint32_t defaultInstanceAddress, RPI::Scene* scene) { ResetRenderState(); m_viewportId = defaultInstanceAddress; m_defaultInstance = true; - RPI::Scene* scene = RPI::RPISystemInterface::Get()->GetDefaultScene().get(); InitInternal(scene, nullptr); } diff --git a/Gems/AtomLyIntegration/AtomBridge/Code/Source/AtomDebugDisplayViewportInterface.h b/Gems/AtomLyIntegration/AtomBridge/Code/Source/AtomDebugDisplayViewportInterface.h index 07f4efdf50..5f902c5884 100644 --- a/Gems/AtomLyIntegration/AtomBridge/Code/Source/AtomDebugDisplayViewportInterface.h +++ b/Gems/AtomLyIntegration/AtomBridge/Code/Source/AtomDebugDisplayViewportInterface.h @@ -124,7 +124,7 @@ namespace AZ::AtomBridge AZ_RTTI(AtomDebugDisplayViewportInterface, "{09AF6A46-0100-4FBF-8F94-E6B221322D14}", AzFramework::DebugDisplayRequestBus::Handler); explicit AtomDebugDisplayViewportInterface(AZ::RPI::ViewportContextPtr viewportContextPtr); - explicit AtomDebugDisplayViewportInterface(uint32_t defaultInstanceAddress); + explicit AtomDebugDisplayViewportInterface(uint32_t defaultInstanceAddress, RPI::Scene* scene); ~AtomDebugDisplayViewportInterface(); void ResetRenderState(); diff --git a/Gems/AtomLyIntegration/AtomFont/Code/Include/AtomLyIntegration/AtomFont/FFont.h b/Gems/AtomLyIntegration/AtomFont/Code/Include/AtomLyIntegration/AtomFont/FFont.h index 2cc8a67cfa..ff6ad7c151 100644 --- a/Gems/AtomLyIntegration/AtomFont/Code/Include/AtomLyIntegration/AtomFont/FFont.h +++ b/Gems/AtomLyIntegration/AtomFont/Code/Include/AtomLyIntegration/AtomFont/FFont.h @@ -133,18 +133,6 @@ namespace AZ typedef std::vector FontEffects; typedef FontEffects::iterator FontEffectsIterator; - struct FontPipelineStateMapKey - { - AZ::RPI::SceneId m_sceneId; // which scene pipeline state is attached to (via Render Pipeline) - AZ::RHI::DrawListTag m_drawListTag; // which render pass this pipeline draws in by default - - bool operator<(const FontPipelineStateMapKey& other) const - { - return m_sceneId < other.m_sceneId - || (m_sceneId == other.m_sceneId && m_drawListTag < other.m_drawListTag); - } - }; - struct FontShaderData { AZ::RHI::ShaderInputNameIndex m_imageInputIndex = "m_texture"; diff --git a/Gems/AtomLyIntegration/AtomViewportDisplayInfo/Code/Source/AtomViewportDisplayInfoSystemComponent.cpp b/Gems/AtomLyIntegration/AtomViewportDisplayInfo/Code/Source/AtomViewportDisplayInfoSystemComponent.cpp index 7d659ebb7a..91844c9b2f 100644 --- a/Gems/AtomLyIntegration/AtomViewportDisplayInfo/Code/Source/AtomViewportDisplayInfoSystemComponent.cpp +++ b/Gems/AtomLyIntegration/AtomViewportDisplayInfo/Code/Source/AtomViewportDisplayInfoSystemComponent.cpp @@ -130,7 +130,8 @@ namespace AZ::Render } AZ::RPI::ViewportContextPtr viewportContext = GetViewportContext(); - if (!m_fontDrawInterface || !viewportContext || !viewportContext->GetRenderScene()) + if (!m_fontDrawInterface || !viewportContext || !viewportContext->GetRenderScene() || + !AZ::Interface::Get()) { return; } diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/Material/MaterialComponentBus.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/Material/MaterialComponentBus.h index 293080367d..d8d0c2dbc3 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/Material/MaterialComponentBus.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/Material/MaterialComponentBus.h @@ -60,52 +60,8 @@ namespace AZ virtual void ClearMaterialOverride(const MaterialAssignmentId& materialAssignmentId) = 0; //! Set a material property override value wrapped by an AZStd::any virtual void SetPropertyOverride(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZStd::any& value) = 0; - //! Set a material property override value to a bool - virtual void SetPropertyOverrideBool(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const bool& value) = 0; - //! Set a material property override value to a integer - virtual void SetPropertyOverrideInt32(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const int32_t& value) = 0; - //! Set a material property override value to a unsigned integer - virtual void SetPropertyOverrideUInt32(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const uint32_t& value) = 0; - //! Set a material property override value to a float - virtual void SetPropertyOverrideFloat(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const float& value) = 0; - //! Set a material property override value to a Vector2 - virtual void SetPropertyOverrideVector2(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZ::Vector2& value) = 0; - //! Set a material property override value to a Vector3 - virtual void SetPropertyOverrideVector3(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZ::Vector3& value) = 0; - //! Set a material property override value to a Vector4 - virtual void SetPropertyOverrideVector4(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZ::Vector4& value) = 0; - //! Set a material property override value to a color - virtual void SetPropertyOverrideColor(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZ::Color& value) = 0; - //! Set a material property override value to an image asset - virtual void SetPropertyOverrideImageAsset(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZ::Data::Asset& value) = 0; - //! Set a material property override value to an image instance - virtual void SetPropertyOverrideImageInstance(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZ::Data::Instance& value) = 0; - //! Set a material property override value to a string - virtual void SetPropertyOverrideString(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZStd::string& value) = 0; //! Get a material property override value wrapped by an AZStd::any virtual AZStd::any GetPropertyOverride(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const = 0; - //! Get a material property override value as a bool - virtual bool GetPropertyOverrideBool(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const = 0; - //! Get a material property override value as an integer - virtual int32_t GetPropertyOverrideInt32(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const = 0; - //! Get a material property override value as an unsigned integer - virtual uint32_t GetPropertyOverrideUInt32(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const = 0; - //! Get a material property override value as a float - virtual float GetPropertyOverrideFloat(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const = 0; - //! Get a material property override value as a Vector2 - virtual AZ::Vector2 GetPropertyOverrideVector2(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const = 0; - //! Get a material property override value as a Vector3 - virtual AZ::Vector3 GetPropertyOverrideVector3(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const = 0; - //! Get a material property override value as a Vector4 - virtual AZ::Vector4 GetPropertyOverrideVector4(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const = 0; - //! Get a material property override value as a Color - virtual AZ::Color GetPropertyOverrideColor(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const = 0; - //! Get a material property override value as an image asset - virtual AZ::Data::Asset GetPropertyOverrideImageAsset(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const = 0; - //! Get a material property override value as an image instance - virtual AZ::Data::Instance GetPropertyOverrideImageInstance(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const = 0; - //! Get a material property override value as a string - virtual AZStd::string GetPropertyOverrideString(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const = 0; //! Clear property override for a specific material assignment virtual void ClearPropertyOverride(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) = 0; //! Clear property overrides for a specific material assignment @@ -122,6 +78,21 @@ namespace AZ const MaterialAssignmentId& materialAssignmentId, const AZ::RPI::MaterialModelUvOverrideMap& modelUvOverrides) = 0; //! Get Model UV overrides for a specific material assignment virtual AZ::RPI::MaterialModelUvOverrideMap GetModelUvOverrides(const MaterialAssignmentId& materialAssignmentId) const = 0; + + //! Set material property override value with a specific type + template + void SetPropertyOverrideT(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const T& value) + { + SetPropertyOverride(materialAssignmentId, propertyName, AZStd::any(value)); + } + + //! Get material property override value with a specific type + template + T GetPropertyOverrideT(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const + { + const AZStd::any& value = GetPropertyOverride(materialAssignmentId, propertyName); + return !value.empty() && value.is() ? AZStd::any_cast(value) : T{}; + } }; using MaterialComponentRequestBus = EBus; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/DiffuseGlobalIllumination/DiffuseGlobalIlluminationComponentController.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/DiffuseGlobalIllumination/DiffuseGlobalIlluminationComponentController.cpp index ee322c8afd..e844219811 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/DiffuseGlobalIllumination/DiffuseGlobalIlluminationComponentController.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/DiffuseGlobalIllumination/DiffuseGlobalIlluminationComponentController.cpp @@ -48,11 +48,7 @@ namespace AZ void DiffuseGlobalIlluminationComponentController::Activate(EntityId entityId) { - AZ_UNUSED(entityId); - - const RPI::Scene* scene = AZ::RPI::RPISystemInterface::Get()->GetDefaultScene().get(); - m_featureProcessor = scene->GetFeatureProcessor(); - + m_featureProcessor = AZ::RPI::Scene::GetFeatureProcessorForEntity(entityId); OnConfigChanged(); } diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Grid/GridComponentController.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Grid/GridComponentController.cpp index b4131894f4..2611021359 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Grid/GridComponentController.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Grid/GridComponentController.cpp @@ -79,7 +79,7 @@ namespace AZ m_entityId = entityId; m_dirty = true; - RPI::ScenePtr scene = RPI::RPISystemInterface::Get()->GetDefaultScene(); + RPI::Scene* scene = RPI::Scene::GetSceneForEntityId(m_entityId); if (scene) { AZ::RPI::SceneNotificationBus::Handler::BusConnect(scene->GetId()); diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentInspector.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentInspector.cpp index 9af3b41b12..bfa9f1d36f 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentInspector.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentInspector.cpp @@ -314,12 +314,21 @@ namespace AZ AtomToolsFramework::ConvertToPropertyConfig(propertyConfig, propertyDefinition); + const auto& propertyIndex = + m_editData.m_materialAsset->GetMaterialPropertiesLayout()->FindPropertyIndex(propertyConfig.m_id); + propertyConfig.m_groupName = groupDisplayName; - const auto& propertyIndex = m_editData.m_materialAsset->GetMaterialPropertiesLayout()->FindPropertyIndex(propertyConfig.m_id); propertyConfig.m_showThumbnail = true; - propertyConfig.m_defaultValue = AtomToolsFramework::ConvertToEditableType(m_editData.m_materialTypeAsset->GetDefaultPropertyValues()[propertyIndex.GetIndex()]); - propertyConfig.m_parentValue = AtomToolsFramework::ConvertToEditableType(m_editData.m_materialTypeAsset->GetDefaultPropertyValues()[propertyIndex.GetIndex()]); - propertyConfig.m_originalValue = AtomToolsFramework::ConvertToEditableType(m_editData.m_materialAsset->GetPropertyValues()[propertyIndex.GetIndex()]); + + propertyConfig.m_defaultValue = AtomToolsFramework::ConvertToEditableType( + m_editData.m_materialTypeAsset->GetDefaultPropertyValues()[propertyIndex.GetIndex()]); + + // There is no explicit parent material here. Material instance property overrides replace the values from the + // assigned material asset. Its values should be treated as parent, for comparison, in this case. + propertyConfig.m_parentValue = AtomToolsFramework::ConvertToEditableType( + m_editData.m_materialAsset->GetPropertyValues()[propertyIndex.GetIndex()]); + propertyConfig.m_originalValue = AtomToolsFramework::ConvertToEditableType( + m_editData.m_materialAsset->GetPropertyValues()[propertyIndex.GetIndex()]); group.m_properties.emplace_back(propertyConfig); } } diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialSystemComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialSystemComponent.cpp index e53010d747..adaebfb889 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialSystemComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialSystemComponent.cpp @@ -196,6 +196,7 @@ namespace AZ { AZ_UNUSED(entityId); AZ_UNUSED(materialAssignmentId); + AZ_Warning( "EditorMaterialSystemComponent", false, "RenderMaterialPreview capture failed for entity %s slot %s.", entityId.ToString().c_str(), materialAssignmentId.ToString().c_str()); diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/MaterialComponentController.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/MaterialComponentController.cpp index 07082a87d5..0ccdae28de 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/MaterialComponentController.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/MaterialComponentController.cpp @@ -54,29 +54,29 @@ namespace AZ ->Event("GetMaterialOverride", &MaterialComponentRequestBus::Events::GetMaterialOverride) ->Event("ClearMaterialOverride", &MaterialComponentRequestBus::Events::ClearMaterialOverride) ->Event("SetPropertyOverride", &MaterialComponentRequestBus::Events::SetPropertyOverride) - ->Event("SetPropertyOverrideBool", &MaterialComponentRequestBus::Events::SetPropertyOverrideBool) - ->Event("SetPropertyOverrideInt32", &MaterialComponentRequestBus::Events::SetPropertyOverrideInt32) - ->Event("SetPropertyOverrideUInt32", &MaterialComponentRequestBus::Events::SetPropertyOverrideUInt32) - ->Event("SetPropertyOverrideFloat", &MaterialComponentRequestBus::Events::SetPropertyOverrideFloat) - ->Event("SetPropertyOverrideVector2", &MaterialComponentRequestBus::Events::SetPropertyOverrideVector2) - ->Event("SetPropertyOverrideVector3", &MaterialComponentRequestBus::Events::SetPropertyOverrideVector3) - ->Event("SetPropertyOverrideVector4", &MaterialComponentRequestBus::Events::SetPropertyOverrideVector4) - ->Event("SetPropertyOverrideColor", &MaterialComponentRequestBus::Events::SetPropertyOverrideColor) - ->Event("SetPropertyOverrideImageAsset", &MaterialComponentRequestBus::Events::SetPropertyOverrideImageAsset) - ->Event("SetPropertyOverrideImageInstance", &MaterialComponentRequestBus::Events::SetPropertyOverrideImageInstance) - ->Event("SetPropertyOverrideString", &MaterialComponentRequestBus::Events::SetPropertyOverrideString) + ->Event("SetPropertyOverrideBool", &MaterialComponentRequestBus::Events::SetPropertyOverrideT) + ->Event("SetPropertyOverrideInt32", &MaterialComponentRequestBus::Events::SetPropertyOverrideT) + ->Event("SetPropertyOverrideUInt32", &MaterialComponentRequestBus::Events::SetPropertyOverrideT) + ->Event("SetPropertyOverrideFloat", &MaterialComponentRequestBus::Events::SetPropertyOverrideT) + ->Event("SetPropertyOverrideVector2", &MaterialComponentRequestBus::Events::SetPropertyOverrideT) + ->Event("SetPropertyOverrideVector3", &MaterialComponentRequestBus::Events::SetPropertyOverrideT) + ->Event("SetPropertyOverrideVector4", &MaterialComponentRequestBus::Events::SetPropertyOverrideT) + ->Event("SetPropertyOverrideColor", &MaterialComponentRequestBus::Events::SetPropertyOverrideT) + ->Event("SetPropertyOverrideImage", &MaterialComponentRequestBus::Events::SetPropertyOverrideT) + ->Event("SetPropertyOverrideString", &MaterialComponentRequestBus::Events::SetPropertyOverrideT) + ->Event("SetPropertyOverrideEnum", &MaterialComponentRequestBus::Events::SetPropertyOverrideT) ->Event("GetPropertyOverride", &MaterialComponentRequestBus::Events::GetPropertyOverride) - ->Event("GetPropertyOverrideBool", &MaterialComponentRequestBus::Events::GetPropertyOverrideBool) - ->Event("GetPropertyOverrideInt32", &MaterialComponentRequestBus::Events::GetPropertyOverrideInt32) - ->Event("GetPropertyOverrideUInt32", &MaterialComponentRequestBus::Events::GetPropertyOverrideUInt32) - ->Event("GetPropertyOverrideFloat", &MaterialComponentRequestBus::Events::GetPropertyOverrideFloat) - ->Event("GetPropertyOverrideVector2", &MaterialComponentRequestBus::Events::GetPropertyOverrideVector2) - ->Event("GetPropertyOverrideVector3", &MaterialComponentRequestBus::Events::GetPropertyOverrideVector3) - ->Event("GetPropertyOverrideVector4", &MaterialComponentRequestBus::Events::GetPropertyOverrideVector4) - ->Event("GetPropertyOverrideColor", &MaterialComponentRequestBus::Events::GetPropertyOverrideColor) - ->Event("GetPropertyOverrideImageAsset", &MaterialComponentRequestBus::Events::GetPropertyOverrideImageAsset) - ->Event("GetPropertyOverrideImageInstance", &MaterialComponentRequestBus::Events::GetPropertyOverrideImageInstance) - ->Event("GetPropertyOverrideString", &MaterialComponentRequestBus::Events::GetPropertyOverrideString) + ->Event("GetPropertyOverrideBool", &MaterialComponentRequestBus::Events::GetPropertyOverrideT) + ->Event("GetPropertyOverrideInt32", &MaterialComponentRequestBus::Events::GetPropertyOverrideT) + ->Event("GetPropertyOverrideUInt32", &MaterialComponentRequestBus::Events::GetPropertyOverrideT) + ->Event("GetPropertyOverrideFloat", &MaterialComponentRequestBus::Events::GetPropertyOverrideT) + ->Event("GetPropertyOverrideVector2", &MaterialComponentRequestBus::Events::GetPropertyOverrideT) + ->Event("GetPropertyOverrideVector3", &MaterialComponentRequestBus::Events::GetPropertyOverrideT) + ->Event("GetPropertyOverrideVector4", &MaterialComponentRequestBus::Events::GetPropertyOverrideT) + ->Event("GetPropertyOverrideColor", &MaterialComponentRequestBus::Events::GetPropertyOverrideT) + ->Event("GetPropertyOverrideImage", &MaterialComponentRequestBus::Events::GetPropertyOverrideT) + ->Event("GetPropertyOverrideString", &MaterialComponentRequestBus::Events::GetPropertyOverrideT) + ->Event("GetPropertyOverrideEnum", &MaterialComponentRequestBus::Events::GetPropertyOverrideT) ->Event("ClearPropertyOverride", &MaterialComponentRequestBus::Events::ClearPropertyOverride) ->Event("ClearPropertyOverrides", &MaterialComponentRequestBus::Events::ClearPropertyOverrides) ->Event("ClearAllPropertyOverrides", &MaterialComponentRequestBus::Events::ClearAllPropertyOverrides) @@ -121,8 +121,13 @@ namespace AZ MaterialComponentRequestBus::Handler::BusDisconnect(); MaterialReceiverNotificationBus::Handler::BusDisconnect(); TickBus::Handler::BusDisconnect(); + ReleaseMaterials(); + // Sending notification to wipe any previously assigned material overrides + MaterialComponentNotificationBus::Event( + m_entityId, &MaterialComponentNotifications::OnMaterialsUpdated, MaterialAssignmentMap()); + m_queuedMaterialUpdateNotification = false; m_entityId = AZ::EntityId(AZ::EntityId::InvalidEntityId); } @@ -221,6 +226,11 @@ namespace AZ if (!anyQueued) { ReleaseMaterials(); + + // If no other materials were loaded, the notification must still be sent in case there are externally managed material + // instances in the configuration + MaterialComponentNotificationBus::Event( + m_entityId, &MaterialComponentNotifications::OnMaterialsUpdated, m_configuration.m_materials); } } @@ -268,8 +278,6 @@ namespace AZ { materialPair.second.Release(); } - - MaterialComponentNotificationBus::Event(m_entityId, &MaterialComponentNotifications::OnMaterialsUpdated, m_configuration.m_materials); } MaterialAssignmentMap MaterialComponentController::GetOriginalMaterialAssignments() const @@ -499,76 +507,6 @@ namespace AZ QueuePropertyChanges(materialAssignmentId); } - void MaterialComponentController::SetPropertyOverrideBool( - const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const bool& value) - { - SetPropertyOverride(materialAssignmentId, propertyName, AZStd::any(value)); - } - - void MaterialComponentController::SetPropertyOverrideInt32( - const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const int32_t& value) - { - SetPropertyOverride(materialAssignmentId, propertyName, AZStd::any(value)); - } - - void MaterialComponentController::SetPropertyOverrideUInt32( - const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const uint32_t& value) - { - SetPropertyOverride(materialAssignmentId, propertyName, AZStd::any(value)); - } - - void MaterialComponentController::SetPropertyOverrideFloat( - const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const float& value) - { - SetPropertyOverride(materialAssignmentId, propertyName, AZStd::any(value)); - } - - void MaterialComponentController::SetPropertyOverrideVector2( - const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZ::Vector2& value) - { - SetPropertyOverride(materialAssignmentId, propertyName, AZStd::any(value)); - } - - void MaterialComponentController::SetPropertyOverrideVector3( - const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZ::Vector3& value) - { - SetPropertyOverride(materialAssignmentId, propertyName, AZStd::any(value)); - } - - void MaterialComponentController::SetPropertyOverrideVector4( - const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZ::Vector4& value) - { - SetPropertyOverride(materialAssignmentId, propertyName, AZStd::any(value)); - } - - void MaterialComponentController::SetPropertyOverrideColor( - const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZ::Color& value) - { - SetPropertyOverride(materialAssignmentId, propertyName, AZStd::any(value)); - } - - void MaterialComponentController::SetPropertyOverrideImageAsset( - const MaterialAssignmentId& materialAssignmentId, - const AZStd::string& propertyName, - const AZ::Data::Asset& value) - { - SetPropertyOverride(materialAssignmentId, propertyName, AZStd::any(value)); - } - - void MaterialComponentController::SetPropertyOverrideImageInstance( - const MaterialAssignmentId& materialAssignmentId, - const AZStd::string& propertyName, - const AZ::Data::Instance& value) - { - SetPropertyOverride(materialAssignmentId, propertyName, AZStd::any(value)); - } - - void MaterialComponentController::SetPropertyOverrideString( - const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZStd::string& value) - { - SetPropertyOverride(materialAssignmentId, propertyName, AZStd::any(value)); - } - AZStd::any MaterialComponentController::GetPropertyOverride(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const { const auto materialIt = m_configuration.m_materials.find(materialAssignmentId); @@ -586,83 +524,6 @@ namespace AZ return propertyIt->second; } - bool MaterialComponentController::GetPropertyOverrideBool( - const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const - { - const AZStd::any& value = GetPropertyOverride(materialAssignmentId, propertyName); - return !value.empty() && value.is() ? AZStd::any_cast(value) : false; - } - - int32_t MaterialComponentController::GetPropertyOverrideInt32( - const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const - { - const AZStd::any& value = GetPropertyOverride(materialAssignmentId, propertyName); - return !value.empty() && value.is() ? AZStd::any_cast(value) : 0; - } - - uint32_t MaterialComponentController::GetPropertyOverrideUInt32( - const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const - { - const AZStd::any& value = GetPropertyOverride(materialAssignmentId, propertyName); - return !value.empty() && value.is() ? AZStd::any_cast(value) : 0; - } - - float MaterialComponentController::GetPropertyOverrideFloat( - const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const - { - const AZStd::any& value = GetPropertyOverride(materialAssignmentId, propertyName); - return !value.empty() && value.is() ? AZStd::any_cast(value) : 0.0f; - } - - AZ::Vector2 MaterialComponentController::GetPropertyOverrideVector2( - const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const - { - const AZStd::any& value = GetPropertyOverride(materialAssignmentId, propertyName); - return !value.empty() && value.is() ? AZStd::any_cast(value) : AZ::Vector2::CreateZero(); - } - - AZ::Vector3 MaterialComponentController::GetPropertyOverrideVector3( - const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const - { - const AZStd::any& value = GetPropertyOverride(materialAssignmentId, propertyName); - return !value.empty() && value.is() ? AZStd::any_cast(value) : AZ::Vector3::CreateZero(); - } - - AZ::Vector4 MaterialComponentController::GetPropertyOverrideVector4( - const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const - { - const AZStd::any& value = GetPropertyOverride(materialAssignmentId, propertyName); - return !value.empty() && value.is() ? AZStd::any_cast(value) : AZ::Vector4::CreateZero(); - } - - AZ::Color MaterialComponentController::GetPropertyOverrideColor( - const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const - { - const AZStd::any& value = GetPropertyOverride(materialAssignmentId, propertyName); - return !value.empty() && value.is() ? AZStd::any_cast(value) : AZ::Color::CreateZero(); - } - - AZ::Data::Asset MaterialComponentController::GetPropertyOverrideImageAsset( - const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const - { - const AZStd::any& value = GetPropertyOverride(materialAssignmentId, propertyName); - return !value.empty() && value.is>() ? AZStd::any_cast>(value) : AZ::Data::Asset(); - } - - AZ::Data::Instance MaterialComponentController::GetPropertyOverrideImageInstance( - const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const - { - const AZStd::any& value = GetPropertyOverride(materialAssignmentId, propertyName); - return !value.empty() && value.is>() ? AZStd::any_cast>(value) : AZ::Data::Instance(); - } - - AZStd::string MaterialComponentController::GetPropertyOverrideString( - const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const - { - const AZStd::any& value = GetPropertyOverride(materialAssignmentId, propertyName); - return !value.empty() && value.is() ? AZStd::any_cast(value) : AZStd::string(); - } - void MaterialComponentController::ClearPropertyOverride(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) { auto materialIt = m_configuration.m_materials.find(materialAssignmentId); diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/MaterialComponentController.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/MaterialComponentController.h index ec542c377c..74b1cfda4d 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/MaterialComponentController.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/MaterialComponentController.h @@ -65,33 +65,8 @@ namespace AZ void SetMaterialOverride(const MaterialAssignmentId& materialAssignmentId, const AZ::Data::AssetId& materialAssetId) override; AZ::Data::AssetId GetMaterialOverride(const MaterialAssignmentId& materialAssignmentId) const override; void ClearMaterialOverride(const MaterialAssignmentId& materialAssignmentId) override; - void SetPropertyOverride(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZStd::any& value) override; - void SetPropertyOverrideBool(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const bool& value) override; - void SetPropertyOverrideInt32(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const int32_t& value) override; - void SetPropertyOverrideUInt32(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const uint32_t& value) override; - void SetPropertyOverrideFloat(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const float& value) override; - void SetPropertyOverrideVector2(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZ::Vector2& value) override; - void SetPropertyOverrideVector3(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZ::Vector3& value) override; - void SetPropertyOverrideVector4(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZ::Vector4& value) override; - void SetPropertyOverrideColor(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZ::Color& value) override; - void SetPropertyOverrideImageAsset(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZ::Data::Asset& value) override; - void SetPropertyOverrideImageInstance(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZ::Data::Instance& value) override; - void SetPropertyOverrideString(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZStd::string& value) override; - AZStd::any GetPropertyOverride(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const override; - bool GetPropertyOverrideBool(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const override; - int32_t GetPropertyOverrideInt32(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const override; - uint32_t GetPropertyOverrideUInt32(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const override; - float GetPropertyOverrideFloat(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const override; - AZ::Vector2 GetPropertyOverrideVector2(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const override; - AZ::Vector3 GetPropertyOverrideVector3(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const override; - AZ::Vector4 GetPropertyOverrideVector4(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const override; - AZ::Color GetPropertyOverrideColor(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const override; - AZ::Data::Asset GetPropertyOverrideImageAsset(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const override; - AZ::Data::Instance GetPropertyOverrideImageInstance(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const override; - AZStd::string GetPropertyOverrideString(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) const override; - void ClearPropertyOverride(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) override; void ClearPropertyOverrides(const MaterialAssignmentId& materialAssignmentId) override; void ClearAllPropertyOverrides() override; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/DisplayMapper/DisplayMapperComponentController.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/DisplayMapper/DisplayMapperComponentController.cpp index e86c909121..a0577c6240 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/DisplayMapper/DisplayMapperComponentController.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/DisplayMapper/DisplayMapperComponentController.cpp @@ -357,8 +357,7 @@ namespace AZ void DisplayMapperComponentController::OnConfigChanged() { // Register the configuration with the AcesDisplayMapperFeatureProcessor for this scene. - const AZ::RPI::Scene* scene = AZ::RPI::RPISystemInterface::Get()->GetDefaultScene().get(); - DisplayMapperFeatureProcessorInterface* fp = scene->GetFeatureProcessor(); + DisplayMapperFeatureProcessorInterface* fp = AZ::RPI::Scene::GetFeatureProcessorForEntity(m_entityId); DisplayMapperConfigurationDescriptor desc; desc.m_operationType = m_configuration.m_displayMapperOperation; desc.m_ldrGradingLutEnabled = m_configuration.m_ldrColorGradingLutEnabled; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SkinnedMesh/SkinnedMeshDebugDisplay.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SkinnedMesh/SkinnedMeshDebugDisplay.h index e789b6dc80..5b060198dc 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SkinnedMesh/SkinnedMeshDebugDisplay.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SkinnedMesh/SkinnedMeshDebugDisplay.h @@ -48,7 +48,7 @@ namespace AZ // CVar for toggling the display of the scene stats int r_skinnedMeshDisplaySceneStats = 0; // SceneId to query for the stats - RPI::SceneId m_sceneId = RPI::SceneId::CreateNull(); + RPI::SceneId m_sceneId; }; }// namespace Render }// namespace AZ diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportRenderer.cpp b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportRenderer.cpp index facf7a7b12..1d62e61b0a 100644 --- a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportRenderer.cpp +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportRenderer.cpp @@ -60,6 +60,7 @@ namespace EMStudio // Create and register a scene with all available feature processors AZ::RPI::SceneDescriptor sceneDesc; + sceneDesc.m_nameId = AZ::Name("AnimViewport"); m_scene = AZ::RPI::Scene::CreateScene(sceneDesc); m_scene->EnableAllFeatureProcessors(); @@ -227,8 +228,7 @@ namespace EMStudio AZ::TransformBus::Event(m_iblEntity->GetId(), &AZ::TransformBus::Events::SetLocalTM, iblTransform); const AZ::Matrix4x4 rotationMatrix = AZ::Matrix4x4::CreateIdentity(); - AZ::RPI::ScenePtr scene = AZ::RPI::RPISystemInterface::Get()->GetDefaultScene(); - auto skyBoxFeatureProcessorInterface = scene->GetFeatureProcessor(); + auto skyBoxFeatureProcessorInterface = m_scene->GetFeatureProcessor(); skyBoxFeatureProcessorInterface->SetCubemapRotationMatrix(rotationMatrix); } diff --git a/Gems/Blast/Code/Source/Components/BlastSystemComponent.cpp b/Gems/Blast/Code/Source/Components/BlastSystemComponent.cpp index 00629eb65d..4c0f15463a 100644 --- a/Gems/Blast/Code/Source/Components/BlastSystemComponent.cpp +++ b/Gems/Blast/Code/Source/Components/BlastSystemComponent.cpp @@ -255,19 +255,22 @@ namespace Blast BlastFamilyComponentRequestBus::Broadcast( &BlastFamilyComponentRequests::FillDebugRenderBuffer, buffer, m_debugRenderMode); - // This is a system component, and thus is not associated with a specific scene, so use the default scene + // This is a system component, and thus is not associated with a specific scene, so use the bootstrap scene // for the debug drawing - const auto defaultScene = AZ::RPI::RPISystemInterface::Get()->GetDefaultScene(); - auto drawQueue = AZ::RPI::AuxGeomFeatureProcessorInterface::GetDrawQueueForScene(defaultScene); - - for (DebugLine& line : buffer.m_lines) + const auto mainScene = AZ::RPI::RPISystemInterface::Get()->GetSceneByName(AZ::Name("Main")); + if (mainScene) { - AZ::RPI::AuxGeomDraw::AuxGeomDynamicDrawArguments drawArguments; - drawArguments.m_verts = &line.m_p0; - drawArguments.m_vertCount = 2; - drawArguments.m_colors = &line.m_color; - drawArguments.m_colorCount = 1; - drawQueue->DrawLines(drawArguments); + auto drawQueue = AZ::RPI::AuxGeomFeatureProcessorInterface::GetDrawQueueForScene(mainScene); + + for (DebugLine& line : buffer.m_lines) + { + AZ::RPI::AuxGeomDraw::AuxGeomDynamicDrawArguments drawArguments; + drawArguments.m_verts = &line.m_p0; + drawArguments.m_vertCount = 2; + drawArguments.m_colors = &line.m_color; + drawArguments.m_colorCount = 1; + drawQueue->DrawLines(drawArguments); + } } } } diff --git a/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/RagdollCommands.cpp b/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/RagdollCommands.cpp index e51c9ece0b..d7c7bf69e5 100644 --- a/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/RagdollCommands.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/RagdollCommands.cpp @@ -105,6 +105,8 @@ namespace EMotionFX *jointTypeId, parentBindRotationWorld, nodeBindRotationWorld, boneDirection, exampleRotationsLocal); AZ_Assert(jointLimitConfig, "Could not create joint limit configuration."); + jointLimitConfig->SetPropertyVisibility(AzPhysics::JointConfiguration::PropertyVisibility::ParentLocalRotation, true); + jointLimitConfig->SetPropertyVisibility(AzPhysics::JointConfiguration::PropertyVisibility::ChildLocalRotation, true); return jointLimitConfig; } } diff --git a/Gems/EMotionFX/Code/Source/Integration/Components/ActorComponent.cpp b/Gems/EMotionFX/Code/Source/Integration/Components/ActorComponent.cpp index 8520896dd2..e38e15d124 100644 --- a/Gems/EMotionFX/Code/Source/Integration/Components/ActorComponent.cpp +++ b/Gems/EMotionFX/Code/Source/Integration/Components/ActorComponent.cpp @@ -576,7 +576,7 @@ namespace EMotionFX // The configuration stores some debug option. When that is enabled, we override it on top of the render flags. m_debugRenderFlags[RENDER_AABB] = m_debugRenderFlags[RENDER_AABB] || m_configuration.m_renderBounds; - m_debugRenderFlags[RENDER_SKELETON] = m_debugRenderFlags[RENDER_SKELETON] || m_configuration.m_renderSkeleton; + m_debugRenderFlags[RENDER_LINESKELETON] = m_debugRenderFlags[RENDER_LINESKELETON] || m_configuration.m_renderSkeleton; m_debugRenderFlags[RENDER_EMFX_DEBUG] = true; m_renderActorInstance->DebugDraw(m_debugRenderFlags); } diff --git a/Gems/EMotionFX/Code/Source/Integration/Editor/Components/EditorActorComponent.cpp b/Gems/EMotionFX/Code/Source/Integration/Editor/Components/EditorActorComponent.cpp index d0274b3c68..78470a1fa5 100644 --- a/Gems/EMotionFX/Code/Source/Integration/Editor/Components/EditorActorComponent.cpp +++ b/Gems/EMotionFX/Code/Source/Integration/Editor/Components/EditorActorComponent.cpp @@ -606,7 +606,7 @@ namespace EMotionFX m_renderActorInstance->UpdateBounds(); m_debugRenderFlags[RENDER_AABB] = m_renderBounds; - m_debugRenderFlags[RENDER_SKELETON] = m_renderSkeleton; + m_debugRenderFlags[RENDER_LINESKELETON] = m_renderSkeleton; m_debugRenderFlags[RENDER_EMFX_DEBUG] = true; m_renderActorInstance->DebugDraw(m_debugRenderFlags); } diff --git a/Gems/InAppPurchases/Code/inapppurchases_files.cmake b/Gems/InAppPurchases/Code/inapppurchases_files.cmake index 5eab792206..2669cbf930 100644 --- a/Gems/InAppPurchases/Code/inapppurchases_files.cmake +++ b/Gems/InAppPurchases/Code/inapppurchases_files.cmake @@ -10,6 +10,7 @@ set(FILES Include/InAppPurchases/InAppPurchasesBus.h Include/InAppPurchases/InAppPurchasesInterface.h Include/InAppPurchases/InAppPurchasesResponseBus.h + Source/InAppPurchasesSystemComponent.h Source/InAppPurchasesSystemComponent.cpp Source/InAppPurchasesInterface.cpp ) diff --git a/Gems/LyShine/Code/Source/Draw2d.cpp b/Gems/LyShine/Code/Source/Draw2d.cpp index b0539900e2..3476c530b2 100644 --- a/Gems/LyShine/Code/Source/Draw2d.cpp +++ b/Gems/LyShine/Code/Source/Draw2d.cpp @@ -65,7 +65,7 @@ CDraw2d::~CDraw2d() } //////////////////////////////////////////////////////////////////////////////////////////////////// -void CDraw2d::OnBootstrapSceneReady([[maybe_unused]] AZ::RPI::Scene* bootstrapScene) +void CDraw2d::OnBootstrapSceneReady(AZ::RPI::Scene* bootstrapScene) { // At this point the RPI is ready for use @@ -74,16 +74,16 @@ void CDraw2d::OnBootstrapSceneReady([[maybe_unused]] AZ::RPI::Scene* bootstrapSc AZ::Data::Instance shader = AZ::RPI::LoadCriticalShader(shaderFilepath); // Set scene to be associated with the dynamic draw context - AZ::RPI::ScenePtr scene; + AZ::RPI::Scene* scene = nullptr; if (m_viewportContext) { // Use scene associated with the specified viewport context - scene = m_viewportContext->GetRenderScene(); + scene = m_viewportContext->GetRenderScene().get(); } else { - // No viewport context specified, use default scene - scene = AZ::RPI::RPISystemInterface::Get()->GetDefaultScene(); + // No viewport context specified, use main scene + scene = bootstrapScene; } AZ_Assert(scene != nullptr, "Attempting to create a DynamicDrawContext for a viewport context that has not been associated with a scene yet."); @@ -113,7 +113,7 @@ void CDraw2d::OnBootstrapSceneReady([[maybe_unused]] AZ::RPI::Scene* bootstrapSc else { // Render target support is disabled - m_dynamicDraw->SetOutputScope(scene.get()); + m_dynamicDraw->SetOutputScope(scene); } m_dynamicDraw->EndInit(); diff --git a/Gems/LyShine/Code/Source/LyShine.cpp b/Gems/LyShine/Code/Source/LyShine.cpp index 2cee4e92eb..19c6e4281e 100644 --- a/Gems/LyShine/Code/Source/LyShine.cpp +++ b/Gems/LyShine/Code/Source/LyShine.cpp @@ -653,12 +653,12 @@ void CLyShine::OnRenderTick() } //////////////////////////////////////////////////////////////////////////////////////////////////// -void CLyShine::OnBootstrapSceneReady([[maybe_unused]] AZ::RPI::Scene* bootstrapScene) +void CLyShine::OnBootstrapSceneReady(AZ::RPI::Scene* bootstrapScene) { // Load cursor if its path was set before RPI was initialized LoadUiCursor(); - LyShinePassDataRequestBus::Handler::BusConnect(AZ::RPI::RPISystemInterface::Get()->GetDefaultScene()->GetId()); + LyShinePassDataRequestBus::Handler::BusConnect(bootstrapScene->GetId()); } //////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Gems/LyShine/Code/Source/UiRenderer.cpp b/Gems/LyShine/Code/Source/UiRenderer.cpp index 3111f31615..98b376ec8b 100644 --- a/Gems/LyShine/Code/Source/UiRenderer.cpp +++ b/Gems/LyShine/Code/Source/UiRenderer.cpp @@ -52,7 +52,7 @@ bool UiRenderer::IsReady() return m_isRPIReady; } -void UiRenderer::OnBootstrapSceneReady([[maybe_unused]] AZ::RPI::Scene* bootstrapScene) +void UiRenderer::OnBootstrapSceneReady(AZ::RPI::Scene* bootstrapScene) { // At this point the RPI is ready for use @@ -64,16 +64,17 @@ void UiRenderer::OnBootstrapSceneReady([[maybe_unused]] AZ::RPI::Scene* bootstra if (m_viewportContext) { // Create a new scene based on the user specified viewport context - m_scene = CreateScene(m_viewportContext); + m_ownedScene = CreateScene(m_viewportContext); + m_scene = m_ownedScene.get(); } else { // No viewport context specified, use default scene - m_scene = AZ::RPI::RPISystemInterface::Get()->GetDefaultScene(); + m_scene = bootstrapScene; } // Create a dynamic draw context for UI Canvas drawing for the scene - m_dynamicDraw = CreateDynamicDrawContext(m_scene, uiShader); + m_dynamicDraw = CreateDynamicDrawContext(uiShader); if (m_dynamicDraw) { @@ -93,6 +94,7 @@ AZ::RPI::ScenePtr UiRenderer::CreateScene(AZStd::shared_ptrEnableAllFeatureProcessors(); // LYSHINE_ATOM_TODO - have a UI pipeline and enable only needed fps @@ -116,7 +118,6 @@ AZ::RPI::ScenePtr UiRenderer::CreateScene(AZStd::shared_ptr UiRenderer::CreateDynamicDrawContext( - AZ::RPI::ScenePtr scene, AZ::Data::Instance uiShader) { // Find the pass that renders the UI canvases after the rtt passes @@ -144,7 +145,7 @@ AZ::RHI::Ptr UiRenderer::CreateDynamicDrawContext( else { // Render target support is disabled - dynamicDraw->SetOutputScope(m_scene.get()); + dynamicDraw->SetOutputScope(m_scene); } dynamicDraw->EndInit(); diff --git a/Gems/LyShine/Code/Source/UiRenderer.h b/Gems/LyShine/Code/Source/UiRenderer.h index 3be3c832d3..0e15d41907 100644 --- a/Gems/LyShine/Code/Source/UiRenderer.h +++ b/Gems/LyShine/Code/Source/UiRenderer.h @@ -152,7 +152,6 @@ private: // member functions //! Create a dynamic draw context for this renderer AZ::RHI::Ptr CreateDynamicDrawContext( - AZ::RPI::ScenePtr scene, AZ::Data::Instance uiShader); //! Bind the global white texture for all the texture units we use @@ -175,7 +174,8 @@ protected: // attributes // Set by user when viewport context is not the main/default viewport AZStd::shared_ptr m_viewportContext; - AZ::RPI::ScenePtr m_scene; + AZ::RPI::ScenePtr m_ownedScene; + AZ::RPI::Scene* m_scene = nullptr; #ifndef _RELEASE int m_debugTextureDataRecordLevel = 0; diff --git a/Gems/LyShine/Code/Source/World/UiCanvasAssetRefComponent.cpp b/Gems/LyShine/Code/Source/World/UiCanvasAssetRefComponent.cpp index d2a307ecaf..59d764f297 100644 --- a/Gems/LyShine/Code/Source/World/UiCanvasAssetRefComponent.cpp +++ b/Gems/LyShine/Code/Source/World/UiCanvasAssetRefComponent.cpp @@ -239,15 +239,16 @@ void UiCanvasAssetRefComponent::Activate() //////////////////////////////////////////////////////////////////////////////////////////////////// void UiCanvasAssetRefComponent::Deactivate() { -#if !defined(DEDICATED_SERVER) - if (m_canvasEntityId.IsValid()) + if (!gEnv->IsDedicated()) { - gEnv->pLyShine->ReleaseCanvasDeferred(m_canvasEntityId); - m_canvasEntityId.SetInvalid(); - } + if (m_canvasEntityId.IsValid()) + { + gEnv->pLyShine->ReleaseCanvasDeferred(m_canvasEntityId); + m_canvasEntityId.SetInvalid(); + } - UiCanvasAssetRefBus::Handler::BusDisconnect(); - UiCanvasRefBus::Handler::BusDisconnect(); - UiCanvasManagerNotificationBus::Handler::BusDisconnect(); -#endif + UiCanvasAssetRefBus::Handler::BusDisconnect(); + UiCanvasRefBus::Handler::BusDisconnect(); + UiCanvasManagerNotificationBus::Handler::BusDisconnect(); + } } diff --git a/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyChildComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyChildComponent.cpp index 59782b1f4d..aa90b1b17b 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyChildComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyChildComponent.cpp @@ -45,6 +45,7 @@ namespace Multiplayer void NetworkHierarchyChildComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided) { provided.push_back(AZ_CRC_CE("NetworkHierarchyChildComponent")); + provided.push_back(AZ_CRC_CE("MultiplayerInputDriver")); } void NetworkHierarchyChildComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible) diff --git a/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyRootComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyRootComponent.cpp index 76f4bddb1a..1404484d5c 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyRootComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetworkHierarchyRootComponent.cpp @@ -53,6 +53,7 @@ namespace Multiplayer void NetworkHierarchyRootComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided) { provided.push_back(AZ_CRC_CE("NetworkHierarchyRootComponent")); + provided.push_back(AZ_CRC_CE("MultiplayerInputDriver")); } void NetworkHierarchyRootComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible) diff --git a/Gems/PhysX/Code/CMakeLists.txt b/Gems/PhysX/Code/CMakeLists.txt index 1acfae3bfd..bb3d7c08a2 100644 --- a/Gems/PhysX/Code/CMakeLists.txt +++ b/Gems/PhysX/Code/CMakeLists.txt @@ -11,12 +11,13 @@ add_subdirectory(NumericalMethods) ly_get_list_relative_pal_filename(pal_source_dir ${CMAKE_CURRENT_LIST_DIR}/Source/Platform/${PAL_PLATFORM_NAME}) include(${pal_source_dir}/PAL_${PAL_PLATFORM_NAME_LOWERCASE}.cmake) # for PAL_TRAIT_PHYSX_SUPPORTED -set(PHYSX_ENABLE_RUNNING_BENCHMARKS OFF CACHE BOOL "Adds a target to allow running of the physx benchmarks.") +set(LY_PHYSX_ENABLE_RUNNING_BENCHMARKS OFF CACHE BOOL "Adds a target to allow running of the physx benchmarks.") if(PAL_TRAIT_PHYSX_SUPPORTED) set(physx_dependency 3rdParty::PhysX) set(physx_files physx_files.cmake) set(physx_shared_files physx_shared_files.cmake) + set(physx_mock_files physx_mocks_files.cmake) set(physx_editor_files physx_editor_files.cmake) else() set(physx_files physx_unsupported_files.cmake) @@ -151,6 +152,17 @@ endif() # Tests ################################################################################ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) + ly_add_target( + NAME PhysX.Mocks HEADERONLY + NAMESPACE Gem + OUTPUT_NAME PhysX.Mocks.Gem + FILES_CMAKE + physx_mocks_files.cmake + INCLUDE_DIRECTORIES + INTERFACE + Mocks + ) + ly_add_target( NAME PhysX.Tests ${PAL_TRAIT_TEST_TARGET_TYPE} NAMESPACE Gem @@ -185,7 +197,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) # Only add the physx benchmarks if this flag is set. The benchmark code is still built, as it is part of the PhysX.Tests project. # Currently jenkins has a 1500sec(25min) timeout, our benchmarks can sometimes take over 1500sec and cause a build failure for timeout. # Jenkins currently doesn't upload the results of the benchmarks, so this is ok. - if(PHYSX_ENABLE_RUNNING_BENCHMARKS) + if(LY_PHYSX_ENABLE_RUNNING_BENCHMARKS) ly_add_googlebenchmark( NAME Gem::PhysX.Benchmarks TARGET Gem::PhysX.Tests @@ -213,6 +225,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) AZ::AzTest AZ::AzToolsFrameworkTestCommon Gem::PhysX.Static + Gem::PhysX.Mocks Gem::PhysX.Editor.Static RUNTIME_DEPENDENCIES Gem::LmbrCentral.Editor diff --git a/Gems/PhysX/Code/Mocks/PhysX/MockPhysXHeightfieldProviderComponent.h b/Gems/PhysX/Code/Mocks/PhysX/MockPhysXHeightfieldProviderComponent.h new file mode 100644 index 0000000000..7e500881c0 --- /dev/null +++ b/Gems/PhysX/Code/Mocks/PhysX/MockPhysXHeightfieldProviderComponent.h @@ -0,0 +1,74 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#pragma once + +#include +#include +#include +#include +#include + +namespace UnitTest +{ + class MockPhysXHeightfieldProviderComponent + : public AZ::Component + { + public: + AZ_COMPONENT(MockPhysXHeightfieldProviderComponent, "{C5F7CCCF-FDB2-40DF-992D-CF028F4A1B59}"); + + static void Reflect([[maybe_unused]] AZ::ReflectContext* context) + { + if (auto serializeContext = azrtti_cast(context)) + { + serializeContext->Class() + ->Version(1); + } + } + + void Activate() override + { + } + + void Deactivate() override + { + } + + static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided) + { + provided.push_back(AZ_CRC_CE("PhysicsHeightfieldProviderService")); + } + + }; + + class MockPhysXHeightfieldProvider + : protected Physics::HeightfieldProviderRequestsBus::Handler + { + public: + MockPhysXHeightfieldProvider(AZ::EntityId entityId) + { + Physics::HeightfieldProviderRequestsBus::Handler::BusConnect(entityId); + } + + ~MockPhysXHeightfieldProvider() + { + Physics::HeightfieldProviderRequestsBus::Handler::BusDisconnect(); + } + + MOCK_CONST_METHOD0(GetHeightsAndMaterials, AZStd::vector()); + MOCK_CONST_METHOD0(GetHeightfieldGridSpacing, AZ::Vector2()); + MOCK_CONST_METHOD2(GetHeightfieldGridSize, void(int32_t&, int32_t&)); + MOCK_CONST_METHOD2(GetHeightfieldHeightBounds, void(float&, float&)); + MOCK_CONST_METHOD0(GetHeightfieldTransform, AZ::Transform()); + MOCK_CONST_METHOD0(GetMaterialList, AZStd::vector()); + MOCK_CONST_METHOD0(GetHeights, AZStd::vector()); + 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()); + }; + +} // namespace UnitTest diff --git a/Gems/PhysX/Code/Source/Utils.cpp b/Gems/PhysX/Code/Source/Utils.cpp index 3e77ef257a..fecea57d9c 100644 --- a/Gems/PhysX/Code/Source/Utils.cpp +++ b/Gems/PhysX/Code/Source/Utils.cpp @@ -102,7 +102,7 @@ namespace PhysX const float scaleFactor = (maxHeightBounds <= minHeightBounds) ? 1.0f : AZStd::numeric_limits::max() / halfBounds; const float heightScale{ 1.0f / scaleFactor }; - [[maybe_unused]] const uint8_t physxMaximumMaterialIndex = 0x7f; + [[maybe_unused]] constexpr uint8_t physxMaximumMaterialIndex = 0x7f; // Delete the cached heightfield object if it is there, and create a new one and save in the shape configuration heightfieldConfig.SetCachedNativeHeightfield(nullptr); diff --git a/Gems/PhysX/Code/Tests/EditorHeightfieldColliderComponentTests.cpp b/Gems/PhysX/Code/Tests/EditorHeightfieldColliderComponentTests.cpp new file mode 100644 index 0000000000..ef112d8623 --- /dev/null +++ b/Gems/PhysX/Code/Tests/EditorHeightfieldColliderComponentTests.cpp @@ -0,0 +1,215 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using ::testing::NiceMock; +using ::testing::Return; + +namespace PhysXEditorTests +{ + AZStd::vector GetSamples() + { + AZStd::vector samples{ { 3.0f, Physics::QuadMeshType::SubdivideUpperLeftToBottomRight }, + { 2.0f, Physics::QuadMeshType::SubdivideUpperLeftToBottomRight }, + { 1.5f, Physics::QuadMeshType::SubdivideUpperLeftToBottomRight }, + { 1.0f, Physics::QuadMeshType::SubdivideUpperLeftToBottomRight }, + { 3.0f, Physics::QuadMeshType::SubdivideUpperLeftToBottomRight }, + { 1.0f, Physics::QuadMeshType::SubdivideUpperLeftToBottomRight }, + { 3.0f, Physics::QuadMeshType::SubdivideUpperLeftToBottomRight }, + { 0.0f, Physics::QuadMeshType::SubdivideUpperLeftToBottomRight }, + { 3.0f, Physics::QuadMeshType::SubdivideUpperLeftToBottomRight } }; + return samples; + } + + EntityPtr SetupHeightfieldComponent() + { + // create an editor entity with a shape collider component and a box shape component + EntityPtr editorEntity = CreateInactiveEditorEntity("HeightfieldColliderComponentEditorEntity"); + editorEntity->CreateComponent(); + editorEntity->CreateComponent(LmbrCentral::EditorAxisAlignedBoxShapeComponentTypeId); + editorEntity->CreateComponent(); + AZ::ComponentApplicationBus::Broadcast( + &AZ::ComponentApplicationRequests::RegisterComponentDescriptor, + UnitTest::MockPhysXHeightfieldProviderComponent::CreateDescriptor()); + return editorEntity; + } + + void CleanupHeightfieldComponent() + { + AZ::ComponentApplicationBus::Broadcast( + &AZ::ComponentApplicationRequests::UnregisterComponentDescriptor, + UnitTest::MockPhysXHeightfieldProviderComponent::CreateDescriptor()); + } + + void SetupMockMethods(NiceMock& mockShapeRequests) + { + ON_CALL(mockShapeRequests, GetHeightfieldTransform).WillByDefault(Return(AZ::Transform::CreateTranslation({ 1, 2, 0 }))); + ON_CALL(mockShapeRequests, GetHeightfieldGridSpacing).WillByDefault(Return(AZ::Vector2(1, 1))); + ON_CALL(mockShapeRequests, GetHeightsAndMaterials).WillByDefault(Return(GetSamples())); + ON_CALL(mockShapeRequests, GetHeightfieldGridSize) + .WillByDefault( + [](int32_t& numColumns, int32_t& numRows) + { + numColumns = 3; + numRows = 3; + }); + ON_CALL(mockShapeRequests, GetHeightfieldHeightBounds) + .WillByDefault( + [](float& x, float& y) + { + x = -3.0f; + y = 3.0f; + }); + } + + EntityPtr TestCreateActiveGameEntityFromEditorEntity(AZ::Entity* editorEntity) + { + EntityPtr gameEntity = AZStd::make_unique(); + AzToolsFramework::ToolsApplicationRequestBus::Broadcast( + &AzToolsFramework::ToolsApplicationRequests::PreExportEntity, *editorEntity, *gameEntity); + gameEntity->Init(); + return gameEntity; + } + + + TEST_F(PhysXEditorFixture, EditorHeightfieldColliderComponentDependenciesSatisfiedEntityIsValid) + { + EntityPtr entity = CreateInactiveEditorEntity("HeightfieldColliderComponentEditorEntity"); + entity->CreateComponent(); + entity->CreateComponent(LmbrCentral::EditorAxisAlignedBoxShapeComponentTypeId); + entity->CreateComponent()->CreateDescriptor(); + + // the entity should be in a valid state because the shape component and + // the Terrain Physics Collider Component requirement is satisfied. + AZ::Entity::DependencySortOutcome sortOutcome = entity->EvaluateDependenciesGetDetails(); + EXPECT_TRUE(sortOutcome.IsSuccess()); + } + + TEST_F(PhysXEditorFixture, EditorHeightfieldColliderComponentDependenciesMissingEntityIsInvalid) + { + EntityPtr entity = CreateInactiveEditorEntity("HeightfieldColliderComponentEditorEntity"); + entity->CreateComponent(); + + // the entity should not be in a valid state because the heightfield collider component requires + // a shape component and the Terrain Physics Collider Component + AZ::Entity::DependencySortOutcome sortOutcome = entity->EvaluateDependenciesGetDetails(); + EXPECT_FALSE(sortOutcome.IsSuccess()); + EXPECT_TRUE(sortOutcome.GetError().m_code == AZ::Entity::DependencySortResult::MissingRequiredService); + } + + TEST_F(PhysXEditorFixture, EditorHeightfieldColliderComponentMultipleHeightfieldColliderComponentsEntityIsInvalid) + { + EntityPtr entity = CreateInactiveEditorEntity("HeightfieldColliderComponentEditorEntity"); + entity->CreateComponent(); + entity->CreateComponent(LmbrCentral::EditorAxisAlignedBoxShapeComponentTypeId); + + // adding a second heightfield collider component should make the entity invalid + entity->CreateComponent(); + + AZ::Entity::DependencySortOutcome sortOutcome = entity->EvaluateDependenciesGetDetails(); + EXPECT_FALSE(sortOutcome.IsSuccess()); + EXPECT_TRUE(sortOutcome.GetError().m_code == AZ::Entity::DependencySortResult::HasIncompatibleServices); + } + + TEST_F(PhysXEditorFixture, EditorHeightfieldColliderComponentHeightfieldColliderWithCorrectComponentsCorrectRuntimeComponents) + { + EntityPtr editorEntity = SetupHeightfieldComponent(); + NiceMock mockShapeRequests(editorEntity->GetId()); + SetupMockMethods(mockShapeRequests); + editorEntity->Activate(); + + EntityPtr gameEntity = TestCreateActiveGameEntityFromEditorEntity(editorEntity.get()); + NiceMock mockShapeRequests2(gameEntity->GetId()); + SetupMockMethods(mockShapeRequests2); + gameEntity->Activate(); + + // check that the runtime entity has the expected components + EXPECT_TRUE(gameEntity->FindComponent() != nullptr); + EXPECT_TRUE(gameEntity->FindComponent() != nullptr); + EXPECT_TRUE(gameEntity->FindComponent(LmbrCentral::AxisAlignedBoxShapeComponentTypeId) != nullptr); + + CleanupHeightfieldComponent(); + } + + TEST_F(PhysXEditorFixture, EditorHeightfieldColliderComponentHeightfieldColliderWithAABoxCorrectRuntimeGeometry) + { + EntityPtr editorEntity = SetupHeightfieldComponent(); + NiceMock mockShapeRequests(editorEntity->GetId()); + SetupMockMethods(mockShapeRequests); + editorEntity->Activate(); + + EntityPtr gameEntity = TestCreateActiveGameEntityFromEditorEntity(editorEntity.get()); + NiceMock mockShapeRequests2(gameEntity->GetId()); + SetupMockMethods(mockShapeRequests2); + gameEntity->Activate(); + + AzPhysics::SimulatedBody* staticBody = nullptr; + AzPhysics::SimulatedBodyComponentRequestsBus::EventResult( + staticBody, gameEntity->GetId(), &AzPhysics::SimulatedBodyComponentRequests::GetSimulatedBody); + const auto* pxRigidStatic = static_cast(staticBody->GetNativePointer()); + + PHYSX_SCENE_READ_LOCK(pxRigidStatic->getScene()); + + // there should be a single shape on the rigid body and it should be a heightfield + EXPECT_EQ(pxRigidStatic->getNbShapes(), 1); + + physx::PxShape* shape = nullptr; + pxRigidStatic->getShapes(&shape, 1, 0); + EXPECT_EQ(shape->getGeometryType(), physx::PxGeometryType::eHEIGHTFIELD); + + physx::PxHeightFieldGeometry heightfieldGeometry; + shape->getHeightFieldGeometry(heightfieldGeometry); + + physx::PxHeightField* heightfield = heightfieldGeometry.heightField; + + int32_t numRows{ 0 }; + int32_t numColumns{ 0 }; + Physics::HeightfieldProviderRequestsBus::Event( + gameEntity->GetId(), &Physics::HeightfieldProviderRequestsBus::Events::GetHeightfieldGridSize, numColumns, numRows); + EXPECT_EQ(numColumns, heightfield->getNbColumns()); + EXPECT_EQ(numRows, heightfield->getNbRows()); + + for (int sampleRow = 0; sampleRow < numRows; ++sampleRow) + { + for (int sampleColumn = 0; sampleColumn < numColumns; ++sampleColumn) + { + float minHeightBounds{ 0.0f }; + float maxHeightBounds{ 0.0f }; + Physics::HeightfieldProviderRequestsBus::Event( + gameEntity->GetId(), &Physics::HeightfieldProviderRequestsBus::Events::GetHeightfieldHeightBounds, minHeightBounds, + maxHeightBounds); + + AZStd::vector samples; + Physics::HeightfieldProviderRequestsBus::EventResult( + samples, gameEntity->GetId(), &Physics::HeightfieldProviderRequestsBus::Events::GetHeightsAndMaterials); + const float halfBounds{ (maxHeightBounds - minHeightBounds) / 2.0f }; + const float scaleFactor = (maxHeightBounds <= minHeightBounds) ? 1.0f : AZStd::numeric_limits::max() / halfBounds; + + physx::PxHeightFieldSample samplePhysX = heightfield->getSample(sampleRow, sampleColumn); + Physics::HeightMaterialPoint samplePhysics = samples[sampleRow * numColumns + sampleColumn]; + EXPECT_EQ(samplePhysX.height, azlossy_cast(samplePhysics.m_height * scaleFactor)); + } + } + CleanupHeightfieldComponent(); + } + +} // namespace PhysXEditorTests + diff --git a/Gems/PhysX/Code/physx_editor_tests_files.cmake b/Gems/PhysX/Code/physx_editor_tests_files.cmake index 36fb139514..953e2a167e 100644 --- a/Gems/PhysX/Code/physx_editor_tests_files.cmake +++ b/Gems/PhysX/Code/physx_editor_tests_files.cmake @@ -18,6 +18,7 @@ set(FILES Tests/PolygonPrismMeshUtilsTest.cpp Tests/PhysXColliderComponentModeTests.cpp Tests/ShapeColliderComponentTests.cpp + Tests/EditorHeightfieldColliderComponentTests.cpp Tests/TestColliderComponent.h Tests/SystemComponentTest.cpp Tests/RigidBodyComponentTests.cpp diff --git a/Gems/PhysX/Code/physx_mocks_files.cmake b/Gems/PhysX/Code/physx_mocks_files.cmake new file mode 100644 index 0000000000..49843eeb6f --- /dev/null +++ b/Gems/PhysX/Code/physx_mocks_files.cmake @@ -0,0 +1,11 @@ +# +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# + +set(FILES + Mocks/PhysX/MockPhysXHeightfieldProviderComponent.h +) diff --git a/Gems/Terrain/Code/Mocks/Terrain/MockTerrain.h b/Gems/Terrain/Code/Mocks/Terrain/MockTerrain.h index 53d93be8ea..f9607fdafb 100644 --- a/Gems/Terrain/Code/Mocks/Terrain/MockTerrain.h +++ b/Gems/Terrain/Code/Mocks/Terrain/MockTerrain.h @@ -34,7 +34,8 @@ namespace UnitTest MOCK_METHOD1(RegisterArea, void(AZ::EntityId areaId)); MOCK_METHOD1(UnregisterArea, void(AZ::EntityId areaId)); - MOCK_METHOD1(RefreshArea, void(AZ::EntityId areaId)); + MOCK_METHOD2(RefreshArea, + void(AZ::EntityId areaId, AzFramework::Terrain::TerrainDataNotifications::TerrainDataChangedMask changeMask)); }; class MockTerrainDataNotificationListener : public AzFramework::Terrain::TerrainDataNotificationBus::Handler diff --git a/Gems/Terrain/Code/Source/Components/TerrainHeightGradientListComponent.cpp b/Gems/Terrain/Code/Source/Components/TerrainHeightGradientListComponent.cpp index 01ad0bb77f..231d5abc28 100644 --- a/Gems/Terrain/Code/Source/Components/TerrainHeightGradientListComponent.cpp +++ b/Gems/Terrain/Code/Source/Components/TerrainHeightGradientListComponent.cpp @@ -119,7 +119,9 @@ namespace Terrain LmbrCentral::DependencyNotificationBus::Handler::BusDisconnect(); // Since this height data will no longer exist, notify the terrain system to refresh the area. - TerrainSystemServiceRequestBus::Broadcast(&TerrainSystemServiceRequestBus::Events::RefreshArea, GetEntityId()); + TerrainSystemServiceRequestBus::Broadcast( + &TerrainSystemServiceRequestBus::Events::RefreshArea, GetEntityId(), + AzFramework::Terrain::TerrainDataNotifications::HeightData); } bool TerrainHeightGradientListComponent::ReadInConfig(const AZ::ComponentConfig* baseConfig) @@ -176,7 +178,9 @@ namespace Terrain void TerrainHeightGradientListComponent::OnCompositionChanged() { RefreshMinMaxHeights(); - TerrainSystemServiceRequestBus::Broadcast(&TerrainSystemServiceRequestBus::Events::RefreshArea, GetEntityId()); + TerrainSystemServiceRequestBus::Broadcast( + &TerrainSystemServiceRequestBus::Events::RefreshArea, GetEntityId(), + AzFramework::Terrain::TerrainDataNotifications::HeightData); } void TerrainHeightGradientListComponent::RefreshMinMaxHeights() diff --git a/Gems/Terrain/Code/Source/Components/TerrainLayerSpawnerComponent.cpp b/Gems/Terrain/Code/Source/Components/TerrainLayerSpawnerComponent.cpp index 00ed9c004f..c3803c25e8 100644 --- a/Gems/Terrain/Code/Source/Components/TerrainLayerSpawnerComponent.cpp +++ b/Gems/Terrain/Code/Source/Components/TerrainLayerSpawnerComponent.cpp @@ -157,6 +157,12 @@ namespace Terrain void TerrainLayerSpawnerComponent::RefreshArea() { - TerrainSystemServiceRequestBus::Broadcast(&TerrainSystemServiceRequestBus::Events::RefreshArea, GetEntityId()); + using Terrain = AzFramework::Terrain::TerrainDataNotifications; + + // Notify the terrain system that the entire layer has changed, so both height and surface data can be affected. + TerrainSystemServiceRequestBus::Broadcast( + &TerrainSystemServiceRequestBus::Events::RefreshArea, GetEntityId(), + static_cast(Terrain::HeightData | Terrain::SurfaceData) + ); } } diff --git a/Gems/Terrain/Code/Source/Components/TerrainSurfaceGradientListComponent.cpp b/Gems/Terrain/Code/Source/Components/TerrainSurfaceGradientListComponent.cpp index 5b76f15d74..748221d69e 100644 --- a/Gems/Terrain/Code/Source/Components/TerrainSurfaceGradientListComponent.cpp +++ b/Gems/Terrain/Code/Source/Components/TerrainSurfaceGradientListComponent.cpp @@ -184,7 +184,9 @@ namespace Terrain void TerrainSurfaceGradientListComponent::OnCompositionChanged() { - TerrainSystemServiceRequestBus::Broadcast(&TerrainSystemServiceRequestBus::Events::RefreshArea, GetEntityId()); + TerrainSystemServiceRequestBus::Broadcast( + &TerrainSystemServiceRequestBus::Events::RefreshArea, GetEntityId(), + AzFramework::Terrain::TerrainDataNotifications::SurfaceData); } } // namespace Terrain diff --git a/Gems/Terrain/Code/Source/Components/TerrainWorldComponent.cpp b/Gems/Terrain/Code/Source/Components/TerrainWorldComponent.cpp index d8b7308ef7..bd65cf6abc 100644 --- a/Gems/Terrain/Code/Source/Components/TerrainWorldComponent.cpp +++ b/Gems/Terrain/Code/Source/Components/TerrainWorldComponent.cpp @@ -32,16 +32,22 @@ namespace Terrain AZ::EditContext* edit = serialize->GetEditContext(); if (edit) { - edit->Class( - "Terrain World Component", "Data required for the terrain system to run") + edit->Class("Terrain World Component", "Data required for the terrain system to run") ->ClassElement(AZ::Edit::ClassElements::EditorData, "") ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZStd::vector({ AZ_CRC_CE("Level") })) ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) ->Attribute(AZ::Edit::Attributes::AutoExpand, true) ->DataElement(AZ::Edit::UIHandlers::Default, &TerrainWorldConfig::m_worldMin, "World Bounds (Min)", "") + // Temporary constraint until the rest of the Terrain system is updated to support larger worlds. + ->Attribute(AZ::Edit::Attributes::Min, -2048.0f) + ->Attribute(AZ::Edit::Attributes::Max, 2048.0f) ->DataElement(AZ::Edit::UIHandlers::Default, &TerrainWorldConfig::m_worldMax, "World Bounds (Max)", "") - ->DataElement(AZ::Edit::UIHandlers::Default, &TerrainWorldConfig::m_heightQueryResolution, "Height Query Resolution (m)", "") + // Temporary constraint until the rest of the Terrain system is updated to support larger worlds. + ->Attribute(AZ::Edit::Attributes::Min, -2048.0f) + ->Attribute(AZ::Edit::Attributes::Max, 2048.0f) + ->DataElement( + AZ::Edit::UIHandlers::Default, &TerrainWorldConfig::m_heightQueryResolution, "Height Query Resolution (m)", "") ; } } diff --git a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.cpp b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.cpp index 571d109fd8..a96fa63c13 100644 --- a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.cpp +++ b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.cpp @@ -218,9 +218,11 @@ namespace Terrain const AZ::Transform transform = AZ::Transform::CreateTranslation(worldBounds.GetCenter()); - AZ::Vector2 queryResolution = AZ::Vector2(1.0f); + AZ::Vector2 queryResolution2D = AZ::Vector2(1.0f); AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult( - queryResolution, &AzFramework::Terrain::TerrainDataRequests::GetTerrainHeightQueryResolution); + queryResolution2D, &AzFramework::Terrain::TerrainDataRequests::GetTerrainHeightQueryResolution); + // Currently query resolution is multidimensional but the rendering system only supports this changing in one dimension. + float queryResolution = queryResolution2D.GetX(); // Sectors need to be rebuilt if the world bounds change in the x/y, or the sample spacing changes. m_areaData.m_rebuildSectors = m_areaData.m_rebuildSectors || @@ -228,16 +230,11 @@ namespace Terrain m_areaData.m_terrainBounds.GetMin().GetY() != worldBounds.GetMin().GetY() || m_areaData.m_terrainBounds.GetMax().GetX() != worldBounds.GetMax().GetX() || m_areaData.m_terrainBounds.GetMax().GetY() != worldBounds.GetMax().GetY() || - m_areaData.m_sampleSpacing != queryResolution.GetX(); + m_areaData.m_sampleSpacing != queryResolution; m_areaData.m_transform = transform; m_areaData.m_terrainBounds = worldBounds; - m_areaData.m_heightmapImageWidth = aznumeric_cast(worldBounds.GetXExtent() / queryResolution.GetX()); - m_areaData.m_heightmapImageHeight = aznumeric_cast(worldBounds.GetYExtent() / queryResolution.GetY()); - m_areaData.m_updateWidth = aznumeric_cast(m_dirtyRegion.GetXExtent() / queryResolution.GetX()); - m_areaData.m_updateHeight = aznumeric_cast(m_dirtyRegion.GetYExtent() / queryResolution.GetY()); - // Currently query resolution is multidimensional but the rendering system only supports this changing in one dimension. - m_areaData.m_sampleSpacing = queryResolution.GetX(); + m_areaData.m_sampleSpacing = queryResolution; m_areaData.m_heightmapUpdated = true; } @@ -778,32 +775,43 @@ namespace Terrain void TerrainFeatureProcessor::UpdateTerrainData() { - uint32_t width = m_areaData.m_updateWidth; - uint32_t height = m_areaData.m_updateHeight; - const AZ::Aabb& worldBounds = m_areaData.m_terrainBounds; + const float queryResolution = m_areaData.m_sampleSpacing; + const AZ::Aabb& worldBounds = m_areaData.m_terrainBounds; - const AZ::RHI::Size worldSize = AZ::RHI::Size(m_areaData.m_heightmapImageWidth, m_areaData.m_heightmapImageHeight, 1); + int32_t heightmapImageXStart = aznumeric_cast(AZStd::ceilf(worldBounds.GetMin().GetX() / queryResolution)); + int32_t heightmapImageXEnd = aznumeric_cast(AZStd::floorf(worldBounds.GetMax().GetX() / queryResolution)) + 1; + int32_t heightmapImageYStart = aznumeric_cast(AZStd::ceilf(worldBounds.GetMin().GetY() / queryResolution)); + int32_t heightmapImageYEnd = aznumeric_cast(AZStd::floorf(worldBounds.GetMax().GetY() / queryResolution)) + 1; + uint32_t heightmapImageWidth = heightmapImageXEnd - heightmapImageXStart; + uint32_t heightmapImageHeight = heightmapImageYEnd - heightmapImageYStart; - if (!m_areaData.m_heightmapImage || m_areaData.m_heightmapImage->GetDescriptor().m_size != worldSize) + const AZ::RHI::Size heightmapSize = AZ::RHI::Size(heightmapImageWidth, heightmapImageHeight, 1); + + if (!m_areaData.m_heightmapImage || m_areaData.m_heightmapImage->GetDescriptor().m_size != heightmapSize) { - // World size changed, so the whole world needs updating. - width = worldSize.m_width; - height = worldSize.m_height; - m_dirtyRegion = worldBounds; - const AZ::Data::Instance imagePool = AZ::RPI::ImageSystemInterface::Get()->GetSystemAttachmentPool(); AZ::RHI::ImageDescriptor imageDescriptor = AZ::RHI::ImageDescriptor::Create2D( - AZ::RHI::ImageBindFlags::ShaderRead, width, height, AZ::RHI::Format::R16_UNORM + AZ::RHI::ImageBindFlags::ShaderRead, heightmapSize.m_width, heightmapSize.m_height, AZ::RHI::Format::R16_UNORM ); const AZ::Name TerrainHeightmapName = AZ::Name(TerrainHeightmapChars); m_areaData.m_heightmapImage = AZ::RPI::AttachmentImage::Create(*imagePool.get(), imageDescriptor, TerrainHeightmapName, nullptr, nullptr); AZ_Error(TerrainFPName, m_areaData.m_heightmapImage, "Failed to initialize the heightmap image."); + + // World size changed, so the whole height map needs updating. + m_dirtyRegion = worldBounds; } + + int32_t xStart = aznumeric_cast(AZStd::ceilf(m_dirtyRegion.GetMin().GetX() / queryResolution)); + int32_t xEnd = aznumeric_cast(AZStd::floorf(m_dirtyRegion.GetMax().GetX() / queryResolution)) + 1; + int32_t yStart = aznumeric_cast(AZStd::ceilf(m_dirtyRegion.GetMin().GetY() / queryResolution)); + int32_t yEnd = aznumeric_cast(AZStd::floorf(m_dirtyRegion.GetMax().GetY() / queryResolution)) + 1; + uint32_t updateWidth = xEnd - xStart; + uint32_t updateHeight = yEnd - yStart; AZStd::vector pixels; - pixels.reserve(width * height); + pixels.reserve(updateWidth * updateHeight); { // Block other threads from accessing the surface data bus while we are in GetHeightFromFloats (which may call into the SurfaceData bus). @@ -815,18 +823,17 @@ namespace Terrain auto& surfaceDataContext = SurfaceData::SurfaceDataSystemRequestBus::GetOrCreateContext(false); typename SurfaceData::SurfaceDataSystemRequestBus::Context::DispatchLockGuard scopeLock(surfaceDataContext.m_contextMutex); - for (uint32_t y = 0; y < height; y++) + for (int32_t y = yStart; y < yEnd; y++) { - for (uint32_t x = 0; x < width; x++) + for (int32_t x = xStart; x < xEnd; x++) { bool terrainExists = true; float terrainHeight = 0.0f; + float xPos = x * queryResolution; + float yPos = y * queryResolution; AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult( terrainHeight, &AzFramework::Terrain::TerrainDataRequests::GetHeightFromFloats, - (x * queryResolution) + m_dirtyRegion.GetMin().GetX(), - (y * queryResolution) + m_dirtyRegion.GetMin().GetY(), - AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT, - &terrainExists); + xPos, yPos, AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT, &terrainExists); const float clampedHeight = AZ::GetClamp((terrainHeight - worldBounds.GetMin().GetZ()) / worldBounds.GetExtents().GetZ(), 0.0f, 1.0f); const float expandedHeight = AZStd::roundf(clampedHeight * AZStd::numeric_limits::max()); @@ -839,16 +846,18 @@ namespace Terrain if (m_areaData.m_heightmapImage) { - const float left = (m_dirtyRegion.GetMin().GetX() - worldBounds.GetMin().GetX()) / queryResolution; - const float top = (m_dirtyRegion.GetMin().GetY() - worldBounds.GetMin().GetY()) / queryResolution; + constexpr uint32_t BytesPerPixel = sizeof(uint16_t); + const float left = xStart - (worldBounds.GetMin().GetX() / queryResolution); + const float top = yStart - (worldBounds.GetMin().GetY() / queryResolution); + AZ::RHI::ImageUpdateRequest imageUpdateRequest; imageUpdateRequest.m_imageSubresourcePixelOffset.m_left = aznumeric_cast(left); imageUpdateRequest.m_imageSubresourcePixelOffset.m_top = aznumeric_cast(top); - imageUpdateRequest.m_sourceSubresourceLayout.m_bytesPerRow = width * sizeof(uint16_t); - imageUpdateRequest.m_sourceSubresourceLayout.m_bytesPerImage = width * height * sizeof(uint16_t); - imageUpdateRequest.m_sourceSubresourceLayout.m_rowCount = height; - imageUpdateRequest.m_sourceSubresourceLayout.m_size.m_width = width; - imageUpdateRequest.m_sourceSubresourceLayout.m_size.m_height = height; + imageUpdateRequest.m_sourceSubresourceLayout.m_bytesPerRow = updateWidth * BytesPerPixel; + imageUpdateRequest.m_sourceSubresourceLayout.m_bytesPerImage = updateWidth * updateHeight * BytesPerPixel; + imageUpdateRequest.m_sourceSubresourceLayout.m_rowCount = updateHeight; + imageUpdateRequest.m_sourceSubresourceLayout.m_size.m_width = updateWidth; + imageUpdateRequest.m_sourceSubresourceLayout.m_size.m_height = updateHeight; imageUpdateRequest.m_sourceSubresourceLayout.m_size.m_depth = 1; imageUpdateRequest.m_sourceData = pixels.data(); imageUpdateRequest.m_image = m_areaData.m_heightmapImage->GetRHIImage(); @@ -1106,6 +1115,12 @@ namespace Terrain m_areaData.m_heightmapUpdated = false; m_areaData.m_macroMaterialsUpdated = false; + AZStd::array uvStep = + { + 1.0f / aznumeric_cast(m_areaData.m_terrainBounds.GetXExtent() / m_areaData.m_sampleSpacing), + 1.0f / aznumeric_cast(m_areaData.m_terrainBounds.GetYExtent() / m_areaData.m_sampleSpacing), + }; + for (SectorData& sectorData : m_sectorData) { ShaderTerrainData terrainDataForSrg; @@ -1123,11 +1138,7 @@ namespace Terrain ((yPatch + GridMeters) - terrainBounds.GetMin().GetY()) / terrainBounds.GetYExtent() }; - terrainDataForSrg.m_uvStep = - { - 1.0f / m_areaData.m_heightmapImageWidth, - 1.0f / m_areaData.m_heightmapImageHeight, - }; + terrainDataForSrg.m_uvStep = uvStep; AZ::Transform transform = m_areaData.m_transform; transform.SetTranslation(xPatch, yPatch, m_areaData.m_transform.GetTranslation().GetZ()); diff --git a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.h b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.h index 962ffe13bf..6c357db996 100644 --- a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.h +++ b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.h @@ -303,10 +303,6 @@ namespace Terrain AZ::Transform m_transform{ AZ::Transform::CreateIdentity() }; AZ::Aabb m_terrainBounds{ AZ::Aabb::CreateNull() }; AZ::Data::Instance m_heightmapImage; - uint32_t m_heightmapImageWidth{ 0 }; - uint32_t m_heightmapImageHeight{ 0 }; - uint32_t m_updateWidth{ 0 }; - uint32_t m_updateHeight{ 0 }; float m_sampleSpacing{ 0.0f }; bool m_heightmapUpdated{ true }; bool m_macroMaterialsUpdated{ true }; diff --git a/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.cpp b/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.cpp index fa1d483a5d..8d39340b06 100644 --- a/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.cpp +++ b/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.cpp @@ -76,6 +76,7 @@ void TerrainSystem::Activate() m_dirtyRegion = AZ::Aabb::CreateNull(); m_terrainHeightDirty = true; m_terrainSettingsDirty = true; + m_terrainSurfacesDirty = true; m_requestedSettings.m_systemActive = true; { @@ -115,6 +116,7 @@ void TerrainSystem::Deactivate() m_dirtyRegion = AZ::Aabb::CreateNull(); m_terrainHeightDirty = true; m_terrainSettingsDirty = true; + m_terrainSurfacesDirty = true; m_requestedSettings.m_systemActive = false; AzFramework::Terrain::TerrainDataNotificationBus::Broadcast( @@ -549,6 +551,7 @@ void TerrainSystem::RegisterArea(AZ::EntityId areaId) m_registeredAreas[areaId] = aabb; m_dirtyRegion.AddAabb(aabb); m_terrainHeightDirty = true; + m_terrainSurfacesDirty = true; } void TerrainSystem::UnregisterArea(AZ::EntityId areaId) @@ -567,14 +570,17 @@ void TerrainSystem::UnregisterArea(AZ::EntityId areaId) { m_dirtyRegion.AddAabb(aabb); m_terrainHeightDirty = true; + m_terrainSurfacesDirty = true; return true; } return false; }); } -void TerrainSystem::RefreshArea(AZ::EntityId areaId) +void TerrainSystem::RefreshArea(AZ::EntityId areaId, AzFramework::Terrain::TerrainDataNotifications::TerrainDataChangedMask changeMask) { + using Terrain = AzFramework::Terrain::TerrainDataNotifications; + AZStd::unique_lock lock(m_areaMutex); auto areaAabb = m_registeredAreas.find(areaId); @@ -588,11 +594,18 @@ void TerrainSystem::RefreshArea(AZ::EntityId areaId) expandedAabb.AddAabb(newAabb); m_dirtyRegion.AddAabb(expandedAabb); - m_terrainHeightDirty = true; + + // Keep track of which types of data have changed so that we can send out the appropriate notifications later. + + m_terrainHeightDirty = m_terrainHeightDirty || ((changeMask & Terrain::HeightData) == Terrain::HeightData); + + m_terrainSurfacesDirty = m_terrainSurfacesDirty || ((changeMask & Terrain::SurfaceData) == Terrain::SurfaceData); } void TerrainSystem::OnTick(float /*deltaTime*/, AZ::ScriptTimePoint /*time*/) { + using Terrain = AzFramework::Terrain::TerrainDataNotifications; + bool terrainSettingsChanged = false; if (m_terrainSettingsDirty) @@ -607,6 +620,7 @@ void TerrainSystem::OnTick(float /*deltaTime*/, AZ::ScriptTimePoint /*time*/) m_dirtyRegion = m_currentSettings.m_worldBounds; m_dirtyRegion.AddAabb(m_requestedSettings.m_worldBounds); m_terrainHeightDirty = true; + m_terrainSurfacesDirty = true; m_currentSettings.m_worldBounds = m_requestedSettings.m_worldBounds; } @@ -614,12 +628,13 @@ void TerrainSystem::OnTick(float /*deltaTime*/, AZ::ScriptTimePoint /*time*/) { m_dirtyRegion = AZ::Aabb::CreateNull(); m_terrainHeightDirty = true; + m_terrainSurfacesDirty = true; } m_currentSettings = m_requestedSettings; } - if (terrainSettingsChanged || m_terrainHeightDirty) + if (terrainSettingsChanged || m_terrainHeightDirty || m_terrainSurfacesDirty) { // Block other threads from accessing the surface data bus while we are in GetValue (which may call into the SurfaceData bus). // We lock our surface data mutex *before* checking / setting "isRequestInProgress" so that we prevent race conditions @@ -629,24 +644,27 @@ void TerrainSystem::OnTick(float /*deltaTime*/, AZ::ScriptTimePoint /*time*/) auto& surfaceDataContext = SurfaceData::SurfaceDataSystemRequestBus::GetOrCreateContext(false); typename SurfaceData::SurfaceDataSystemRequestBus::Context::DispatchLockGuard scopeLock(surfaceDataContext.m_contextMutex); - AzFramework::Terrain::TerrainDataNotifications::TerrainDataChangedMask changeMask = - AzFramework::Terrain::TerrainDataNotifications::TerrainDataChangedMask::None; + Terrain::TerrainDataChangedMask changeMask = Terrain::TerrainDataChangedMask::None; if (terrainSettingsChanged) { - changeMask = static_cast( - changeMask | AzFramework::Terrain::TerrainDataNotifications::TerrainDataChangedMask::Settings); + changeMask = static_cast(changeMask | Terrain::TerrainDataChangedMask::Settings); } if (m_terrainHeightDirty) { - changeMask = static_cast( - changeMask | AzFramework::Terrain::TerrainDataNotifications::TerrainDataChangedMask::HeightData); + changeMask = static_cast(changeMask | Terrain::TerrainDataChangedMask::HeightData); + } + + if (m_terrainSurfacesDirty) + { + changeMask = static_cast(changeMask | Terrain::TerrainDataChangedMask::SurfaceData); } // Make sure to set these *before* calling OnTerrainDataChanged, since it's possible that subsystems reacting to that call will // cause the data to become dirty again. AZ::Aabb dirtyRegion = m_dirtyRegion; m_terrainHeightDirty = false; + m_terrainSurfacesDirty = false; m_dirtyRegion = AZ::Aabb::CreateNull(); AzFramework::Terrain::TerrainDataNotificationBus::Broadcast( diff --git a/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.h b/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.h index 956424f048..022cd218cc 100644 --- a/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.h +++ b/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystem.h @@ -47,7 +47,8 @@ namespace Terrain void RegisterArea(AZ::EntityId areaId) override; void UnregisterArea(AZ::EntityId areaId) override; - void RefreshArea(AZ::EntityId areaId) override; + void RefreshArea( + AZ::EntityId areaId, AzFramework::Terrain::TerrainDataNotifications::TerrainDataChangedMask changeMask) override; /////////////////////////////////////////// // TerrainDataRequestBus::Handler Impl @@ -164,6 +165,7 @@ namespace Terrain bool m_terrainSettingsDirty = true; bool m_terrainHeightDirty = false; + bool m_terrainSurfacesDirty = false; AZ::Aabb m_dirtyRegion; mutable AZStd::shared_mutex m_areaMutex; diff --git a/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystemBus.h b/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystemBus.h index cda6d65a1e..013d82d94d 100644 --- a/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystemBus.h +++ b/Gems/Terrain/Code/Source/TerrainSystem/TerrainSystemBus.h @@ -44,7 +44,7 @@ namespace Terrain // register an area to override terrain virtual void RegisterArea(AZ::EntityId areaId) = 0; virtual void UnregisterArea(AZ::EntityId areaId) = 0; - virtual void RefreshArea(AZ::EntityId areaId) = 0; + virtual void RefreshArea(AZ::EntityId areaId, AzFramework::Terrain::TerrainDataNotifications::TerrainDataChangedMask changeMask) = 0; }; using TerrainSystemServiceRequestBus = AZ::EBus; diff --git a/Gems/Terrain/Code/Tests/LayerSpawnerTests.cpp b/Gems/Terrain/Code/Tests/LayerSpawnerTests.cpp index 0ca5e7d99a..3778ba860f 100644 --- a/Gems/Terrain/Code/Tests/LayerSpawnerTests.cpp +++ b/Gems/Terrain/Code/Tests/LayerSpawnerTests.cpp @@ -190,7 +190,7 @@ TEST_F(LayerSpawnerComponentTest, LayerSpawnerTransformChangedUpdatesTerrainSyst CreateMockTerrainSystem(); // The TransformChanged call should refresh the area. - EXPECT_CALL(*m_terrainSystem, RefreshArea(_)).Times(1); + EXPECT_CALL(*m_terrainSystem, RefreshArea(_, _)).Times(1); AddLayerSpawnerAndShapeComponentToEntity(); @@ -211,7 +211,7 @@ TEST_F(LayerSpawnerComponentTest, LayerSpawnerShapeChangedUpdatesTerrainSystem) CreateMockTerrainSystem(); // The ShapeChanged call should refresh the area. - EXPECT_CALL(*m_terrainSystem, RefreshArea(_)).Times(1); + EXPECT_CALL(*m_terrainSystem, RefreshArea(_, _)).Times(1); AddLayerSpawnerAndShapeComponentToEntity(); diff --git a/Gems/Terrain/Code/Tests/TerrainHeightGradientListTests.cpp b/Gems/Terrain/Code/Tests/TerrainHeightGradientListTests.cpp index c314e4e968..ec500d6ada 100644 --- a/Gems/Terrain/Code/Tests/TerrainHeightGradientListTests.cpp +++ b/Gems/Terrain/Code/Tests/TerrainHeightGradientListTests.cpp @@ -93,7 +93,7 @@ TEST_F(TerrainHeightGradientListComponentTest, TerrainHeightGradientRefreshesTer // As the TerrainHeightGradientListComponent subscribes to the dependency monitor, RefreshArea will be called twice: // once due to OnCompositionChanged being picked up by the the dependency monitor and resending the notification, // and once when the HeightGradientListComponent gets the OnCompositionChanged directly through the DependencyNotificationBus. - EXPECT_CALL(terrainSystem, RefreshArea(_)).Times(2); + EXPECT_CALL(terrainSystem, RefreshArea(_, _)).Times(2); LmbrCentral::DependencyNotificationBus::Event(m_entity->GetId(), &LmbrCentral::DependencyNotificationBus::Events::OnCompositionChanged); diff --git a/Registry/CMakeLists.txt b/Registry/CMakeLists.txt index 773adac07f..df309ba657 100644 --- a/Registry/CMakeLists.txt +++ b/Registry/CMakeLists.txt @@ -12,6 +12,11 @@ endif() ly_install_directory(DIRECTORIES .) -ly_install_directory(DIRECTORIES ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/$/Registry - DESTINATION ${runtime_output_directory} -) +foreach(conf IN LISTS CMAKE_CONFIGURATION_TYPES) + string(TOUPPER ${conf} UCONF) + string(REPLACE "$" "${conf}" output ${runtime_output_directory}) + ly_install_directory(DIRECTORIES ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${conf}/Registry + DESTINATION ${output} + COMPONENT ${LY_INSTALL_PERMUTATION_COMPONENT}_${UCONF} + ) +endforeach() diff --git a/Templates/CMakeLists.txt b/Templates/CMakeLists.txt index 84a708989a..9735907a6a 100644 --- a/Templates/CMakeLists.txt +++ b/Templates/CMakeLists.txt @@ -9,8 +9,8 @@ ly_install_directory( DIRECTORIES AssetGem - CustomTool - PythonGem + CppToolGem + PythonToolGem DefaultGem DefaultProject MinimalProject diff --git a/Templates/CustomTool/Template/CMakeLists.txt b/Templates/CppToolGem/Template/CMakeLists.txt similarity index 100% rename from Templates/CustomTool/Template/CMakeLists.txt rename to Templates/CppToolGem/Template/CMakeLists.txt diff --git a/Templates/CustomTool/Template/Code/${NameLower}_editor_files.cmake b/Templates/CppToolGem/Template/Code/${NameLower}_editor_files.cmake similarity index 100% rename from Templates/CustomTool/Template/Code/${NameLower}_editor_files.cmake rename to Templates/CppToolGem/Template/Code/${NameLower}_editor_files.cmake diff --git a/Templates/CustomTool/Template/Code/${NameLower}_editor_shared_files.cmake b/Templates/CppToolGem/Template/Code/${NameLower}_editor_shared_files.cmake similarity index 100% rename from Templates/CustomTool/Template/Code/${NameLower}_editor_shared_files.cmake rename to Templates/CppToolGem/Template/Code/${NameLower}_editor_shared_files.cmake diff --git a/Templates/CustomTool/Template/Code/${NameLower}_editor_tests_files.cmake b/Templates/CppToolGem/Template/Code/${NameLower}_editor_tests_files.cmake similarity index 100% rename from Templates/CustomTool/Template/Code/${NameLower}_editor_tests_files.cmake rename to Templates/CppToolGem/Template/Code/${NameLower}_editor_tests_files.cmake diff --git a/Templates/CustomTool/Template/Code/${NameLower}_files.cmake b/Templates/CppToolGem/Template/Code/${NameLower}_files.cmake similarity index 100% rename from Templates/CustomTool/Template/Code/${NameLower}_files.cmake rename to Templates/CppToolGem/Template/Code/${NameLower}_files.cmake diff --git a/Templates/CustomTool/Template/Code/${NameLower}_shared_files.cmake b/Templates/CppToolGem/Template/Code/${NameLower}_shared_files.cmake similarity index 100% rename from Templates/CustomTool/Template/Code/${NameLower}_shared_files.cmake rename to Templates/CppToolGem/Template/Code/${NameLower}_shared_files.cmake diff --git a/Templates/CustomTool/Template/Code/${NameLower}_tests_files.cmake b/Templates/CppToolGem/Template/Code/${NameLower}_tests_files.cmake similarity index 100% rename from Templates/CustomTool/Template/Code/${NameLower}_tests_files.cmake rename to Templates/CppToolGem/Template/Code/${NameLower}_tests_files.cmake diff --git a/Templates/CustomTool/Template/Code/CMakeLists.txt b/Templates/CppToolGem/Template/Code/CMakeLists.txt similarity index 100% rename from Templates/CustomTool/Template/Code/CMakeLists.txt rename to Templates/CppToolGem/Template/Code/CMakeLists.txt diff --git a/Templates/CustomTool/Template/Code/Include/${Name}/${Name}Bus.h b/Templates/CppToolGem/Template/Code/Include/${Name}/${Name}Bus.h similarity index 100% rename from Templates/CustomTool/Template/Code/Include/${Name}/${Name}Bus.h rename to Templates/CppToolGem/Template/Code/Include/${Name}/${Name}Bus.h diff --git a/Templates/CustomTool/Template/Code/Platform/Android/${NameLower}_android_files.cmake b/Templates/CppToolGem/Template/Code/Platform/Android/${NameLower}_android_files.cmake similarity index 100% rename from Templates/CustomTool/Template/Code/Platform/Android/${NameLower}_android_files.cmake rename to Templates/CppToolGem/Template/Code/Platform/Android/${NameLower}_android_files.cmake diff --git a/Templates/CustomTool/Template/Code/Platform/Android/${NameLower}_shared_android_files.cmake b/Templates/CppToolGem/Template/Code/Platform/Android/${NameLower}_shared_android_files.cmake similarity index 100% rename from Templates/CustomTool/Template/Code/Platform/Android/${NameLower}_shared_android_files.cmake rename to Templates/CppToolGem/Template/Code/Platform/Android/${NameLower}_shared_android_files.cmake diff --git a/Templates/CustomTool/Template/Code/Platform/Android/PAL_android.cmake b/Templates/CppToolGem/Template/Code/Platform/Android/PAL_android.cmake similarity index 100% rename from Templates/CustomTool/Template/Code/Platform/Android/PAL_android.cmake rename to Templates/CppToolGem/Template/Code/Platform/Android/PAL_android.cmake diff --git a/Templates/CustomTool/Template/Code/Platform/Linux/${NameLower}_linux_files.cmake b/Templates/CppToolGem/Template/Code/Platform/Linux/${NameLower}_linux_files.cmake similarity index 100% rename from Templates/CustomTool/Template/Code/Platform/Linux/${NameLower}_linux_files.cmake rename to Templates/CppToolGem/Template/Code/Platform/Linux/${NameLower}_linux_files.cmake diff --git a/Templates/CustomTool/Template/Code/Platform/Linux/${NameLower}_shared_linux_files.cmake b/Templates/CppToolGem/Template/Code/Platform/Linux/${NameLower}_shared_linux_files.cmake similarity index 100% rename from Templates/CustomTool/Template/Code/Platform/Linux/${NameLower}_shared_linux_files.cmake rename to Templates/CppToolGem/Template/Code/Platform/Linux/${NameLower}_shared_linux_files.cmake diff --git a/Templates/CustomTool/Template/Code/Platform/Linux/PAL_linux.cmake b/Templates/CppToolGem/Template/Code/Platform/Linux/PAL_linux.cmake similarity index 100% rename from Templates/CustomTool/Template/Code/Platform/Linux/PAL_linux.cmake rename to Templates/CppToolGem/Template/Code/Platform/Linux/PAL_linux.cmake diff --git a/Templates/CustomTool/Template/Code/Platform/Mac/${NameLower}_mac_files.cmake b/Templates/CppToolGem/Template/Code/Platform/Mac/${NameLower}_mac_files.cmake similarity index 100% rename from Templates/CustomTool/Template/Code/Platform/Mac/${NameLower}_mac_files.cmake rename to Templates/CppToolGem/Template/Code/Platform/Mac/${NameLower}_mac_files.cmake diff --git a/Templates/CustomTool/Template/Code/Platform/Mac/${NameLower}_shared_mac_files.cmake b/Templates/CppToolGem/Template/Code/Platform/Mac/${NameLower}_shared_mac_files.cmake similarity index 100% rename from Templates/CustomTool/Template/Code/Platform/Mac/${NameLower}_shared_mac_files.cmake rename to Templates/CppToolGem/Template/Code/Platform/Mac/${NameLower}_shared_mac_files.cmake diff --git a/Templates/CustomTool/Template/Code/Platform/Mac/PAL_mac.cmake b/Templates/CppToolGem/Template/Code/Platform/Mac/PAL_mac.cmake similarity index 100% rename from Templates/CustomTool/Template/Code/Platform/Mac/PAL_mac.cmake rename to Templates/CppToolGem/Template/Code/Platform/Mac/PAL_mac.cmake diff --git a/Templates/CustomTool/Template/Code/Platform/Windows/${NameLower}_shared_windows_files.cmake b/Templates/CppToolGem/Template/Code/Platform/Windows/${NameLower}_shared_windows_files.cmake similarity index 100% rename from Templates/CustomTool/Template/Code/Platform/Windows/${NameLower}_shared_windows_files.cmake rename to Templates/CppToolGem/Template/Code/Platform/Windows/${NameLower}_shared_windows_files.cmake diff --git a/Templates/CustomTool/Template/Code/Platform/Windows/${NameLower}_windows_files.cmake b/Templates/CppToolGem/Template/Code/Platform/Windows/${NameLower}_windows_files.cmake similarity index 100% rename from Templates/CustomTool/Template/Code/Platform/Windows/${NameLower}_windows_files.cmake rename to Templates/CppToolGem/Template/Code/Platform/Windows/${NameLower}_windows_files.cmake diff --git a/Templates/CustomTool/Template/Code/Platform/Windows/PAL_windows.cmake b/Templates/CppToolGem/Template/Code/Platform/Windows/PAL_windows.cmake similarity index 100% rename from Templates/CustomTool/Template/Code/Platform/Windows/PAL_windows.cmake rename to Templates/CppToolGem/Template/Code/Platform/Windows/PAL_windows.cmake diff --git a/Templates/CustomTool/Template/Code/Platform/iOS/${NameLower}_ios_files.cmake b/Templates/CppToolGem/Template/Code/Platform/iOS/${NameLower}_ios_files.cmake similarity index 100% rename from Templates/CustomTool/Template/Code/Platform/iOS/${NameLower}_ios_files.cmake rename to Templates/CppToolGem/Template/Code/Platform/iOS/${NameLower}_ios_files.cmake diff --git a/Templates/CustomTool/Template/Code/Platform/iOS/${NameLower}_shared_ios_files.cmake b/Templates/CppToolGem/Template/Code/Platform/iOS/${NameLower}_shared_ios_files.cmake similarity index 100% rename from Templates/CustomTool/Template/Code/Platform/iOS/${NameLower}_shared_ios_files.cmake rename to Templates/CppToolGem/Template/Code/Platform/iOS/${NameLower}_shared_ios_files.cmake diff --git a/Templates/CustomTool/Template/Code/Platform/iOS/PAL_ios.cmake b/Templates/CppToolGem/Template/Code/Platform/iOS/PAL_ios.cmake similarity index 100% rename from Templates/CustomTool/Template/Code/Platform/iOS/PAL_ios.cmake rename to Templates/CppToolGem/Template/Code/Platform/iOS/PAL_ios.cmake diff --git a/Templates/CustomTool/Template/Code/Source/${Name}.qrc b/Templates/CppToolGem/Template/Code/Source/${Name}.qrc similarity index 100% rename from Templates/CustomTool/Template/Code/Source/${Name}.qrc rename to Templates/CppToolGem/Template/Code/Source/${Name}.qrc diff --git a/Templates/CustomTool/Template/Code/Source/${Name}EditorModule.cpp b/Templates/CppToolGem/Template/Code/Source/${Name}EditorModule.cpp similarity index 100% rename from Templates/CustomTool/Template/Code/Source/${Name}EditorModule.cpp rename to Templates/CppToolGem/Template/Code/Source/${Name}EditorModule.cpp diff --git a/Templates/CustomTool/Template/Code/Source/${Name}EditorSystemComponent.cpp b/Templates/CppToolGem/Template/Code/Source/${Name}EditorSystemComponent.cpp similarity index 100% rename from Templates/CustomTool/Template/Code/Source/${Name}EditorSystemComponent.cpp rename to Templates/CppToolGem/Template/Code/Source/${Name}EditorSystemComponent.cpp diff --git a/Templates/CustomTool/Template/Code/Source/${Name}EditorSystemComponent.h b/Templates/CppToolGem/Template/Code/Source/${Name}EditorSystemComponent.h similarity index 100% rename from Templates/CustomTool/Template/Code/Source/${Name}EditorSystemComponent.h rename to Templates/CppToolGem/Template/Code/Source/${Name}EditorSystemComponent.h diff --git a/Templates/CustomTool/Template/Code/Source/${Name}Module.cpp b/Templates/CppToolGem/Template/Code/Source/${Name}Module.cpp similarity index 100% rename from Templates/CustomTool/Template/Code/Source/${Name}Module.cpp rename to Templates/CppToolGem/Template/Code/Source/${Name}Module.cpp diff --git a/Templates/CustomTool/Template/Code/Source/${Name}ModuleInterface.h b/Templates/CppToolGem/Template/Code/Source/${Name}ModuleInterface.h similarity index 100% rename from Templates/CustomTool/Template/Code/Source/${Name}ModuleInterface.h rename to Templates/CppToolGem/Template/Code/Source/${Name}ModuleInterface.h diff --git a/Templates/CustomTool/Template/Code/Source/${Name}SystemComponent.cpp b/Templates/CppToolGem/Template/Code/Source/${Name}SystemComponent.cpp similarity index 100% rename from Templates/CustomTool/Template/Code/Source/${Name}SystemComponent.cpp rename to Templates/CppToolGem/Template/Code/Source/${Name}SystemComponent.cpp diff --git a/Templates/CustomTool/Template/Code/Source/${Name}SystemComponent.h b/Templates/CppToolGem/Template/Code/Source/${Name}SystemComponent.h similarity index 100% rename from Templates/CustomTool/Template/Code/Source/${Name}SystemComponent.h rename to Templates/CppToolGem/Template/Code/Source/${Name}SystemComponent.h diff --git a/Templates/CustomTool/Template/Code/Source/${Name}Widget.cpp b/Templates/CppToolGem/Template/Code/Source/${Name}Widget.cpp similarity index 100% rename from Templates/CustomTool/Template/Code/Source/${Name}Widget.cpp rename to Templates/CppToolGem/Template/Code/Source/${Name}Widget.cpp diff --git a/Templates/CustomTool/Template/Code/Source/${Name}Widget.h b/Templates/CppToolGem/Template/Code/Source/${Name}Widget.h similarity index 100% rename from Templates/CustomTool/Template/Code/Source/${Name}Widget.h rename to Templates/CppToolGem/Template/Code/Source/${Name}Widget.h diff --git a/Templates/CustomTool/Template/Code/Source/toolbar_icon.svg b/Templates/CppToolGem/Template/Code/Source/toolbar_icon.svg similarity index 100% rename from Templates/CustomTool/Template/Code/Source/toolbar_icon.svg rename to Templates/CppToolGem/Template/Code/Source/toolbar_icon.svg diff --git a/Templates/CustomTool/Template/Code/Tests/${Name}EditorTest.cpp b/Templates/CppToolGem/Template/Code/Tests/${Name}EditorTest.cpp similarity index 100% rename from Templates/CustomTool/Template/Code/Tests/${Name}EditorTest.cpp rename to Templates/CppToolGem/Template/Code/Tests/${Name}EditorTest.cpp diff --git a/Templates/CustomTool/Template/Code/Tests/${Name}Test.cpp b/Templates/CppToolGem/Template/Code/Tests/${Name}Test.cpp similarity index 100% rename from Templates/CustomTool/Template/Code/Tests/${Name}Test.cpp rename to Templates/CppToolGem/Template/Code/Tests/${Name}Test.cpp diff --git a/Templates/CustomTool/Template/Platform/Android/android_gem.cmake b/Templates/CppToolGem/Template/Platform/Android/android_gem.cmake similarity index 100% rename from Templates/CustomTool/Template/Platform/Android/android_gem.cmake rename to Templates/CppToolGem/Template/Platform/Android/android_gem.cmake diff --git a/Templates/CustomTool/Template/Platform/Android/android_gem.json b/Templates/CppToolGem/Template/Platform/Android/android_gem.json similarity index 100% rename from Templates/CustomTool/Template/Platform/Android/android_gem.json rename to Templates/CppToolGem/Template/Platform/Android/android_gem.json diff --git a/Templates/CustomTool/Template/Platform/Linux/linux_gem.cmake b/Templates/CppToolGem/Template/Platform/Linux/linux_gem.cmake similarity index 100% rename from Templates/CustomTool/Template/Platform/Linux/linux_gem.cmake rename to Templates/CppToolGem/Template/Platform/Linux/linux_gem.cmake diff --git a/Templates/CustomTool/Template/Platform/Linux/linux_gem.json b/Templates/CppToolGem/Template/Platform/Linux/linux_gem.json similarity index 100% rename from Templates/CustomTool/Template/Platform/Linux/linux_gem.json rename to Templates/CppToolGem/Template/Platform/Linux/linux_gem.json diff --git a/Templates/CustomTool/Template/Platform/Mac/mac_gem.cmake b/Templates/CppToolGem/Template/Platform/Mac/mac_gem.cmake similarity index 100% rename from Templates/CustomTool/Template/Platform/Mac/mac_gem.cmake rename to Templates/CppToolGem/Template/Platform/Mac/mac_gem.cmake diff --git a/Templates/CustomTool/Template/Platform/Mac/mac_gem.json b/Templates/CppToolGem/Template/Platform/Mac/mac_gem.json similarity index 100% rename from Templates/CustomTool/Template/Platform/Mac/mac_gem.json rename to Templates/CppToolGem/Template/Platform/Mac/mac_gem.json diff --git a/Templates/CustomTool/Template/Platform/Windows/windows_gem.cmake b/Templates/CppToolGem/Template/Platform/Windows/windows_gem.cmake similarity index 100% rename from Templates/CustomTool/Template/Platform/Windows/windows_gem.cmake rename to Templates/CppToolGem/Template/Platform/Windows/windows_gem.cmake diff --git a/Templates/CustomTool/Template/Platform/Windows/windows_gem.json b/Templates/CppToolGem/Template/Platform/Windows/windows_gem.json similarity index 100% rename from Templates/CustomTool/Template/Platform/Windows/windows_gem.json rename to Templates/CppToolGem/Template/Platform/Windows/windows_gem.json diff --git a/Templates/CustomTool/Template/Platform/iOS/ios_gem.cmake b/Templates/CppToolGem/Template/Platform/iOS/ios_gem.cmake similarity index 100% rename from Templates/CustomTool/Template/Platform/iOS/ios_gem.cmake rename to Templates/CppToolGem/Template/Platform/iOS/ios_gem.cmake diff --git a/Templates/CustomTool/Template/Platform/iOS/ios_gem.json b/Templates/CppToolGem/Template/Platform/iOS/ios_gem.json similarity index 100% rename from Templates/CustomTool/Template/Platform/iOS/ios_gem.json rename to Templates/CppToolGem/Template/Platform/iOS/ios_gem.json diff --git a/Templates/CustomTool/Template/gem.json b/Templates/CppToolGem/Template/gem.json similarity index 100% rename from Templates/CustomTool/Template/gem.json rename to Templates/CppToolGem/Template/gem.json diff --git a/Templates/CustomTool/Template/preview.png b/Templates/CppToolGem/Template/preview.png similarity index 100% rename from Templates/CustomTool/Template/preview.png rename to Templates/CppToolGem/Template/preview.png diff --git a/Templates/CustomTool/template.json b/Templates/CppToolGem/template.json similarity index 98% rename from Templates/CustomTool/template.json rename to Templates/CppToolGem/template.json index e3221db106..b516dbaec3 100644 --- a/Templates/CustomTool/template.json +++ b/Templates/CppToolGem/template.json @@ -1,12 +1,12 @@ { - "template_name": "CustomTool", - "origin": "The primary repo for CustomTool goes here: i.e. http://www.mydomain.com", - "license": "What license CustomTool uses goes here: i.e. https://opensource.org/licenses/MIT", - "display_name": "CustomTool", + "template_name": "CppToolGem", + "origin": "The primary repo for CppToolGem goes here: i.e. http://www.mydomain.com", + "license": "What license CppToolGem uses goes here: i.e. https://opensource.org/licenses/MIT", + "display_name": "CppToolGem", "summary": "A gem template for a custom tool in C++ that gets registered with the Editor.", "canonical_tags": [], "user_tags": [ - "CustomTool" + "CppToolGem" ], "icon_path": "preview.png", "copyFiles": [ diff --git a/Templates/PythonGem/Template/CMakeLists.txt b/Templates/PythonToolGem/Template/CMakeLists.txt similarity index 100% rename from Templates/PythonGem/Template/CMakeLists.txt rename to Templates/PythonToolGem/Template/CMakeLists.txt diff --git a/Templates/PythonGem/Template/Code/${NameLower}_editor_files.cmake b/Templates/PythonToolGem/Template/Code/${NameLower}_editor_files.cmake similarity index 100% rename from Templates/PythonGem/Template/Code/${NameLower}_editor_files.cmake rename to Templates/PythonToolGem/Template/Code/${NameLower}_editor_files.cmake diff --git a/Templates/PythonGem/Template/Code/${NameLower}_editor_shared_files.cmake b/Templates/PythonToolGem/Template/Code/${NameLower}_editor_shared_files.cmake similarity index 100% rename from Templates/PythonGem/Template/Code/${NameLower}_editor_shared_files.cmake rename to Templates/PythonToolGem/Template/Code/${NameLower}_editor_shared_files.cmake diff --git a/Templates/PythonGem/Template/Code/${NameLower}_editor_tests_files.cmake b/Templates/PythonToolGem/Template/Code/${NameLower}_editor_tests_files.cmake similarity index 100% rename from Templates/PythonGem/Template/Code/${NameLower}_editor_tests_files.cmake rename to Templates/PythonToolGem/Template/Code/${NameLower}_editor_tests_files.cmake diff --git a/Templates/PythonGem/Template/Code/CMakeLists.txt b/Templates/PythonToolGem/Template/Code/CMakeLists.txt similarity index 100% rename from Templates/PythonGem/Template/Code/CMakeLists.txt rename to Templates/PythonToolGem/Template/Code/CMakeLists.txt diff --git a/Templates/PythonGem/Template/Code/Include/${Name}/${Name}Bus.h b/Templates/PythonToolGem/Template/Code/Include/${Name}/${Name}Bus.h similarity index 100% rename from Templates/PythonGem/Template/Code/Include/${Name}/${Name}Bus.h rename to Templates/PythonToolGem/Template/Code/Include/${Name}/${Name}Bus.h diff --git a/Templates/PythonGem/Template/Code/Platform/Linux/${NameLower}_linux_files.cmake b/Templates/PythonToolGem/Template/Code/Platform/Linux/${NameLower}_linux_files.cmake similarity index 100% rename from Templates/PythonGem/Template/Code/Platform/Linux/${NameLower}_linux_files.cmake rename to Templates/PythonToolGem/Template/Code/Platform/Linux/${NameLower}_linux_files.cmake diff --git a/Templates/PythonGem/Template/Code/Platform/Linux/${NameLower}_shared_linux_files.cmake b/Templates/PythonToolGem/Template/Code/Platform/Linux/${NameLower}_shared_linux_files.cmake similarity index 100% rename from Templates/PythonGem/Template/Code/Platform/Linux/${NameLower}_shared_linux_files.cmake rename to Templates/PythonToolGem/Template/Code/Platform/Linux/${NameLower}_shared_linux_files.cmake diff --git a/Templates/PythonGem/Template/Code/Platform/Linux/PAL_linux.cmake b/Templates/PythonToolGem/Template/Code/Platform/Linux/PAL_linux.cmake similarity index 100% rename from Templates/PythonGem/Template/Code/Platform/Linux/PAL_linux.cmake rename to Templates/PythonToolGem/Template/Code/Platform/Linux/PAL_linux.cmake diff --git a/Templates/PythonGem/Template/Code/Platform/Mac/${NameLower}_mac_files.cmake b/Templates/PythonToolGem/Template/Code/Platform/Mac/${NameLower}_mac_files.cmake similarity index 100% rename from Templates/PythonGem/Template/Code/Platform/Mac/${NameLower}_mac_files.cmake rename to Templates/PythonToolGem/Template/Code/Platform/Mac/${NameLower}_mac_files.cmake diff --git a/Templates/PythonGem/Template/Code/Platform/Mac/${NameLower}_shared_mac_files.cmake b/Templates/PythonToolGem/Template/Code/Platform/Mac/${NameLower}_shared_mac_files.cmake similarity index 100% rename from Templates/PythonGem/Template/Code/Platform/Mac/${NameLower}_shared_mac_files.cmake rename to Templates/PythonToolGem/Template/Code/Platform/Mac/${NameLower}_shared_mac_files.cmake diff --git a/Templates/PythonGem/Template/Code/Platform/Mac/PAL_mac.cmake b/Templates/PythonToolGem/Template/Code/Platform/Mac/PAL_mac.cmake similarity index 100% rename from Templates/PythonGem/Template/Code/Platform/Mac/PAL_mac.cmake rename to Templates/PythonToolGem/Template/Code/Platform/Mac/PAL_mac.cmake diff --git a/Templates/PythonGem/Template/Code/Platform/Windows/${NameLower}_shared_windows_files.cmake b/Templates/PythonToolGem/Template/Code/Platform/Windows/${NameLower}_shared_windows_files.cmake similarity index 100% rename from Templates/PythonGem/Template/Code/Platform/Windows/${NameLower}_shared_windows_files.cmake rename to Templates/PythonToolGem/Template/Code/Platform/Windows/${NameLower}_shared_windows_files.cmake diff --git a/Templates/PythonGem/Template/Code/Platform/Windows/${NameLower}_windows_files.cmake b/Templates/PythonToolGem/Template/Code/Platform/Windows/${NameLower}_windows_files.cmake similarity index 100% rename from Templates/PythonGem/Template/Code/Platform/Windows/${NameLower}_windows_files.cmake rename to Templates/PythonToolGem/Template/Code/Platform/Windows/${NameLower}_windows_files.cmake diff --git a/Templates/PythonGem/Template/Code/Platform/Windows/PAL_windows.cmake b/Templates/PythonToolGem/Template/Code/Platform/Windows/PAL_windows.cmake similarity index 100% rename from Templates/PythonGem/Template/Code/Platform/Windows/PAL_windows.cmake rename to Templates/PythonToolGem/Template/Code/Platform/Windows/PAL_windows.cmake diff --git a/Templates/PythonGem/Template/Code/Source/${Name}EditorModule.cpp b/Templates/PythonToolGem/Template/Code/Source/${Name}EditorModule.cpp similarity index 100% rename from Templates/PythonGem/Template/Code/Source/${Name}EditorModule.cpp rename to Templates/PythonToolGem/Template/Code/Source/${Name}EditorModule.cpp diff --git a/Templates/PythonGem/Template/Code/Source/${Name}EditorSystemComponent.cpp b/Templates/PythonToolGem/Template/Code/Source/${Name}EditorSystemComponent.cpp similarity index 100% rename from Templates/PythonGem/Template/Code/Source/${Name}EditorSystemComponent.cpp rename to Templates/PythonToolGem/Template/Code/Source/${Name}EditorSystemComponent.cpp diff --git a/Templates/PythonGem/Template/Code/Source/${Name}EditorSystemComponent.h b/Templates/PythonToolGem/Template/Code/Source/${Name}EditorSystemComponent.h similarity index 100% rename from Templates/PythonGem/Template/Code/Source/${Name}EditorSystemComponent.h rename to Templates/PythonToolGem/Template/Code/Source/${Name}EditorSystemComponent.h diff --git a/Templates/PythonGem/Template/Code/Source/${Name}ModuleInterface.h b/Templates/PythonToolGem/Template/Code/Source/${Name}ModuleInterface.h similarity index 100% rename from Templates/PythonGem/Template/Code/Source/${Name}ModuleInterface.h rename to Templates/PythonToolGem/Template/Code/Source/${Name}ModuleInterface.h diff --git a/Templates/PythonGem/Template/Code/Tests/${Name}EditorTest.cpp b/Templates/PythonToolGem/Template/Code/Tests/${Name}EditorTest.cpp similarity index 100% rename from Templates/PythonGem/Template/Code/Tests/${Name}EditorTest.cpp rename to Templates/PythonToolGem/Template/Code/Tests/${Name}EditorTest.cpp diff --git a/Templates/PythonGem/Template/Editor/Scripts/${NameLower}_dialog.py b/Templates/PythonToolGem/Template/Editor/Scripts/${NameLower}_dialog.py similarity index 100% rename from Templates/PythonGem/Template/Editor/Scripts/${NameLower}_dialog.py rename to Templates/PythonToolGem/Template/Editor/Scripts/${NameLower}_dialog.py diff --git a/Templates/PythonGem/Template/Editor/Scripts/__init__.py b/Templates/PythonToolGem/Template/Editor/Scripts/__init__.py similarity index 100% rename from Templates/PythonGem/Template/Editor/Scripts/__init__.py rename to Templates/PythonToolGem/Template/Editor/Scripts/__init__.py diff --git a/Templates/PythonGem/Template/Editor/Scripts/bootstrap.py b/Templates/PythonToolGem/Template/Editor/Scripts/bootstrap.py similarity index 100% rename from Templates/PythonGem/Template/Editor/Scripts/bootstrap.py rename to Templates/PythonToolGem/Template/Editor/Scripts/bootstrap.py diff --git a/Templates/PythonGem/Template/gem.json b/Templates/PythonToolGem/Template/gem.json similarity index 100% rename from Templates/PythonGem/Template/gem.json rename to Templates/PythonToolGem/Template/gem.json diff --git a/Templates/PythonGem/Template/preview.png b/Templates/PythonToolGem/Template/preview.png similarity index 100% rename from Templates/PythonGem/Template/preview.png rename to Templates/PythonToolGem/Template/preview.png diff --git a/Templates/PythonGem/template.json b/Templates/PythonToolGem/template.json similarity index 94% rename from Templates/PythonGem/template.json rename to Templates/PythonToolGem/template.json index 75be757abb..4d85373ead 100644 --- a/Templates/PythonGem/template.json +++ b/Templates/PythonToolGem/template.json @@ -1,14 +1,14 @@ { - "template_name": "PythonGem", + "template_name": "PythonToolGem", "restricted_name": "o3de", "restricted_platform_relative_path": "Templates", - "origin": "The primary repo for PythonGem goes here: i.e. http://www.mydomain.com", - "license": "What license PythonGem uses goes here: i.e. https://opensource.org/licenses/MIT", - "display_name": "PythonGem", - "summary": "A short description of PythonGem.", + "origin": "The primary repo for PythonToolGem goes here: i.e. http://www.mydomain.com", + "license": "What license PythonToolGem uses goes here: i.e. https://opensource.org/licenses/MIT", + "display_name": "PythonToolGem", + "summary": "A gem template for a custom tool in Python that gets registered with the Editor.", "canonical_tags": [], "user_tags": [ - "PythonGem" + "PythonToolGem" ], "icon_path": "preview.png", "copyFiles": [ diff --git a/Tools/LyTestTools/ly_test_tools/_internal/pytest_plugin/editor_test.py b/Tools/LyTestTools/ly_test_tools/_internal/pytest_plugin/editor_test.py index 80a562977b..1e9d4c3654 100644 --- a/Tools/LyTestTools/ly_test_tools/_internal/pytest_plugin/editor_test.py +++ b/Tools/LyTestTools/ly_test_tools/_internal/pytest_plugin/editor_test.py @@ -3,34 +3,49 @@ 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 -""" +Utility for specifying an Editor test, supports seamless parallelization and/or batching of tests. This is not a set of +tools to directly invoke, but a plugin with functions intended to be called by only the Pytest framework. """ -Utility for specifying an Editor test, supports seamless parallelization and/or batching of tests. -""" - +from __future__ import annotations import pytest import inspect __test__ = False -def pytest_addoption(parser): +def pytest_addoption(parser: argparse.ArgumentParser) -> None: + """ + Options when running editor tests in batches or parallel. + :param parser: The ArgumentParser object + :return: None + """ parser.addoption("--no-editor-batch", action="store_true", help="Don't batch multiple tests in single editor") parser.addoption("--no-editor-parallel", action="store_true", help="Don't run multiple editors in parallel") parser.addoption("--editors-parallel", type=int, action="store", help="Override the number editors to run at the same time") -# Create a custom custom item collection if the class defines pytest_custom_makeitem function -# This is used for automtically generating test functions with a custom collector -def pytest_pycollect_makeitem(collector, name, obj): +def pytest_pycollect_makeitem(collector: PyCollector, name: str, obj: object) -> PyCollector: + """ + Create a custom custom item collection if the class defines pytest_custom_makeitem function. This is used for + automatically generating test functions with a custom collector. + :param collector: The Pytest collector + :param name: Name of the collector + :param obj: The custom collector, normally an EditorTestSuite.EditorTestClass object + :return: Returns the custom collector + """ if inspect.isclass(obj): for base in obj.__bases__: if hasattr(base, "pytest_custom_makeitem"): return base.pytest_custom_makeitem(collector, name, obj) -# Add custom modification of items. -# This is used for adding the runners into the item list @pytest.hookimpl(hookwrapper=True) -def pytest_collection_modifyitems(session, items, config): +def pytest_collection_modifyitems(session: Session, items: list[EditorTestBase], config: Config) -> None: + """ + Add custom modification of items. This is used for adding the runners into the item list. + :param session: The Pytest Session + :param items: The test case functions + :param config: The Pytest Config object + :return: None + """ all_classes = set() for item in items: all_classes.add(item.instance.__class__) @@ -40,4 +55,4 @@ def pytest_collection_modifyitems(session, items, config): for cls in all_classes: if hasattr(cls, "pytest_custom_modify_items"): cls.pytest_custom_modify_items(session, items, config) - + diff --git a/Tools/LyTestTools/ly_test_tools/_internal/pytest_plugin/test_tools_fixtures.py b/Tools/LyTestTools/ly_test_tools/_internal/pytest_plugin/test_tools_fixtures.py index f4bda9b293..af29064766 100755 --- a/Tools/LyTestTools/ly_test_tools/_internal/pytest_plugin/test_tools_fixtures.py +++ b/Tools/LyTestTools/ly_test_tools/_internal/pytest_plugin/test_tools_fixtures.py @@ -55,14 +55,6 @@ def pytest_configure(config): ly_test_tools._internal.pytest_plugin.build_directory = _get_build_directory(config) ly_test_tools._internal.pytest_plugin.output_path = _get_output_path(config) - -def pytest_pycollect_makeitem(collector, name, obj): - import inspect - if inspect.isclass(obj): - for base in obj.__bases__: - if hasattr(base, "pytest_custom_makeitem"): - return base.pytest_custom_makeitem(collector, name, obj) - def _get_build_directory(config): """ Fetch and verify the cmake build directory CLI arg, without creating an error when unset diff --git a/Tools/LyTestTools/ly_test_tools/launchers/launcher_helper.py b/Tools/LyTestTools/ly_test_tools/launchers/launcher_helper.py index ac4ad621da..5e96f34e95 100755 --- a/Tools/LyTestTools/ly_test_tools/launchers/launcher_helper.py +++ b/Tools/LyTestTools/ly_test_tools/launchers/launcher_helper.py @@ -60,7 +60,7 @@ def create_editor(workspace, launcher_platform=ly_test_tools.HOST_OS_EDITOR, arg Editor is only officially supported on the Windows Platform. :param workspace: lumberyard workspace to use - :param launcher_platform: the platform to target for a launcher (i.e. 'windows_dedicated' for DedicatedWinLauncher) + :param launcher_platform: the platform to target for a launcher (i.e. 'windows_dedicated' for DedicatedWinLauncher) :param args: List of arguments to pass to the launcher's 'args' argument during construction :return: Editor instance """ diff --git a/Tools/LyTestTools/ly_test_tools/o3de/editor_test.py b/Tools/LyTestTools/ly_test_tools/o3de/editor_test.py index 781977cf33..ae6ed16321 100644 --- a/Tools/LyTestTools/ly_test_tools/o3de/editor_test.py +++ b/Tools/LyTestTools/ly_test_tools/o3de/editor_test.py @@ -3,8 +3,29 @@ 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 file provides editor testing functionality to easily write automated editor tests for O3DE. +For using these utilities, you can subclass your test suite from EditorTestSuite, this allows an easy way of +specifying python test scripts that the editor will run without needing to write any boilerplace code. +It supports out of the box parallelization(running multiple editor instances at once), batching(running multiple tests +in the same editor instance) and crash detection. +Usage example: + class MyTestSuite(EditorTestSuite): + + class MyFirstTest(EditorSingleTest): + from . import script_to_be_run_by_editor as test_module + + class MyTestInParallel_1(EditorParallelTest): + from . import another_script_to_be_run_by_editor as test_module + + class MyTestInParallel_2(EditorParallelTest): + from . import yet_another_script_to_be_run_by_editor as test_module + + +EditorTestSuite does introspection of the defined classes inside of it and automatically prepares the tests, +parallelizing/batching as required +""" +from __future__ import annotations import pytest from _pytest.skipping import pytest_runtest_setup as skipping_pytest_runtest_setup @@ -25,30 +46,11 @@ import re import ly_test_tools.environment.file_system as file_system import ly_test_tools.environment.waiter as waiter import ly_test_tools.environment.process_utils as process_utils +import ly_test_tools.o3de.editor_test +import ly_test_tools.o3de.editor_test_utils as editor_utils from ly_test_tools.o3de.asset_processor import AssetProcessor from ly_test_tools.launchers.exceptions import WaitTimeoutError -from . import editor_test_utils as editor_utils - -# This file provides editor testing functionality to easily write automated editor tests for O3DE. -# For using these utilities, you can subclass your test suite from EditorTestSuite, this allows an easy way of specifying -# python test scripts that the editor will run without needing to write any boilerplace code. -# It supports out of the box parallelization(running multiple editor instances at once), batching(running multiple tests in the same editor instance) and -# crash detection. -# Usage example: -# class MyTestSuite(EditorTestSuite): -# -# class MyFirstTest(EditorSingleTest): -# from . import script_to_be_run_by_editor as test_module -# -# class MyTestInParallel_1(EditorParallelTest): -# from . import another_script_to_be_run_by_editor as test_module -# -# class MyTestInParallel_2(EditorParallelTest): -# from . import yet_another_script_to_be_run_by_editor as test_module -# -# -# EditorTestSuite does introspection of the defined classes inside of it and automatically prepares the tests, parallelizing/batching as required # This file contains no tests, but with this we make sure it won't be picked up by the runner since the file ends with _test __test__ = False @@ -109,12 +111,22 @@ class EditorBatchedTest(EditorSharedTest): class Result: class Base: def get_output_str(self): + # type () -> str + """ + Checks if the output attribute exists and returns it. + :return: Either the output string or a no output message + """ if hasattr(self, "output") and self.output is not None: return self.output else: return "-- No output --" def get_editor_log_str(self): + # type () -> str + """ + Checks if the editor_log attribute exists and returns it. + :return: Either the editor_log string or a no output message + """ if hasattr(self, "editor_log") and self.editor_log is not None: return self.editor_log else: @@ -122,7 +134,14 @@ class Result: class Pass(Base): @classmethod - def create(cls, test_spec : EditorTestBase, output : str, editor_log : str): + def create(cls, test_spec: EditorTestBase, output: str, editor_log: str) -> Pass: + """ + Creates a Pass object with a given test spec, output string, and editor log string. + :test_spec: The type of EditorTestBase + :output: The test output + :editor_log: The editor log's output + :return: the Pass object + """ r = cls() r.test_spec = test_spec r.output = output @@ -141,7 +160,14 @@ class Result: class Fail(Base): @classmethod - def create(cls, test_spec : EditorTestBase, output, editor_log : str): + def create(cls, test_spec: EditorTestBase, output: str, editor_log: str) -> Fail: + """ + Creates a Fail object with a given test spec, output string, and editor log string. + :test_spec: The type of EditorTestBase + :output: The test output + :editor_log: The editor log's output + :return: the Fail object + """ r = cls() r.test_spec = test_spec r.output = output @@ -164,7 +190,17 @@ class Result: class Crash(Base): @classmethod - def create(cls, test_spec : EditorTestBase, output : str, ret_code : int, stacktrace : str, editor_log : str): + def create(cls, test_spec: EditorTestBase, output: str, ret_code: int, stacktrace: str, editor_log: str) -> Crash: + """ + Creates a Crash object with a given test spec, output string, and editor log string. This also includes the + return code and stacktrace. + :test_spec: The type of EditorTestBase + :output: The test output + :ret_code: The test's return code + :stacktrace: The test's stacktrace if available + :editor_log: The editor log's output + :return: The Crash object + """ r = cls() r.output = output r.test_spec = test_spec @@ -190,12 +226,20 @@ class Result: f"--------------\n" f"{self.get_editor_log_str()}\n" ) - crash_str = "-- No crash information found --" return output class Timeout(Base): @classmethod - def create(cls, test_spec : EditorTestBase, output : str, time_secs : float, editor_log : str): + def create(cls, test_spec: EditorTestBase, output: str, time_secs: float, editor_log: str) -> Timeout: + """ + Creates a Timeout object with a given test spec, output string, and editor log string. The timeout time + should be provided in seconds + :test_spec: The type of EditorTestBase + :output: The test output + :time_secs: The timeout duration in seconds + :editor_log: The editor log's output + :return: The Timeout object + """ r = cls() r.output = output r.test_spec = test_spec @@ -219,14 +263,22 @@ class Result: class Unknown(Base): @classmethod - def create(cls, test_spec : EditorTestBase, output : str, extra_info : str, editor_log : str): + def create(cls, test_spec: EditorTestBase, output: str, extra_info: str, editor_log: str) -> Unknown: + """ + Creates an Unknown test results object if something goes wrong. + :test_spec: The type of EditorTestBase + :output: The test output + :extra_info: Any extra information as a string + :editor_log: The editor log's output + :return: The Unknown object + """ r = cls() r.output = output r.test_spec = test_spec r.editor_log = editor_log r.extra_info = extra_info return r - + def __str__(self): output = ( f"Unknown test result, possible cause: {self.extra_info}\n" @@ -262,7 +314,19 @@ class EditorTestSuite(): _TEST_FAIL_RETCODE = 0xF # Return code for test failure @pytest.fixture(scope="class") - def editor_test_data(self, request): + def editor_test_data(self, request: Request) -> TestData: + """ + Yields a per-testsuite structure to store the data of each test result and an AssetProcessor object that will be + re-used on the whole suite + :request: The Pytest request + :yield: The TestData object + """ + yield from self._editor_test_data(request) + + def _editor_test_data(self, request: Request) -> TestData: + """ + A wrapper function for unit testing to call directly + """ class TestData(): def __init__(self): self.results = {} # Dict of str(test_spec.__name__) -> Result @@ -444,9 +508,15 @@ class EditorTestSuite(): return EditorTestSuite.EditorTestClass(name, collector) @classmethod - def pytest_custom_modify_items(cls, session, items, config): - # Add here the runners functions and filter the tests that will be run. - # The runners will be added if they have any selected tests + def pytest_custom_modify_items(cls, session: Session, items: list[EditorTestBase], config: Config) -> None: + """ + Adds the runners' functions and filters the tests that will run. The runners will be added if they have any + selected tests + :param session: The Pytest Session + :param items: The test case functions + :param config: The Pytest Config object + :return: None + """ new_items = [] for runner in cls._runners: runner.tests[:] = cls.filter_session_shared_tests(items, runner.tests) @@ -462,24 +532,50 @@ class EditorTestSuite(): items[:] = items + new_items @classmethod - def get_single_tests(cls): + def get_single_tests(cls) -> list[EditorSingleTest]: + """ + Grabs all of the EditorSingleTests subclassed tests from the EditorTestSuite class + Usage example: + class MyTestSuite(EditorTestSuite): + class MyFirstTest(EditorSingleTest): + from . import script_to_be_run_by_editor as test_module + :return: The list of single tests + """ single_tests = [c[1] for c in cls.__dict__.items() if inspect.isclass(c[1]) and issubclass(c[1], EditorSingleTest)] return single_tests @classmethod - def get_shared_tests(cls): + def get_shared_tests(cls) -> list[EditorSharedTest]: + """ + Grabs all of the EditorSharedTests from the EditorTestSuite + Usage example: + class MyTestSuite(EditorTestSuite): + class MyFirstTest(EditorSharedTest): + from . import script_to_be_run_by_editor as test_module + :return: The list of shared tests + """ shared_tests = [c[1] for c in cls.__dict__.items() if inspect.isclass(c[1]) and issubclass(c[1], EditorSharedTest)] return shared_tests @classmethod - def get_session_shared_tests(cls, session): + def get_session_shared_tests(cls, session: Session) -> list[EditorTestBase]: + """ + Filters and returns all of the shared tests in a given session. + :session: The test session + :return: The list of tests + """ shared_tests = cls.get_shared_tests() return cls.filter_session_shared_tests(session, shared_tests) @staticmethod - def filter_session_shared_tests(session_items, shared_tests): - # Retrieve the test sub-set that was collected - # this can be less than the original set if were overriden via -k argument or similars + def filter_session_shared_tests(session_items: list[EditorTestBase], shared_tests: list[EditorSharedTest]) -> list[EditorTestBase]: + """ + Retrieve the test sub-set that was collected this can be less than the original set if were overriden via -k + argument or similars + :session_items: The tests in a session to run + :shared_tests: All of the shared tests + :return: The list of filtered tests + """ def will_run(item): try: skipping_pytest_runtest_setup(item) @@ -488,13 +584,20 @@ class EditorTestSuite(): return False session_items_by_name = { item.originalname:item for item in session_items } - selected_shared_tests = [test for test in shared_tests if test.__name__ in session_items_by_name.keys() and will_run(session_items_by_name[test.__name__])] + selected_shared_tests = [test for test in shared_tests if test.__name__ in session_items_by_name.keys() and + will_run(session_items_by_name[test.__name__])] return selected_shared_tests @staticmethod - def filter_shared_tests(shared_tests, is_batchable=False, is_parallelizable=False): - # Retrieve the test sub-set that was collected - # this can be less than the original set if were overriden via -k argument or similars + def filter_shared_tests(shared_tests: list[EditorSharedTest], is_batchable: bool = False, + is_parallelizable: bool = False) -> list[EditorSharedTest]: + """ + Filters and returns all tests based off of if they are batchable and/or parallelizable + :shared_tests: All shared tests + :is_batchable: Filter to batchable tests + :is_parallelizable: Filter to parallelizable tests + :return: The list of filtered tests + """ return [ t for t in shared_tests if ( getattr(t, "is_batchable", None) is is_batchable @@ -504,9 +607,14 @@ class EditorTestSuite(): ] ### Utils ### - - # Prepares the asset processor for the test - def _prepare_asset_processor(self, workspace, editor_test_data): + def _prepare_asset_processor(self, workspace: AbstractWorkspace, editor_test_data: TestData) -> None: + """ + Prepares the asset processor for the test depending on whether or not the process is open and if the current + test owns it. + :workspace: The workspace object in case an AssetProcessor object needs to be created + :editor_test_data: The test data from calling editor_test_data() + :return: None + """ try: # Start-up an asset processor if we are not running one # If another AP process exist, don't kill it, as we don't own it @@ -524,15 +632,28 @@ class EditorTestSuite(): editor_test_data.asset_processor = None raise ex - def _setup_editor_test(self, editor, workspace, editor_test_data): + def _setup_editor_test(self, editor: Editor, workspace: AbstractWorkspace, editor_test_data: TestData) -> None: + """ + Sets up an editor test by preparing the Asset Processor, killing all other O3DE processes, and configuring + :editor: The launcher Editor object + :workspace: The test Workspace object + :editor_test_data: The TestData from calling editor_test_data() + :return: None + """ self._prepare_asset_processor(workspace, editor_test_data) editor_utils.kill_all_ly_processes(include_asset_processor=False) editor.configure_settings() - # Utility function for parsing the output information from the editor. - # It deserializes the JSON content printed in the output for every test and returns that information. @staticmethod - def _get_results_using_output(test_spec_list, output, editor_log_content): + def _get_results_using_output(test_spec_list: list[EditorTestBase], output: str, editor_log_content: str) -> dict[str, Result]: + """ + Utility function for parsing the output information from the editor. It deserializes the JSON content printed in + the output for every test and returns that information. + :test_spec_list: The list of EditorTests + :output: The Editor from Editor.get_output() + :editor_log_content: The contents of the editor log as a string + :return: A dict of the tests and their respective Result objects + """ results = {} pattern = re.compile(r"JSON_START\((.+?)\)JSON_END") out_matches = pattern.finditer(output) @@ -558,7 +679,9 @@ class EditorTestSuite(): for test_spec in test_spec_list: name = editor_utils.get_module_filename(test_spec.test_module) if name not in found_jsons.keys(): - results[test_spec.__name__] = Result.Unknown.create(test_spec, output, "Couldn't find any test run information on stdout", editor_log_content) + results[test_spec.__name__] = Result.Unknown.create(test_spec, output, + "Couldn't find any test run information on stdout", + editor_log_content) else: result = None json_result = found_jsons[name] @@ -581,9 +704,14 @@ class EditorTestSuite(): return results - # Fails the test if the test result is not a PASS, specifying the information @staticmethod - def _report_result(name : str, result : Result.Base): + def _report_result(name: str, result: Result) -> None: + """ + Fails the test if the test result is not a PASS, specifying the information + :name: Name of the test + :result: The Result object which denotes if the test passed or not + :return: None + """ if isinstance(result, Result.Pass): output_str = f"Test {name}:\n{str(result)}" print(output_str) @@ -592,10 +720,19 @@ class EditorTestSuite(): pytest.fail(error_str) ### Running tests ### - # Starts the editor with the given test and retuns an result dict with a single element specifying the result - def _exec_editor_test(self, request, workspace, editor, run_id : int, log_name : str, - test_spec : EditorTestBase, cmdline_args : List[str] = []): - + def _exec_editor_test(self, request: Request, workspace: AbstractWorkspace, editor: Editor, run_id: int, + log_name: str, test_spec: EditorTestBase, cmdline_args: list[str] = []) -> dict[str, Result]: + """ + Starts the editor with the given test and retuns an result dict with a single element specifying the result + :request: The pytest request + :workspace: The LyTestTools Workspace object + :editor: The LyTestTools Editor object + :run_id: The unique run id + :log_name: The name of the editor log to retrieve + :test_spec: The type of EditorTestBase + :cmdline_args: Any additional command line args + :return: a dictionary of Result objects + """ test_cmdline_args = self.global_extra_cmdline_args + cmdline_args test_spec_uses_null_renderer = getattr(test_spec, "use_null_renderer", None) if test_spec_uses_null_renderer or (test_spec_uses_null_renderer is None and self.use_null_renderer): @@ -629,12 +766,14 @@ class EditorTestSuite(): else: has_crashed = return_code != EditorTestSuite._TEST_FAIL_RETCODE if has_crashed: - test_result = Result.Crash.create(test_spec, output, return_code, editor_utils.retrieve_crash_output(run_id, workspace, self._TIMEOUT_CRASH_LOG), None) + test_result = Result.Crash.create(test_spec, output, return_code, editor_utils.retrieve_crash_output + (run_id, workspace, self._TIMEOUT_CRASH_LOG), None) editor_utils.cycle_crash_report(run_id, workspace) else: test_result = Result.Fail.create(test_spec, output, editor_log_content) except WaitTimeoutError: - editor.kill() + 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) @@ -643,11 +782,21 @@ class EditorTestSuite(): results[test_spec.__name__] = test_result return results - # Starts an editor executable with a list of tests and returns a dict of the result of every test ran within that editor - # instance. In case of failure this function also parses the editor output to find out what specific tests failed - def _exec_editor_multitest(self, request, workspace, editor, run_id : int, log_name : str, - test_spec_list : List[EditorTestBase], cmdline_args=[]): - + def _exec_editor_multitest(self, request: Request, workspace: AbstractWorkspace, editor: Editor, run_id: int, log_name: str, + test_spec_list: list[EditorTestBase], cmdline_args: list[str] = []) -> dict[str, Result]: + """ + Starts an editor executable with a list of tests and returns a dict of the result of every test ran within that + editor instance. In case of failure this function also parses the editor output to find out what specific tests + failed. + :request: The pytest request + :workspace: The LyTestTools Workspace object + :editor: The LyTestTools Editor object + :run_id: The unique run id + :log_name: The name of the editor log to retrieve + :test_spec_list: A list of EditorTestBase tests to run in the same editor instance + :cmdline_args: Any additional command line args + :return: A dict of Result objects + """ test_cmdline_args = self.global_extra_cmdline_args + cmdline_args if self.use_null_renderer: test_cmdline_args += ["-rhi=null"] @@ -695,50 +844,66 @@ class EditorTestSuite(): if isinstance(result, Result.Unknown): if not crashed_result: # The first test with "Unknown" result (no data in output) is likely the one that crashed - crash_error = editor_utils.retrieve_crash_output(run_id, workspace, self._TIMEOUT_CRASH_LOG) + crash_error = editor_utils.retrieve_crash_output(run_id, workspace, + self._TIMEOUT_CRASH_LOG) editor_utils.cycle_crash_report(run_id, workspace) - results[test_spec_name] = Result.Crash.create(result.test_spec, output, return_code, crash_error, result.editor_log) + results[test_spec_name] = Result.Crash.create(result.test_spec, output, return_code, + crash_error, result.editor_log) crashed_result = result else: - # If there are remaning "Unknown" results, these couldn't execute because of the crash, update with info about the offender - results[test_spec_name].extra_info = f"This test has unknown result, test '{crashed_result.test_spec.__name__}' crashed before this test could be executed" - + # If there are remaning "Unknown" results, these couldn't execute because of the crash, + # update with info about the offender + results[test_spec_name].extra_info = f"This test has unknown result," \ + f"test '{crashed_result.test_spec.__name__}'" \ + f"crashed before this test could be executed" # if all the tests ran, the one that has caused the crash is the last test if not crashed_result: crash_error = editor_utils.retrieve_crash_output(run_id, workspace, self._TIMEOUT_CRASH_LOG) editor_utils.cycle_crash_report(run_id, workspace) - results[test_spec_name] = Result.Crash.create(crashed_result.test_spec, output, return_code, crash_error, crashed_result.editor_log) - - + results[test_spec_name] = Result.Crash.create(crashed_result.test_spec, output, return_code, + crash_error, crashed_result.editor_log) except WaitTimeoutError: editor.kill() - output = editor.get_output() editor_log_content = editor_utils.retrieve_editor_log_content(run_id, log_name, workspace) # The editor timed out when running the tests, get the data from the output to find out which ones ran results = self._get_results_using_output(test_spec_list, output, editor_log_content) assert len(results) == len(test_spec_list), "bug in _get_results_using_output(), the number of results don't match the tests ran" - # Similar logic here as crashes, the first test that has no result is the one that timed out timed_out_result = None for test_spec_name, result in results.items(): if isinstance(result, Result.Unknown): if not timed_out_result: - results[test_spec_name] = Result.Timeout.create(result.test_spec, result.output, self.timeout_editor_shared_test, result.editor_log) + results[test_spec_name] = Result.Timeout.create(result.test_spec, result.output, + self.timeout_editor_shared_test, + result.editor_log) timed_out_result = result else: - # If there are remaning "Unknown" results, these couldn't execute because of the timeout, update with info about the offender - results[test_spec_name].extra_info = f"This test has unknown result, test '{timed_out_result.test_spec.__name__}' timed out before this test could be executed" - + # If there are remaning "Unknown" results, these couldn't execute because of the timeout, + # update with info about the offender + results[test_spec_name].extra_info = f"This test has unknown result, test " \ + f"'{timed_out_result.test_spec.__name__}' timed out " \ + f"before this test could be executed" # if all the tests ran, the one that has caused the timeout is the last test, as it didn't close the editor if not timed_out_result: - results[test_spec_name] = Result.Timeout.create(timed_out_result.test_spec, results[test_spec_name].output, self.timeout_editor_shared_test, result.editor_log) + results[test_spec_name] = Result.Timeout.create(timed_out_result.test_spec, + results[test_spec_name].output, + self.timeout_editor_shared_test, result.editor_log) return results - # Runs a single test (one editor, one test) with the given specs - def _run_single_test(self, request, workspace, editor, editor_test_data, test_spec : EditorSingleTest): + def _run_single_test(self, request: Request, workspace: AbstractWorkspace, editor: Editor, + editor_test_data: TestData, test_spec: EditorSingleTest) -> None: + """ + Runs a single test (one editor, one test) with the given specs + :request: The Pytest Request + :workspace: The LyTestTools Workspace object + :editor: The LyTestTools Editor object + :editor_test_data: The TestData from calling editor_test_data() + :test_spec: The test class that should be a subclass of EditorSingleTest + :return: None + """ self._setup_editor_test(editor, workspace, editor_test_data) extra_cmdline_args = [] if hasattr(test_spec, "extra_cmdline_args"): @@ -749,18 +914,39 @@ class EditorTestSuite(): test_name, test_result = next(iter(results.items())) self._report_result(test_name, test_result) - # Runs a batch of tests in one single editor with the given spec list (one editor, multiple tests) - def _run_batched_tests(self, request, workspace, editor, editor_test_data, test_spec_list : List[EditorSharedTest], extra_cmdline_args=[]): + def _run_batched_tests(self, request: Request, workspace: AbstractWorkspace, editor: Editor, editor_test_data: TestData, + test_spec_list: list[EditorSharedTest], extra_cmdline_args: list[str] = []) -> None: + """ + Runs a batch of tests in one single editor with the given spec list (one editor, multiple tests) + :request: The Pytest Request + :workspace: The LyTestTools Workspace object + :editor: The LyTestTools Editor object + :editor_test_data: The TestData from calling editor_test_data() + :test_spec_list: A list of EditorSharedTest tests to run + :extra_cmdline_args: Any extra command line args in a list + :return: None + """ if not test_spec_list: return self._setup_editor_test(editor, workspace, editor_test_data) - results = self._exec_editor_multitest(request, workspace, editor, 1, "editor_test.log", test_spec_list, extra_cmdline_args) + results = self._exec_editor_multitest(request, workspace, editor, 1, "editor_test.log", test_spec_list, + extra_cmdline_args) assert results is not None editor_test_data.results.update(results) - # Runs multiple editors with one test on each editor (multiple editor, one test each) - def _run_parallel_tests(self, request, workspace, editor, editor_test_data, test_spec_list : List[EditorSharedTest], extra_cmdline_args=[]): + def _run_parallel_tests(self, request: Request, workspace: AbstractWorkspace, editor: Editor, editor_test_data: TestData, + test_spec_list: list[EditorSharedTest], extra_cmdline_args: list[str] = []) -> None: + """ + Runs multiple editors with one test on each editor (multiple editor, one test each) + :request: The Pytest Request + :workspace: The LyTestTools Workspace object + :editor: The LyTestTools Editor object + :editor_test_data: The TestData from calling editor_test_data() + :test_spec_list: A list of EditorSharedTest tests to run + :extra_cmdline_args: Any extra command line args in a list + :return: None + """ if not test_spec_list: return @@ -778,7 +964,8 @@ class EditorTestSuite(): for i in range(total_threads): def make_func(test_spec, index, my_editor): def run(request, workspace, extra_cmdline_args): - results = self._exec_editor_test(request, workspace, my_editor, index+1, f"editor_test.log", test_spec, extra_cmdline_args) + results = self._exec_editor_test(request, workspace, my_editor, index+1, f"editor_test.log", + test_spec, extra_cmdline_args) assert results is not None results_per_thread[index] = results return run @@ -796,8 +983,18 @@ class EditorTestSuite(): for result in results_per_thread: editor_test_data.results.update(result) - # Runs multiple editors with a batch of tests for each editor (multiple editor, multiple tests each) - def _run_parallel_batched_tests(self, request, workspace, editor, editor_test_data, test_spec_list : List[EditorSharedTest], extra_cmdline_args=[]): + def _run_parallel_batched_tests(self, request: Request, workspace: AbstractWorkspace, editor: Editor, editor_test_data: TestData, + test_spec_list: list[EditorSharedTest], extra_cmdline_args: list[str] = []) -> None: + """ + Runs multiple editors with a batch of tests for each editor (multiple editor, multiple tests each) + :request: The Pytest Request + :workspace: The LyTestTools Workspace object + :editor: The LyTestTools Editor object + :editor_test_data: The TestData from calling editor_test_data() + :test_spec_list: A list of EditorSharedTest tests to run + :extra_cmdline_args: Any extra command line args in a list + :return: None + """ if not test_spec_list: return @@ -813,7 +1010,9 @@ class EditorTestSuite(): def run(request, workspace, extra_cmdline_args): results = None if len(test_spec_list_for_editor) > 0: - results = self._exec_editor_multitest(request, workspace, my_editor, index+1, f"editor_test.log", test_spec_list_for_editor, extra_cmdline_args) + results = self._exec_editor_multitest(request, workspace, my_editor, index+1, + f"editor_test.log", test_spec_list_for_editor, + extra_cmdline_args) assert results is not None else: results = {} @@ -833,8 +1032,12 @@ class EditorTestSuite(): for result in results_per_thread: editor_test_data.results.update(result) - # Retrieves the number of parallel preference cmdline overrides - def _get_number_parallel_editors(self, request): + def _get_number_parallel_editors(self, request: Request) -> int: + """ + Retrieves the number of parallel preference cmdline overrides + :request: The Pytest Request + :return: The number of parallel editors to use + """ parallel_editors_value = request.config.getoption("--editors-parallel", None) if parallel_editors_value: return int(parallel_editors_value) diff --git a/Tools/LyTestTools/ly_test_tools/o3de/editor_test_utils.py b/Tools/LyTestTools/ly_test_tools/o3de/editor_test_utils.py index feff78d866..35fbe93d37 100644 --- a/Tools/LyTestTools/ly_test_tools/o3de/editor_test_utils.py +++ b/Tools/LyTestTools/ly_test_tools/o3de/editor_test_utils.py @@ -3,8 +3,10 @@ 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 -""" +Utility functions mostly for the editor_test module. They can also be used for assisting Editor tests. +""" +from __future__ import annotations import os import time import logging @@ -14,7 +16,13 @@ import ly_test_tools.environment.waiter as waiter logger = logging.getLogger(__name__) -def kill_all_ly_processes(include_asset_processor=True): +def kill_all_ly_processes(include_asset_processor: bool = True) -> None: + """ + Kills all common O3DE processes such as the Editor, Game Launchers, and optionally Asset Processor. Defaults to + killing the Asset Processor. + :param include_asset_processor: Boolean flag whether or not to kill the AP + :return: None + """ LY_PROCESSES = [ 'Editor', 'Profiler', 'RemoteConsole', ] @@ -27,8 +35,7 @@ def kill_all_ly_processes(include_asset_processor=True): else: process_utils.kill_processes_named(LY_PROCESSES, ignore_extensions=True) -def get_testcase_module_filepath(testcase_module): - # type: (Module) -> str +def get_testcase_module_filepath(testcase_module: Module) -> str: """ return the full path of the test module using always '.py' extension :param testcase_module: The testcase python module being tested @@ -36,8 +43,7 @@ def get_testcase_module_filepath(testcase_module): """ return os.path.splitext(testcase_module.__file__)[0] + ".py" -def get_module_filename(testcase_module): - # type: (Module) -> str +def get_module_filename(testcase_module: Module): """ return The filename of the module without path Note: This is differs from module.__name__ in the essence of not having the package directory. @@ -47,7 +53,7 @@ def get_module_filename(testcase_module): """ return os.path.splitext(os.path.basename(testcase_module.__file__))[0] -def retrieve_log_path(run_id : int, workspace): +def retrieve_log_path(run_id: int, workspace: AbstractWorkspaceManager) -> str: """ return the log/ project path for this test run. :param run_id: editor id that will be used for differentiating paths @@ -56,7 +62,7 @@ def retrieve_log_path(run_id : int, workspace): """ return os.path.join(workspace.paths.project(), "user", f"log_test_{run_id}") -def retrieve_crash_output(run_id : int, workspace, timeout : float): +def retrieve_crash_output(run_id: int, workspace: AbstractWorkspaceManager, timeout: float = 10) -> str: """ returns the crash output string for the given test run. :param run_id: editor id that will be used for differentiating paths @@ -79,7 +85,7 @@ def retrieve_crash_output(run_id : int, workspace, timeout : float): crash_info += f"\n{str(ex)}" return crash_info -def cycle_crash_report(run_id : int, workspace): +def cycle_crash_report(run_id: int, workspace: AbstractWorkspaceManager) -> None: """ Attempts to rename error.log and error.dmp(crash files) into new names with the timestamp on it. :param run_id: editor id that will be used for differentiating paths @@ -99,10 +105,11 @@ def cycle_crash_report(run_id : int, workspace): except Exception as ex: logger.warning(f"Couldn't cycle file {filepath}. Error: {str(ex)}") -def retrieve_editor_log_content(run_id : int, log_name : str, workspace, timeout=10): +def retrieve_editor_log_content(run_id: int, log_name: str, workspace: AbstractWorkspaceManager, timeout: int = 10) -> str: """ Retrieves the contents of the given editor log file. :param run_id: editor id that will be used for differentiating paths + :log_name: The name of the editor log to retrieve :param workspace: Workspace fixture :timeout: Maximum time to wait for the log file to appear :return str: The contents of the log @@ -124,7 +131,7 @@ def retrieve_editor_log_content(run_id : int, log_name : str, workspace, timeout editor_info = f"-- Error reading editor.log: {str(ex)} --" return editor_info -def retrieve_last_run_test_index_from_output(test_spec_list, output : str): +def retrieve_last_run_test_index_from_output(test_spec_list: list[EditorTestBase], output: str) -> int: """ Finds out what was the last test that was run by inspecting the input. This is used for determining what was the batched test has crashed the editor diff --git a/Tools/LyTestTools/tests/unit/test_editor_test_utils.py b/Tools/LyTestTools/tests/unit/test_editor_test_utils.py new file mode 100644 index 0000000000..bf7fae7189 --- /dev/null +++ b/Tools/LyTestTools/tests/unit/test_editor_test_utils.py @@ -0,0 +1,162 @@ +""" +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 +""" +import pytest +import os +import unittest.mock as mock +import unittest + +import ly_test_tools.o3de.editor_test_utils as editor_test_utils + +pytestmark = pytest.mark.SUITE_smoke + +class TestEditorTestUtils(unittest.TestCase): + + @mock.patch('ly_test_tools.environment.process_utils.kill_processes_named') + def test_KillAllLyProcesses_IncludeAP_CallsCorrectly(self, mock_kill_processes_named): + process_list = ['Editor', 'Profiler', 'RemoteConsole', 'AssetProcessor', 'AssetProcessorBatch', 'AssetBuilder'] + + editor_test_utils.kill_all_ly_processes(include_asset_processor=True) + mock_kill_processes_named.assert_called_once_with(process_list, ignore_extensions=True) + + @mock.patch('ly_test_tools.environment.process_utils.kill_processes_named') + def test_KillAllLyProcesses_NotIncludeAP_CallsCorrectly(self, mock_kill_processes_named): + process_list = ['Editor', 'Profiler', 'RemoteConsole'] + ap_process_list = ['AssetProcessor', 'AssetProcessorBatch', 'AssetBuilder'] + + editor_test_utils.kill_all_ly_processes(include_asset_processor=False) + mock_kill_processes_named.assert_called_once() + assert ap_process_list not in mock_kill_processes_named.call_args[0] + + def test_GetTestcaseModuleFilepath_NoExtension_ReturnsPYExtension(self): + mock_module = mock.MagicMock() + file_path = os.path.join('path', 'under_test') + mock_module.__file__ = file_path + + assert file_path + '.py' == editor_test_utils.get_testcase_module_filepath(mock_module) + + def test_GetTestcaseModuleFilepath_PYExtension_ReturnsPYExtension(self): + mock_module = mock.MagicMock() + file_path = os.path.join('path', 'under_test.py') + mock_module.__file__ = file_path + + assert file_path == editor_test_utils.get_testcase_module_filepath(mock_module) + + def test_GetModuleFilename_PythonModule_ReturnsFilename(self): + mock_module = mock.MagicMock() + file_path = os.path.join('path', 'under_test.py') + mock_module.__file__ = file_path + + assert 'under_test' == editor_test_utils.get_module_filename(mock_module) + + def test_RetrieveLogPath_NormalProject_ReturnsLogPath(self): + mock_workspace = mock.MagicMock() + mock_workspace.paths.project.return_value = 'mock_project_path' + expected = os.path.join('mock_project_path', 'user', 'log_test_0') + + assert expected == editor_test_utils.retrieve_log_path(0, mock_workspace) + + @mock.patch('ly_test_tools.o3de.editor_test_utils.retrieve_log_path') + @mock.patch('ly_test_tools.environment.waiter.wait_for', mock.MagicMock()) + def test_RetrieveCrashOutput_CrashLogExists_ReturnsLogInfo(self, mock_retrieve_log_path): + mock_retrieve_log_path.return_value = 'mock_log_path' + mock_workspace = mock.MagicMock() + mock_log = 'mock crash info' + + with mock.patch('builtins.open', mock.mock_open(read_data=mock_log)) as mock_file: + assert mock_log == editor_test_utils.retrieve_crash_output(0, mock_workspace, 0) + + @mock.patch('ly_test_tools.o3de.editor_test_utils.retrieve_log_path') + @mock.patch('ly_test_tools.environment.waiter.wait_for', mock.MagicMock()) + def test_RetrieveCrashOutput_CrashLogNotExists_ReturnsError(self, mock_retrieve_log_path): + mock_retrieve_log_path.return_value = 'mock_log_path' + mock_workspace = mock.MagicMock() + expected = "-- No crash log available --\n[Errno 2] No such file or directory: 'mock_log_path\\\\error.log'" + + assert expected == editor_test_utils.retrieve_crash_output(0, mock_workspace, 0) + + @mock.patch('os.path.getmtime', mock.MagicMock()) + @mock.patch('os.rename') + @mock.patch('time.strftime') + @mock.patch('ly_test_tools.o3de.editor_test_utils.retrieve_log_path') + @mock.patch('os.path.exists') + def test_CycleCrashReport_DmpExists_NamedCorrectly(self, mock_exists, mock_retrieve_log_path, mock_strftime, + mock_rename): + mock_exists.side_effect = [False, True] + mock_retrieve_log_path.return_value = 'mock_log_path' + mock_workspace = mock.MagicMock() + mock_strftime.return_value = 'mock_strftime' + + editor_test_utils.cycle_crash_report(0, mock_workspace) + mock_rename.assert_called_once_with(os.path.join('mock_log_path', 'error.dmp'), + os.path.join('mock_log_path', 'error_mock_strftime.dmp')) + + @mock.patch('os.path.getmtime', mock.MagicMock()) + @mock.patch('os.rename') + @mock.patch('time.strftime') + @mock.patch('ly_test_tools.o3de.editor_test_utils.retrieve_log_path') + @mock.patch('os.path.exists') + def test_CycleCrashReport_LogExists_NamedCorrectly(self, mock_exists, mock_retrieve_log_path, mock_strftime, + mock_rename): + mock_exists.side_effect = [True, False] + mock_retrieve_log_path.return_value = 'mock_log_path' + mock_workspace = mock.MagicMock() + mock_strftime.return_value = 'mock_strftime' + + editor_test_utils.cycle_crash_report(0, mock_workspace) + mock_rename.assert_called_once_with(os.path.join('mock_log_path', 'error.log'), + os.path.join('mock_log_path', 'error_mock_strftime.log')) + + @mock.patch('ly_test_tools.o3de.editor_test_utils.retrieve_log_path') + @mock.patch('ly_test_tools.environment.waiter.wait_for', mock.MagicMock()) + def test_RetrieveEditorLogContent_CrashLogExists_ReturnsLogInfo(self, mock_retrieve_log_path): + mock_retrieve_log_path.return_value = 'mock_log_path' + mock_logname = 'mock_log.log' + mock_workspace = mock.MagicMock() + mock_log = 'mock log info' + + with mock.patch('builtins.open', mock.mock_open(read_data=mock_log)) as mock_file: + assert f'[editor.log] {mock_log}' == editor_test_utils.retrieve_editor_log_content(0, mock_logname, mock_workspace) + + @mock.patch('ly_test_tools.o3de.editor_test_utils.retrieve_log_path') + @mock.patch('ly_test_tools.environment.waiter.wait_for', mock.MagicMock()) + def test_RetrieveEditorLogContent_CrashLogNotExists_ReturnsError(self, mock_retrieve_log_path): + mock_retrieve_log_path.return_value = 'mock_log_path' + mock_logname = 'mock_log.log' + mock_workspace = mock.MagicMock() + expected = f"-- Error reading editor.log" + + assert expected in editor_test_utils.retrieve_editor_log_content(0, mock_logname, mock_workspace) + + def test_RetrieveLastRunTestIndexFromOutput_SecondTestFailed_Returns0(self): + mock_test = mock.MagicMock() + mock_test.__name__ = 'mock_test_name' + mock_test_list = [mock_test] + mock_editor_output = 'mock_test_name\n' \ + 'mock_test_name_1' + + assert 0 == editor_test_utils.retrieve_last_run_test_index_from_output(mock_test_list, mock_editor_output) + + def test_RetrieveLastRunTestIndexFromOutput_TenthTestFailed_Returns9(self): + mock_test_list = [] + mock_editor_output = '' + for x in range(10): + mock_test = mock.MagicMock() + mock_test.__name__ = f'mock_test_name_{x}' + mock_test_list.append(mock_test) + mock_editor_output += f'{mock_test.__name__}\n' + mock_editor_output += 'mock_test_name_x' + assert 9 == editor_test_utils.retrieve_last_run_test_index_from_output(mock_test_list, mock_editor_output) + + def test_RetrieveLastRunTestIndexFromOutput_FirstItemFailed_Returns0(self): + mock_test_list = [] + mock_editor_output = '' + for x in range(10): + mock_test = mock.MagicMock() + mock_test.__name__ = f'mock_test_name_{x}' + mock_test_list.append(mock_test) + + assert 0 == editor_test_utils.retrieve_last_run_test_index_from_output(mock_test_list, mock_editor_output) diff --git a/Tools/LyTestTools/tests/unit/test_o3de_editor_test.py b/Tools/LyTestTools/tests/unit/test_o3de_editor_test.py new file mode 100644 index 0000000000..3f6c23ea3d --- /dev/null +++ b/Tools/LyTestTools/tests/unit/test_o3de_editor_test.py @@ -0,0 +1,1099 @@ +""" +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 +""" +import unittest + +import pytest +import unittest.mock as mock + +import ly_test_tools +import ly_test_tools.o3de.editor_test as editor_test + +pytestmark = pytest.mark.SUITE_smoke + +class TestEditorTestBase(unittest.TestCase): + + def test_EditorSharedTest_Init_CorrectAttributes(self): + mock_editorsharedtest = editor_test.EditorSharedTest() + assert mock_editorsharedtest.is_batchable == True + assert mock_editorsharedtest.is_parallelizable == True + + def test_EditorParallelTest_Init_CorrectAttributes(self): + mock_editorsharedtest = editor_test.EditorParallelTest() + assert mock_editorsharedtest.is_batchable == False + assert mock_editorsharedtest.is_parallelizable == True + + def test_EditorBatchedTest_Init_CorrectAttributes(self): + mock_editorsharedtest = editor_test.EditorBatchedTest() + assert mock_editorsharedtest.is_batchable == True + assert mock_editorsharedtest.is_parallelizable == False + +class TestResultBase(unittest.TestCase): + + def setUp(self): + self.mock_result = editor_test.Result.Base() + + def test_GetOutputStr_HasOutput_ReturnsCorrectly(self): + self.mock_result.output = 'expected output' + assert self.mock_result.get_output_str() == 'expected output' + + def test_GetOutputStr_NoOutput_ReturnsCorrectly(self): + self.mock_result.output = None + assert self.mock_result.get_output_str() == '-- No output --' + + def test_GetEditorLogStr_HasOutput_ReturnsCorrectly(self): + self.mock_result.editor_log = 'expected log output' + assert self.mock_result.get_editor_log_str() == 'expected log output' + + def test_GetEditorLogStr_NoOutput_ReturnsCorrectly(self): + self.mock_result.editor_log = None + assert self.mock_result.get_editor_log_str() == '-- No editor log found --' + +class TestResultPass(unittest.TestCase): + + def test_Create_ValidArgs_CorrectAttributes(self): + mock_test_spec = mock.MagicMock() + mock_output = mock.MagicMock() + mock_editor_log = mock.MagicMock() + + mock_pass = editor_test.Result.Pass.create(mock_test_spec, mock_output, mock_editor_log) + assert mock_pass.test_spec == mock_test_spec + assert mock_pass.output == mock_output + assert mock_pass.editor_log == mock_editor_log + + def test_Str_ValidString_ReturnsOutput(self): + mock_test_spec = mock.MagicMock() + mock_output = 'mock_output' + mock_editor_log = mock.MagicMock() + + mock_pass = editor_test.Result.Pass.create(mock_test_spec, mock_output, mock_editor_log) + assert mock_output in str(mock_pass) + +class TestResultFail(unittest.TestCase): + + def test_Create_ValidArgs_CorrectAttributes(self): + mock_test_spec = mock.MagicMock() + mock_output = mock.MagicMock() + mock_editor_log = mock.MagicMock() + + mock_pass = editor_test.Result.Fail.create(mock_test_spec, mock_output, mock_editor_log) + assert mock_pass.test_spec == mock_test_spec + assert mock_pass.output == mock_output + assert mock_pass.editor_log == mock_editor_log + + def test_Str_ValidString_ReturnsOutput(self): + mock_test_spec = mock.MagicMock() + mock_output = 'mock_output' + mock_editor_log = 'mock_editor_log' + + mock_pass = editor_test.Result.Fail.create(mock_test_spec, mock_output, mock_editor_log) + assert mock_output in str(mock_pass) + assert mock_editor_log in str(mock_pass) + +class TestResultCrash(unittest.TestCase): + + def test_Create_ValidArgs_CorrectAttributes(self): + mock_test_spec = mock.MagicMock() + mock_output = mock.MagicMock() + mock_editor_log = mock.MagicMock() + mock_ret_code = mock.MagicMock() + mock_stacktrace = mock.MagicMock() + + mock_pass = editor_test.Result.Crash.create(mock_test_spec, mock_output, mock_ret_code, mock_stacktrace, + mock_editor_log) + assert mock_pass.test_spec == mock_test_spec + assert mock_pass.output == mock_output + assert mock_pass.editor_log == mock_editor_log + assert mock_pass.ret_code == mock_ret_code + assert mock_pass.stacktrace == mock_stacktrace + + def test_Str_ValidString_ReturnsOutput(self): + mock_test_spec = mock.MagicMock() + mock_output = 'mock_output' + mock_editor_log = 'mock_editor_log' + mock_return_code = 0 + mock_stacktrace = 'mock stacktrace' + + mock_pass = editor_test.Result.Crash.create(mock_test_spec, mock_output, mock_return_code, mock_stacktrace, + mock_editor_log) + assert mock_stacktrace in str(mock_pass) + assert mock_output in str(mock_pass) + assert mock_editor_log in str(mock_pass) + + def test_Str_MissingStackTrace_ReturnsCorrectly(self): + mock_test_spec = mock.MagicMock() + mock_output = 'mock_output' + mock_editor_log = 'mock_editor_log' + mock_return_code = 0 + mock_stacktrace = None + mock_pass = editor_test.Result.Crash.create(mock_test_spec, mock_output, mock_return_code, mock_stacktrace, + mock_editor_log) + assert mock_output in str(mock_pass) + assert mock_editor_log in str(mock_pass) + +class TestResultTimeout(unittest.TestCase): + + def test_Create_ValidArgs_CorrectAttributes(self): + mock_test_spec = mock.MagicMock() + mock_output = mock.MagicMock() + mock_editor_log = mock.MagicMock() + mock_timeout = mock.MagicMock() + + mock_pass = editor_test.Result.Timeout.create(mock_test_spec, mock_output, mock_timeout, mock_editor_log) + assert mock_pass.test_spec == mock_test_spec + assert mock_pass.output == mock_output + assert mock_pass.editor_log == mock_editor_log + assert mock_pass.time_secs == mock_timeout + + def test_Str_ValidString_ReturnsOutput(self): + mock_test_spec = mock.MagicMock() + mock_output = 'mock_output' + mock_editor_log = 'mock_editor_log' + mock_timeout = 0 + + mock_pass = editor_test.Result.Timeout.create(mock_test_spec, mock_output, mock_timeout, mock_editor_log) + assert mock_output in str(mock_pass) + assert mock_editor_log in str(mock_pass) + +class TestResultUnknown(unittest.TestCase): + + def test_Create_ValidArgs_CorrectAttributes(self): + mock_test_spec = mock.MagicMock() + mock_output = mock.MagicMock() + mock_editor_log = mock.MagicMock() + mock_extra_info = mock.MagicMock() + + mock_pass = editor_test.Result.Unknown.create(mock_test_spec, mock_output, mock_extra_info, mock_editor_log) + assert mock_pass.test_spec == mock_test_spec + assert mock_pass.output == mock_output + assert mock_pass.editor_log == mock_editor_log + assert mock_pass.extra_info == mock_extra_info + + def test_Str_ValidString_ReturnsOutput(self): + mock_test_spec = mock.MagicMock() + mock_output = 'mock_output' + mock_editor_log = 'mock_editor_log' + mock_extra_info = 'mock extra info' + + mock_pass = editor_test.Result.Unknown.create(mock_test_spec, mock_output, mock_extra_info, mock_editor_log) + assert mock_output in str(mock_pass) + assert mock_editor_log in str(mock_pass) + +class TestEditorTestSuite(unittest.TestCase): + + @mock.patch('ly_test_tools.o3de.editor_test_utils.kill_all_ly_processes') + def test_EditorTestData_ValidAP_TeardownAPOnce(self, mock_kill_processes): + mock_editor_test_suite = editor_test.EditorTestSuite() + mock_test_data_generator = mock_editor_test_suite._editor_test_data(mock.MagicMock()) + mock_asset_processor = mock.MagicMock() + for test_data in mock_test_data_generator: + test_data.asset_processor = mock_asset_processor + mock_asset_processor.stop.assert_called_once_with(1) + mock_asset_processor.teardown.assert_called() + assert test_data.asset_processor is None + mock_kill_processes.assert_called_once_with(include_asset_processor=True) + + @mock.patch('ly_test_tools.o3de.editor_test_utils.kill_all_ly_processes') + def test_EditorTestData_NoAP_NoTeardownAP(self, mock_kill_processes): + mock_editor_test_suite = editor_test.EditorTestSuite() + mock_test_data_generator = mock_editor_test_suite._editor_test_data(mock.MagicMock()) + for test_data in mock_test_data_generator: + test_data.asset_processor = None + mock_kill_processes.assert_called_once_with(include_asset_processor=False) + + @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite.filter_session_shared_tests') + def test_PytestCustomModifyItems_FunctionsMatch_AddsRunners(self, mock_filter_tests): + class MockTestSuite(editor_test.EditorTestSuite): + pass + mock_func_1 = mock.MagicMock() + mock_test = mock.MagicMock() + runner_1 = editor_test.EditorTestSuite.Runner('mock_runner_1', mock_func_1, [mock_test]) + mock_run_pytest_func = mock.MagicMock() + runner_1.run_pytestfunc = mock_run_pytest_func + mock_result_pytestfuncs = [mock.MagicMock()] + runner_1.result_pytestfuncs = mock_result_pytestfuncs + mock_items = [] + mock_items.extend(mock_result_pytestfuncs) + + MockTestSuite._runners = [runner_1] + mock_test_1 = mock.MagicMock() + mock_test_2 = mock.MagicMock() + mock_filter_tests.return_value = [mock_test_1, mock_test_2] + + MockTestSuite.pytest_custom_modify_items(mock.MagicMock(), mock_items, mock.MagicMock()) + assert mock_items == [mock_run_pytest_func, mock_result_pytestfuncs[0]] + + def test_GetSingleTests_NoSingleTests_EmptyList(self): + class MockTestSuite(editor_test.EditorTestSuite): + pass + mock_test_suite = MockTestSuite() + tests = mock_test_suite.get_single_tests() + assert len(tests) == 0 + + def test_GetSingleTests_OneSingleTests_ReturnsOne(self): + class MockTestSuite(editor_test.EditorTestSuite): + class MockSingleTest(editor_test.EditorSingleTest): + pass + mock_test_suite = MockTestSuite() + tests = mock_test_suite.get_single_tests() + assert len(tests) == 1 + assert tests[0].__name__ == "MockSingleTest" + assert issubclass(tests[0], editor_test.EditorSingleTest) + + def test_GetSingleTests_AllTests_ReturnsOnlySingles(self): + class MockTestSuite(editor_test.EditorTestSuite): + class MockSingleTest(editor_test.EditorSingleTest): + pass + class MockAnotherSingleTest(editor_test.EditorSingleTest): + pass + class MockNotSingleTest(editor_test.EditorSharedTest): + pass + mock_test_suite = MockTestSuite() + tests = mock_test_suite.get_single_tests() + assert len(tests) == 2 + for test in tests: + assert issubclass(test, editor_test.EditorSingleTest) + + def test_GetSharedTests_NoSharedTests_EmptyList(self): + class MockTestSuite(editor_test.EditorTestSuite): + pass + mock_test_suite = MockTestSuite() + tests = mock_test_suite.get_shared_tests() + assert len(tests) == 0 + + def test_GetSharedTests_OneSharedTests_ReturnsOne(self): + class MockTestSuite(editor_test.EditorTestSuite): + class MockSharedTest(editor_test.EditorSharedTest): + pass + mock_test_suite = MockTestSuite() + tests = mock_test_suite.get_shared_tests() + assert len(tests) == 1 + assert tests[0].__name__ == 'MockSharedTest' + assert issubclass(tests[0], editor_test.EditorSharedTest) + + def test_GetSharedTests_AllTests_ReturnsOnlyShared(self): + class MockTestSuite(editor_test.EditorTestSuite): + class MockSharedTest(editor_test.EditorSharedTest): + pass + class MockAnotherSharedTest(editor_test.EditorSharedTest): + pass + class MockNotSharedTest(editor_test.EditorSingleTest): + pass + mock_test_suite = MockTestSuite() + tests = mock_test_suite.get_shared_tests() + assert len(tests) == 2 + for test in tests: + assert issubclass(test, editor_test.EditorSharedTest) + + @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite.filter_session_shared_tests') + @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite.get_shared_tests') + def test_GetSessionSharedTests_Valid_CallsCorrectly(self, mock_get_shared_tests, mock_filter_session): + editor_test.EditorTestSuite.get_session_shared_tests(mock.MagicMock()) + assert mock_get_shared_tests.called + assert mock_filter_session.called + + @mock.patch('ly_test_tools.o3de.editor_test.skipping_pytest_runtest_setup', mock.MagicMock()) + def test_FilterSessionSharedTests_OneSharedTest_ReturnsOne(self): + def mock_test(): + pass + mock_test.originalname = 'mock_test' + mock_test.__name__ = mock_test.originalname + mock_session_items = [mock_test] + mock_shared_tests = [mock_test] + + selected_tests = editor_test.EditorTestSuite.filter_session_shared_tests(mock_session_items, mock_shared_tests) + assert selected_tests == mock_session_items + assert len(selected_tests) == 1 + + @mock.patch('ly_test_tools.o3de.editor_test.skipping_pytest_runtest_setup', mock.MagicMock()) + def test_FilterSessionSharedTests_ManyTests_ReturnsCorrectTests(self): + def mock_test(): + pass + def mock_test_2(): + pass + def mock_test_3(): + pass + mock_test.originalname = 'mock_test' + mock_test.__name__ = mock_test.originalname + mock_test_2.originalname = 'mock_test_2' + mock_test_2.__name__ = mock_test_2.originalname + mock_test_3.originalname = 'mock_test_3' + mock_test_3.__name__ = mock_test_3.originalname + mock_session_items = [mock_test, mock_test_2] + mock_shared_tests = [mock_test, mock_test_2, mock_test_3] + + selected_tests = editor_test.EditorTestSuite.filter_session_shared_tests(mock_session_items, mock_shared_tests) + assert selected_tests == mock_session_items + + @mock.patch('ly_test_tools.o3de.editor_test.skipping_pytest_runtest_setup') + def test_FilterSessionSharedTests_SkipOneTest_ReturnsCorrectTests(self, mock_skip): + def mock_test(): + pass + def mock_test_2(): + pass + def mock_test_3(): + pass + mock_skip.side_effect = [True, Exception] + mock_test.originalname = 'mock_test' + mock_test.__name__ = mock_test.originalname + mock_test_2.originalname = 'mock_test_2' + mock_test_2.__name__ = mock_test_2.originalname + mock_test_3.originalname = 'mock_test_3' + mock_test_3.__name__ = mock_test_3.originalname + mock_session_items = [mock_test, mock_test_2] + mock_shared_tests = [mock_test, mock_test_2, mock_test_3] + + selected_tests = editor_test.EditorTestSuite.filter_session_shared_tests(mock_session_items, mock_shared_tests) + assert selected_tests == [mock_test] + + @mock.patch('ly_test_tools.o3de.editor_test.skipping_pytest_runtest_setup', mock.MagicMock(side_effect=Exception)) + def test_FilterSessionSharedTests_ExceptionDuringSkipSetup_SkipsAddingTest(self): + def mock_test(): + pass + mock_test.originalname = 'mock_test' + mock_test.__name__ = mock_test.originalname + mock_session_items = [mock_test] + mock_shared_tests = [mock_test] + + selected_tests = editor_test.EditorTestSuite.filter_session_shared_tests(mock_session_items, mock_shared_tests) + assert len(selected_tests) == 0 + + def test_FilterSharedTests_TrueParams_ReturnsTrueTests(self): + mock_test = mock.MagicMock() + mock_test.is_batchable = True + mock_test.is_parallelizable = True + mock_test_2 = mock.MagicMock() + mock_test_2.is_batchable = False + mock_test_2.is_parallelizable = False + mock_shared_tests = [mock_test, mock_test_2] + + filtered_tests = editor_test.EditorTestSuite.filter_shared_tests( + mock_shared_tests, is_batchable=True, is_parallelizable=True) + assert filtered_tests == [mock_test] + + def test_FilterSharedTests_FalseParams_ReturnsFalseTests(self): + mock_test = mock.MagicMock() + mock_test.is_batchable = True + mock_test.is_parallelizable = True + mock_test_2 = mock.MagicMock() + mock_test_2.is_batchable = False + mock_test_2.is_parallelizable = False + mock_shared_tests = [mock_test, mock_test_2] + + filtered_tests = editor_test.EditorTestSuite.filter_shared_tests( + mock_shared_tests, is_batchable=False, is_parallelizable=False) + assert filtered_tests == [mock_test_2] + +class TestUtils(unittest.TestCase): + + @mock.patch('ly_test_tools.o3de.editor_test_utils.kill_all_ly_processes') + def test_PrepareAssetProcessor_APExists_StartsAP(self, mock_kill_processes): + mock_test_suite = editor_test.EditorTestSuite() + mock_workspace = mock.MagicMock() + mock_editor_data = mock.MagicMock() + mock_ap = mock.MagicMock() + mock_editor_data.asset_processor = mock_ap + + mock_test_suite._prepare_asset_processor(mock_workspace, mock_editor_data) + assert mock_ap.start.called + assert not mock_kill_processes.called + + @mock.patch('ly_test_tools.o3de.asset_processor.AssetProcessor.start') + @mock.patch('ly_test_tools.environment.process_utils.process_exists') + @mock.patch('ly_test_tools.o3de.editor_test_utils.kill_all_ly_processes') + def test_PrepareAssetProcessor_NoAP_KillAndCreateAP(self, mock_kill_processes, mock_proc_exists, mock_start): + mock_test_suite = editor_test.EditorTestSuite() + mock_workspace = mock.MagicMock() + mock_editor_data = mock.MagicMock() + mock_editor_data.asset_processor = None + mock_proc_exists.return_value = False + + mock_test_suite._prepare_asset_processor(mock_workspace, mock_editor_data) + mock_kill_processes.assert_called_with(include_asset_processor=True) + assert isinstance(mock_editor_data.asset_processor, ly_test_tools.o3de.asset_processor.AssetProcessor) + assert mock_start.called + + @mock.patch('ly_test_tools.o3de.asset_processor.AssetProcessor.start') + @mock.patch('ly_test_tools.environment.process_utils.process_exists') + @mock.patch('ly_test_tools.o3de.editor_test_utils.kill_all_ly_processes') + def test_PrepareAssetProcessor_NoAPButProcExists_NoKill(self, mock_kill_processes, mock_proc_exists, mock_start): + mock_test_suite = editor_test.EditorTestSuite() + mock_workspace = mock.MagicMock() + mock_editor_data = mock.MagicMock() + mock_editor_data.asset_processor = None + mock_proc_exists.return_value = True + + mock_test_suite._prepare_asset_processor(mock_workspace, mock_editor_data) + mock_kill_processes.assert_called_with(include_asset_processor=False) + assert not mock_start.called + assert mock_editor_data.asset_processor is None + + + @mock.patch('ly_test_tools.o3de.asset_processor.AssetProcessor.start') + @mock.patch('ly_test_tools.environment.process_utils.process_exists') + @mock.patch('ly_test_tools.o3de.editor_test_utils.kill_all_ly_processes') + def test_PrepareAssetProcessor_NoAPButProcExists_NoKill(self, mock_kill_processes, mock_proc_exists, mock_start): + mock_test_suite = editor_test.EditorTestSuite() + mock_workspace = mock.MagicMock() + mock_editor_data = mock.MagicMock() + mock_editor_data.asset_processor = None + mock_proc_exists.return_value = True + + mock_test_suite._prepare_asset_processor(mock_workspace, mock_editor_data) + mock_kill_processes.assert_called_with(include_asset_processor=False) + assert not mock_start.called + assert mock_editor_data.asset_processor is None + + @mock.patch('ly_test_tools.o3de.editor_test_utils.kill_all_ly_processes') + @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._prepare_asset_processor') + def test_SetupEditorTest_ValidArgs_CallsCorrectly(self, mock_prepare_ap, mock_kill_processes): + mock_test_suite = editor_test.EditorTestSuite() + mock_editor = mock.MagicMock() + mock_test_suite._setup_editor_test(mock_editor, mock.MagicMock(), mock.MagicMock()) + + assert mock_editor.configure_settings.called + assert mock_prepare_ap.called + mock_kill_processes.assert_called_once_with(include_asset_processor=False) + + @mock.patch('ly_test_tools.o3de.editor_test.Result.Pass.create') + @mock.patch('ly_test_tools.o3de.editor_test_utils.get_module_filename') + def test_GetResultsUsingOutput_ValidJsonSuccess_CreatesPassResult(self, mock_get_module, mock_create): + mock_get_module.return_value = 'mock_module_name' + mock_test_suite = editor_test.EditorTestSuite() + mock_test = mock.MagicMock() + mock_test.__name__ = 'mock_test_name' + mock_test_list = [mock_test] + mock_output = 'JSON_START(' \ + '{"name": "mock_module_name", "output": "mock_std_out", "success": "mock_success_data"}' \ + ')JSON_END' + mock_pass = mock.MagicMock() + mock_create.return_value = mock_pass + + results = mock_test_suite._get_results_using_output(mock_test_list, mock_output, '') + assert mock_create.called + assert len(results) == 1 + assert results[mock_test.__name__] == mock_pass + + @mock.patch('ly_test_tools.o3de.editor_test.Result.Fail.create') + @mock.patch('ly_test_tools.o3de.editor_test_utils.get_module_filename') + def test_GetResultsUsingOutput_ValidJsonFail_CreatesFailResult(self, mock_get_module, mock_create): + mock_get_module.return_value = 'mock_module_name' + mock_test_suite = editor_test.EditorTestSuite() + mock_test = mock.MagicMock() + mock_test.__name__ = 'mock_test_name' + mock_test_list = [mock_test] + mock_output = 'JSON_START(' \ + '{"name": "mock_module_name", "output": "mock_std_out", "failed": "mock_fail_data", "success": ""}' \ + ')JSON_END' + mock_fail = mock.MagicMock() + mock_create.return_value = mock_fail + + results = mock_test_suite._get_results_using_output(mock_test_list, mock_output, '') + assert mock_create.called + assert len(results) == 1 + assert results[mock_test.__name__] == mock_fail + + @mock.patch('ly_test_tools.o3de.editor_test.Result.Unknown.create') + @mock.patch('ly_test_tools.o3de.editor_test_utils.get_module_filename') + def test_GetResultsUsingOutput_ModuleNotInLog_CreatesUnknownResult(self, mock_get_module, mock_create): + mock_get_module.return_value = 'different_module_name' + mock_test_suite = editor_test.EditorTestSuite() + mock_test = mock.MagicMock() + mock_test.__name__ = 'mock_test_name' + mock_test_list = [mock_test] + mock_output = 'JSON_START(' \ + '{"name": "mock_module_name", "output": "mock_std_out", "failed": "mock_fail_data"}' \ + ')JSON_END' + mock_unknown = mock.MagicMock() + mock_create.return_value = mock_unknown + + results = mock_test_suite._get_results_using_output(mock_test_list, mock_output, '') + assert mock_create.called + assert len(results) == 1 + assert results[mock_test.__name__] == mock_unknown + + @mock.patch('ly_test_tools.o3de.editor_test.Result.Pass.create') + @mock.patch('ly_test_tools.o3de.editor_test.Result.Fail.create') + @mock.patch('ly_test_tools.o3de.editor_test.Result.Unknown.create') + @mock.patch('ly_test_tools.o3de.editor_test_utils.get_module_filename') + def test_GetResultsUsingOutput_MultipleTests_CreatesCorrectResults(self, mock_get_module, mock_create_unknown, + mock_create_fail, mock_create_pass): + mock_get_module.side_effect = ['mock_module_name_pass', 'mock_module_name_fail', 'different_module_name'] + mock_test_suite = editor_test.EditorTestSuite() + mock_test_pass = mock.MagicMock() + mock_test_pass.__name__ = 'mock_test_name_pass' + mock_test_fail = mock.MagicMock() + mock_test_fail.__name__ = 'mock_test_name_fail' + mock_test_unknown = mock.MagicMock() + mock_test_unknown.__name__ = 'mock_test_name_unknown' + mock_test_list = [mock_test_pass, mock_test_fail, mock_test_unknown] + mock_output = 'JSON_START(' \ + '{"name": "mock_module_name_pass", "output": "mock_std_out", "success": "mock_success_data"}' \ + ')JSON_END' \ + 'JSON_START(' \ + '{"name": "mock_module_name_fail", "output": "mock_std_out", "failed": "mock_fail_data", "success": ""}' \ + ')JSON_END' \ + 'JSON_START(' \ + '{"name": "mock_module_name_unknown", "output": "mock_std_out", "failed": "mock_fail_data", "success": ""}' \ + ')JSON_END' + mock_editor_log = 'JSON_START(' \ + '{"name": "mock_module_name_pass"}' \ + ')JSON_END' \ + 'JSON_START(' \ + '{"name": "mock_module_name_fail"}' \ + ')JSON_END' + mock_unknown = mock.MagicMock() + mock_pass = mock.MagicMock() + mock_fail = mock.MagicMock() + mock_create_unknown.return_value = mock_unknown + mock_create_pass.return_value = mock_pass + mock_create_fail.return_value = mock_fail + + results = mock_test_suite._get_results_using_output(mock_test_list, mock_output, mock_editor_log) + mock_create_pass.assert_called_with( + mock_test_pass, 'mock_std_out', 'JSON_START({"name": "mock_module_name_pass"})JSON_END') + mock_create_fail.assert_called_with( + mock_test_fail, 'mock_std_out', 'JSON_START({"name": "mock_module_name_fail"})JSON_END') + mock_create_unknown.assert_called_with( + mock_test_unknown, mock_output, "Couldn't find any test run information on stdout", mock_editor_log) + assert len(results) == 3 + assert results[mock_test_pass.__name__] == mock_pass + assert results[mock_test_fail.__name__] == mock_fail + assert results[mock_test_unknown.__name__] == mock_unknown + + @mock.patch('builtins.print') + def test_ReportResult_TestPassed_ReportsCorrectly(self, mock_print): + mock_test_name = 'mock name' + mock_pass = ly_test_tools.o3de.editor_test.Result.Pass() + ly_test_tools.o3de.editor_test.EditorTestSuite._report_result(mock_test_name, mock_pass) + mock_print.assert_called_with(f'Test {mock_test_name}:\nTest Passed\n------------\n| Output |\n------------\n' + f'-- No output --\n') + + @mock.patch('pytest.fail') + def test_ReportResult_TestFailed_FailsCorrectly(self, mock_pytest_fail): + mock_fail = ly_test_tools.o3de.editor_test.Result.Fail() + + ly_test_tools.o3de.editor_test.EditorTestSuite._report_result('mock_test_name', mock_fail) + mock_pytest_fail.assert_called_with('Test mock_test_name:\nTest FAILED\n------------\n| Output |' + '\n------------\n-- No output --\n--------------\n| Editor log |' + '\n--------------\n-- No editor log found --\n') + +class TestRunningTests(unittest.TestCase): + + @mock.patch('ly_test_tools.o3de.editor_test.Result.Pass.create') + @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._get_results_using_output') + @mock.patch('ly_test_tools.o3de.editor_test_utils.retrieve_editor_log_content') + @mock.patch('ly_test_tools.o3de.editor_test_utils.retrieve_log_path') + @mock.patch('ly_test_tools.o3de.editor_test_utils.get_testcase_module_filepath') + @mock.patch('ly_test_tools.o3de.editor_test_utils.cycle_crash_report') + def test_ExecEditorTest_TestSucceeds_ReturnsPass(self, mock_cycle_crash, mock_get_testcase_filepath, + mock_retrieve_log, mock_retrieve_editor_log, + mock_get_output_results, mock_create): + mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite() + mock_workspace = mock.MagicMock() + mock_editor = mock.MagicMock() + mock_test_spec = mock.MagicMock() + mock_test_spec.__name__ = 'mock_test_name' + mock_editor.get_returncode.return_value = 0 + mock_get_output_results.return_value = {} + mock_pass = mock.MagicMock() + mock_create.return_value = mock_pass + + results = mock_test_suite._exec_editor_test(mock.MagicMock(), mock_workspace, mock_editor, 0, + 'mock_log_name', mock_test_spec, []) + assert mock_cycle_crash.called + assert mock_editor.start.called + assert mock_create.called + assert results == {mock_test_spec.__name__: mock_pass} + + @mock.patch('ly_test_tools.o3de.editor_test.Result.Fail.create') + @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._get_results_using_output') + @mock.patch('ly_test_tools.o3de.editor_test_utils.retrieve_editor_log_content') + @mock.patch('ly_test_tools.o3de.editor_test_utils.retrieve_log_path') + @mock.patch('ly_test_tools.o3de.editor_test_utils.get_testcase_module_filepath') + @mock.patch('ly_test_tools.o3de.editor_test_utils.cycle_crash_report') + def test_ExecEditorTest_TestFails_ReturnsFail(self, mock_cycle_crash, mock_get_testcase_filepath, + mock_retrieve_log, mock_retrieve_editor_log, + mock_get_output_results, mock_create): + mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite() + mock_workspace = mock.MagicMock() + mock_editor = mock.MagicMock() + mock_test_spec = mock.MagicMock() + mock_test_spec.__name__ = 'mock_test_name' + mock_editor.get_returncode.return_value = 15 + mock_get_output_results.return_value = {} + mock_fail = mock.MagicMock() + mock_create.return_value = mock_fail + + results = mock_test_suite._exec_editor_test(mock.MagicMock(), mock_workspace, mock_editor, 0, + 'mock_log_name', mock_test_spec, []) + assert mock_cycle_crash.called + assert mock_editor.start.called + assert mock_create.called + assert results == {mock_test_spec.__name__: mock_fail} + + @mock.patch('ly_test_tools.o3de.editor_test.Result.Crash.create') + @mock.patch('ly_test_tools.o3de.editor_test_utils.retrieve_crash_output') + @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._get_results_using_output') + @mock.patch('ly_test_tools.o3de.editor_test_utils.retrieve_editor_log_content') + @mock.patch('ly_test_tools.o3de.editor_test_utils.retrieve_log_path') + @mock.patch('ly_test_tools.o3de.editor_test_utils.get_testcase_module_filepath') + @mock.patch('ly_test_tools.o3de.editor_test_utils.cycle_crash_report') + def test_ExecEditorTest_TestCrashes_ReturnsCrash(self, mock_cycle_crash, mock_get_testcase_filepath, + mock_retrieve_log, mock_retrieve_editor_log, + mock_get_output_results, mock_retrieve_crash, mock_create): + mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite() + mock_workspace = mock.MagicMock() + mock_editor = mock.MagicMock() + mock_test_spec = mock.MagicMock() + mock_test_spec.__name__ = 'mock_test_name' + mock_editor.get_returncode.return_value = 1 + mock_get_output_results.return_value = {} + mock_crash = mock.MagicMock() + mock_create.return_value = mock_crash + + results = mock_test_suite._exec_editor_test(mock.MagicMock(), mock_workspace, mock_editor, 0, + 'mock_log_name', mock_test_spec, []) + assert mock_cycle_crash.call_count == 2 + assert mock_editor.start.called + assert mock_retrieve_crash.called + assert mock_create.called + assert results == {mock_test_spec.__name__: mock_crash} + + @mock.patch('ly_test_tools.o3de.editor_test.Result.Timeout.create') + @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._get_results_using_output') + @mock.patch('ly_test_tools.o3de.editor_test_utils.retrieve_editor_log_content') + @mock.patch('ly_test_tools.o3de.editor_test_utils.retrieve_log_path') + @mock.patch('ly_test_tools.o3de.editor_test_utils.get_testcase_module_filepath') + @mock.patch('ly_test_tools.o3de.editor_test_utils.cycle_crash_report') + def test_ExecEditorTest_TestTimeout_ReturnsTimeout(self, mock_cycle_crash, mock_get_testcase_filepath, + mock_retrieve_log, mock_retrieve_editor_log, + mock_get_output_results, mock_create): + mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite() + mock_workspace = mock.MagicMock() + mock_editor = mock.MagicMock() + mock_test_spec = mock.MagicMock() + mock_test_spec.__name__ = 'mock_test_name' + mock_editor.wait.side_effect = ly_test_tools.launchers.exceptions.WaitTimeoutError() + mock_get_output_results.return_value = {} + mock_timeout = mock.MagicMock() + mock_create.return_value = mock_timeout + + results = mock_test_suite._exec_editor_test(mock.MagicMock(), mock_workspace, mock_editor, 0, + 'mock_log_name', mock_test_spec, []) + assert mock_cycle_crash.called + assert mock_editor.start.called + assert mock_editor.kill.called + assert mock_create.called + assert results == {mock_test_spec.__name__: mock_timeout} + + @mock.patch('ly_test_tools.o3de.editor_test.Result.Pass.create') + @mock.patch('ly_test_tools.o3de.editor_test_utils.retrieve_editor_log_content') + @mock.patch('ly_test_tools.o3de.editor_test_utils.retrieve_log_path') + @mock.patch('ly_test_tools.o3de.editor_test_utils.get_testcase_module_filepath') + @mock.patch('ly_test_tools.o3de.editor_test_utils.cycle_crash_report') + def test_ExecEditorMultitest_AllTestsPass_ReturnsPasses(self, mock_cycle_crash, mock_get_testcase_filepath, + mock_retrieve_log, mock_retrieve_editor_log, mock_create): + mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite() + mock_workspace = mock.MagicMock() + mock_editor = mock.MagicMock() + mock_editor.get_returncode.return_value = 0 + mock_test_spec = mock.MagicMock() + mock_test_spec.__name__ = 'mock_test_name' + mock_test_spec_2 = mock.MagicMock() + mock_test_spec_2.__name__ = 'mock_test_name_2' + mock_test_spec_list = [mock_test_spec, mock_test_spec_2] + mock_get_testcase_filepath.side_effect = ['mock_path', 'mock_path_2'] + mock_pass = mock.MagicMock() + mock_pass_2 = mock.MagicMock() + mock_create.side_effect = [mock_pass, mock_pass_2] + + results = mock_test_suite._exec_editor_multitest(mock.MagicMock(), mock_workspace, mock_editor, 0, + 'mock_log_name', mock_test_spec_list, []) + assert results == {mock_test_spec.__name__: mock_pass, mock_test_spec_2.__name__: mock_pass_2} + assert mock_cycle_crash.called + assert mock_create.call_count == 2 + + @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._get_results_using_output') + @mock.patch('ly_test_tools.o3de.editor_test_utils.retrieve_editor_log_content') + @mock.patch('ly_test_tools.o3de.editor_test_utils.retrieve_log_path') + @mock.patch('ly_test_tools.o3de.editor_test_utils.get_testcase_module_filepath') + @mock.patch('ly_test_tools.o3de.editor_test_utils.cycle_crash_report') + def test_ExecEditorMultitest_OneFailure_CallsCorrectFunc(self, mock_cycle_crash, mock_get_testcase_filepath, + mock_retrieve_log, mock_retrieve_editor_log, mock_get_results): + mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite() + mock_workspace = mock.MagicMock() + mock_editor = mock.MagicMock() + mock_editor.get_returncode.return_value = 15 + mock_test_spec = mock.MagicMock() + mock_test_spec_2 = mock.MagicMock() + mock_test_spec_list = [mock_test_spec, mock_test_spec_2] + mock_get_testcase_filepath.side_effect = ['mock_path', 'mock_path_2'] + mock_get_results.return_value = {'mock_test_name': mock.MagicMock(), 'mock_test_name_2': mock.MagicMock()} + + results = mock_test_suite._exec_editor_multitest(mock.MagicMock(), mock_workspace, mock_editor, 0, + 'mock_log_name', mock_test_spec_list, []) + assert mock_cycle_crash.called + assert mock_get_results.called + + @mock.patch('ly_test_tools.o3de.editor_test.Result.Crash.create') + @mock.patch('ly_test_tools.o3de.editor_test_utils.retrieve_crash_output') + @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._get_results_using_output') + @mock.patch('ly_test_tools.o3de.editor_test_utils.retrieve_editor_log_content') + @mock.patch('ly_test_tools.o3de.editor_test_utils.retrieve_log_path') + @mock.patch('ly_test_tools.o3de.editor_test_utils.get_testcase_module_filepath') + @mock.patch('ly_test_tools.o3de.editor_test_utils.cycle_crash_report') + def test_ExecEditorMultitest_OneCrash_ReportsOnUnknownResult(self, mock_cycle_crash, mock_get_testcase_filepath, + mock_retrieve_log, mock_retrieve_editor_log, + mock_get_results, mock_retrieve_crash, mock_create): + mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite() + mock_workspace = mock.MagicMock() + mock_editor = mock.MagicMock() + mock_editor.get_returncode.return_value = 1 + mock_test_spec = mock.MagicMock() + mock_test_spec.__name__ = 'mock_test_name' + mock_test_spec_2 = mock.MagicMock() + mock_test_spec_2.__name__ = 'mock_test_name_2' + mock_test_spec_list = [mock_test_spec, mock_test_spec_2] + mock_unknown_result = ly_test_tools.o3de.editor_test.Result.Unknown() + mock_unknown_result.test_spec = mock.MagicMock() + mock_unknown_result.editor_log = mock.MagicMock() + mock_get_testcase_filepath.side_effect = ['mock_path', 'mock_path_2'] + mock_get_results.return_value = {mock_test_spec.__name__: mock_unknown_result, + mock_test_spec_2.__name__: mock.MagicMock()} + mock_crash = mock.MagicMock() + mock_create.return_value = mock_crash + + results = mock_test_suite._exec_editor_multitest(mock.MagicMock(), mock_workspace, mock_editor, 0, + 'mock_log_name', mock_test_spec_list, []) + assert mock_cycle_crash.call_count == 2 + assert mock_get_results.called + assert results[mock_test_spec.__name__] == mock_crash + + @mock.patch('ly_test_tools.o3de.editor_test.Result.Crash.create') + @mock.patch('ly_test_tools.o3de.editor_test_utils.retrieve_crash_output') + @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._get_results_using_output') + @mock.patch('ly_test_tools.o3de.editor_test_utils.retrieve_editor_log_content') + @mock.patch('ly_test_tools.o3de.editor_test_utils.retrieve_log_path') + @mock.patch('ly_test_tools.o3de.editor_test_utils.get_testcase_module_filepath') + @mock.patch('ly_test_tools.o3de.editor_test_utils.cycle_crash_report') + def test_ExecEditorMultitest_ManyUnknown_ReportsUnknownResults(self, mock_cycle_crash, mock_get_testcase_filepath, + mock_retrieve_log, mock_retrieve_editor_log, + mock_get_results, mock_retrieve_crash, mock_create): + mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite() + mock_workspace = mock.MagicMock() + mock_editor = mock.MagicMock() + mock_editor.get_returncode.return_value = 1 + mock_test_spec = mock.MagicMock() + mock_test_spec.__name__ = 'mock_test_name' + mock_test_spec_2 = mock.MagicMock() + mock_test_spec_2.__name__ = 'mock_test_name_2' + mock_test_spec_list = [mock_test_spec, mock_test_spec_2] + mock_unknown_result = ly_test_tools.o3de.editor_test.Result.Unknown() + mock_unknown_result.__name__ = 'mock_test_name' + mock_unknown_result.test_spec = mock.MagicMock() + mock_unknown_result.test_spec.__name__ = 'mock_test_spec_name' + mock_unknown_result.editor_log = mock.MagicMock() + mock_get_testcase_filepath.side_effect = ['mock_path', 'mock_path_2'] + mock_get_results.return_value = {mock_test_spec.__name__: mock_unknown_result, + mock_test_spec_2.__name__: mock_unknown_result} + mock_crash = mock.MagicMock() + mock_create.return_value = mock_crash + + results = mock_test_suite._exec_editor_multitest(mock.MagicMock(), mock_workspace, mock_editor, 0, + 'mock_log_name', mock_test_spec_list, []) + assert mock_cycle_crash.call_count == 2 + assert mock_get_results.called + assert results[mock_test_spec.__name__] == mock_crash + assert results[mock_test_spec_2.__name__].extra_info + + @mock.patch('ly_test_tools.o3de.editor_test.Result.Timeout.create') + @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._get_results_using_output') + @mock.patch('ly_test_tools.o3de.editor_test_utils.retrieve_editor_log_content') + @mock.patch('ly_test_tools.o3de.editor_test_utils.retrieve_log_path') + @mock.patch('ly_test_tools.o3de.editor_test_utils.get_testcase_module_filepath') + @mock.patch('ly_test_tools.o3de.editor_test_utils.cycle_crash_report') + def test_ExecEditorMultitest_EditorTimeout_ReportsCorrectly(self, mock_cycle_crash, mock_get_testcase_filepath, + mock_retrieve_log, mock_retrieve_editor_log, + mock_get_results, mock_create): + mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite() + mock_workspace = mock.MagicMock() + mock_editor = mock.MagicMock() + mock_editor.wait.side_effect = ly_test_tools.launchers.exceptions.WaitTimeoutError() + mock_test_spec = mock.MagicMock() + mock_test_spec.__name__ = 'mock_test_name' + mock_test_spec_2 = mock.MagicMock() + mock_test_spec_2.__name__ = 'mock_test_name_2' + mock_test_spec_list = [mock_test_spec, mock_test_spec_2] + mock_unknown_result = ly_test_tools.o3de.editor_test.Result.Unknown() + mock_unknown_result.test_spec = mock.MagicMock() + mock_unknown_result.test_spec.__name__ = 'mock_test_spec_name' + mock_unknown_result.output = mock.MagicMock() + mock_unknown_result.editor_log = mock.MagicMock() + mock_get_testcase_filepath.side_effect = ['mock_path', 'mock_path_2'] + mock_get_results.return_value = {mock_test_spec.__name__: mock_unknown_result, + mock_test_spec_2.__name__: mock_unknown_result} + mock_timeout = mock.MagicMock() + mock_create.return_value = mock_timeout + + results = mock_test_suite._exec_editor_multitest(mock.MagicMock(), mock_workspace, mock_editor, 0, + 'mock_log_name', mock_test_spec_list, []) + assert mock_cycle_crash.called + assert mock_get_results.called + assert results[mock_test_spec_2.__name__].extra_info + assert results[mock_test_spec.__name__] == mock_timeout + + @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._report_result') + @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._exec_editor_test') + @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._setup_editor_test') + def test_RunSingleTest_ValidTest_ReportsResults(self, mock_setup_test, mock_exec_editor_test, mock_report_result): + mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite() + mock_test_data = mock.MagicMock() + mock_test_spec = mock.MagicMock() + mock_result = mock.MagicMock() + mock_test_name = 'mock_test_result' + mock_exec_editor_test.return_value = {mock_test_name: mock_result} + + mock_test_suite._run_single_test(mock.MagicMock(), mock.MagicMock(), mock.MagicMock(), mock_test_data, + mock_test_spec) + + assert mock_setup_test.called + assert mock_exec_editor_test.called + assert mock_test_data.results.update.called + mock_report_result.assert_called_with(mock_test_name, mock_result) + + @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._exec_editor_multitest') + @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._setup_editor_test') + def test_RunBatchedTests_ValidTests_CallsCorrectly(self, mock_setup_test, mock_exec_multitest): + mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite() + mock_test_data = mock.MagicMock() + + mock_test_suite._run_batched_tests(mock.MagicMock(), mock.MagicMock(), mock.MagicMock(), mock_test_data, + mock.MagicMock(), []) + + assert mock_setup_test.called + assert mock_exec_multitest.called + assert mock_test_data.results.update.called + + @mock.patch('threading.Thread') + @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._get_number_parallel_editors') + @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._setup_editor_test') + def test_RunParallelTests_TwoTestsAndEditors_TwoThreads(self, mock_setup_test, mock_get_num_editors, mock_thread): + mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite() + mock_get_num_editors.return_value = 2 + mock_test_spec_list = [mock.MagicMock(), mock.MagicMock()] + mock_test_data = mock.MagicMock() + + mock_test_suite._run_parallel_tests(mock.MagicMock(), mock.MagicMock(), mock.MagicMock(), mock_test_data, + mock_test_spec_list, []) + + assert mock_setup_test.called + assert mock_test_data.results.update.call_count == len(mock_test_spec_list) + assert mock_thread.call_count == len(mock_test_spec_list) + + @mock.patch('threading.Thread') + @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._get_number_parallel_editors') + @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._setup_editor_test') + def test_RunParallelTests_TenTestsAndTwoEditors_TenThreads(self, mock_setup_test, mock_get_num_editors, mock_thread): + mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite() + mock_get_num_editors.return_value = 2 + mock_test_spec_list = [] + for i in range(10): + mock_test_spec_list.append(mock.MagicMock()) + mock_test_data = mock.MagicMock() + + mock_test_suite._run_parallel_tests(mock.MagicMock(), mock.MagicMock(), mock.MagicMock(), mock_test_data, + mock_test_spec_list, []) + + assert mock_setup_test.called + assert mock_test_data.results.update.call_count == len(mock_test_spec_list) + assert mock_thread.call_count == len(mock_test_spec_list) + + @mock.patch('threading.Thread') + @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._get_number_parallel_editors') + @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._setup_editor_test') + def test_RunParallelTests_TenTestsAndThreeEditors_TenThreads(self, mock_setup_test, mock_get_num_editors, + mock_thread): + mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite() + mock_get_num_editors.return_value = 3 + mock_test_spec_list = [] + for i in range(10): + mock_test_spec_list.append(mock.MagicMock()) + mock_test_data = mock.MagicMock() + + mock_test_suite._run_parallel_tests(mock.MagicMock(), mock.MagicMock(), mock.MagicMock(), mock_test_data, + mock_test_spec_list, []) + + assert mock_setup_test.called + assert mock_test_data.results.update.call_count == len(mock_test_spec_list) + assert mock_thread.call_count == len(mock_test_spec_list) + + @mock.patch('threading.Thread') + @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._get_number_parallel_editors') + @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._setup_editor_test') + def test_RunParallelBatchedTests_TwoTestsAndEditors_TwoThreads(self, mock_setup_test, mock_get_num_editors, + mock_thread): + mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite() + mock_get_num_editors.return_value = 2 + mock_test_spec_list = [mock.MagicMock(), mock.MagicMock()] + mock_test_data = mock.MagicMock() + + mock_test_suite._run_parallel_batched_tests(mock.MagicMock(), mock.MagicMock(), mock.MagicMock(), + mock_test_data, mock_test_spec_list, []) + + assert mock_setup_test.called + assert mock_test_data.results.update.call_count == len(mock_test_spec_list) + assert mock_thread.call_count == len(mock_test_spec_list) + + @mock.patch('threading.Thread') + @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._get_number_parallel_editors') + @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._setup_editor_test') + def test_RunParallelBatchedTests_TenTestsAndTwoEditors_2Threads(self, mock_setup_test, mock_get_num_editors, + mock_thread): + mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite() + mock_get_num_editors.return_value = 2 + mock_test_spec_list = [] + for i in range(10): + mock_test_spec_list.append(mock.MagicMock()) + mock_test_data = mock.MagicMock() + + mock_test_suite._run_parallel_batched_tests(mock.MagicMock(), mock.MagicMock(), mock.MagicMock(), + mock_test_data, mock_test_spec_list, []) + + assert mock_setup_test.called + assert mock_test_data.results.update.call_count == 2 + assert mock_thread.call_count == 2 + + @mock.patch('threading.Thread') + @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._get_number_parallel_editors') + @mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._setup_editor_test') + def test_RunParallelBatchedTests_TenTestsAndThreeEditors_ThreeThreads(self, mock_setup_test, mock_get_num_editors, + mock_thread): + mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite() + mock_get_num_editors.return_value = 3 + mock_test_spec_list = [] + for i in range(10): + mock_test_spec_list.append(mock.MagicMock()) + mock_test_data = mock.MagicMock() + + mock_test_suite._run_parallel_batched_tests(mock.MagicMock(), mock.MagicMock(), mock.MagicMock(), + mock_test_data, mock_test_spec_list, []) + + assert mock_setup_test.called + assert mock_test_data.results.update.call_count == 3 + assert mock_thread.call_count == 3 + + def test_GetNumberParallelEditors_ConfigExists_ReturnsConfig(self): + mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite() + mock_request = mock.MagicMock() + mock_request.config.getoption.return_value = 1 + + num_of_editors = mock_test_suite._get_number_parallel_editors(mock_request) + assert num_of_editors == 1 + + def test_GetNumberParallelEditors_ConfigNotExists_ReturnsDefault(self): + mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite() + mock_request = mock.MagicMock() + mock_request.config.getoption.return_value = None + + num_of_editors = mock_test_suite._get_number_parallel_editors(mock_request) + assert num_of_editors == mock_test_suite.get_number_parallel_editors() + +@mock.patch('_pytest.python.Class.collect') +class TestEditorTestClass(unittest.TestCase): + + def setUp(self): + mock_name = mock.MagicMock() + mock_collector = mock.MagicMock() + self.mock_test_class = ly_test_tools.o3de.editor_test.EditorTestSuite.EditorTestClass(mock_name, mock_collector) + self.mock_test_class.obj = mock.MagicMock() + single_1 = mock.MagicMock() + single_1.__name__ = 'single_1_name' + single_2 = mock.MagicMock() + single_2.__name__ = 'single_2_name' + self.mock_test_class.obj.get_single_tests.return_value = [single_1, single_2] + batch_1 = mock.MagicMock() + batch_1.__name__ = 'batch_1_name' + batch_2 = mock.MagicMock() + batch_2.__name__ = 'batch_2_name' + parallel_1 = mock.MagicMock() + parallel_1.__name__ = 'parallel_1_name' + parallel_2 = mock.MagicMock() + parallel_2.__name__ = 'parallel_2_name' + both_1 = mock.MagicMock() + both_1.__name__ = 'both_1_name' + both_2 = mock.MagicMock() + both_2.__name__ = 'both_2_name' + self.mock_test_class.obj.filter_shared_tests.side_effect = [ [batch_1, batch_2], + [parallel_1, parallel_2], + [both_1, both_2] ] + + def test_Collect_NoParallelNoBatched_RunsAsSingleTests(self, mock_collect): + self.mock_test_class.config.getoption.return_value = True + self.mock_test_class.collect() + assert self.mock_test_class.obj.single_1_name.__name__ == 'single_run' + assert self.mock_test_class.obj.single_2_name.__name__ == 'single_run' + assert self.mock_test_class.obj.batch_1_name.__name__ == 'single_run' + assert self.mock_test_class.obj.batch_2_name.__name__ == 'single_run' + assert self.mock_test_class.obj.parallel_1_name.__name__ == 'single_run' + assert self.mock_test_class.obj.parallel_2_name.__name__ == 'single_run' + assert self.mock_test_class.obj.both_1_name.__name__ == 'single_run' + assert self.mock_test_class.obj.both_2_name.__name__ == 'single_run' + + def test_Collect_AllValidTests_RunsAsInteded(self, mock_collect): + self.mock_test_class.config.getoption.return_value = False + self.mock_test_class.collect() + assert self.mock_test_class.obj.single_1_name.__name__ == 'single_run' + assert self.mock_test_class.obj.single_2_name.__name__ == 'single_run' + assert self.mock_test_class.obj.batch_1_name.__name__ == 'result' + assert self.mock_test_class.obj.batch_2_name.__name__ == 'result' + assert self.mock_test_class.obj.parallel_1_name.__name__ == 'result' + assert self.mock_test_class.obj.parallel_2_name.__name__ == 'result' + assert self.mock_test_class.obj.both_1_name.__name__ == 'result' + assert self.mock_test_class.obj.both_2_name.__name__ == 'result' + + def test_Collect_AllValidTests_CallsCollect(self, mock_collect): + self.mock_test_class.collect() + assert mock_collect.called + + def test_Collect_NormalCollection_ReturnsFilteredRuns(self, mock_collect): + mock_run = mock.MagicMock() + mock_run.obj.marks = {"run_type": 'run_shared'} + mock_run_2 = mock.MagicMock() + mock_run_2.obj.marks = {"run_type": 'result'} + mock_instance = mock.MagicMock() + mock_instance.collect.return_value = [mock_run, mock_run_2] + mock_collect.return_value = [mock_instance] + + collection = self.mock_test_class.collect() + assert collection == [mock_run_2] + + def test_Collect_NormalRun_ReturnsRunners(self, mock_collect): + self.mock_test_class.collect() + runners = self.mock_test_class.obj._runners + + assert runners[0].name == 'run_batched_tests' + assert runners[1].name == 'run_parallel_tests' + assert runners[2].name == 'run_parallel_batched_tests' + + def test_Collect_NormalCollection_StoresRunners(self, mock_collect): + mock_runner = mock.MagicMock() + mock_run = mock.MagicMock() + mock_run.obj.marks = {"run_type": 'run_shared'} + mock_run.function.marks = {"runner": mock_runner} + mock_runner_2 = mock.MagicMock() + mock_runner_2.result_pytestfuncs = [] + mock_run_2 = mock.MagicMock() + mock_run_2.obj.marks = {"run_type": 'result'} + mock_run_2.function.marks = {"runner": mock_runner_2} + mock_instance = mock.MagicMock() + mock_instance.collect.return_value = [mock_run, mock_run_2] + mock_collect.return_value = [mock_instance] + + self.mock_test_class.collect() + + assert mock_runner.run_pytestfunc == mock_run + assert mock_run_2 in mock_runner_2.result_pytestfuncs diff --git a/Tools/LyTestTools/tests/unit/test_pytest_plugin_editor_test.py b/Tools/LyTestTools/tests/unit/test_pytest_plugin_editor_test.py new file mode 100644 index 0000000000..10c779e546 --- /dev/null +++ b/Tools/LyTestTools/tests/unit/test_pytest_plugin_editor_test.py @@ -0,0 +1,41 @@ +""" +Copyright (c) Contributors to the Open 3D Engine Project. +For complete copyright and license terms please see the LICENSE at the root of this distribution. + +SPDX-License-Identifier: Apache-2.0 OR MIT +""" +import pytest +import os +import unittest.mock as mock +import unittest + +import ly_test_tools._internal.pytest_plugin.editor_test as editor_test + +pytestmark = pytest.mark.SUITE_smoke + +class TestEditorTest(unittest.TestCase): + + @mock.patch('inspect.isclass', mock.MagicMock(return_value=True)) + def test_PytestPycollectMakeitem_ValidArgs_CallsCorrectly(self): + mock_collector = mock.MagicMock() + mock_name = mock.MagicMock() + mock_obj = mock.MagicMock() + mock_base = mock.MagicMock() + mock_obj.__bases__ = [mock_base] + + editor_test.pytest_pycollect_makeitem(mock_collector, mock_name, mock_obj) + mock_base.pytest_custom_makeitem.assert_called_once_with(mock_collector, mock_name, mock_obj) + + def test_PytestCollectionModifyitem_OneValidClass_CallsOnce(self): + mock_item = mock.MagicMock() + mock_class = mock.MagicMock() + mock_class.pytest_custom_modify_items = mock.MagicMock() + mock_item.instance.__class__ = mock_class + mock_session = mock.MagicMock() + mock_items = [mock_item, mock.MagicMock()] + mock_config = mock.MagicMock() + + generator = editor_test.pytest_collection_modifyitems(mock_session, mock_items, mock_config) + for x in generator: + pass + assert mock_class.pytest_custom_modify_items.call_count == 1 diff --git a/cmake/Install.cmake b/cmake/Install.cmake index 3b74e6c654..b558590b9e 100644 --- a/cmake/Install.cmake +++ b/cmake/Install.cmake @@ -8,16 +8,38 @@ set(LY_INSTALL_ENABLED TRUE CACHE BOOL "Indicates if the install process is enabled") -if(LY_INSTALL_ENABLED) - ly_get_absolute_pal_filename(pal_dir ${CMAKE_CURRENT_SOURCE_DIR}/cmake/Platform/${PAL_PLATFORM_NAME}) - include(${pal_dir}/Install_${PAL_PLATFORM_NAME_LOWERCASE}.cmake) -endif() +#! ly_install: wrapper to install that handles common functionality +# +# \notes: +# - this wrapper handles the case where common installs are called multiple times from different +# build folders (when using LY_INSTALL_EXTERNAL_BUILD_DIRS) to generate install layouts that +# have multiple build permutations +# +function(ly_install) + + if(NOT LY_INSTALL_ENABLED) + return() + endif() + + cmake_parse_arguments(ly_install "" "COMPONENT" "" ${ARGN}) + if (NOT ly_install_COMPONENT OR "${ly_install_COMPONENT}" STREQUAL "${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME}") + # if it is installing under the default component, we need to de-duplicate since we can have + # cases coming from different build directories (when using LY_INSTALL_EXTERNAL_BUILD_DIRS) + install(CODE "if(NOT LY_CORE_COMPONENT_ALREADY_INCLUDED)" ALL_COMPONENTS) + install(${ARGN}) + install(CODE "endif()\n" ALL_COMPONENTS) + else() + install(${ARGN}) + endif() + +endfunction() #! ly_install_directory: specifies a directory to be copied to the install layout at install time # # \arg:DIRECTORIES directories to install # \arg:DESTINATION (optional) destination to install the directory to (relative to CMAKE_PREFIX_PATH) # \arg:EXCLUDE_PATTERNS (optional) patterns to exclude +# \arg:COMPONENT (optional) component to use (defaults to CMAKE_INSTALL_DEFAULT_COMPONENT_NAME) # \arg:VERBATIM (optional) copies the directories as they are, this excludes the default exclude patterns # # \notes: @@ -34,7 +56,7 @@ function(ly_install_directory) endif() set(options VERBATIM) - set(oneValueArgs DESTINATION) + set(oneValueArgs DESTINATION COMPONENT) set(multiValueArgs DIRECTORIES EXCLUDE_PATTERNS) cmake_parse_arguments(ly_install_directory "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) @@ -42,6 +64,10 @@ function(ly_install_directory) if(NOT ly_install_directory_DIRECTORIES) message(FATAL_ERROR "You must provide at least a directory to install") endif() + + if(NOT ly_install_directory_COMPONENT) + set(ly_install_directory_COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME}) + endif() foreach(directory ${ly_install_directory_DIRECTORIES}) @@ -77,11 +103,12 @@ function(ly_install_directory) list(APPEND exclude_patterns PATTERN *.egg-info EXCLUDE) endif() - install(DIRECTORY ${directory} + ly_install(DIRECTORY ${directory} DESTINATION ${ly_install_directory_DESTINATION} - COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} # use the deafult for the time being + COMPONENT ${ly_install_directory_COMPONENT} ${exclude_patterns} ) + endforeach() endfunction() @@ -126,7 +153,7 @@ function(ly_install_files) set(install_type FILES) endif() - install(${install_type} ${files} + ly_install(${install_type} ${files} DESTINATION ${ly_install_files_DESTINATION} COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} # use the default for the time being ) @@ -144,7 +171,7 @@ function(ly_install_run_code CODE) return() endif() - install(CODE ${CODE} + ly_install(CODE ${CODE} COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} # use the default for the time being ) @@ -161,8 +188,13 @@ function(ly_install_run_script SCRIPT) return() endif() - install(SCRIPT ${SCRIPT} + ly_install(SCRIPT ${SCRIPT} COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} # use the default for the time being ) -endfunction() \ No newline at end of file +endfunction() + +if(LY_INSTALL_ENABLED) + ly_get_absolute_pal_filename(pal_dir ${CMAKE_CURRENT_SOURCE_DIR}/cmake/Platform/${PAL_PLATFORM_NAME}) + include(${pal_dir}/Install_${PAL_PLATFORM_NAME_LOWERCASE}.cmake) +endif() diff --git a/cmake/LYWrappers.cmake b/cmake/LYWrappers.cmake index fb3d420c26..0416d41ece 100644 --- a/cmake/LYWrappers.cmake +++ b/cmake/LYWrappers.cmake @@ -403,7 +403,7 @@ function(ly_target_link_libraries TARGET) message(FATAL_ERROR "You must provide a target") endif() - set_property(GLOBAL APPEND PROPERTY LY_DELAYED_LINK_${TARGET} ${ARGN}) + set_property(TARGET ${TARGET} APPEND PROPERTY LY_DELAYED_LINK ${ARGN}) set_property(GLOBAL APPEND PROPERTY LY_DELAYED_LINK_TARGETS ${TARGET}) # to walk them at the end endfunction() @@ -430,7 +430,7 @@ function(ly_delayed_target_link_libraries) get_property(delayed_targets GLOBAL PROPERTY LY_DELAYED_LINK_TARGETS) foreach(target ${delayed_targets}) - get_property(delayed_link GLOBAL PROPERTY LY_DELAYED_LINK_${target}) + get_property(delayed_link TARGET ${target} PROPERTY LY_DELAYED_LINK) if(delayed_link) cmake_parse_arguments(ly_delayed_target_link_libraries "" "" "${visibilities}" ${delayed_link}) @@ -458,7 +458,6 @@ function(ly_delayed_target_link_libraries) endforeach() endforeach() - set_property(GLOBAL PROPERTY LY_DELAYED_LINK_${target}) endif() diff --git a/cmake/Packaging.cmake b/cmake/Packaging.cmake index 006689549b..6a1e2d8cef 100644 --- a/cmake/Packaging.cmake +++ b/cmake/Packaging.cmake @@ -134,8 +134,9 @@ if(NOT EXISTS ${_cmake_package_dest}) endif() endif() -install(FILES ${_cmake_package_dest} +ly_install(FILES ${_cmake_package_dest} DESTINATION ./Tools/Redistributables/CMake + COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} ) # the version string and git tags are intended to be synchronized so it should be safe to use that instead @@ -157,8 +158,9 @@ if(${CPACK_PACKAGE_VERSION} VERSION_GREATER "0.0.0.0") list(POP_FRONT _status _status_code) if (${_status_code} EQUAL 0 AND EXISTS ${_3rd_party_license_dest}) - install(FILES ${_3rd_party_license_dest} + ly_install(FILES ${_3rd_party_license_dest} DESTINATION . + COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} ) else() file(REMOVE ${_3rd_party_license_dest}) @@ -202,44 +204,69 @@ endif() # IMPORTANT: required to be included AFTER setting all property overrides include(CPack REQUIRED) -function(ly_configure_cpack_component ly_configure_cpack_component_NAME) - - set(options REQUIRED) - set(oneValueArgs DISPLAY_NAME DESCRIPTION LICENSE_NAME LICENSE_FILE) - set(multiValueArgs) - - cmake_parse_arguments(ly_configure_cpack_component "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) - - # default to optional - set(component_type DISABLED) - - if(ly_configure_cpack_component_REQUIRED) - set(component_type REQUIRED) - endif() - - set(license_name ${DEFAULT_LICENSE_NAME}) - set(license_file ${DEFAULT_LICENSE_FILE}) - - if(ly_configure_cpack_component_LICENSE_NAME AND ly_configure_cpack_component_LICENSE_FILE) - set(license_name ${ly_configure_cpack_component_LICENSE_NAME}) - set(license_file ${ly_configure_cpack_component_LICENSE_FILE}) - elseif(ly_configure_cpack_component_LICENSE_NAME OR ly_configure_cpack_component_LICENSE_FILE) - message(FATAL_ERROR "Invalid argument configuration. Both LICENSE_NAME and LICENSE_FILE must be set for ly_configure_cpack_component") - endif() - - cpack_add_component( - ${ly_configure_cpack_component_NAME} ${component_type} - DISPLAY_NAME ${ly_configure_cpack_component_DISPLAY_NAME} - DESCRIPTION ${ly_configure_cpack_component_DESCRIPTION} - ) -endfunction() - # configure ALL components here -ly_configure_cpack_component( - ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} REQUIRED - DISPLAY_NAME "${PROJECT_NAME} Core" - DESCRIPTION "${PROJECT_NAME} Headers, Libraries and Tools" -) +file(APPEND "${CPACK_OUTPUT_CONFIG_FILE}" " +set(CPACK_COMPONENTS_ALL ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME}) +set(CPACK_COMPONENT_${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME}_DISPLAY_NAME \"Common files\") +set(CPACK_COMPONENT_${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME}_DESCRIPTION \"${PROJECT_NAME} Headers, scripts and common files\") +set(CPACK_COMPONENT_${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME}_REQUIRED TRUE) +set(CPACK_COMPONENT_${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME}_DISABLED FALSE) + +include(CPackComponents.cmake) +") + +# Generate a file (CPackComponents.config) that we will include that defines the components +# for this build permutation. This way we can get components for other permutations being passed +# through LY_INSTALL_EXTERNAL_BUILD_DIRS +unset(cpack_components_contents) + +set(required "FALSE") +set(disabled "FALSE") +if(${LY_INSTALL_PERMUTATION_COMPONENT} STREQUAL DEFAULT) + set(required "TRUE") +else() + set(disabled "TRUE") +endif() +string(APPEND cpack_components_contents " +list(APPEND CPACK_COMPONENTS_ALL ${LY_INSTALL_PERMUTATION_COMPONENT}) +set(CPACK_COMPONENT_${LY_INSTALL_PERMUTATION_COMPONENT}_DISPLAY_NAME \"${LY_BUILD_PERMUTATION} common files\") +set(CPACK_COMPONENT_${LY_INSTALL_PERMUTATION_COMPONENT}_DESCRIPTION \"${PROJECT_NAME} scripts and common files for ${LY_BUILD_PERMUTATION}\") +set(CPACK_COMPONENT_${LY_INSTALL_PERMUTATION_COMPONENT}_DEPENDS ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME}) +set(CPACK_COMPONENT_${LY_INSTALL_PERMUTATION_COMPONENT}_REQUIRED ${required}) +set(CPACK_COMPONENT_${LY_INSTALL_PERMUTATION_COMPONENT}_DISABLED ${disabled}) +") + +foreach(conf IN LISTS CMAKE_CONFIGURATION_TYPES) + string(TOUPPER ${conf} UCONF) + set(required "FALSE") + set(disabled "FALSE") + if(${conf} STREQUAL profile AND ${LY_INSTALL_PERMUTATION_COMPONENT} STREQUAL DEFAULT) + set(required "TRUE") + else() + set(disabled "TRUE") + endif() + + # Inject a check to not declare components that have not been built. We are using AzCore since that is a + # common target that will always be build, in every permutation and configuration + string(APPEND cpack_components_contents " +if(EXISTS \"${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}/${conf}/${CMAKE_STATIC_LIBRARY_PREFIX}AzCore${CMAKE_STATIC_LIBRARY_SUFFIX}\") + list(APPEND CPACK_COMPONENTS_ALL ${LY_INSTALL_PERMUTATION_COMPONENT}_${UCONF}) + set(CPACK_COMPONENT_${LY_INSTALL_PERMUTATION_COMPONENT}_${UCONF}_DISPLAY_NAME \"Binaries for ${LY_BUILD_PERMUTATION} ${conf}\") + set(CPACK_COMPONENT_${LY_INSTALL_PERMUTATION_COMPONENT}_${UCONF}_DESCRIPTION \"${PROJECT_NAME} libraries and applications for ${LY_BUILD_PERMUTATION} ${conf}\") + set(CPACK_COMPONENT_${LY_INSTALL_PERMUTATION_COMPONENT}_${UCONF}_DEPENDS ${LY_INSTALL_PERMUTATION_COMPONENT}) + set(CPACK_COMPONENT_${LY_INSTALL_PERMUTATION_COMPONENT}_${UCONF}_REQUIRED ${required}) + set(CPACK_COMPONENT_${LY_INSTALL_PERMUTATION_COMPONENT}_${UCONF}_DISABLED ${disabled}) +endif() +") +endforeach() +file(WRITE "${CMAKE_BINARY_DIR}/CPackComponents.cmake" ${cpack_components_contents}) + +# Inject other build directories +foreach(external_dir ${LY_INSTALL_EXTERNAL_BUILD_DIRS}) + file(APPEND "${CPACK_OUTPUT_CONFIG_FILE}" + "include(${external_dir}/CPackComponents.cmake)\n" + ) +endforeach() if(LY_INSTALLER_DOWNLOAD_URL) strip_trailing_slash(${LY_INSTALLER_DOWNLOAD_URL} LY_INSTALLER_DOWNLOAD_URL) diff --git a/cmake/Platform/Common/Install_common.cmake b/cmake/Platform/Common/Install_common.cmake index 44cdeb1994..2960a99c64 100644 --- a/cmake/Platform/Common/Install_common.cmake +++ b/cmake/Platform/Common/Install_common.cmake @@ -8,6 +8,14 @@ include(cmake/FileUtil.cmake) +set(LY_INSTALL_EXTERNAL_BUILD_DIRS "" CACHE PATH "External build directories to be included in the install process. This allows to package non-monolithic and monolithic.") +unset(normalized_external_build_dirs) +foreach(external_dir ${LY_INSTALL_EXTERNAL_BUILD_DIRS}) + cmake_path(ABSOLUTE_PATH external_dir BASE_DIRECTORY ${LY_ROOT_FOLDER} NORMALIZE) + list(APPEND normalized_external_build_dirs ${external_dir}) +endforeach() +set(LY_INSTALL_EXTERNAL_BUILD_DIRS ${normalized_external_build_dirs}) + set(CMAKE_INSTALL_MESSAGE NEVER) # Simplify messages to reduce output noise define_property(TARGET PROPERTY LY_INSTALL_GENERATE_RUN_TARGET @@ -19,13 +27,25 @@ define_property(TARGET PROPERTY LY_INSTALL_GENERATE_RUN_TARGET ]] ) -ly_set(CMAKE_INSTALL_DEFAULT_COMPONENT_NAME Core) +# We can have elements being installed under the following components: +# - Core (required for all) (default) +# - Default +# - Default_$ +# - Monolithic +# - Monolithic_$ +# Debug/Monolithic are build permutations, so for a CMake run, it can only generate +# one of the permutations. Each build permutation can generate only one cmake_install.cmake. +# Each build permutation will generate the same elements in Core. +# CPack is able to put the two together by taking Core from one permutation and then taking +# each permutation. +ly_set(CMAKE_INSTALL_DEFAULT_COMPONENT_NAME CORE) if(LY_MONOLITHIC_GAME) set(LY_BUILD_PERMUTATION Monolithic) else() set(LY_BUILD_PERMUTATION Default) endif() +string(TOUPPER ${LY_BUILD_PERMUTATION} LY_INSTALL_PERMUTATION_COMPONENT) cmake_path(RELATIVE_PATH CMAKE_RUNTIME_OUTPUT_DIRECTORY BASE_DIRECTORY ${CMAKE_BINARY_DIR} OUTPUT_VARIABLE runtime_output_directory) cmake_path(RELATIVE_PATH CMAKE_LIBRARY_OUTPUT_DIRECTORY BASE_DIRECTORY ${CMAKE_BINARY_DIR} OUTPUT_VARIABLE library_output_directory) @@ -65,14 +85,24 @@ function(ly_setup_target OUTPUT_CONFIGURED_TARGET ALIAS_TARGET_NAME absolute_tar continue() endif() + # For some cases (e.g. codegen) we generate headers that end up in the BUILD_DIR. Since the BUILD_DIR + # is per-permutation, we need to install such headers per permutation. For the other cases, we can install + # under the default component since they are shared across permutations/configs. + cmake_path(IS_PREFIX CMAKE_BINARY_DIR ${include_directory} NORMALIZE include_directory_child_of_build) + if(NOT include_directory_child_of_build) + set(include_directory_component ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME}) + else() + set(include_directory_component ${LY_INSTALL_PERMUTATION_COMPONENT}) + endif() + unset(rel_include_dir) cmake_path(RELATIVE_PATH include_directory BASE_DIRECTORY ${LY_ROOT_FOLDER} OUTPUT_VARIABLE rel_include_dir) cmake_path(APPEND rel_include_dir "..") cmake_path(NORMAL_PATH rel_include_dir OUTPUT_VARIABLE destination_dir) - - install(DIRECTORY ${include_directory} + + ly_install(DIRECTORY ${include_directory} DESTINATION ${destination_dir} - COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} + COMPONENT ${include_directory_component} FILES_MATCHING PATTERN *.h PATTERN *.hpp @@ -94,9 +124,9 @@ function(ly_setup_target OUTPUT_CONFIGURED_TARGET ALIAS_TARGET_NAME absolute_tar cmake_path(RELATIVE_PATH target_library_output_directory BASE_DIRECTORY ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} OUTPUT_VARIABLE target_library_output_subdirectory) endif() - if(COMMAND ly_install_target_override) + if(COMMAND ly_setup_target_install_targets_override) # Mac needs special handling because of a cmake issue - ly_install_target_override(TARGET ${TARGET_NAME} + ly_setup_target_install_targets_override(TARGET ${TARGET_NAME} ARCHIVE_DIR ${archive_output_directory} LIBRARY_DIR ${library_output_directory} RUNTIME_DIR ${runtime_output_directory} @@ -104,18 +134,23 @@ function(ly_setup_target OUTPUT_CONFIGURED_TARGET ALIAS_TARGET_NAME absolute_tar RUNTIME_SUBDIR ${target_runtime_output_subdirectory} ) else() - install( - TARGETS ${TARGET_NAME} - ARCHIVE - DESTINATION ${archive_output_directory} - COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} - LIBRARY - DESTINATION ${library_output_directory}/${target_library_output_subdirectory} - COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} - RUNTIME - DESTINATION ${runtime_output_directory}/${target_runtime_output_subdirectory} - COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} - ) + foreach(conf IN LISTS CMAKE_CONFIGURATION_TYPES) + string(TOUPPER ${conf} UCONF) + ly_install(TARGETS ${TARGET_NAME} + ARCHIVE + DESTINATION ${archive_output_directory} + COMPONENT ${LY_INSTALL_PERMUTATION_COMPONENT}_${UCONF} + CONFIGURATIONS ${conf} + LIBRARY + DESTINATION ${library_output_directory}/${target_library_output_subdirectory} + COMPONENT ${LY_INSTALL_PERMUTATION_COMPONENT}_${UCONF} + CONFIGURATIONS ${conf} + RUNTIME + DESTINATION ${runtime_output_directory}/${target_runtime_output_subdirectory} + COMPONENT ${LY_INSTALL_PERMUTATION_COMPONENT}_${UCONF} + CONFIGURATIONS ${conf} + ) + endforeach() endif() # CMakeLists.txt related files @@ -157,16 +192,15 @@ function(ly_setup_target OUTPUT_CONFIGURED_TARGET ALIAS_TARGET_NAME absolute_tar endif() # Includes need additional processing to add the install root - if(include_directories) - foreach(include ${include_directories}) - string(GENEX_STRIP ${include} include_genex_expr) - if(include_genex_expr STREQUAL include) # only for cases where there are no generation expressions - # Make the include path relative to the source dir where the target will be declared - cmake_path(RELATIVE_PATH include BASE_DIRECTORY ${absolute_target_source_dir} OUTPUT_VARIABLE target_include) - string(APPEND INCLUDE_DIRECTORIES_PLACEHOLDER "${PLACEHOLDER_INDENT}${target_include}\n") - endif() - endforeach() - endif() + foreach(include IN LISTS include_directories) + string(GENEX_STRIP ${include} include_genex_expr) + if(include_genex_expr STREQUAL include) # only for cases where there are no generation expressions + # Make the include path relative to the source dir where the target will be declared + cmake_path(RELATIVE_PATH include BASE_DIRECTORY ${absolute_target_source_dir} OUTPUT_VARIABLE target_include) + list(APPEND INCLUDE_DIRECTORIES_PLACEHOLDER "${PLACEHOLDER_INDENT}${target_include}") + endif() + endforeach() + list(JOIN INCLUDE_DIRECTORIES_PLACEHOLDER "\n" INCLUDE_DIRECTORIES_PLACEHOLDER) string(REPEAT " " 8 PLACEHOLDER_INDENT) get_target_property(RUNTIME_DEPENDENCIES_PLACEHOLDER ${TARGET_NAME} MANUALLY_ADDED_DEPENDENCIES) @@ -178,27 +212,27 @@ function(ly_setup_target OUTPUT_CONFIGURED_TARGET ALIAS_TARGET_NAME absolute_tar endif() string(REPEAT " " 12 PLACEHOLDER_INDENT) - get_target_property(inteface_build_dependencies_props ${TARGET_NAME} INTERFACE_LINK_LIBRARIES) + get_property(interface_build_dependencies_props TARGET ${TARGET_NAME} PROPERTY LY_DELAYED_LINK) unset(INTERFACE_BUILD_DEPENDENCIES_PLACEHOLDER) - if(inteface_build_dependencies_props) - foreach(build_dependency ${inteface_build_dependencies_props}) + if(interface_build_dependencies_props) + cmake_parse_arguments(build_deps "" "" "PRIVATE;PUBLIC;INTERFACE" ${interface_build_dependencies_props}) + # Interface and public dependencies should always be exposed + set(build_deps_target ${build_deps_INTERFACE}) + if(build_deps_PUBLIC) + set(build_deps_target "${build_deps_target};${build_deps_PUBLIC}") + endif() + # Private dependencies should only be exposed if it is a static library, since in those cases, link + # dependencies are transfered to the downstream dependencies + if("${target_type}" STREQUAL "STATIC_LIBRARY") + set(build_deps_target "${build_deps_target};${build_deps_PRIVATE}") + endif() + foreach(build_dependency IN LISTS build_deps_target) # Skip wrapping produced when targets are not created in the same directory - if(NOT ${build_dependency} MATCHES "^::@") + if(build_dependency) list(APPEND INTERFACE_BUILD_DEPENDENCIES_PLACEHOLDER "${PLACEHOLDER_INDENT}${build_dependency}") endif() endforeach() endif() - # We also need to pass the private link libraries since we will use that to generate the runtime dependencies - get_target_property(private_build_dependencies_props ${TARGET_NAME} LINK_LIBRARIES) - if(private_build_dependencies_props) - foreach(build_dependency ${private_build_dependencies_props}) - # Skip wrapping produced when targets are not created in the same directory - if(NOT ${build_dependency} MATCHES "^::@") - list(APPEND INTERFACE_BUILD_DEPENDENCIES_PLACEHOLDER "${PLACEHOLDER_INDENT}${build_dependency}") - endif() - endforeach() - endif() - list(REMOVE_DUPLICATES INTERFACE_BUILD_DEPENDENCIES_PLACEHOLDER) list(JOIN INTERFACE_BUILD_DEPENDENCIES_PLACEHOLDER "\n" INTERFACE_BUILD_DEPENDENCIES_PLACEHOLDER) string(REPEAT " " 8 PLACEHOLDER_INDENT) @@ -269,10 +303,15 @@ set_property(TARGET ${NAME_PLACEHOLDER} set(target_install_source_dir ${CMAKE_CURRENT_BINARY_DIR}/install/${relative_target_source_dir}) file(GENERATE OUTPUT "${target_install_source_dir}/Platform/${PAL_PLATFORM_NAME}/${LY_BUILD_PERMUTATION}/${NAME_PLACEHOLDER}_$.cmake" CONTENT "${target_file_contents}") - install(FILES "${target_install_source_dir}/Platform/${PAL_PLATFORM_NAME}/${LY_BUILD_PERMUTATION}/${NAME_PLACEHOLDER}_$.cmake" - DESTINATION ${relative_target_source_dir}/Platform/${PAL_PLATFORM_NAME}/${LY_BUILD_PERMUTATION} - COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} - ) + + foreach(conf IN LISTS CMAKE_CONFIGURATION_TYPES) + string(TOUPPER ${conf} UCONF) + ly_install(FILES "${target_install_source_dir}/Platform/${PAL_PLATFORM_NAME}/${LY_BUILD_PERMUTATION}/${NAME_PLACEHOLDER}_${conf}.cmake" + DESTINATION ${relative_target_source_dir}/Platform/${PAL_PLATFORM_NAME}/${LY_BUILD_PERMUTATION} + COMPONENT ${LY_INSTALL_PERMUTATION_COMPONENT}_${UCONF} + CONFIGURATIONS ${conf} + ) + endforeach() # Since a CMakeLists.txt could contain multiple targets, we generate it in a folder per target ly_file_read(${LY_ROOT_FOLDER}/cmake/install/InstalledTarget.in target_cmakelists_template) @@ -312,7 +351,8 @@ function(ly_setup_subdirectory absolute_target_source_dir) @cmake_copyright_comment@ include(Platform/${PAL_PLATFORM_NAME}/platform_${PAL_PLATFORM_NAME_LOWERCASE}.cmake) ]] @ONLY) - install(FILES "${target_install_source_dir}/CMakeLists.txt" + + ly_install(FILES "${target_install_source_dir}/CMakeLists.txt" DESTINATION ${relative_target_source_dir} COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} ) @@ -322,12 +362,12 @@ include(Platform/${PAL_PLATFORM_NAME}/platform_${PAL_PLATFORM_NAME_LOWERCASE}.cm file(CONFIGURE OUTPUT "${target_install_source_dir}/Platform/${PAL_PLATFORM_NAME}/platform_${PAL_PLATFORM_NAME_LOWERCASE}.cmake" CONTENT [[ @cmake_copyright_comment@ if(LY_MONOLITHIC_GAME) - include(Platform/${PAL_PLATFORM_NAME}/Monolithic/permutation.cmake) + include(Platform/${PAL_PLATFORM_NAME}/Monolithic/permutation.cmake OPTIONAL) else() include(Platform/${PAL_PLATFORM_NAME}/Default/permutation.cmake) endif() ]]) - install(FILES "${target_install_source_dir}/Platform/${PAL_PLATFORM_NAME}/platform_${PAL_PLATFORM_NAME_LOWERCASE}.cmake" + ly_install(FILES "${target_install_source_dir}/Platform/${PAL_PLATFORM_NAME}/platform_${PAL_PLATFORM_NAME_LOWERCASE}.cmake" DESTINATION ${relative_target_source_dir}/Platform/${PAL_PLATFORM_NAME} COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} ) @@ -347,63 +387,45 @@ endif() "${GEM_VARIANT_TO_LOAD_PLACEHOLDER}" "${ENABLE_GEMS_PLACEHOLDER}" ) - install(FILES "${target_install_source_dir}/Platform/${PAL_PLATFORM_NAME}/${LY_BUILD_PERMUTATION}/permutation.cmake" - DESTINATION ${relative_target_source_dir}//Platform/${PAL_PLATFORM_NAME}/${LY_BUILD_PERMUTATION} - COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} + + ly_install(FILES "${target_install_source_dir}/Platform/${PAL_PLATFORM_NAME}/${LY_BUILD_PERMUTATION}/permutation.cmake" + DESTINATION ${relative_target_source_dir}/Platform/${PAL_PLATFORM_NAME}/${LY_BUILD_PERMUTATION} + COMPONENT ${LY_INSTALL_PERMUTATION_COMPONENT} ) endfunction() -#! ly_setup_o3de_install: orchestrates the installation of the different parts. This is the entry point from the root CMakeLists.txt -function(ly_setup_o3de_install) - - ly_setup_subdirectories() - ly_setup_cmake_install() - ly_setup_runtime_dependencies() - ly_setup_assets() - - # Misc - install(FILES - ${LY_ROOT_FOLDER}/pytest.ini - ${LY_ROOT_FOLDER}/LICENSE.txt - ${LY_ROOT_FOLDER}/README.md - DESTINATION . - COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} - ) - - if(COMMAND ly_post_install_steps) - ly_post_install_steps() - endif() - -endfunction() - #! ly_setup_cmake_install: install the "cmake" folder function(ly_setup_cmake_install) - install(DIRECTORY "${LY_ROOT_FOLDER}/cmake" + ly_install(DIRECTORY "${LY_ROOT_FOLDER}/cmake" DESTINATION . COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} PATTERN "__pycache__" EXCLUDE PATTERN "Findo3de.cmake" EXCLUDE + PATTERN "cmake/ConfigurationTypes.cmake" EXCLUDE REGEX "3rdParty/Platform\/.*\/BuiltInPackages_.*\.cmake" EXCLUDE ) + # Connect configuration types - install(FILES "${LY_ROOT_FOLDER}/cmake/install/ConfigurationTypes.cmake" + ly_install(FILES "${LY_ROOT_FOLDER}/cmake/install/ConfigurationTypes.cmake" DESTINATION cmake COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} ) - # Inject code that will generate each ConfigurationType_.cmake file - set(install_configuration_type_template [=[ - configure_file(@LY_ROOT_FOLDER@/cmake/install/ConfigurationType_config.cmake.in - ${CMAKE_INSTALL_PREFIX}/cmake/Platform/@PAL_PLATFORM_NAME@/@LY_BUILD_PERMUTATION@/ConfigurationTypes_${CMAKE_INSTALL_CONFIG_NAME}.cmake + + # generate each ConfigurationType_.cmake file and install it under that configuration + foreach(conf IN LISTS CMAKE_CONFIGURATION_TYPES) + string(TOUPPER ${conf} UCONF) + configure_file("${LY_ROOT_FOLDER}/cmake/install/ConfigurationType_config.cmake.in" + "${CMAKE_BINARY_DIR}/cmake/Platform/${PAL_PLATFORM_NAME}/${LY_BUILD_PERMUTATION}/ConfigurationTypes_${conf}.cmake" @ONLY ) - message(STATUS "Generated ${CMAKE_INSTALL_PREFIX}/cmake/Platform/@PAL_PLATFORM_NAME@/@LY_BUILD_PERMUTATION@/ConfigurationTypes_${CMAKE_INSTALL_CONFIG_NAME}.cmake") - ]=]) - string(CONFIGURE "${install_configuration_type_template}" install_configuration_type @ONLY) - install(CODE "${install_configuration_type}" - COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} - ) + ly_install(FILES "${CMAKE_BINARY_DIR}/cmake/Platform/${PAL_PLATFORM_NAME}/${LY_BUILD_PERMUTATION}/ConfigurationTypes_${conf}.cmake" + DESTINATION cmake/Platform/${PAL_PLATFORM_NAME}/${LY_BUILD_PERMUTATION} + COMPONENT ${LY_INSTALL_PERMUTATION_COMPONENT}_${UCONF} + CONFIGURATIONS ${conf} + ) + endforeach() # Transform the LY_EXTERNAL_SUBDIRS list into a json array set(indent " ") @@ -422,8 +444,7 @@ function(ly_setup_cmake_install) configure_file(${LY_ROOT_FOLDER}/cmake/install/engine.json.in ${CMAKE_CURRENT_BINARY_DIR}/cmake/engine.json @ONLY) - install( - FILES + ly_install(FILES "${LY_ROOT_FOLDER}/CMakeLists.txt" "${CMAKE_CURRENT_BINARY_DIR}/cmake/engine.json" DESTINATION . @@ -446,11 +467,12 @@ function(ly_setup_cmake_install) list(APPEND additional_platform_files "${plat_files}") endforeach() endforeach() - install(FILES ${additional_find_files} + + ly_install(FILES ${additional_find_files} DESTINATION cmake/3rdParty COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} ) - install(FILES ${additional_platform_files} + ly_install(FILES ${additional_platform_files} DESTINATION cmake/3rdParty/Platform/${PAL_PLATFORM_NAME} COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} ) @@ -467,7 +489,7 @@ function(ly_setup_cmake_install) endforeach() configure_file(${LY_ROOT_FOLDER}/cmake/install/Findo3de.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/cmake/Findo3de.cmake @ONLY) - install(FILES "${CMAKE_CURRENT_BINARY_DIR}/cmake/Findo3de.cmake" + ly_install(FILES "${CMAKE_CURRENT_BINARY_DIR}/cmake/Findo3de.cmake" DESTINATION cmake COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} ) @@ -476,10 +498,12 @@ function(ly_setup_cmake_install) # all the associations in ly_associate_package and then generate them into BuiltInPackages_.cmake. This # will consolidate all associations in one file get_property(all_package_names GLOBAL PROPERTY LY_PACKAGE_NAMES) + list(REMOVE_DUPLICATES all_package_names) set(builtinpackages "# Generated by O3DE install\n\n") foreach(package_name IN LISTS all_package_names) get_property(package_hash GLOBAL PROPERTY LY_PACKAGE_HASH_${package_name}) get_property(targets GLOBAL PROPERTY LY_PACKAGE_TARGETS_${package_name}) + list(REMOVE_DUPLICATES targets) string(APPEND builtinpackages "ly_associate_package(PACKAGE_NAME ${package_name} TARGETS ${targets} PACKAGE_HASH ${package_hash})\n") endforeach() @@ -487,7 +511,7 @@ function(ly_setup_cmake_install) file(GENERATE OUTPUT ${pal_builtin_file} CONTENT ${builtinpackages} ) - install(FILES "${pal_builtin_file}" + ly_install(FILES "${pal_builtin_file}" DESTINATION cmake/3rdParty/Platform/${PAL_PLATFORM_NAME} COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} ) @@ -498,15 +522,23 @@ endfunction() function(ly_setup_runtime_dependencies) # Common functions used by the bellow code - if(COMMAND ly_install_code_function_override) - ly_install_code_function_override() + if(COMMAND ly_setup_runtime_dependencies_copy_function_override) + ly_setup_runtime_dependencies_copy_function_override() else() - install(CODE + # despite this copy function being the same, we need to install it per component that uses it + # (which is per-configuration per-permutation component) + foreach(conf IN LISTS CMAKE_CONFIGURATION_TYPES) + string(TOUPPER ${conf} UCONF) + ly_install(CODE "function(ly_copy source_file target_directory) - file(COPY \"\${source_file}\" DESTINATION \"\${target_directory}\" FILE_PERMISSIONS ${LY_COPY_PERMISSIONS}) + cmake_path(GET source_file FILENAME file_name) + if(NOT EXISTS \${target_directory}/\${file_name}) + file(COPY \"\${source_file}\" DESTINATION \"\${target_directory}\" FILE_PERMISSIONS ${LY_COPY_PERMISSIONS}) + endif() endfunction()" - COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} - ) + COMPONENT ${LY_INSTALL_PERMUTATION_COMPONENT}_${UCONF} + ) + endforeach() endif() unset(runtime_commands) @@ -545,9 +577,12 @@ endfunction()" list(REMOVE_DUPLICATES runtime_commands) list(JOIN runtime_commands " " runtime_commands_str) # the spaces are just to see the right identation in the cmake_install.cmake file - install(CODE "${runtime_commands_str}" - COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} - ) + foreach(conf IN LISTS CMAKE_CONFIGURATION_TYPES) + string(TOUPPER ${conf} UCONF) + ly_install(CODE "${runtime_commands_str}" + COMPONENT ${LY_INSTALL_PERMUTATION_COMPONENT}_${UCONF} + ) + endforeach() endfunction() @@ -632,17 +667,19 @@ function(ly_setup_assets) if (NOT gem_install_dest_dir) cmake_path(SET gem_install_dest_dir .) endif() + if(IS_DIRECTORY ${gem_absolute_path}) - install(DIRECTORY "${gem_absolute_path}" + ly_install(DIRECTORY "${gem_absolute_path}" DESTINATION ${gem_install_dest_dir} COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} ) elseif (EXISTS ${gem_absolute_path}) - install(FILES ${gem_absolute_path} + ly_install(FILES ${gem_absolute_path} DESTINATION ${gem_install_dest_dir} COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} ) endif() + endforeach() endforeach() @@ -702,4 +739,37 @@ function(ly_setup_subdirectory_enable_gems absolute_target_source_dir output_scr string(APPEND enable_gems_calls ${enable_gems_command}) endforeach() set(${output_script} ${enable_gems_calls} PARENT_SCOPE) +endfunction() + +#! ly_setup_o3de_install: orchestrates the installation of the different parts. This is the entry point from the root CMakeLists.txt +function(ly_setup_o3de_install) + + ly_setup_subdirectories() + ly_setup_cmake_install() + ly_setup_runtime_dependencies() + ly_setup_assets() + + # Misc + ly_install(FILES + ${LY_ROOT_FOLDER}/pytest.ini + ${LY_ROOT_FOLDER}/LICENSE.txt + ${LY_ROOT_FOLDER}/README.md + DESTINATION . + COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} + ) + + # Inject other build directories + foreach(external_dir ${LY_INSTALL_EXTERNAL_BUILD_DIRS}) + ly_install(CODE +"set(LY_CORE_COMPONENT_ALREADY_INCLUDED TRUE) +include(${external_dir}/cmake_install.cmake) +set(LY_CORE_COMPONENT_ALREADY_INCLUDED FALSE)" + ALL_COMPONENTS + ) + endforeach() + + if(COMMAND ly_post_install_steps) + ly_post_install_steps() + endif() + endfunction() \ No newline at end of file diff --git a/cmake/Platform/Linux/Install_linux.cmake b/cmake/Platform/Linux/Install_linux.cmake index dea26e5872..02baa6e61e 100644 --- a/cmake/Platform/Linux/Install_linux.cmake +++ b/cmake/Platform/Linux/Install_linux.cmake @@ -6,7 +6,7 @@ # # -#! ly_install_code_function_override: Linux-specific copy function to handle RPATH fixes +#! ly_setup_runtime_dependencies_copy_function_override: Linux-specific copy function to handle RPATH fixes set(ly_copy_template [[ function(ly_copy source_file target_directory) file(COPY "${source_file}" DESTINATION "${target_directory}" FILE_PERMISSIONS @LY_COPY_PERMISSIONS@ FOLLOW_SYMLINK_CHAIN) @@ -20,11 +20,14 @@ function(ly_copy source_file target_directory) endif() endfunction()]]) -function(ly_install_code_function_override) +function(ly_setup_runtime_dependencies_copy_function_override) string(CONFIGURE "${ly_copy_template}" ly_copy_function_linux @ONLY) - install(CODE "${ly_copy_function_linux}" - COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} - ) + foreach(conf IN LISTS CMAKE_CONFIGURATION_TYPES) + string(TOUPPER ${conf} UCONF) + ly_install(CODE "${ly_copy_function_linux}" + COMPONENT ${LY_INSTALL_PERMUTATION_COMPONENT}_${UCONF} + ) + endforeach() endfunction() include(cmake/Platform/Common/Install_common.cmake) diff --git a/cmake/Platform/Mac/Install_mac.cmake b/cmake/Platform/Mac/Install_mac.cmake index bdc2300131..c75d860c3e 100644 --- a/cmake/Platform/Mac/Install_mac.cmake +++ b/cmake/Platform/Mac/Install_mac.cmake @@ -39,10 +39,10 @@ file(GENERATE # This needs to be done here because it needs to update the install prefix # before cmake does anything else in the install process. configure_file(${LY_ROOT_FOLDER}/cmake/Platform/Mac/PreInstallSteps_mac.cmake.in ${CMAKE_BINARY_DIR}/runtime_install/PreInstallSteps_mac.cmake @ONLY) -install(SCRIPT ${CMAKE_BINARY_DIR}/runtime_install/PreInstallSteps_mac.cmake COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME}) +ly_install(SCRIPT ${CMAKE_BINARY_DIR}/runtime_install/PreInstallSteps_mac.cmake COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME}) -#! ly_install_target_override: Mac specific target installation -function(ly_install_target_override) +#! ly_setup_target_install_targets_override: Mac specific target installation +function(ly_setup_target_install_targets_override) set(options) set(oneValueArgs TARGET ARCHIVE_DIR LIBRARY_DIR RUNTIME_DIR LIBRARY_SUBDIR RUNTIME_SUBDIR) @@ -58,24 +58,31 @@ function(ly_install_target_override) set_property(TARGET ${ly_platform_install_target_TARGET} PROPERTY RESOURCE "") endif() - install( - TARGETS ${ly_platform_install_target_TARGET} - ARCHIVE - DESTINATION ${ly_platform_install_target_ARCHIVE_DIR} - COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} - LIBRARY - DESTINATION ${ly_platform_install_target_LIBRARY_DIR}/${ly_platform_install_target_LIBRARY_SUBDIR} - COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} - RUNTIME - DESTINATION ${ly_platform_install_target_RUNTIME_DIR}/${ly_platform_install_target_RUNTIME_SUBDIR} - COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} - BUNDLE - DESTINATION ${ly_platform_install_target_RUNTIME_DIR}/${ly_platform_install_target_RUNTIME_SUBDIR} - COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} - RESOURCE - DESTINATION ${ly_platform_install_target_RUNTIME_DIR}/${ly_platform_install_target_RUNTIME_SUBDIR} - COMPONENT ${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME} - ) + foreach(conf IN LISTS CMAKE_CONFIGURATION_TYPES) + string(TOUPPER ${conf} UCONF) + ly_install(TARGETS ${TARGET_NAME} + ARCHIVE + DESTINATION ${ly_platform_install_target_ARCHIVE_DIR} + COMPONENT ${LY_INSTALL_PERMUTATION_COMPONENT}_${UCONF} + CONFIGURATIONS ${conf} + LIBRARY + DESTINATION ${ly_platform_install_target_LIBRARY_DIR}/${ly_platform_install_target_LIBRARY_SUBDIR} + COMPONENT ${LY_INSTALL_PERMUTATION_COMPONENT}_${UCONF} + CONFIGURATIONS ${conf} + RUNTIME + DESTINATION ${ly_platform_install_target_RUNTIME_DIR}/${ly_platform_install_target_RUNTIME_SUBDIR} + COMPONENT ${LY_INSTALL_PERMUTATION_COMPONENT}_${UCONF} + CONFIGURATIONS ${conf} + BUNDLE + DESTINATION ${ly_platform_install_target_RUNTIME_DIR}/${ly_platform_install_target_RUNTIME_SUBDIR} + COMPONENT ${LY_INSTALL_PERMUTATION_COMPONENT}_${UCONF} + CONFIGURATIONS ${conf} + RESOURCE + DESTINATION ${ly_platform_install_target_RUNTIME_DIR}/${ly_platform_install_target_RUNTIME_SUBDIR} + COMPONENT ${LY_INSTALL_PERMUTATION_COMPONENT}_${UCONF} + CONFIGURATIONS ${conf} + ) + endforeach() set(install_relative_binaries_path "${ly_platform_install_target_RUNTIME_DIR}/${ly_platform_install_target_RUNTIME_SUBDIR}") @@ -102,11 +109,16 @@ function(ly_install_target_override) endif() endfunction() -#! ly_install_code_function_override: Mac specific copy function to handle frameworks -function(ly_install_code_function_override) +#! ly_setup_runtime_dependencies_copy_function_override: Mac specific copy function to handle frameworks +function(ly_setup_runtime_dependencies_copy_function_override) configure_file(${LY_ROOT_FOLDER}/cmake/Platform/Mac/InstallUtils_mac.cmake.in ${CMAKE_BINARY_DIR}/runtime_install/InstallUtils_mac.cmake @ONLY) - ly_install_run_script(${CMAKE_BINARY_DIR}/runtime_install/InstallUtils_mac.cmake) + foreach(conf IN LISTS CMAKE_CONFIGURATION_TYPES) + string(TOUPPER ${conf} UCONF) + ly_install(SCRIPT "${CMAKE_BINARY_DIR}/runtime_install/InstallUtils_mac.cmake" + COMPONENT ${LY_INSTALL_PERMUTATION_COMPONENT}_${UCONF} + ) + endforeach() endfunction() diff --git a/cmake/Platform/Windows/PackagingPostBuild.cmake b/cmake/Platform/Windows/PackagingPostBuild.cmake index 377a9fb221..ac457bea87 100644 --- a/cmake/Platform/Windows/PackagingPostBuild.cmake +++ b/cmake/Platform/Windows/PackagingPostBuild.cmake @@ -32,9 +32,6 @@ set(_addtional_defines -dCPACK_RESOURCE_PATH=${CPACK_SOURCE_DIR}/Platform/Windows/Packaging ) -file(REAL_PATH "${CPACK_SOURCE_DIR}/.." _root_path) -file(TO_NATIVE_PATH "${_root_path}/scripts/signer/Platform/Windows/signer.ps1" _sign_script) - if(CPACK_LICENSE_URL) list(APPEND _addtional_defines -dCPACK_LICENSE_URL=${CPACK_LICENSE_URL}) endif() @@ -58,28 +55,41 @@ set(_light_command -o "${_bootstrap_output_file}" ) -set(_signing_command - psexec.exe - -accepteula - -nobanner - -s - powershell.exe - -NoLogo - -ExecutionPolicy Bypass - -File ${_sign_script} -) +if(CPACK_UPLOAD_URL) # Skip signing if we are not uploading the package + file(REAL_PATH "${CPACK_SOURCE_DIR}/.." _root_path) + file(TO_NATIVE_PATH "${_root_path}/scripts/signer/Platform/Windows/signer.ps1" _sign_script) -message(STATUS "Signing package files in ${_cpack_wix_out_dir}") -execute_process( - COMMAND ${_signing_command} -packagePath ${_cpack_wix_out_dir} - RESULT_VARIABLE _signing_result - ERROR_VARIABLE _signing_errors - OUTPUT_VARIABLE _signing_output - ECHO_OUTPUT_VARIABLE -) + unset(_signing_command) + find_program(_psiexec_path psexec.exe) + if(_psiexec_path) + list(APPEND _signing_command + ${_psiexec_path} + -accepteula + -nobanner + -s + ) + endif() -if(NOT ${_signing_result} EQUAL 0) - message(FATAL_ERROR "An error occurred during signing package files. ${_signing_errors}") + find_program(_powershell_path powershell.exe REQUIRED) + list(APPEND _signing_command + ${_powershell_path} + -NoLogo + -ExecutionPolicy Bypass + -File ${_sign_script} + ) + + message(STATUS "Signing package files in ${_cpack_wix_out_dir}") + execute_process( + COMMAND ${_signing_command} -packagePath ${_cpack_wix_out_dir} + RESULT_VARIABLE _signing_result + ERROR_VARIABLE _signing_errors + OUTPUT_VARIABLE _signing_output + ECHO_OUTPUT_VARIABLE + ) + + if(NOT ${_signing_result} EQUAL 0) + message(FATAL_ERROR "An error occurred during signing package files. ${_signing_errors}") + endif() endif() message(STATUS "Creating Bootstrap Installer...") @@ -107,17 +117,19 @@ file(COPY ${_bootstrap_output_file} message(STATUS "Bootstrap installer generated to ${CPACK_PACKAGE_DIRECTORY}/${_bootstrap_filename}") -message(STATUS "Signing bootstrap installer in ${CPACK_PACKAGE_DIRECTORY}") -execute_process( - COMMAND ${_signing_command} -bootstrapPath ${CPACK_PACKAGE_DIRECTORY}/${_bootstrap_filename} - RESULT_VARIABLE _signing_result - ERROR_VARIABLE _signing_errors - OUTPUT_VARIABLE _signing_output - ECHO_OUTPUT_VARIABLE -) +if(CPACK_UPLOAD_URL) # Skip signing if we are not uploading the package + message(STATUS "Signing bootstrap installer in ${CPACK_PACKAGE_DIRECTORY}") + execute_process( + COMMAND ${_signing_command} -bootstrapPath ${CPACK_PACKAGE_DIRECTORY}/${_bootstrap_filename} + RESULT_VARIABLE _signing_result + ERROR_VARIABLE _signing_errors + OUTPUT_VARIABLE _signing_output + ECHO_OUTPUT_VARIABLE + ) -if(NOT ${_signing_result} EQUAL 0) - message(FATAL_ERROR "An error occurred during signing bootstrap installer. ${_signing_errors}") + if(NOT ${_signing_result} EQUAL 0) + message(FATAL_ERROR "An error occurred during signing bootstrap installer. ${_signing_errors}") + endif() endif() # use the internal default path if somehow not specified from cpack_configure_downloads diff --git a/cmake/Platform/Windows/PackagingPreBuild.cmake b/cmake/Platform/Windows/PackagingPreBuild.cmake index d3924c7a02..7f2eedf352 100644 --- a/cmake/Platform/Windows/PackagingPreBuild.cmake +++ b/cmake/Platform/Windows/PackagingPreBuild.cmake @@ -6,21 +6,41 @@ # # +if(NOT CPACK_UPLOAD_URL) # Skip signing if we are not uploading the package + return() +endif() + file(REAL_PATH "${CPACK_SOURCE_DIR}/.." _root_path) set(_cpack_wix_out_dir ${CPACK_TOPLEVEL_DIRECTORY}) file(TO_NATIVE_PATH "${_root_path}/scripts/signer/Platform/Windows/signer.ps1" _sign_script) -set(_signing_command - psexec.exe - -accepteula - -nobanner - -s - powershell.exe +unset(_signing_command) +find_program(_psiexec_path psexec.exe) +if(_psiexec_path) + list(APPEND _signing_command + ${_psiexec_path} + -accepteula + -nobanner + -s + ) +endif() + +find_program(_powershell_path powershell.exe REQUIRED) +list(APPEND _signing_command + ${_powershell_path} -NoLogo - -ExecutionPolicy Bypass + -ExecutionPolicy Bypass -File ${_sign_script} ) +# This requires to have a valid local certificate. In continuous integration, these certificates are stored +# in the machine directly. +# You can generate a test certificate to be able to run this in a PowerShell elevated promp with: +# New-SelfSignedCertificate -DnsName foo.o3de.com -Type CodeSigning -CertStoreLocation Cert:\CurrentUser\My +# Export-Certificate -Cert (Get-ChildItem Cert:\CurrentUser\My\) -Filepath "c:\selfsigned.crt" +# Import-Certificate -FilePath "c:\selfsigned.crt" -Cert Cert:\CurrentUser\TrustedPublisher +# Import-Certificate -FilePath "c:\selfsigned.crt" -Cert Cert:\CurrentUser\Root + message(STATUS "Signing executable files in ${_cpack_wix_out_dir}") execute_process( COMMAND ${_signing_command} -exePath ${_cpack_wix_out_dir} @@ -32,6 +52,6 @@ execute_process( if(NOT ${_signing_result} EQUAL 0) message(FATAL_ERROR "An error occurred during signing executable files. ${_signing_errors}") +else() + message(STATUS "Signing exes complete!") endif() - -message(STATUS "Signing exes complete!") diff --git a/cmake/Platform/Windows/Packaging_windows.cmake b/cmake/Platform/Windows/Packaging_windows.cmake index 4a03df2fd2..7b8f5a6c19 100644 --- a/cmake/Platform/Windows/Packaging_windows.cmake +++ b/cmake/Platform/Windows/Packaging_windows.cmake @@ -28,11 +28,8 @@ set(_cmake_package_name "cmake-${CPACK_DESIRED_CMAKE_VERSION}-windows-x86_64") set(CPACK_CMAKE_PACKAGE_FILE "${_cmake_package_name}.zip") set(CPACK_CMAKE_PACKAGE_HASH "15a49e2ab81c1822d75b1b1a92f7863f58e31f6d6aac1c4103eef2b071be3112") -# workaround for shortening the path cpack installs to by stripping the platform directory and forcing monolithic -# mode to strip out component folders. this unfortunately is the closest we can get to changing the install location -# as CPACK_PACKAGING_INSTALL_PREFIX/CPACK_SET_DESTDIR isn't supported for the WiX generator +# workaround for shortening the path cpack installs to by stripping the platform directory set(CPACK_TOPLEVEL_TAG "") -set(CPACK_MONOLITHIC_INSTALL ON) # CPack will generate the WiX product/upgrade GUIDs further down the chain if they weren't supplied # however, they are unique for each run. instead, let's do the auto generation here and add it to @@ -108,6 +105,8 @@ set(_raw_text_license [[ #(loc.InstallEulaAcceptance) ]]) +# The offline installer generation will be a single monolithic MSI. The WIX burn tool for the bootstrapper EXE has a size limitation. +# So we will exclude the generation of the boostrapper EXE in the offline case. if(LY_INSTALLER_DOWNLOAD_URL) set(WIX_THEME_WARNING_IMAGE ${CPACK_SOURCE_DIR}/Platform/Windows/Packaging/warning.png) diff --git a/cmake/Projects.cmake b/cmake/Projects.cmake index c09fe0fc6f..34ef3efd9b 100644 --- a/cmake/Projects.cmake +++ b/cmake/Projects.cmake @@ -150,22 +150,43 @@ foreach(project ${LY_PROJECTS}) # Get project name o3de_read_json_key(project_name ${full_directory_path}/project.json "project_name") + # The cmake tar command has a bit of a flaw + # Any paths within the archive files it creates are relative to the current working directory. + # That means with the setup of: + # cwd = "/Cache/pc" + # project product assets = "/Cache/pc/*" + # cmake dependency registry files = "/build/bin/Release/Registry/*" + # Running the tar command would result in the assets being placed in the to layout + # correctly, but the registry files + # engine.pak/ + # ../...build/bin/Release/Registry/cmake_dependencies.*.setreg -> Not correct + # project.json -> Correct + # Generate pak for project in release installs cmake_path(RELATIVE_PATH CMAKE_RUNTIME_OUTPUT_DIRECTORY BASE_DIRECTORY ${CMAKE_BINARY_DIR} OUTPUT_VARIABLE install_base_runtime_output_directory) set(install_engine_pak_template [=[ if("${CMAKE_INSTALL_CONFIG_NAME}" MATCHES "^([Rr][Ee][Ll][Ee][Aa][Ss][Ee])$") set(install_output_folder "${CMAKE_INSTALL_PREFIX}/@install_base_runtime_output_directory@/@PAL_PLATFORM_NAME@/${CMAKE_INSTALL_CONFIG_NAME}/@LY_BUILD_PERMUTATION@") set(install_pak_output_folder "${install_output_folder}/Cache/@LY_ASSET_DEPLOY_ASSET_TYPE@") + set(runtime_output_directory_RELEASE @CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE@) if(NOT DEFINED LY_ASSET_DEPLOY_ASSET_TYPE) set(LY_ASSET_DEPLOY_ASSET_TYPE @LY_ASSET_DEPLOY_ASSET_TYPE@) endif() message(STATUS "Generating ${install_pak_output_folder}/engine.pak from @full_directory_path@/Cache/${LY_ASSET_DEPLOY_ASSET_TYPE}") file(MAKE_DIRECTORY "${install_pak_output_folder}") cmake_path(SET cache_product_path "@full_directory_path@/Cache/${LY_ASSET_DEPLOY_ASSET_TYPE}") + # Copy the generated cmake_dependencies.*.setreg files for loading gems in non-monolithic to the cache + file(GLOB gem_source_paths_setreg "${runtime_output_directory_RELEASE}/Registry/*.setreg") + # The MergeSettingsToRegistry_TargetBuildDependencyRegistry function looks for lowercase "registry" + # So make sure the to copy it to a lowercase path, so that it works on non-case sensitive filesystems + file(MAKE_DIRECTORY "${cache_product_path}/registry") + file(COPY ${gem_source_paths_setreg} DESTINATION "${cache_product_path}/registry") + file(GLOB product_assets "${cache_product_path}/*") - if(product_assets) + list(APPEND pak_artifacts ${product_assets}) + if(pak_artifacts) execute_process( - COMMAND ${CMAKE_COMMAND} -E tar "cf" "${install_pak_output_folder}/engine.pak" --format=zip -- ${product_assets} + COMMAND ${CMAKE_COMMAND} -E tar "cf" "${install_pak_output_folder}/engine.pak" --format=zip -- ${pak_artifacts} WORKING_DIRECTORY "${cache_product_path}" RESULT_VARIABLE archive_creation_result ) diff --git a/cmake/install/ConfigurationType_config.cmake.in b/cmake/install/ConfigurationType_config.cmake.in index 074e034899..0a6940d7ff 100644 --- a/cmake/install/ConfigurationType_config.cmake.in +++ b/cmake/install/ConfigurationType_config.cmake.in @@ -8,4 +8,4 @@ include_guard(GLOBAL) -list(APPEND CMAKE_CONFIGURATION_TYPES @CMAKE_INSTALL_CONFIG_NAME@) +list(APPEND CMAKE_CONFIGURATION_TYPES @conf@) diff --git a/engine.json b/engine.json index 14deffecb8..05ccd0abfd 100644 --- a/engine.json +++ b/engine.json @@ -92,8 +92,9 @@ "templates": [ "Templates/AssetGem", "Templates/DefaultGem", - "Templates/CustomTool", "Templates/DefaultProject", - "Templates/MinimalProject" + "Templates/CppToolGem", + "Templates/MinimalProject", + "Templates/PythonToolGem" ] } diff --git a/scripts/build/Jenkins/Jenkinsfile b/scripts/build/Jenkins/Jenkinsfile index f5e5edcf66..127d6b80ca 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 + '' @@ -449,8 +449,8 @@ def UploadAPLogs(Map options, String branchName, String jobName, String workspac } def command = "${pythonPath} -u ${s3UploadScriptPath} --base_dir ${apLogsPath} " + "--file_regex \".*\" --bucket ${env.AP_LOGS_S3_BUCKET} " + - "--search_subdirectories True --key_prefix ${env.JOB_NAME}/${branchName}/${env.BUILD_NUMBER}/${jobName}" + - "--extra-args {\"ACL\": \"bucket-owner-full-control\"}" + "--search_subdirectories True --key_prefix ${env.JENKINS_JOB_NAME}/${branchName}/${env.BUILD_NUMBER}/${jobName} " + + "--extra_args {\"ACL\": \"bucket-owner-full-control\"}" palSh(command, "Uploading AP logs for job ${jobName} for branch ${branchName}", false) } } @@ -806,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/build/Platform/Android/build_config.json b/scripts/build/Platform/Android/build_config.json index c1fbb1dd87..5e2da44a3c 100644 --- a/scripts/build/Platform/Android/build_config.json +++ b/scripts/build/Platform/Android/build_config.json @@ -35,7 +35,7 @@ "PARAMETERS": { "CONFIGURATION":"debug", "OUTPUT_DIRECTORY":"build\\android", - "CMAKE_OPTIONS":"-G \"Ninja Multi-Config\" -DCMAKE_TOOLCHAIN_FILE=cmake\\Platform\\Android\\Toolchain_android.cmake -DANDROID_ABI=arm64-v8a -DANDROID_ARM_MODE=arm -DANDROID_ARM_NEON=FALSE -DANDROID_NATIVE_API_LEVEL=21 -DLY_NDK_DIR=\"!LY_3RDPARTY_PATH!\\android-ndk\\r21d\" -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS":"-G \"Ninja Multi-Config\" -DCMAKE_TOOLCHAIN_FILE=cmake\\Platform\\Android\\Toolchain_android.cmake -DANDROID_ABI=arm64-v8a -DANDROID_ARM_MODE=arm -DANDROID_ARM_NEON=FALSE -DANDROID_NATIVE_API_LEVEL=21 -DLY_NDK_DIR=\"!LY_3RDPARTY_PATH!\\android-ndk\\r21d\"", "CMAKE_LY_PROJECTS":"AutomatedTesting", "CMAKE_TARGET":"all", "CMAKE_BUILD_ARGS":"-j!NUMBER_OF_PROCESSORS!" @@ -60,7 +60,7 @@ "PARAMETERS": { "CONFIGURATION":"profile", "OUTPUT_DIRECTORY":"build\\android", - "CMAKE_OPTIONS":"-G \"Ninja Multi-Config\" -DCMAKE_TOOLCHAIN_FILE=cmake\\Platform\\Android\\Toolchain_android.cmake -DANDROID_ABI=arm64-v8a -DANDROID_ARM_MODE=arm -DANDROID_ARM_NEON=FALSE -DANDROID_NATIVE_API_LEVEL=21 -DLY_NDK_DIR=\"!LY_3RDPARTY_PATH!\\android-ndk\\r21d\" -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS":"-G \"Ninja Multi-Config\" -DCMAKE_TOOLCHAIN_FILE=cmake\\Platform\\Android\\Toolchain_android.cmake -DANDROID_ABI=arm64-v8a -DANDROID_ARM_MODE=arm -DANDROID_ARM_NEON=FALSE -DANDROID_NATIVE_API_LEVEL=21 -DLY_NDK_DIR=\"!LY_3RDPARTY_PATH!\\android-ndk\\r21d\"", "CMAKE_LY_PROJECTS":"AutomatedTesting", "CMAKE_TARGET":"all", "CMAKE_BUILD_ARGS":"-j!NUMBER_OF_PROCESSORS!" @@ -93,7 +93,7 @@ "PARAMETERS": { "CONFIGURATION":"profile", "OUTPUT_DIRECTORY":"build\\windows_vs2019", - "CMAKE_OPTIONS":"-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS":"-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0", "CMAKE_LY_PROJECTS":"AutomatedTesting", "CMAKE_TARGET":"AssetProcessorBatch", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo", @@ -112,7 +112,7 @@ "PARAMETERS": { "CONFIGURATION":"release", "OUTPUT_DIRECTORY":"build\\android", - "CMAKE_OPTIONS":"-G \"Ninja Multi-Config\" -DCMAKE_TOOLCHAIN_FILE=cmake\\Platform\\Android\\Toolchain_android.cmake -DANDROID_ABI=arm64-v8a -DANDROID_ARM_MODE=arm -DANDROID_ARM_NEON=FALSE -DANDROID_NATIVE_API_LEVEL=21 -DLY_NDK_DIR=\"!LY_3RDPARTY_PATH!\\android-ndk\\r21d\" -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS":"-G \"Ninja Multi-Config\" -DCMAKE_TOOLCHAIN_FILE=cmake\\Platform\\Android\\Toolchain_android.cmake -DANDROID_ABI=arm64-v8a -DANDROID_ARM_MODE=arm -DANDROID_ARM_NEON=FALSE -DANDROID_NATIVE_API_LEVEL=21 -DLY_NDK_DIR=\"!LY_3RDPARTY_PATH!\\android-ndk\\r21d\"", "CMAKE_LY_PROJECTS":"AutomatedTesting", "CMAKE_TARGET":"all", "CMAKE_BUILD_ARGS":"-j!NUMBER_OF_PROCESSORS!" @@ -128,7 +128,7 @@ "PARAMETERS": { "CONFIGURATION":"release", "OUTPUT_DIRECTORY":"build\\mono_android", - "CMAKE_OPTIONS":"-G \"Ninja Multi-Config\" -DCMAKE_TOOLCHAIN_FILE=cmake\\Platform\\Android\\Toolchain_android.cmake -DANDROID_ABI=arm64-v8a -DANDROID_ARM_MODE=arm -DANDROID_ARM_NEON=FALSE -DANDROID_NATIVE_API_LEVEL=21 -DLY_NDK_DIR=\"!LY_3RDPARTY_PATH!\\android-ndk\\r21d\" -DLY_MONOLITHIC_GAME=TRUE -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS":"-G \"Ninja Multi-Config\" -DCMAKE_TOOLCHAIN_FILE=cmake\\Platform\\Android\\Toolchain_android.cmake -DANDROID_ABI=arm64-v8a -DANDROID_ARM_MODE=arm -DANDROID_ARM_NEON=FALSE -DANDROID_NATIVE_API_LEVEL=21 -DLY_NDK_DIR=\"!LY_3RDPARTY_PATH!\\android-ndk\\r21d\" -DLY_MONOLITHIC_GAME=TRUE", "CMAKE_LY_PROJECTS":"AutomatedTesting", "CMAKE_TARGET":"all", "CMAKE_BUILD_ARGS":"-j!NUMBER_OF_PROCESSORS!" diff --git a/scripts/build/Platform/Linux/build_config.json b/scripts/build/Platform/Linux/build_config.json index b76a950beb..c0307de23d 100644 --- a/scripts/build/Platform/Linux/build_config.json +++ b/scripts/build/Platform/Linux/build_config.json @@ -37,7 +37,7 @@ "PARAMETERS": { "CONFIGURATION": "debug", "OUTPUT_DIRECTORY": "build/linux", - "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4", + "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_PARALLEL_LINK_JOBS=4", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "all" } @@ -53,7 +53,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/linux", - "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4", + "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_PARALLEL_LINK_JOBS=4", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "all" } @@ -80,7 +80,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/linux", - "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4", + "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_PARALLEL_LINK_JOBS=4", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "all", "CTEST_OPTIONS": "-E Gem::EMotionFX.Editor.Tests -LE (SUITE_sandbox|SUITE_awsi) -L FRAMEWORK_googletest --no-tests=error", @@ -110,7 +110,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/linux", - "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4", + "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_PARALLEL_LINK_JOBS=4", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "AssetProcessorBatch", "ASSET_PROCESSOR_BINARY": "bin/profile/AssetProcessorBatch", @@ -142,7 +142,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/linux", - "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4", + "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_PARALLEL_LINK_JOBS=4", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "TEST_SUITE_periodic", "CTEST_OPTIONS": "-L (SUITE_periodic) --no-tests=error", @@ -162,7 +162,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/linux", - "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4 -DO3DE_HOME_PATH=\"${WORKSPACE}/home\" -DO3DE_REGISTER_ENGINE_PATH=\"${WORKSPACE}/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", + "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_PARALLEL_LINK_JOBS=4 -DO3DE_HOME_PATH=\"${WORKSPACE}/home\" -DO3DE_REGISTER_ENGINE_PATH=\"${WORKSPACE}/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "all", "CTEST_OPTIONS": "-L (SUITE_sandbox) --no-tests=error" @@ -178,7 +178,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/linux", - "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4", + "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_PARALLEL_LINK_JOBS=4", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "TEST_SUITE_benchmark", "CTEST_OPTIONS": "-L (SUITE_benchmark) --no-tests=error", @@ -195,7 +195,7 @@ "PARAMETERS": { "CONFIGURATION": "release", "OUTPUT_DIRECTORY": "build/linux", - "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4", + "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_PARALLEL_LINK_JOBS=4", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "all" } @@ -210,7 +210,7 @@ "PARAMETERS": { "CONFIGURATION": "release", "OUTPUT_DIRECTORY": "build/mono_linux", - "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_MONOLITHIC_GAME=TRUE -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4", + "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_MONOLITHIC_GAME=TRUE -DLY_PARALLEL_LINK_JOBS=4", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "all" } diff --git a/scripts/build/Platform/Mac/build_config.json b/scripts/build/Platform/Mac/build_config.json index 7eb3cb5699..b57cc522bc 100644 --- a/scripts/build/Platform/Mac/build_config.json +++ b/scripts/build/Platform/Mac/build_config.json @@ -37,7 +37,7 @@ "PARAMETERS": { "CONFIGURATION": "debug", "OUTPUT_DIRECTORY": "build/mac", - "CMAKE_OPTIONS": "-G Xcode -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G Xcode", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "ALL_BUILD" } @@ -51,7 +51,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/mac", - "CMAKE_OPTIONS": "-G Xcode -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G Xcode", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "ALL_BUILD" } @@ -81,7 +81,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/mac", - "CMAKE_OPTIONS": "-G Xcode -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G Xcode", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "AssetProcessorBatch", "ASSET_PROCESSOR_BINARY": "bin/profile/AssetProcessorBatch", @@ -99,7 +99,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/mac", - "CMAKE_OPTIONS": "-G Xcode -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G Xcode", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "TEST_SUITE_periodic", "CTEST_OPTIONS": "-L \"(SUITE_periodic)\"", @@ -116,7 +116,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/mac", - "CMAKE_OPTIONS": "-G Xcode -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G Xcode", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "TEST_SUITE_benchmark", "CTEST_OPTIONS": "-L \"(SUITE_benchmark)\"", @@ -133,7 +133,7 @@ "PARAMETERS": { "CONFIGURATION": "release", "OUTPUT_DIRECTORY": "build/mac", - "CMAKE_OPTIONS": "-G Xcode -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G Xcode", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "ALL_BUILD" } @@ -148,7 +148,7 @@ "PARAMETERS": { "CONFIGURATION": "release", "OUTPUT_DIRECTORY": "build/mono_mac", - "CMAKE_OPTIONS": "-G Xcode -DLY_MONOLITHIC_GAME=TRUE -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G Xcode -DLY_MONOLITHIC_GAME=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "ALL_BUILD" } diff --git a/scripts/build/Platform/Windows/build_config.json b/scripts/build/Platform/Windows/build_config.json index de83294639..b185a845ec 100644 --- a/scripts/build/Platform/Windows/build_config.json +++ b/scripts/build/Platform/Windows/build_config.json @@ -99,7 +99,7 @@ "PARAMETERS": { "CONFIGURATION": "debug", "OUTPUT_DIRECTORY": "build\\windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "ALL_BUILD", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo" @@ -113,7 +113,7 @@ "PARAMETERS": { "CONFIGURATION": "debug", "OUTPUT_DIRECTORY": "build\\windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "TEST_SUITE_smoke TEST_SUITE_main", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo", @@ -131,7 +131,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build\\windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE -DLY_TEST_IMPACT_INSTRUMENTATION_BIN=!TEST_IMPACT_WIN_BINARY!", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_TEST_IMPACT_INSTRUMENTATION_BIN=!TEST_IMPACT_WIN_BINARY!", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "ALL_BUILD", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo" @@ -162,7 +162,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build\\windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "TEST_SUITE_smoke TEST_SUITE_main", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo", @@ -183,7 +183,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build\\windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "TEST_SUITE_smoke TEST_SUITE_main", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo", @@ -203,7 +203,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build\\windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "AssetProcessorBatch", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo", @@ -234,7 +234,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build\\windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "TEST_SUITE_awsi", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo", @@ -253,7 +253,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build\\windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "TEST_SUITE_periodic", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo", @@ -275,7 +275,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build\\windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "TEST_SUITE_sandbox", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo", @@ -294,7 +294,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build\\windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "TEST_SUITE_benchmark", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo", @@ -314,7 +314,7 @@ "PARAMETERS": { "CONFIGURATION": "release", "OUTPUT_DIRECTORY": "build\\windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "ALL_BUILD", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo" @@ -330,7 +330,7 @@ "PARAMETERS": { "CONFIGURATION": "release", "OUTPUT_DIRECTORY": "build\\mono_windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_MONOLITHIC_GAME=TRUE -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_MONOLITHIC_GAME=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "ALL_BUILD", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo" @@ -345,7 +345,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build\\windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE -DLY_DISABLE_TEST_MODULES=TRUE", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_DISABLE_TEST_MODULES=TRUE", "CMAKE_LY_PROJECTS": "", "CMAKE_TARGET": "INSTALL", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo" @@ -363,7 +363,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build\\windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE -DLY_DISABLE_TEST_MODULES=TRUE -DLY_VERSION_ENGINE_NAME=o3de-sdk -DLY_INSTALLER_WIX_ROOT=\"!WIX! \"", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_DISABLE_TEST_MODULES=TRUE -DLY_VERSION_ENGINE_NAME=o3de-sdk -DLY_INSTALLER_WIX_ROOT=\"!WIX! \"", "EXTRA_CMAKE_OPTIONS": "-DLY_INSTALLER_AUTO_GEN_TAG=ON -DLY_INSTALLER_DOWNLOAD_URL=!INSTALLER_DOWNLOAD_URL! -DLY_INSTALLER_LICENSE_URL=!INSTALLER_DOWNLOAD_URL!/license", "CPACK_BUCKET": "!INSTALLER_BUCKET!", "CMAKE_LY_PROJECTS": "", @@ -382,7 +382,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build\\windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE -DCMAKE_MODULE_PATH=!WORKSPACE!/o3de/cmake", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DCMAKE_MODULE_PATH=!WORKSPACE!/o3de/cmake", "CMAKE_LY_PROJECTS": "", "CMAKE_TARGET": "ALL_BUILD", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo" @@ -397,7 +397,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build\\windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE -DCMAKE_MODULE_PATH=!WORKSPACE!/o3de/install/cmake", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DCMAKE_MODULE_PATH=!WORKSPACE!/o3de/install/cmake", "CMAKE_LY_PROJECTS": "", "CMAKE_TARGET": "ALL_BUILD", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo" diff --git a/scripts/build/Platform/Windows/package_build_config.json b/scripts/build/Platform/Windows/package_build_config.json index 89431ad96e..5eb96f1e38 100644 --- a/scripts/build/Platform/Windows/package_build_config.json +++ b/scripts/build/Platform/Windows/package_build_config.json @@ -4,7 +4,7 @@ "PARAMETERS": { "CONFIGURATION":"profile", "OUTPUT_DIRECTORY":"windows_vs2019", - "CMAKE_OPTIONS":"-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS":"-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0", "CMAKE_LY_PROJECTS":"AtomTest;AtomSampleViewer", "CMAKE_TARGET":"ALL_BUILD", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo" diff --git a/scripts/build/Platform/iOS/build_config.json b/scripts/build/Platform/iOS/build_config.json index 01f545ffb6..895f74daae 100644 --- a/scripts/build/Platform/iOS/build_config.json +++ b/scripts/build/Platform/iOS/build_config.json @@ -27,7 +27,7 @@ "PARAMETERS": { "CONFIGURATION": "debug", "OUTPUT_DIRECTORY": "build/ios", - "CMAKE_OPTIONS": "-G Xcode -DCMAKE_TOOLCHAIN_FILE=cmake/Platform/iOS/Toolchain_ios.cmake -DLY_MONOLITHIC_GAME=TRUE -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED=FALSE -DLY_IOS_CODE_SIGNING_IDENTITY=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED=FALSE -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G Xcode -DCMAKE_TOOLCHAIN_FILE=cmake/Platform/iOS/Toolchain_ios.cmake -DLY_MONOLITHIC_GAME=TRUE -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED=FALSE -DLY_IOS_CODE_SIGNING_IDENTITY=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED=FALSE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "ALL_BUILD", "CMAKE_NATIVE_BUILD_ARGS": "-destination generic/platform=iOS" @@ -44,7 +44,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/ios", - "CMAKE_OPTIONS": "-G Xcode -DCMAKE_TOOLCHAIN_FILE=cmake/Platform/iOS/Toolchain_ios.cmake -DLY_MONOLITHIC_GAME=TRUE -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED=FALSE -DLY_IOS_CODE_SIGNING_IDENTITY=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED=FALSE -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G Xcode -DCMAKE_TOOLCHAIN_FILE=cmake/Platform/iOS/Toolchain_ios.cmake -DLY_MONOLITHIC_GAME=TRUE -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED=FALSE -DLY_IOS_CODE_SIGNING_IDENTITY=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED=FALSE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "ALL_BUILD", "CMAKE_NATIVE_BUILD_ARGS": "-destination generic/platform=iOS" @@ -76,7 +76,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/mac", - "CMAKE_OPTIONS": "-G Xcode -DLY_UNITY_BUILD=TRUE -DO3DE_HOME_PATH=\"${WORKSPACE}/home\" -DO3DE_REGISTER_ENGINE_PATH=\"${WORKSPACE}/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", + "CMAKE_OPTIONS": "-G Xcode -DO3DE_HOME_PATH=\"${WORKSPACE}/home\" -DO3DE_REGISTER_ENGINE_PATH=\"${WORKSPACE}/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "AssetProcessorBatch", "ASSET_PROCESSOR_BINARY": "bin/profile/AssetProcessorBatch", @@ -94,7 +94,7 @@ "PARAMETERS": { "CONFIGURATION": "release", "OUTPUT_DIRECTORY": "build/ios", - "CMAKE_OPTIONS": "-G Xcode -DCMAKE_TOOLCHAIN_FILE=cmake/Platform/iOS/Toolchain_ios.cmake -DLY_MONOLITHIC_GAME=TRUE -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED=FALSE -DLY_IOS_CODE_SIGNING_IDENTITY=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED=FALSE -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G Xcode -DCMAKE_TOOLCHAIN_FILE=cmake/Platform/iOS/Toolchain_ios.cmake -DLY_MONOLITHIC_GAME=TRUE -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED=FALSE -DLY_IOS_CODE_SIGNING_IDENTITY=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED=FALSE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "ALL_BUILD", "CMAKE_NATIVE_BUILD_ARGS": "-destination generic/platform=iOS" @@ -112,7 +112,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/ios_test", - "CMAKE_OPTIONS": "-G Xcode -DCMAKE_TOOLCHAIN_FILE=cmake/Platform/iOS/Toolchain_ios.cmake -DLY_MONOLITHIC_GAME=FALSE -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED=TRUE -DLY_IOS_CODE_SIGNING_IDENTITY=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED=TRUE -DLY_UNITY_BUILD=TRUE -DO3DE_HOME_PATH=\"${WORKSPACE}/home\" -DO3DE_REGISTER_ENGINE_PATH=\"${WORKSPACE}/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", + "CMAKE_OPTIONS": "-G Xcode -DCMAKE_TOOLCHAIN_FILE=cmake/Platform/iOS/Toolchain_ios.cmake -DLY_MONOLITHIC_GAME=FALSE -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED=TRUE -DLY_IOS_CODE_SIGNING_IDENTITY=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED=TRUE -DO3DE_HOME_PATH=\"${WORKSPACE}/home\" -DO3DE_REGISTER_ENGINE_PATH=\"${WORKSPACE}/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", "CMAKE_TARGET": "ALL_BUILD", "CMAKE_NATIVE_BUILD_ARGS": "", "TARGET_DEVICE_NAME": "Lumberyard", diff --git a/scripts/commit_validation/commit_validation/commit_validation.py b/scripts/commit_validation/commit_validation/commit_validation.py index b9d992fc9c..513244a735 100755 --- a/scripts/commit_validation/commit_validation/commit_validation.py +++ b/scripts/commit_validation/commit_validation/commit_validation.py @@ -173,16 +173,12 @@ EXCLUDED_VALIDATION_PATTERNS = [ '*/3rdParty/*', '*/__pycache__/*', '*/External/*', - 'build', - 'Cache', - '*/Code/Framework/AzCore/azgnmx/azgnmx/*', - 'Code/Tools/CryFXC', - 'Code/Tools/HLSLCrossCompiler', - 'Code/Tools/HLSLCrossCompilerMETAL', - 'Docs', + 'build', # build artifacts + '*/Cache/*', # Asset processing artifacts + 'install', # install layout artifacts 'python/runtime', + 'restricted/*/Code/Framework/AzCore/azgnmx/azgnmx/*', 'restricted/*/Tools/*RemoteControl', - 'Tools/3dsmax', '*/user/Cache/*', '*/user/log/*', ] diff --git a/scripts/commit_validation/commit_validation/pal_allowedlist.txt b/scripts/commit_validation/commit_validation/pal_allowedlist.txt index 4c90bcb6b4..6fd5aab62e 100644 --- a/scripts/commit_validation/commit_validation/pal_allowedlist.txt +++ b/scripts/commit_validation/commit_validation/pal_allowedlist.txt @@ -23,7 +23,6 @@ */Code/Framework/AzCore/AzCore/std/parallel/binary_semaphore.h */Code/Framework/AzCore/AzCore/std/parallel/semaphore.h */Code/Framework/AzCore/Platform/Android/AzCore/AzCore_Traits_Android.h -*/Code/Framework/AzCore/Platform/AppleTV/AzCore/AzCore_Traits_AppleTV.h */Code/Framework/AzCore/Platform/iOS/AzCore/AzCore_Traits_iOS.h */Code/Framework/AzCore/Platform/Jasper/AzCore/AzCore_Traits_Jasper.h */Code/Framework/AzCore/Platform/Linux/AzCore/AzCore_Traits_Linux.h @@ -49,7 +48,6 @@ */Code/Tools/* */Gems/*/3rdParty/* */Gems/*/External/* -*/Gems/CryLegacy* */Gems/EMotionFX/Code/EMotionFX/Rendering/OpenGL2/Source/GLInclude.h */Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/MainWindow.cpp */Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/PluginManager.cpp @@ -61,4 +59,3 @@ */Gems/SaveData/Code/Tests/SaveDataTest.cpp */Gems/WhiteBox/Code/Source/Rendering/Legacy/WhiteBoxLegacyRenderMesh.cpp */restricted/*/Code/Framework/AzCore/AzCore/AzCore_Traits_*.h -*/Tools/CryDeprecation/precompile_check_defines.h diff --git a/scripts/scrubbing/validator_data_LEGAL_REVIEW_REQUIRED.py b/scripts/scrubbing/validator_data_LEGAL_REVIEW_REQUIRED.py index 666e04a2b0..0c008cbe65 100755 --- a/scripts/scrubbing/validator_data_LEGAL_REVIEW_REQUIRED.py +++ b/scripts/scrubbing/validator_data_LEGAL_REVIEW_REQUIRED.py @@ -100,14 +100,10 @@ def get_prohibited_platforms_for_package(package): def get_bypassed_directories(is_all): # Temporarily exempt folders to not fail validation while people is fixing validation errors, they will be removed once the errors are fixed. temp_bypass_directories = [ - 'commit_validation', - 'LauncherTestTools', - 'AutomatedTesting', - 'Atom' + 'commit_validation' ] bypassed_directories = [ - 'python', - 'AWSPythonSDK' + 'python' ] if not is_all: bypassed_directories.extend([ @@ -116,13 +112,7 @@ def get_bypassed_directories(is_all): 'Cache', 'logs', 'AssetProcessorTemp', - 'JenkinsScripts', - 'BuildLambdaFunctions', - 'layouts', - '.idea', 'user/log', - 'DirectXShaderCompiler', - 'v-hacd', 'External' ]) bypassed_directories.extend(temp_bypass_directories)