diff --git a/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/hydra_test_utils.py b/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/hydra_test_utils.py index 2583109573..382a6f27f7 100644 --- a/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/hydra_test_utils.py +++ b/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/hydra_test_utils.py @@ -6,13 +6,13 @@ SPDX-License-Identifier: Apache-2.0 OR MIT import logging import os -import tempfile +import psutil import ly_test_tools.log.log_monitor import ly_test_tools.environment.process_utils as process_utils import ly_test_tools.environment.waiter as waiter -from ly_remote_console.remote_console_commands import RemoteConsole as RemoteConsole from ly_remote_console.remote_console_commands import send_command_and_expect_response as send_command_and_expect_response + logger = logging.getLogger(__name__) @@ -95,7 +95,7 @@ def launch_and_validate_results_launcher(launcher, level, remote_console_instanc return port_listening if null_renderer: - launcher.args.extend(["-NullRenderer"]) + launcher.args.extend(["-rhi=Null"]) # Start the Launcher with launcher.start(): @@ -110,8 +110,8 @@ def launch_and_validate_results_launcher(launcher, level, remote_console_instanc # Load the specified level in the launcher send_command_and_expect_response(remote_console_instance, - f"map {level}", - "LEVEL_LOAD_COMPLETE", timeout=30) + f"LoadLevel {level}", + "LEVEL_LOAD_END", timeout=30) # Monitor the console for expected lines for line in expected_lines: diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/AltitudeFilter_FilterStageToggle.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/AltitudeFilter_FilterStageToggle.py index bbdb925585..2fac361ced 100755 --- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/AltitudeFilter_FilterStageToggle.py +++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/AltitudeFilter_FilterStageToggle.py @@ -36,8 +36,8 @@ class TestAltitudeFilterFilterStageToggle(EditorTestHelper): :return: None """ - PREPROCESS_INSTANCE_COUNT = 24 - POSTPROCESS_INSTANCE_COUNT = 18 + PREPROCESS_INSTANCE_COUNT = 44 + POSTPROCESS_INSTANCE_COUNT = 34 # Create empty level self.test_success = self.create_level( @@ -62,25 +62,7 @@ class TestAltitudeFilterFilterStageToggle(EditorTestHelper): dynveg.create_surface_entity("Surface_Entity_Parent", position, 16.0, 16.0, 1.0) # Add entity with Mesh to replicate creation of hills - hill_entity = dynveg.create_mesh_surface_entity_with_slopes("hill", position, 40.0, 40.0, 40.0) - - # Disable/Re-enable Mesh component due to ATOM-14299 - general.idle_wait(1.0) - editor.EditorComponentAPIBus(bus.Broadcast, 'DisableComponents', [hill_entity.components[0]]) - is_enabled = editor.EditorComponentAPIBus(bus.Broadcast, 'IsComponentEnabled', hill_entity.components[0]) - if is_enabled: - print("Mesh component is still enabled") - else: - print("Mesh component was disabled") - editor.EditorComponentAPIBus(bus.Broadcast, 'EnableComponents', [hill_entity.components[0]]) - is_enabled = editor.EditorComponentAPIBus(bus.Broadcast, 'IsComponentEnabled', hill_entity.components[0]) - if is_enabled: - print("Mesh component is now enabled") - else: - print("Mesh component is still disabled") - - # Increase Box Shape size to encompass the hills - vegetation.get_set_test(1, "Box Shape|Box Configuration|Dimensions", math.Vector3(100.0, 100.0, 100.0)) + hill_entity = dynveg.create_mesh_surface_entity_with_slopes("hill", position, 10.0) # Set a Min Altitude of 38 and Max of 40 in Vegetation Altitude Filter vegetation.get_set_test(3, "Configuration|Altitude Min", 38.0) diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/DynamicSliceInstanceSpawner_Embedded_E2E.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/DynamicSliceInstanceSpawner_Embedded_E2E.py index 4b0951acaf..6a99979a1f 100755 --- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/DynamicSliceInstanceSpawner_Embedded_E2E.py +++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/DynamicSliceInstanceSpawner_Embedded_E2E.py @@ -9,9 +9,10 @@ import sys sys.path.append(os.path.dirname(os.path.abspath(__file__))) import azlmbr.asset as asset +import azlmbr.components as components import azlmbr.legacy.general as general import azlmbr.bus as bus -import azlmbr.entity as EntityId +import azlmbr.entity as entity import azlmbr.editor as editor import azlmbr.math as math import azlmbr.paths @@ -83,18 +84,12 @@ class TestDynamicSliceInstanceSpawnerEmbeddedEditor(EditorTestHelper): self.log(f"Expected {num_expected_instances} instances - Found {num_found} instances") self.test_success = self.test_success and num_found == num_expected_instances - # 5) Create a new entity with a Camera component for testing in the launcher + # 5) Move the default Camera entity for testing in the launcher cam_position = math.Vector3(512.0, 500.0, 35.0) - camera_component = ["Camera"] - new_entity_id2 = editor.ToolsApplicationRequestBus( - bus.Broadcast, "CreateNewEntityAtPosition", cam_position, EntityId.EntityId() - ) - if new_entity_id2.IsValid(): - self.log("Camera entity created") - camera_entity = hydra.Entity("Camera Entity", new_entity_id2) - camera_entity.components = [] - for component in camera_component: - camera_entity.components.append(hydra.add_component(component, new_entity_id2)) + search_filter = entity.SearchFilter() + search_filter.names = ["Camera"] + search_entity_ids = entity.SearchBus(bus.Broadcast, 'SearchEntities', search_filter) + components.TransformBus(bus.Event, "MoveEntity", search_entity_ids[0], cam_position) # 6) Save and export to engine general.save_level() diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/DynamicSliceInstanceSpawner_External_E2E.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/DynamicSliceInstanceSpawner_External_E2E.py index 892549d414..70030ff359 100755 --- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/DynamicSliceInstanceSpawner_External_E2E.py +++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/DynamicSliceInstanceSpawner_External_E2E.py @@ -11,7 +11,8 @@ sys.path.append(os.path.dirname(os.path.abspath(__file__))) import azlmbr.legacy.general as general import azlmbr.asset as asset import azlmbr.bus as bus -import azlmbr.entity as EntityId +import azlmbr.components as components +import azlmbr.entity as entity import azlmbr.editor as editor import azlmbr.math as math import azlmbr.paths @@ -68,7 +69,7 @@ class TestDynamicSliceInstanceSpawnerExternalEditor(EditorTestHelper): veg_area_required_components = ["Vegetation Layer Spawner", "Box Shape", "Vegetation Asset List", "Script Canvas"] new_entity_id = editor.ToolsApplicationRequestBus( - bus.Broadcast, "CreateNewEntityAtPosition", entity_position, EntityId.EntityId() + bus.Broadcast, "CreateNewEntityAtPosition", entity_position, entity.EntityId() ) if new_entity_id.IsValid(): self.log("Spawner entity created") @@ -106,18 +107,12 @@ class TestDynamicSliceInstanceSpawnerExternalEditor(EditorTestHelper): self.log(f"Expected {num_expected_instances} instances - Found {num_found} instances") self.test_success = self.test_success and num_found == num_expected_instances - # 5) Create a new entity with a Camera component for testing in the launcher - entity_position = math.Vector3(512.0, 500.0, 35.0) - camera_component = ["Camera"] - new_entity_id2 = editor.ToolsApplicationRequestBus( - bus.Broadcast, "CreateNewEntityAtPosition", entity_position, EntityId.EntityId() - ) - if new_entity_id2.IsValid(): - self.log("Camera entity created") - camera_entity = hydra.Entity("Camera Entity", new_entity_id2) - camera_entity.components = [] - for component in camera_component: - camera_entity.components.append(hydra.add_component(component, new_entity_id2)) + # 5) Move the default Camera entity for testing in the launcher + cam_position = math.Vector3(512.0, 500.0, 35.0) + search_filter = entity.SearchFilter() + search_filter.names = ["Camera"] + search_entity_ids = entity.SearchBus(bus.Broadcast, 'SearchEntities', search_filter) + components.TransformBus(bus.Event, "MoveEntity", search_entity_ids[0], cam_position) # 6) Save and export to engine general.save_level() diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/LayerBlender_E2E_Editor.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/LayerBlender_E2E_Editor.py index c4dfbbf4fe..3543b4c23c 100755 --- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/LayerBlender_E2E_Editor.py +++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/LayerBlender_E2E_Editor.py @@ -18,9 +18,9 @@ import azlmbr.areasystem as areasystem import azlmbr.legacy.general as general import azlmbr import azlmbr.bus as bus -import azlmbr.editor as editor +import azlmbr.components as components import azlmbr.math as math -import azlmbr.entity as EntityId +import azlmbr.entity as entity import azlmbr.paths sys.path.append(os.path.join(azlmbr.paths.devroot, 'AutomatedTesting', 'Gem', 'PythonTests')) @@ -134,20 +134,14 @@ class TestVegLayerBlenderCreated(EditorTestHelper): purple_count += 1 self.test_success = pink_count == purple_count and (pink_count + purple_count == num_expected) and self.test_success - # 5) Create a new entity with a Camera component for testing in the launcher - entity_position = math.Vector3(500.0, 500.0, 47.0) - rot_degrees_vector = math.Vector3(radians(-55.0), radians(28.5), radians(-17.0)) - camera_component = ["Camera"] - camera_id = editor.ToolsApplicationRequestBus( - bus.Broadcast, "CreateNewEntityAtPosition", entity_position, EntityId.EntityId() - ) - if camera_id.IsValid(): - self.log("Camera entity created") - camera_entity = hydra.Entity("Camera Entity", camera_id) - camera_entity.components = [] - for component in camera_component: - camera_entity.components.append(hydra.add_component(component, camera_id)) - azlmbr.components.TransformBus(bus.Event, "SetLocalRotation", camera_id, rot_degrees_vector) + # 5) Move the default Camera entity for testing in the launcher + cam_position = math.Vector3(500.0, 500.0, 47.0) + cam_rot_degrees_vector = math.Vector3(radians(-55.0), radians(28.5), radians(-17.0)) + search_filter = entity.SearchFilter() + search_filter.names = ["Camera"] + search_entity_ids = entity.SearchBus(bus.Broadcast, 'SearchEntities', search_filter) + components.TransformBus(bus.Event, "MoveEntity", search_entity_ids[0], cam_position) + azlmbr.components.TransformBus(bus.Event, "SetLocalRotation", search_entity_ids[0], cam_rot_degrees_vector) # 6) Save and export level general.save_level() diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/LayerSpawner_FilterStageToggle.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/LayerSpawner_FilterStageToggle.py index 0224f6bc0a..fb1ac5a5ce 100755 --- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/LayerSpawner_FilterStageToggle.py +++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/LayerSpawner_FilterStageToggle.py @@ -34,8 +34,8 @@ class TestLayerSpawnerFilterStageToggle(EditorTestHelper): :return: None """ - PREPROCESS_INSTANCE_COUNT = 425 - POSTPROCESS_INSTANCE_COUNT = 430 + PREPROCESS_INSTANCE_COUNT = 21 + POSTPROCESS_INSTANCE_COUNT = 19 # Create empty level self.test_success = self.create_level( @@ -56,7 +56,6 @@ class TestLayerSpawnerFilterStageToggle(EditorTestHelper): vegetation_entity.add_component("Vegetation Altitude Filter") vegetation_entity.add_component("Vegetation Position Modifier") - # Create a child entity under vegetation area child_entity = hydra.Entity("child_entity") components_to_add = ["Random Noise Gradient", "Gradient Transform Modifier", "Box Shape"] @@ -66,29 +65,13 @@ class TestLayerSpawnerFilterStageToggle(EditorTestHelper): vegetation_entity.get_set_test(4, "Configuration|Position X|Gradient|Gradient Entity Id", child_entity.id) vegetation_entity.get_set_test(4, "Configuration|Position Y|Gradient|Gradient Entity Id", child_entity.id) - # Set the min and max values for Altitude Filter - vegetation_entity.get_set_test(3, "Configuration|Altitude Min", 32.0) - vegetation_entity.get_set_test(3, "Configuration|Altitude Max", 35.0) + vegetation_entity.get_set_test(3, "Configuration|Altitude Min", 34.0) + vegetation_entity.get_set_test(3, "Configuration|Altitude Max", 38.0) # Add entity with Mesh to replicate creation of hills and a flat surface to plant on dynveg.create_surface_entity("Flat Surface", position, 32.0, 32.0, 1.0) - hill_entity = dynveg.create_mesh_surface_entity_with_slopes("hill", position, 4.0, 4.0, 4.0) - - # Disable/Re-enable Mesh component due to ATOM-14299 - general.idle_wait(1.0) - editor.EditorComponentAPIBus(bus.Broadcast, 'DisableComponents', [hill_entity.components[0]]) - is_enabled = editor.EditorComponentAPIBus(bus.Broadcast, 'IsComponentEnabled', hill_entity.components[0]) - if is_enabled: - print("Mesh component is still enabled") - else: - print("Mesh component was disabled") - editor.EditorComponentAPIBus(bus.Broadcast, 'EnableComponents', [hill_entity.components[0]]) - is_enabled = editor.EditorComponentAPIBus(bus.Broadcast, 'IsComponentEnabled', hill_entity.components[0]) - if is_enabled: - print("Mesh component is now enabled") - else: - print("Mesh component is still disabled") + hill_entity = dynveg.create_mesh_surface_entity_with_slopes("hill", position, 4.0) # Set the filter stage to preprocess and postprocess respectively and verify instance count vegetation_entity.get_set_test(0, "Configuration|Filter Stage", 1) diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/LayerSpawner_InstancesRefreshUsingCorrectViewportCamera.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/LayerSpawner_InstancesRefreshUsingCorrectViewportCamera.py index c99d180253..4752aeddbc 100755 --- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/LayerSpawner_InstancesRefreshUsingCorrectViewportCamera.py +++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/LayerSpawner_InstancesRefreshUsingCorrectViewportCamera.py @@ -13,7 +13,7 @@ import azlmbr.legacy.general as general import azlmbr.math as math sys.path.append(os.path.join(azlmbr.paths.devroot, 'AutomatedTesting', 'Gem', 'PythonTests')) -from automatedtesting_shared.editor_test_helper import EditorTestHelper +from editor_python_test_tools.editor_test_helper import EditorTestHelper from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/MeshBlocker_InstancesBlockedByMesh.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/MeshBlocker_InstancesBlockedByMesh.py index d23f811f58..89367ca475 100755 --- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/MeshBlocker_InstancesBlockedByMesh.py +++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/MeshBlocker_InstancesBlockedByMesh.py @@ -82,22 +82,7 @@ class test_MeshBlocker_InstancesBlockedByMesh(EditorTestHelper): bus.Broadcast, "GetAssetIdByPath", os.path.join("objects", "_primitives", "_box_1x1.azmodel"), math.Uuid(), False) blocker_entity.get_set_test(1, "Controller|Configuration|Mesh Asset", cubeId) - components.TransformBus(bus.Event, "SetLocalScale", blocker_entity.id, math.Vector3(2.0, 2.0, 2.0)) - - # Disable/Re-enable Mesh component due to ATOM-14299 - general.idle_wait(1.0) - editor.EditorComponentAPIBus(bus.Broadcast, 'DisableComponents', [blocker_entity.components[1]]) - is_enabled = editor.EditorComponentAPIBus(bus.Broadcast, 'IsComponentEnabled', blocker_entity.components[1]) - if is_enabled: - print("Mesh component is still enabled") - else: - print("Mesh component was disabled") - editor.EditorComponentAPIBus(bus.Broadcast, 'EnableComponents', [blocker_entity.components[1]]) - is_enabled = editor.EditorComponentAPIBus(bus.Broadcast, 'IsComponentEnabled', blocker_entity.components[1]) - if is_enabled: - print("Mesh component is now enabled") - else: - print("Mesh component is still disabled") + components.TransformBus(bus.Event, "SetLocalUniformScale", blocker_entity.id, 2.0) # Verify spawned instance counts are accurate after addition of Blocker Entity num_expected = 160 # Number of "PurpleFlower"s that plant on a 10 x 10 surface minus 2m blocker cube diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/MeshBlocker_InstancesBlockedByMeshHeightTuning.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/MeshBlocker_InstancesBlockedByMeshHeightTuning.py index ad57f80776..4a3edcd0e9 100755 --- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/MeshBlocker_InstancesBlockedByMeshHeightTuning.py +++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/MeshBlocker_InstancesBlockedByMeshHeightTuning.py @@ -88,24 +88,9 @@ class test_MeshBlocker_InstancesBlockedByMeshHeightTuning(EditorTestHelper): bus.Broadcast, "GetAssetIdByPath", os.path.join("objects", "_primitives", "_box_1x1.azmodel"), math.Uuid(), False) blocker_entity.get_set_test(1, "Controller|Configuration|Mesh Asset", sphere_id) - components.TransformBus(bus.Event, "SetLocalScale", blocker_entity.id, math.Vector3(5.0, 5.0, 5.0)) + components.TransformBus(bus.Event, "SetLocalUniformScale", blocker_entity.id, 5.0) components.TransformBus(bus.Event, "SetLocalRotation", blocker_entity.id, math.Vector3(0.0, y_rotation, 0.0)) - # Disable/Re-enable Mesh component due to ATOM-14299 - general.idle_wait(1.0) - editor.EditorComponentAPIBus(bus.Broadcast, 'DisableComponents', [blocker_entity.components[1]]) - is_enabled = editor.EditorComponentAPIBus(bus.Broadcast, 'IsComponentEnabled', blocker_entity.components[1]) - if is_enabled: - print("Mesh component is still enabled") - else: - print("Mesh component was disabled") - editor.EditorComponentAPIBus(bus.Broadcast, 'EnableComponents', [blocker_entity.components[1]]) - is_enabled = editor.EditorComponentAPIBus(bus.Broadcast, 'IsComponentEnabled', blocker_entity.components[1]) - if is_enabled: - print("Mesh component is now enabled") - else: - print("Mesh component is still disabled") - # 5) Adjust the height Max percentage values of blocker blocker_entity.get_set_test(0, "Configuration|Mesh Height Percent Max", 0.8) diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_DynamicSliceInstanceSpawner.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_DynamicSliceInstanceSpawner.py index 053c4ccbfd..eb74a5a144 100755 --- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_DynamicSliceInstanceSpawner.py +++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_DynamicSliceInstanceSpawner.py @@ -90,7 +90,6 @@ class TestDynamicSliceInstanceSpawner(object): @pytest.mark.SUITE_periodic @pytest.mark.dynveg_area @pytest.mark.parametrize("launcher_platform", ['windows']) - @pytest.mark.skip # ATOM-14703 def test_DynamicSliceInstanceSpawner_Embedded_E2E_Launcher(self, workspace, launcher, level, remote_console_instance, project, launcher_platform): @@ -126,7 +125,6 @@ class TestDynamicSliceInstanceSpawner(object): @pytest.mark.SUITE_periodic @pytest.mark.dynveg_area @pytest.mark.parametrize("launcher_platform", ['windows']) - @pytest.mark.skip # ATOM-14703 def test_DynamicSliceInstanceSpawner_External_E2E_Launcher(self, workspace, launcher, level, remote_console_instance, project, launcher_platform): diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_LayerBlender.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_LayerBlender.py index 3c16420a30..2620a7d50a 100755 --- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_LayerBlender.py +++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_LayerBlender.py @@ -68,7 +68,6 @@ class TestLayerBlender(object): "Entity has a Box Shape component", "Blender Configuration|Vegetation Areas: SUCCESS", "Blender Box Shape|Box Configuration|Dimensions: SUCCESS", - "Camera entity created", "LayerBlender_E2E_Editor: result=SUCCESS" ] @@ -85,12 +84,11 @@ class TestLayerBlender(object): @pytest.mark.BAT @pytest.mark.SUITE_periodic @pytest.mark.dynveg_area - @pytest.mark.xfail @pytest.mark.parametrize("launcher_platform", ['windows']) def test_LayerBlender_E2E_Launcher(self, workspace, project, launcher, level, remote_console_instance, launcher_platform): - launcher.args.extend(["-NullRenderer"]) + launcher.args.extend(["-rhi=Null"]) launcher.start() assert launcher.is_alive(), "Launcher failed to start" diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_LayerSpawner.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_LayerSpawner.py index 410bdbafed..2af848ffb1 100755 --- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_LayerSpawner.py +++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/test_LayerSpawner.py @@ -121,7 +121,7 @@ class TestLayerSpawner(object): @pytest.mark.test_case_id("C30000751") @pytest.mark.SUITE_sandbox @pytest.mark.dynveg_misc - @pytest.mark.skip # ATOM-14828 + @pytest.mark.skip # https://github.com/o3de/o3de/issues/2038 def test_LayerSpawner_InstancesRefreshUsingCorrectViewportCamera(self, request, editor, level, launcher_platform): expected_lines = [ @@ -136,5 +136,6 @@ class TestLayerSpawner(object): editor, "LayerSpawner_InstancesRefreshUsingCorrectViewportCamera.py", expected_lines, - cfg_args=[level] + cfg_args=[level], + null_renderer=False ) diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/large_worlds_utils/editor_dynveg_test_helper.py b/AutomatedTesting/Gem/PythonTests/largeworlds/large_worlds_utils/editor_dynveg_test_helper.py index 515009cb3a..09d4746fa6 100755 --- a/AutomatedTesting/Gem/PythonTests/largeworlds/large_worlds_utils/editor_dynveg_test_helper.py +++ b/AutomatedTesting/Gem/PythonTests/largeworlds/large_worlds_utils/editor_dynveg_test_helper.py @@ -34,7 +34,7 @@ def create_surface_entity(name, center_point, box_size_x, box_size_y, box_size_z return surface_entity -def create_mesh_surface_entity_with_slopes(name, center_point, scale_x, scale_y, scale_z): +def create_mesh_surface_entity_with_slopes(name, center_point, uniform_scale): # Creates an entity with the assigned mesh_asset as the specified scale and sets up as a planting surface mesh_asset_path = os.path.join("models", "sphere.azmodel") mesh_asset = asset.AssetCatalogRequestBus(bus.Broadcast, "GetAssetIdByPath", mesh_asset_path, math.Uuid(), @@ -47,7 +47,7 @@ def create_mesh_surface_entity_with_slopes(name, center_point, scale_x, scale_y, if surface_entity.id.IsValid(): print(f"'{surface_entity.name}' created") hydra.get_set_test(surface_entity, 0, "Controller|Configuration|Mesh Asset", mesh_asset) - components.TransformBus(bus.Event, "SetLocalScale", surface_entity.id, math.Vector3(scale_x, scale_y, scale_z)) + components.TransformBus(bus.Event, "SetLocalUniformScale", surface_entity.id, uniform_scale) return surface_entity diff --git a/AutomatedTesting/Levels/Physics/C14902098_ScriptCanvas_PostPhysicsUpdate/onpostphysicsupdate.scriptcanvas b/AutomatedTesting/Levels/Physics/C14902098_ScriptCanvas_PostPhysicsUpdate/onpostphysicsupdate.scriptcanvas index f4ca23b882..10c40fbb9f 100644 --- a/AutomatedTesting/Levels/Physics/C14902098_ScriptCanvas_PostPhysicsUpdate/onpostphysicsupdate.scriptcanvas +++ b/AutomatedTesting/Levels/Physics/C14902098_ScriptCanvas_PostPhysicsUpdate/onpostphysicsupdate.scriptcanvas @@ -742,14 +742,14 @@ - + - + diff --git a/AutomatedTesting/ScriptCanvas/C14195074_ScriptCanvas_PostUpdateEvent.scriptcanvas b/AutomatedTesting/ScriptCanvas/C14195074_ScriptCanvas_PostUpdateEvent.scriptcanvas index b9f438a4f3..ffdac5dd8c 100644 --- a/AutomatedTesting/ScriptCanvas/C14195074_ScriptCanvas_PostUpdateEvent.scriptcanvas +++ b/AutomatedTesting/ScriptCanvas/C14195074_ScriptCanvas_PostUpdateEvent.scriptcanvas @@ -1045,14 +1045,14 @@ - + - + diff --git a/AutomatedTesting/ScriptCanvas/C14902097_ScriptCanvas_PreUpdateEvent.scriptcanvas b/AutomatedTesting/ScriptCanvas/C14902097_ScriptCanvas_PreUpdateEvent.scriptcanvas index 7db4f3a35c..4954eb4684 100644 --- a/AutomatedTesting/ScriptCanvas/C14902097_ScriptCanvas_PreUpdateEvent.scriptcanvas +++ b/AutomatedTesting/ScriptCanvas/C14902097_ScriptCanvas_PreUpdateEvent.scriptcanvas @@ -1085,14 +1085,14 @@ - + - + diff --git a/Code/Editor/EditorViewportWidget.cpp b/Code/Editor/EditorViewportWidget.cpp index 3f86260f8d..c2f16a84b1 100644 --- a/Code/Editor/EditorViewportWidget.cpp +++ b/Code/Editor/EditorViewportWidget.cpp @@ -1896,7 +1896,7 @@ void EditorViewportWidget::SetViewTM(const Matrix34& viewTM, bool bMoveOnly) if (m_pressedKeyState != KeyPressedState::PressedInPreviousFrame) { - CUndo undo("Move Camera"); + AzToolsFramework::ScopedUndoBatch undo("Move Camera"); if (bMoveOnly) { // specify eObjectUpdateFlags_UserInput so that an undo command gets logged @@ -1932,7 +1932,7 @@ void EditorViewportWidget::SetViewTM(const Matrix34& viewTM, bool bMoveOnly) if (m_pressedKeyState != KeyPressedState::PressedInPreviousFrame) { - CUndo undo("Move Camera"); + AzToolsFramework::ScopedUndoBatch undo("Move Camera"); if (bMoveOnly) { AZ::TransformBus::Event( @@ -1945,6 +1945,8 @@ void EditorViewportWidget::SetViewTM(const Matrix34& viewTM, bool bMoveOnly) m_viewEntityId, &AZ::TransformInterface::SetWorldTM, LYTransformToAZTransform(camMatrix)); } + + AzToolsFramework::ToolsApplicationRequestBus::Broadcast(&AzToolsFramework::ToolsApplicationRequests::AddDirtyEntity, m_viewEntityId); } else { diff --git a/Code/Editor/LegacyViewportCameraController.cpp b/Code/Editor/LegacyViewportCameraController.cpp index f437196548..b18e807942 100644 --- a/Code/Editor/LegacyViewportCameraController.cpp +++ b/Code/Editor/LegacyViewportCameraController.cpp @@ -83,9 +83,9 @@ AZ::RPI::ViewportContextPtr LegacyViewportCameraControllerInstance::GetViewportC } bool LegacyViewportCameraControllerInstance::HandleMouseMove( - const AzFramework::ScreenPoint& currentMousePos, const AzFramework::ScreenPoint& previousMousePos) + int dx, int dy) { - if (previousMousePos == currentMousePos) + if (dx == 0 && dy == 0) { return false; } @@ -105,7 +105,7 @@ bool LegacyViewportCameraControllerInstance::HandleMouseMove( if (m_inMoveMode || m_inOrbitMode || m_inRotateMode || m_inZoomMode) { - m_totalMouseMoveDelta += (QPoint(currentMousePos.m_x, currentMousePos.m_y)-QPoint(previousMousePos.m_x, previousMousePos.m_y)).manhattanLength(); + m_totalMouseMoveDelta += AZStd::abs(dx) + AZStd::abs(dy); } if ((m_inRotateMode && m_inMoveMode) || m_inZoomMode) @@ -115,7 +115,7 @@ bool LegacyViewportCameraControllerInstance::HandleMouseMove( Vec3 ydir = m.GetColumn1().GetNormalized(); Vec3 pos = m.GetTranslation(); - const float posDelta = 0.2f * (previousMousePos.m_y - currentMousePos.m_y) * speedScale; + const float posDelta = 0.2f * dy * speedScale; pos = pos - ydir * posDelta; m_orbitDistance = m_orbitDistance + posDelta; m_orbitDistance = fabs(m_orbitDistance); @@ -126,7 +126,7 @@ bool LegacyViewportCameraControllerInstance::HandleMouseMove( } else if (m_inRotateMode) { - Ang3 angles(-currentMousePos.m_y + previousMousePos.m_y, 0, -currentMousePos.m_x + previousMousePos.m_x); + Ang3 angles(dy, 0, dx); angles = angles * 0.002f * gSettings.cameraRotateSpeed; if (gSettings.invertYRotation) { @@ -158,7 +158,7 @@ bool LegacyViewportCameraControllerInstance::HandleMouseMove( } Vec3 pos = m.GetTranslation(); - pos += 0.1f * xdir * (currentMousePos.m_x - previousMousePos.m_x) * speedScale + 0.1f * zdir * (previousMousePos.m_y - currentMousePos.m_y) * speedScale; + pos += 0.1f * xdir * dx * speedScale + 0.1f * zdir * dy * speedScale; m.SetTranslation(pos); AZ::Transform transform = viewportContext->GetCameraTransform(); @@ -168,7 +168,7 @@ bool LegacyViewportCameraControllerInstance::HandleMouseMove( } else if (m_inOrbitMode) { - Ang3 angles(-currentMousePos.m_y + previousMousePos.m_y, 0, -currentMousePos.m_x + previousMousePos.m_x); + Ang3 angles(dy, 0, dx); angles = angles * 0.002f * gSettings.cameraRotateSpeed; if (gSettings.invertPan) @@ -302,20 +302,19 @@ bool LegacyViewportCameraControllerInstance::HandleInputChannelEvent(const AzFra bool shouldCaptureCursor = m_capturingCursor; bool shouldConsumeEvent = false; - if (id == AzFramework::InputDeviceMouse::SystemCursorPosition) + if (id == AzFramework::InputDeviceMouse::Movement::X || id == AzFramework::InputDeviceMouse::Movement::Y) { - bool result = false; - AzToolsFramework::ViewportInteraction::ViewportMouseCursorRequestBus::Event( - GetViewportId(), - [this, &result](AzToolsFramework::ViewportInteraction::ViewportMouseCursorRequests* mouseRequests) - { - if (auto previousMousePosition = mouseRequests->PreviousViewportCursorScreenPosition(); - previousMousePosition.has_value()) - { - result = HandleMouseMove(mouseRequests->ViewportCursorScreenPosition(), previousMousePosition.value()); - } - }); - return result; + int dx = 0; + int dy = 0; + if (id == AzFramework::InputDeviceMouse::Movement::X) + { + dx = -aznumeric_cast(event.m_inputChannel.GetValue()); + } + else + { + dy = -aznumeric_cast(event.m_inputChannel.GetValue()); + } + return HandleMouseMove(dx, dy); } else if (id == MouseButton::Left) { diff --git a/Code/Editor/LegacyViewportCameraController.h b/Code/Editor/LegacyViewportCameraController.h index d3ff439b16..ff752e27ab 100644 --- a/Code/Editor/LegacyViewportCameraController.h +++ b/Code/Editor/LegacyViewportCameraController.h @@ -69,7 +69,7 @@ namespace SandboxEditor AZ::RPI::ViewportContextPtr GetViewportContext(); - bool HandleMouseMove(const AzFramework::ScreenPoint& currentMousePos, const AzFramework::ScreenPoint& previousMousePos); + bool HandleMouseMove(int dx, int dy); bool HandleMouseWheel(float zDelta); bool IsKeyDown(Qt::Key key) const; void UpdateCursorCapture(bool shouldCaptureCursor); diff --git a/Code/Editor/Plugins/ProjectSettingsTool/ProjectSettingsToolWindow.cpp b/Code/Editor/Plugins/ProjectSettingsTool/ProjectSettingsToolWindow.cpp index f4e4fed973..63ce446e97 100644 --- a/Code/Editor/Plugins/ProjectSettingsTool/ProjectSettingsToolWindow.cpp +++ b/Code/Editor/Plugins/ProjectSettingsTool/ProjectSettingsToolWindow.cpp @@ -20,12 +20,12 @@ #include "ValidationHandler.h" #include +#include -#include "AzToolsFramework/UI/PropertyEditor/InstanceDataHierarchy.h" +#include #include #include #include -#include #include #include @@ -52,7 +52,7 @@ namespace ProjectSettingsTool PlatformEnabled(PlatformId::Ios) ? ProjectSettingsContainer::PlistInitVector({ ProjectSettingsContainer::PlatformAndPath - { PlatformId::Ios, m_projectRoot + PlatformResourcesFolder(PlatformId::Ios) } + { PlatformId::Ios, GetPlatformResource(PlatformId::Ios) } }) : ProjectSettingsContainer::PlistInitVector()) @@ -647,33 +647,38 @@ namespace ProjectSettingsTool // iOS can be disabled if the plist file is missing if (platformId == PlatformId::Ios) { - const AZStd::string filename = m_projectRoot + PlatformResourcesFolder(platformId); - return CFileUtil::FileExists(filename.c_str()); + AZStd::string plistPath = GetPlatformResource(platformId); + return !plistPath.empty(); } return true; } - const char* ProjectSettingsToolWindow::PlatformResourcesFolder(PlatformId platformId) + AZStd::string ProjectSettingsToolWindow::GetPlatformResource(PlatformId platformId) { if (platformId == PlatformId::Ios) { - const AZStd::string firstfilename = m_projectRoot + "/Gem/Resources/Platform/iOS/Info.plist"; - if (CFileUtil::FileExists(firstfilename.c_str())) + const char* searchPaths[] = { + "Resources/Platform/iOS/Info.plist", + + // legacy paths + "Gem/Resources/Platform/iOS/Info.plist", + "Gem/Resources/IOSLauncher/Info.plist", + }; + + for (auto relPath : searchPaths) { - return "/Gem/Resources/Platform/iOS/Info.plist"; - } - else - { - const AZStd::string filename = m_projectRoot + "/Gem/Resources/IOSLauncher/Info.plist"; - if (CFileUtil::FileExists(filename.c_str())) + AZ::IO::FixedMaxPath projectPlist{ m_projectRoot }; + projectPlist /= relPath; + + if (AZ::IO::SystemFile::Exists(projectPlist.c_str())) { - return "/Gem/Resources/IOSLauncher/Info.plist"; + return projectPlist.LexicallyNormal().String(); } } } - return nullptr; + return AZStd::string(); } #include diff --git a/Code/Editor/Plugins/ProjectSettingsTool/ProjectSettingsToolWindow.h b/Code/Editor/Plugins/ProjectSettingsTool/ProjectSettingsToolWindow.h index 8979672f99..64a6646e7f 100644 --- a/Code/Editor/Plugins/ProjectSettingsTool/ProjectSettingsToolWindow.h +++ b/Code/Editor/Plugins/ProjectSettingsTool/ProjectSettingsToolWindow.h @@ -137,8 +137,8 @@ namespace ProjectSettingsTool // returns true if the platform is enabled bool PlatformEnabled(PlatformId platformId); - // returns the resource folder - const char* PlatformResourcesFolder(PlatformId platformId); + // returns the main platform specific resource file e.g. for iOS it would be the Info.plist + AZStd::string GetPlatformResource(PlatformId platformId); // The ui for the window QScopedPointer m_ui; diff --git a/Code/Framework/AzCore/AzCore/Math/MathReflection.cpp b/Code/Framework/AzCore/AzCore/Math/MathReflection.cpp index 46717440d5..82e230d54a 100644 --- a/Code/Framework/AzCore/AzCore/Math/MathReflection.cpp +++ b/Code/Framework/AzCore/AzCore/Math/MathReflection.cpp @@ -116,7 +116,7 @@ namespace AZ { const char* uuidString = nullptr; unsigned int uuidStringLength = 0; - if (dc.ReadArg(0, uuidString) && dc.ReadValue(1, uuidStringLength)) + if (dc.ReadArg(0, uuidString) && dc.ReadArg(1, uuidStringLength)) { dc.PushResult(Uuid(uuidString, uuidStringLength)); } diff --git a/Code/Framework/AzFramework/AzFramework/Windowing/WindowBus.h b/Code/Framework/AzFramework/AzFramework/Windowing/WindowBus.h index f21993b80a..0369e703b5 100644 --- a/Code/Framework/AzFramework/AzFramework/Windowing/WindowBus.h +++ b/Code/Framework/AzFramework/AzFramework/Windowing/WindowBus.h @@ -97,6 +97,9 @@ namespace AzFramework //! This is called when the window is deactivated from code or if the user closes the window. virtual void OnWindowClosed() {}; + + //! This is called when vsync interval is changed. + virtual void OnVsyncIntervalChanged(uint32_t interval) { AZ_UNUSED(interval); }; }; using WindowNotificationBus = AZ::EBus; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/API/EditorPythonConsoleBus.h b/Code/Framework/AzToolsFramework/AzToolsFramework/API/EditorPythonConsoleBus.h index 51aecbd8e7..6dd6b0b448 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/API/EditorPythonConsoleBus.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/API/EditorPythonConsoleBus.h @@ -63,6 +63,9 @@ namespace AzToolsFramework //! Signal the Python handler to stop virtual bool StopPython(bool silenceWarnings = false) = 0; + //! Query to determine if the Python VM has been initialized indicating an active state + virtual bool IsPythonActive() = 0; + //! Determines if the caller needs to wait for the Python VM to initialize (non-main thread only) virtual void WaitForInitialization() {} diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Input/QtEventToAzInputManager.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Input/QtEventToAzInputManager.cpp index eb5034fdd5..754c04e12f 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Input/QtEventToAzInputManager.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Input/QtEventToAzInputManager.cpp @@ -11,6 +11,7 @@ #include #include +#include #include #include @@ -187,12 +188,6 @@ namespace AzToolsFramework bool QtEventToAzInputMapper::HandlesInputEvent(const AzFramework::InputChannel& channel) const { - const AzFramework::InputChannelId& channelId = channel.GetInputChannelId(); - if (channelId == AzFramework::InputDeviceMouse::Movement::X || channelId == AzFramework::InputDeviceMouse::Movement::Y) - { - return false; - } - // We map keyboard and mouse events from Qt, so flag all events coming from those devices // as handled by our synthetic event system. const AzFramework::InputDeviceId& deviceId = channel.GetInputDevice().GetInputDeviceId(); @@ -210,6 +205,22 @@ namespace AzToolsFramework } } + void QtEventToAzInputMapper::SetCursorCaptureEnabled(bool enabled) + { + if (m_capturingCursor != enabled) + { + m_capturingCursor = enabled; + if (m_capturingCursor) + { + qApp->setOverrideCursor(Qt::BlankCursor); + } + else + { + qApp->restoreOverrideCursor(); + } + } + } + bool QtEventToAzInputMapper::eventFilter(QObject* object, QEvent* event) { // Abort if processing isn't enabled. @@ -284,13 +295,25 @@ namespace AzToolsFramework { auto systemCursorChannel = GetInputChannel(AzFramework::InputDeviceMouse::SystemCursorPosition); + auto movementXChannel = + GetInputChannel(AzFramework::InputDeviceMouse::Movement::X); + auto movementYChannel = + GetInputChannel(AzFramework::InputDeviceMouse::Movement::Y); auto mouseWheelChannel = GetInputChannel(AzFramework::InputDeviceMouse::Movement::Z); systemCursorChannel->ProcessRawInputEvent(m_cursorPosition->m_normalizedPositionDelta.GetLength()); + // Generate movement events based on the pixel delta divided by the DPI scaling factor, to calculate a rough approximation + // of cursor movement velocity. + movementXChannel->ProcessRawInputEvent( + m_cursorPosition->m_normalizedPositionDelta.GetX() * aznumeric_cast(m_sourceWidget->width()) / m_sourceWidget->devicePixelRatioF()); + movementYChannel->ProcessRawInputEvent( + m_cursorPosition->m_normalizedPositionDelta.GetY() * aznumeric_cast(m_sourceWidget->height()) / m_sourceWidget->devicePixelRatioF()); mouseWheelChannel->ProcessRawInputEvent(0.f); NotifyUpdateChannelIfNotIdle(systemCursorChannel, nullptr); + NotifyUpdateChannelIfNotIdle(movementXChannel, nullptr); + NotifyUpdateChannelIfNotIdle(movementYChannel, nullptr); NotifyUpdateChannelIfNotIdle(mouseWheelChannel, nullptr); } @@ -318,16 +341,42 @@ namespace AzToolsFramework } } + AZ::Vector2 QtEventToAzInputMapper::WidgetPositionToNormalizedPosition(QPoint position) + { + const float normalizedX = aznumeric_cast(position.x()) / aznumeric_cast(m_sourceWidget->width()); + const float normalizedY = aznumeric_cast(position.y()) / aznumeric_cast(m_sourceWidget->height()); + return AZ::Vector2{normalizedX, normalizedY}; + } + + QPoint QtEventToAzInputMapper::NormalizedPositionToWidgetPosition(AZ::Vector2 normalizedPosition) + { + const int denormalizedX = aznumeric_cast(normalizedPosition.GetX() * m_sourceWidget->width()); + const int denormalizedY = aznumeric_cast(normalizedPosition.GetY() * m_sourceWidget->height()); + return QPoint{denormalizedX, denormalizedY}; + } + void QtEventToAzInputMapper::HandleMouseMoveEvent(QMouseEvent* mouseEvent) { + AZ::Vector2 lastCursorPosition = m_cursorPosition->m_normalizedPosition; + const QPoint mousePos = mouseEvent->pos(); - const float normalizedX = aznumeric_cast(mousePos.x()) / aznumeric_cast(m_sourceWidget->width()); - const float normalizedY = aznumeric_cast(mousePos.y()) / aznumeric_cast(m_sourceWidget->height()); - const AZ::Vector2 normalizedPosition(normalizedX, normalizedY); + const AZ::Vector2 normalizedPosition = WidgetPositionToNormalizedPosition(mousePos); m_cursorPosition->m_normalizedPositionDelta = normalizedPosition - m_cursorPosition->m_normalizedPosition; m_cursorPosition->m_normalizedPosition = normalizedPosition; ProcessPendingMouseEvents(); m_mouseChannelsNeedUpdate = true; + + if (m_capturingCursor) + { + // Reset our cursor position to the previous point. + QPoint targetScreenPosition = m_sourceWidget->mapToGlobal(NormalizedPositionToWidgetPosition(lastCursorPosition)); + AzQtComponents::SetCursorPos(targetScreenPosition); + + // Even though we just set the cursor position, there are edge cases such as remote desktop that will leave + // the cursor position unchanged. For safety, we re-cache our last cursor position for delta generation. + QPoint actualWidgetPosition = m_sourceWidget->mapFromGlobal(QCursor::pos()); + m_cursorPosition->m_normalizedPosition = WidgetPositionToNormalizedPosition(actualWidgetPosition); + } } void QtEventToAzInputMapper::HandleKeyEvent(QKeyEvent* keyEvent) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Input/QtEventToAzInputManager.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Input/QtEventToAzInputManager.h index 6d95f3a4b0..6946a2c8e7 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Input/QtEventToAzInputManager.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Input/QtEventToAzInputManager.h @@ -47,6 +47,12 @@ namespace AzToolsFramework //! Sets whether or not this input mapper should be updating its input channels from Qt events. void SetEnabled(bool enabled); + //! Sets whether or not the cursor should be constrained to the source widget and invisible. + //! Internally, this will reset the cursor position after each move event to ensure movement + //! events don't allow the cursor to escape. This can be used for typical camera controls + //! like a dolly or rotation, where mouse movement is important but cursor location is not. + void SetCursorCaptureEnabled(bool enabled); + // QObject overrides... bool eventFilter(QObject* object, QEvent* event) override; @@ -106,6 +112,11 @@ namespace AzToolsFramework // Processes any pending mouse movement events, this allows mouse movement channels to close themselves. void ProcessPendingMouseEvents(); + // Converts a point in logical source widget space [0..m_sourceWidget->size()] to normalized [0..1] space. + AZ::Vector2 WidgetPositionToNormalizedPosition(QPoint position); + // Converts a point in normalized [0..1] space to logical source widget space [0..m_sourceWidget->size()]. + QPoint NormalizedPositionToWidgetPosition(AZ::Vector2 normalizedPosition); + // Handle mouse click events. void HandleMouseButtonEvent(QMouseEvent* mouseEvent); // Handle mouse move events. @@ -144,6 +155,8 @@ namespace AzToolsFramework bool m_mouseChannelsNeedUpdate = false; // Flags whether or not Qt events should currently be processed. bool m_enabled = true; + // Flags whether or not the cursor is being constrained to the source widget (for invisible mouse movement). + bool m_capturingCursor = false; // Our viewport-specific AZ devices. We control their internal input channel states. AZStd::unique_ptr m_mouseDevice; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.h index 4d3af0e878..5b25034de8 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.h @@ -276,11 +276,6 @@ namespace AzToolsFramework virtual void EndCursorCapture() = 0; //! Gets the most recent recorded cursor position in the viewport in screen space coordinates. virtual AzFramework::ScreenPoint ViewportCursorScreenPosition() = 0; - //! Gets the cursor position recorded prior to the most recent cursor position. - //! Note: The cursor may be captured by the viewport, in which case this may not correspond to the last result - //! from ViewportCursorScreenPosition. This method will always return the correct position to generate a mouse - //! position delta. - virtual AZStd::optional PreviousViewportCursorScreenPosition() = 0; //! Is mouse over viewport. virtual bool IsMouseOver() const = 0; diff --git a/Code/Tools/SceneAPI/SceneCore/Components/ExportingComponent.cpp b/Code/Tools/SceneAPI/SceneCore/Components/ExportingComponent.cpp index 62daba1197..911f469b2f 100644 --- a/Code/Tools/SceneAPI/SceneCore/Components/ExportingComponent.cpp +++ b/Code/Tools/SceneAPI/SceneCore/Components/ExportingComponent.cpp @@ -7,6 +7,8 @@ #include #include +#include +#include namespace AZ { @@ -31,6 +33,12 @@ namespace AZ { serializeContext->Class()->Version(2); } + + AZ::BehaviorContext* behaviorContext = azrtti_cast(context); + if (behaviorContext) + { + Events::ExportProductList::Reflect(behaviorContext); + } } } // namespace SceneCore } // namespace SceneAPI diff --git a/Code/Tools/SceneAPI/SceneCore/DllMain.cpp b/Code/Tools/SceneAPI/SceneCore/DllMain.cpp index 7874ada7be..ac3c307b6c 100644 --- a/Code/Tools/SceneAPI/SceneCore/DllMain.cpp +++ b/Code/Tools/SceneAPI/SceneCore/DllMain.cpp @@ -211,6 +211,7 @@ namespace AZ AZ::SceneAPI::Containers::SceneGraph::Reflect(context); AZ::SceneAPI::Containers::SceneManifest::Reflect(context); AZ::SceneAPI::Containers::RuleContainer::Reflect(context); + AZ::SceneAPI::SceneCore::ExportingComponent::Reflect(context); } void Activate() diff --git a/Code/Tools/SceneAPI/SceneCore/Events/ExportProductList.cpp b/Code/Tools/SceneAPI/SceneCore/Events/ExportProductList.cpp index c3a9abcbd2..798978024e 100644 --- a/Code/Tools/SceneAPI/SceneCore/Events/ExportProductList.cpp +++ b/Code/Tools/SceneAPI/SceneCore/Events/ExportProductList.cpp @@ -6,6 +6,8 @@ */ #include +#include +#include namespace AZ { @@ -49,6 +51,45 @@ namespace AZ return *this; } + void ExportProductList::Reflect(ReflectContext* context) + { + if (auto* serializeContext = azrtti_cast(context)) + { + serializeContext->Class()->Version(1); + serializeContext->Class()->Version(1); + } + + if (auto* behaviorContext = azrtti_cast(context)) + { + behaviorContext->Class("ExportProduct") + ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common) + ->Attribute(AZ::Script::Attributes::Module, "scene") + ->Property("filename", BehaviorValueProperty(&ExportProduct::m_filename)) + ->Property("sourceId", BehaviorValueProperty(&ExportProduct::m_id)) + ->Property("assetType", BehaviorValueProperty(&ExportProduct::m_assetType)) + ->Property("productDependencies", BehaviorValueProperty(&ExportProduct::m_productDependencies)) + ->Property("subId", + [](ExportProduct* self) { return self->m_subId.has_value() ? self->m_subId.value() : 0; }, + [](ExportProduct* self, u32 subId) { self->m_subId = AZStd::optional(subId); }); + + behaviorContext->Class("ExportProductList") + ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common) + ->Attribute(AZ::Script::Attributes::Module, "scene") + ->Method("AddProduct", [](ExportProductList& self, ExportProduct& product) + { + self.AddProduct( + product.m_filename, + product.m_id, + product.m_assetType, + product.m_lod, + product.m_subId, + product.m_dependencyFlags); + }) + ->Method("GetProducts", &ExportProductList::GetProducts) + ->Method("AddDependencyToProduct", &ExportProductList::AddDependencyToProduct); + } + } + ExportProduct& ExportProductList::AddProduct(const AZStd::string& filename, Uuid id, Data::AssetType assetType, AZStd::optional lod, AZStd::optional subId, Data::ProductDependencyInfo::ProductDependencyFlags dependencyFlags) { diff --git a/Code/Tools/SceneAPI/SceneCore/Events/ExportProductList.h b/Code/Tools/SceneAPI/SceneCore/Events/ExportProductList.h index 38deea3f9f..a99303e08b 100644 --- a/Code/Tools/SceneAPI/SceneCore/Events/ExportProductList.h +++ b/Code/Tools/SceneAPI/SceneCore/Events/ExportProductList.h @@ -14,6 +14,8 @@ namespace AZ { + class ReflectContext; + namespace SceneAPI { namespace Events @@ -24,6 +26,7 @@ namespace AZ Data::ProductDependencyInfo::ProductDependencyFlags dependencyFlags = Data::ProductDependencyInfo::CreateFlags(Data::AssetLoadBehavior::NoLoad)); SCENE_CORE_API ExportProduct(AZStd::string&& filename, Uuid id, Data::AssetType assetType, AZStd::optional lod, AZStd::optional subId, Data::ProductDependencyInfo::ProductDependencyFlags dependencyFlags = Data::ProductDependencyInfo::CreateFlags(Data::AssetLoadBehavior::NoLoad)); + ExportProduct() = default; ExportProduct(const ExportProduct& rhs) = default; SCENE_CORE_API ExportProduct(ExportProduct&& rhs); @@ -54,6 +57,8 @@ namespace AZ class ExportProductList { public: + static void Reflect(ReflectContext* context); + SCENE_CORE_API ExportProduct& AddProduct(const AZStd::string& filename, Uuid id, Data::AssetType assetType, AZStd::optional lod, AZStd::optional subId, Data::ProductDependencyInfo::ProductDependencyFlags dependencyFlags = Data::ProductDependencyInfo::CreateFlags(Data::AssetLoadBehavior::NoLoad)); SCENE_CORE_API ExportProduct& AddProduct(AZStd::string&& filename, Uuid id, Data::AssetType assetType, AZStd::optional lod, AZStd::optional subId, @@ -69,3 +74,9 @@ namespace AZ } // namespace Events } // namespace SceneAPI } // namespace AZ + +namespace AZ +{ + AZ_TYPE_INFO_SPECIALIZE(SceneAPI::Events::ExportProduct, "{6054EDCB-4C04-4D96-BF26-704999FFB725}"); + AZ_TYPE_INFO_SPECIALIZE(SceneAPI::Events::ExportProductList, "{1C76A51F-431B-4987-B653-CFCC940D0D0F}"); +} diff --git a/Code/Tools/SceneAPI/SceneCore/Import/ManifestImportRequestHandler.cpp b/Code/Tools/SceneAPI/SceneCore/Import/ManifestImportRequestHandler.cpp index a4b002db7d..073f357ee6 100644 --- a/Code/Tools/SceneAPI/SceneCore/Import/ManifestImportRequestHandler.cpp +++ b/Code/Tools/SceneAPI/SceneCore/Import/ManifestImportRequestHandler.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -75,15 +76,16 @@ namespace AZ filename += s_extension; filename += s_generated; - AZStd::string altManifestPath = path; + AZStd::string altManifestFolder = path; AzFramework::ApplicationRequests::Bus::Broadcast( - &AzFramework::ApplicationRequests::Bus::Events::MakePathRootRelative, - altManifestPath); + &AzFramework::ApplicationRequests::Bus::Events::MakePathRelative, + altManifestFolder, + AZ::Utils::GetProjectPath().c_str()); - AZ::StringFunc::Path::GetFolderPath(altManifestPath.c_str(), altManifestPath); + AZ::StringFunc::Path::GetFolderPath(altManifestFolder.c_str(), altManifestFolder); AZStd::string generatedAssetInfoPath; - AZ::StringFunc::Path::Join(assetCacheRoot.c_str(), altManifestPath.c_str(), generatedAssetInfoPath); + AZ::StringFunc::Path::Join(assetCacheRoot.c_str(), altManifestFolder.c_str(), generatedAssetInfoPath); AZ::StringFunc::Path::ConstructFull(generatedAssetInfoPath.c_str(), filename.c_str(), generatedAssetInfoPath); if (!AZ::IO::FileIOBase::GetInstance()->Exists(generatedAssetInfoPath.c_str())) diff --git a/Code/Tools/SceneAPI/SceneCore/Tests/Containers/SceneBehaviorTests.cpp b/Code/Tools/SceneAPI/SceneCore/Tests/Containers/SceneBehaviorTests.cpp index e1c72bc8b4..4e08181b83 100644 --- a/Code/Tools/SceneAPI/SceneCore/Tests/Containers/SceneBehaviorTests.cpp +++ b/Code/Tools/SceneAPI/SceneCore/Tests/Containers/SceneBehaviorTests.cpp @@ -28,710 +28,731 @@ extern "C" AZ_DLL_EXPORT void ReflectBehavior(AZ::BehaviorContext* context); // the DLL entry point for SceneCore to reflect its serialize context extern "C" AZ_DLL_EXPORT void ReflectTypes(AZ::SerializeContext* context); -namespace AZ +namespace AZ::SceneAPI::Containers { - namespace SceneAPI + class MockManifestRule : public DataTypes::IManifestObject { - namespace Containers + public: + AZ_RTTI(MockManifestRule, "{D6F96B48-4E6F-4EE8-A5A3-959B76F90DA8}", IManifestObject); + AZ_CLASS_ALLOCATOR(MockManifestRule, AZ::SystemAllocator, 0); + + MockManifestRule() = default; + + MockManifestRule(double value) + : m_value(value) { - class MockManifestRule : public DataTypes::IManifestObject + } + + double GetValue() const + { + return m_value; + } + + void SetValue(double value) + { + m_value = value; + } + + static void Reflect(AZ::ReflectContext* context) + { + AZ::SerializeContext* serializeContext = azrtti_cast(context); + if (serializeContext) { - public: - AZ_RTTI(MockManifestRule, "{D6F96B48-4E6F-4EE8-A5A3-959B76F90DA8}", IManifestObject); - AZ_CLASS_ALLOCATOR(MockManifestRule, AZ::SystemAllocator, 0); - - MockManifestRule() = default; - - MockManifestRule(double value) - : m_value(value) - { - } - - double GetValue() const - { - return m_value; - } - - void SetValue(double value) - { - m_value = value; - } - - static void Reflect(AZ::ReflectContext* context) - { - AZ::SerializeContext* serializeContext = azrtti_cast(context); - if (serializeContext) - { - serializeContext->Class() - ->Version(1) - ->Field("value", &MockManifestRule::m_value); - } - } - - private: - double m_value = 0.0; - }; - - struct MockBuilder final - { - AZ_TYPE_INFO(MockBuilder, "{ECF0FB2C-E5C0-4B89-993C-8511A7EF6894}"); - - AZStd::unique_ptr m_scene; - - MockBuilder() - { - m_scene = AZStd::make_unique("unit_scene"); - } - - ~MockBuilder() - { - m_scene.reset(); - } - - void BuildSceneGraph() - { - m_scene->SetManifestFilename("manifest_filename"); - m_scene->SetSource("unit_source_filename", azrtti_typeid()); - - auto& graph = m_scene->GetGraph(); - - /*----------------------------\ - | Root | - | / \ | - | | | | - | A B | - | | /|\ | - | C I J K | - | / | \ \ | - | D E F L | - | / \ | - | G H | - \----------------------------*/ - - //Build up the graph - const auto indexA = graph.AddChild(graph.GetRoot(), "A", AZStd::make_shared(1)); - const auto indexC = graph.AddChild(indexA, "C", AZStd::make_shared(3)); - const auto indexE = graph.AddChild(indexC, "E", AZStd::make_shared(4)); - graph.AddChild(indexC, "D", AZStd::make_shared(5)); - graph.AddChild(indexC, "F", AZStd::make_shared(6)); - graph.AddChild(indexE, "G", AZStd::make_shared(7)); - graph.AddChild(indexE, "H", AZStd::make_shared(8)); - const auto indexB = graph.AddChild(graph.GetRoot(), "B", AZStd::make_shared(2)); - const auto indexK = graph.AddChild(indexB, "K", AZStd::make_shared(2)); - graph.AddChild(indexB, "I", AZStd::make_shared(9)); - graph.AddChild(indexB, "J", AZStd::make_shared(10)); - graph.AddChild(indexK, "L", AZStd::make_shared(12)); - - m_scene->GetManifest().AddEntry(AZStd::make_shared(0.1)); - m_scene->GetManifest().AddEntry(AZStd::make_shared(2.3)); - m_scene->GetManifest().AddEntry(AZStd::make_shared(4.5)); - } - - static void Reflect(ReflectContext* context) - { - BehaviorContext* behaviorContext = azrtti_cast(context); - if (behaviorContext) - { - behaviorContext->Class() - ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common) - ->Attribute(AZ::Script::Attributes::Module, "scene") - ->Method("BuildSceneGraph", [](MockBuilder& self) - { - return self.BuildSceneGraph(); - }) - ->Method("GetScene", [](MockBuilder& self) - { - return self.m_scene.get(); - }); - } - } - }; - - class SceneGraphBehaviorTest - : public ::testing::Test - { - public: - void SetUp() override - { - m_behaviorContext = AZStd::make_unique(); - ReflectBehavior(m_behaviorContext.get()); - } - - void TearDown() override - { - m_behaviorContext.reset(); - } - - AZ::BehaviorClass* GetBehaviorClass(const AZ::TypeId& behaviorClassType) - { - auto entry = m_behaviorContext->m_typeToClassMap.find(behaviorClassType); - return (entry != m_behaviorContext->m_typeToClassMap.end()) ? entry->second : nullptr; - } - - AZ::BehaviorProperty* GetBehaviorProperty(AZ::BehaviorClass& behaviorClass, AZStd::string_view propertyName) - { - auto entry = behaviorClass.m_properties.find(propertyName); - return (entry != behaviorClass.m_properties.end()) ? entry->second : nullptr; - } - - bool HasBehaviorClass(const AZ::TypeId& behaviorClassType) - { - return GetBehaviorClass(behaviorClassType) != nullptr; - } - - bool HasProperty(AZ::BehaviorClass& behaviorClass, AZStd::string_view propertyName, const AZ::TypeId& propertyClassType) - { - AZ::BehaviorProperty* behaviorProperty = GetBehaviorProperty(behaviorClass, propertyName); - if (behaviorProperty) - { - return behaviorProperty->m_getter->GetResult()->m_typeId == propertyClassType; - } - return false; - } - - using ArgList = AZStd::vector; - - bool HasMethodWithInput(AZ::BehaviorClass& behaviorClass, AZStd::string_view methodName, const ArgList& input) - { - auto entry = behaviorClass.m_methods.find(methodName); - if (entry == behaviorClass.m_methods.end()) - { - return false; - } - AZ::BehaviorMethod* method = entry->second; - - const size_t methodArgsCount = method->IsMember() ? method->GetNumArguments() - 1 : method->GetNumArguments(); - if (input.size() != methodArgsCount) - { - return false; - } - - for (size_t argIndex = 0; argIndex < input.size(); ++argIndex) - { - const size_t thisPointerOffset = method->IsMember() ? 1 : 0; - const auto argType = method->GetArgument(argIndex + thisPointerOffset)->m_typeId; - const auto inputType = input[argIndex]; - if (inputType != argType) - { - return false; - } - } - return true; - } - - bool HasMethodWithOutput(AZ::BehaviorClass& behaviorClass, AZStd::string_view methodName, const AZ::TypeId& output, const ArgList& input) - { - auto entry = behaviorClass.m_methods.find(methodName); - if (entry == behaviorClass.m_methods.end()) - { - return false; - } - AZ::BehaviorMethod* method = entry->second; - if (method->HasResult()) - { - if (method->GetResult()->m_typeId != output) - { - return false; - } - } - else - { - return false; - } - return HasMethodWithInput(behaviorClass, methodName, input); - } - - AZStd::unique_ptr m_behaviorContext; - }; - - TEST_F(SceneGraphBehaviorTest, SceneClass_BehaviorContext_Exists) - { - EXPECT_TRUE(HasBehaviorClass(azrtti_typeid())); - } - - TEST_F(SceneGraphBehaviorTest, SceneClass_BehaviorContext_HasExpectedProperties) - { - AZ::BehaviorClass* behaviorClass = GetBehaviorClass(azrtti_typeid()); - ASSERT_NE(nullptr, behaviorClass); - EXPECT_TRUE(HasProperty(*behaviorClass, "name", azrtti_typeid())); - EXPECT_TRUE(HasProperty(*behaviorClass, "manifestFilename", azrtti_typeid())); - EXPECT_TRUE(HasProperty(*behaviorClass, "sourceFilename", azrtti_typeid())); - EXPECT_TRUE(HasProperty(*behaviorClass, "sourceGuid", azrtti_typeid())); - EXPECT_TRUE(HasProperty(*behaviorClass, "graph", azrtti_typeid())); - EXPECT_TRUE(HasProperty(*behaviorClass, "manifest", azrtti_typeid())); - } - - TEST_F(SceneGraphBehaviorTest, SceneGraphClass_BehaviorContext_Exists) - { - EXPECT_TRUE(HasBehaviorClass(azrtti_typeid())); - EXPECT_TRUE(HasBehaviorClass(azrtti_typeid())); - EXPECT_TRUE(HasBehaviorClass(azrtti_typeid())); - } - - TEST_F(SceneGraphBehaviorTest, SceneGraphClass_BehaviorContext_HasExpectedProperties) - { - using namespace AZ::SceneAPI::Containers; - - AZ::BehaviorClass* behaviorClass = GetBehaviorClass(azrtti_typeid()); - ASSERT_NE(nullptr, behaviorClass); - EXPECT_TRUE(HasMethodWithOutput(*behaviorClass, "GetNodeName", azrtti_typeid(), { azrtti_typeid() })); - EXPECT_TRUE(HasMethodWithOutput(*behaviorClass, "GetRoot", azrtti_typeid(), {})); - EXPECT_TRUE(HasMethodWithOutput(*behaviorClass, "HasNodeContent", azrtti_typeid(), { azrtti_typeid() })); - EXPECT_TRUE(HasMethodWithOutput(*behaviorClass, "HasNodeSibling", azrtti_typeid(), { azrtti_typeid() })); - EXPECT_TRUE(HasMethodWithOutput(*behaviorClass, "HasNodeChild", azrtti_typeid(), { azrtti_typeid() })); - EXPECT_TRUE(HasMethodWithOutput(*behaviorClass, "HasNodeParent", azrtti_typeid(), { azrtti_typeid() })); - EXPECT_TRUE(HasMethodWithOutput(*behaviorClass, "IsNodeEndPoint", azrtti_typeid(), { azrtti_typeid() })); - EXPECT_TRUE(HasMethodWithOutput(*behaviorClass, "GetNodeCount", azrtti_typeid(), {})); - EXPECT_TRUE(HasMethodWithOutput( - *behaviorClass, - "GetNodeParent", - azrtti_typeid(), - { azrtti_typeid(), azrtti_typeid() } - )); - EXPECT_TRUE(HasMethodWithOutput( - *behaviorClass, - "GetNodeSibling", - azrtti_typeid(), - { azrtti_typeid(), azrtti_typeid() } - )); - EXPECT_TRUE(HasMethodWithOutput( - *behaviorClass, - "GetNodeChild", - azrtti_typeid(), - { azrtti_typeid(), azrtti_typeid() } - )); - EXPECT_TRUE(HasMethodWithOutput( - *behaviorClass, - "FindWithPath", - azrtti_typeid(), - { azrtti_typeid(), azrtti_typeid() } - )); - EXPECT_TRUE(HasMethodWithOutput( - *behaviorClass, - "FindWithRootAndPath", - azrtti_typeid(), - { azrtti_typeid(), azrtti_typeid(), azrtti_typeid() } - )); - } - - TEST_F(SceneGraphBehaviorTest, SceneGraphNodeIndexClass_BehaviorContext_HasExpectedProperties) - { - using namespace AZ::SceneAPI::Containers; - - AZ::BehaviorClass* behaviorClass = GetBehaviorClass(azrtti_typeid()); - ASSERT_NE(nullptr, behaviorClass); - EXPECT_TRUE(HasMethodWithOutput(*behaviorClass, "AsNumber", azrtti_typeid(), {})); - EXPECT_TRUE(HasMethodWithOutput(*behaviorClass, "Distance", azrtti_typeid(), { azrtti_typeid() })); - EXPECT_TRUE(HasMethodWithOutput(*behaviorClass, "IsValid", azrtti_typeid(), {})); - EXPECT_TRUE(HasMethodWithOutput(*behaviorClass, "Equal", azrtti_typeid(), { azrtti_typeid() })); - } - - TEST_F(SceneGraphBehaviorTest, SceneGraphNameClass_BehaviorContext_HasExpectedProperties) - { - using namespace AZ::SceneAPI::Containers; - - AZ::BehaviorClass* behaviorClass = GetBehaviorClass(azrtti_typeid()); - ASSERT_NE(nullptr, behaviorClass); - EXPECT_TRUE(HasMethodWithOutput(*behaviorClass, "GetPath", azrtti_typeid(), {})); - EXPECT_TRUE(HasMethodWithOutput(*behaviorClass, "GetName", azrtti_typeid(), {})); - } - - class MockSceneComponentApplication - : public AZ::ComponentApplicationBus::Handler - { - public: - MockSceneComponentApplication() - { - AZ::ComponentApplicationBus::Handler::BusConnect(); - AZ::Interface::Register(this); - } - - ~MockSceneComponentApplication() - { - AZ::Interface::Unregister(this); - AZ::ComponentApplicationBus::Handler::BusDisconnect(); - } - - MOCK_METHOD1(FindEntity, AZ::Entity* (const AZ::EntityId&)); - MOCK_METHOD1(AddEntity, bool(AZ::Entity*)); - MOCK_METHOD0(Destroy, void()); - MOCK_METHOD1(RegisterComponentDescriptor, void(const AZ::ComponentDescriptor*)); - MOCK_METHOD1(UnregisterComponentDescriptor, void(const AZ::ComponentDescriptor*)); - MOCK_METHOD1(RegisterEntityAddedEventHandler, void(AZ::EntityAddedEvent::Handler&)); - MOCK_METHOD1(RegisterEntityRemovedEventHandler, void(AZ::EntityRemovedEvent::Handler&)); - MOCK_METHOD1(RegisterEntityActivatedEventHandler, void(AZ::EntityActivatedEvent::Handler&)); - MOCK_METHOD1(RegisterEntityDeactivatedEventHandler, void(AZ::EntityDeactivatedEvent::Handler&)); - MOCK_METHOD1(SignalEntityActivated, void(AZ::Entity*)); - MOCK_METHOD1(SignalEntityDeactivated, void(AZ::Entity*)); - MOCK_METHOD1(RemoveEntity, bool(AZ::Entity*)); - MOCK_METHOD1(DeleteEntity, bool(const AZ::EntityId&)); - MOCK_METHOD1(GetEntityName, AZStd::string(const AZ::EntityId&)); - MOCK_METHOD1(EnumerateEntities, void(const ComponentApplicationRequests::EntityCallback&)); - MOCK_METHOD0(GetApplication, AZ::ComponentApplication* ()); - MOCK_METHOD0(GetSerializeContext, AZ::SerializeContext* ()); - MOCK_METHOD0(GetJsonRegistrationContext, AZ::JsonRegistrationContext* ()); - MOCK_METHOD0(GetBehaviorContext, AZ::BehaviorContext* ()); - MOCK_CONST_METHOD0(GetAppRoot, const char*()); - MOCK_CONST_METHOD0(GetEngineRoot, const char*()); - MOCK_CONST_METHOD0(GetExecutableFolder, const char* ()); - MOCK_METHOD0(GetDrillerManager, AZ::Debug::DrillerManager* ()); - MOCK_CONST_METHOD1(QueryApplicationType, void(AZ::ApplicationTypeQuery&)); - }; - - class MockEditorPythonConsoleInterface final - : public AzToolsFramework::EditorPythonConsoleInterface - { - public: - MockEditorPythonConsoleInterface() - { - AZ::Interface::Register(this); - } - - ~MockEditorPythonConsoleInterface() - { - AZ::Interface::Unregister(this); - } - - MOCK_CONST_METHOD1(GetModuleList, void(AZStd::vector&)); - MOCK_CONST_METHOD1(GetGlobalFunctionList, void(GlobalFunctionCollection&)); - MOCK_METHOD1(FetchPythonTypeName, AZStd::string(const AZ::BehaviorParameter&)); - }; - - // - // SceneGraphBehaviorScriptTest - // - class SceneGraphBehaviorScriptTest - : public UnitTest::AllocatorsFixture - { - public: - AZStd::unique_ptr m_componentApplication; - AZStd::unique_ptr m_editorPythonConsoleInterface; - AZStd::unique_ptr m_scriptContext; - AZStd::unique_ptr m_behaviorContext; - AZStd::unique_ptr m_serializeContext; - - static void TestExpectTrue(bool value) - { - EXPECT_TRUE(value); - } - - static void TestExpectEquals(AZ::s64 lhs, AZ::s64 rhs) - { - EXPECT_EQ(lhs, rhs); - } - - static void ReflectTestTypes(AZ::ReflectContext* context) - { - AZ::BehaviorContext* behaviorContext = azrtti_cast(context); - if (behaviorContext) - { - behaviorContext->Class() - ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common) - ->Attribute(AZ::Script::Attributes::Module, "scene.graph.test") - ->Method("GetId", [](const DataTypes::MockIGraphObject& self) - { - return self.m_id; - }) - ->Method("SetId", [](DataTypes::MockIGraphObject& self, int value) - { - self.m_id = value; - }) - ->Method("AddAndSet", [](DataTypes::MockIGraphObject& self, int lhs, int rhs) - { - self.m_id = lhs + rhs; - }); - } - } - - void SetUp() override - { - UnitTest::AllocatorsFixture::SetUp(); - - m_serializeContext = AZStd::make_unique(); - - m_behaviorContext = AZStd::make_unique(); - m_behaviorContext->Method("TestExpectTrue", &TestExpectTrue); - m_behaviorContext->Method("TestExpectEquals", &TestExpectEquals); - - AZ::MathReflect(m_behaviorContext.get()); - ReflectBehavior(m_behaviorContext.get()); - ReflectTestTypes(m_behaviorContext.get()); - MockBuilder::Reflect(m_behaviorContext.get()); - - m_scriptContext = AZStd::make_unique(); - m_scriptContext->BindTo(m_behaviorContext.get()); - - m_componentApplication = AZStd::make_unique<::testing::NiceMock>(); - - ON_CALL(*m_componentApplication, GetBehaviorContext()) - .WillByDefault(::testing::Invoke([this]() - { - return this->m_behaviorContext.get(); - })); - - ON_CALL(*m_componentApplication, GetSerializeContext()) - .WillByDefault(::testing::Invoke([this]() - { - return this->m_serializeContext.get(); - })); - - m_editorPythonConsoleInterface = AZStd::make_unique(); - } - - void SetupEditorPythonConsoleInterface() - { - EXPECT_CALL(*m_editorPythonConsoleInterface, FetchPythonTypeName(::testing::_)) - .Times(4) - .WillRepeatedly(::testing::Invoke([](const AZ::BehaviorParameter&) {return "int"; })); - } - - void TearDown() override - { - m_scriptContext.reset(); - m_serializeContext.reset(); - m_behaviorContext.reset(); - - UnitTest::AllocatorsFixture::TearDown(); - } - - void ExpectExecute(AZStd::string_view script) - { - EXPECT_TRUE(m_scriptContext->Execute(script.data())); - } - }; - - TEST_F(SceneGraphBehaviorScriptTest, Scene_ScriptContext_Access) - { - ExpectExecute("builder = MockBuilder()"); - ExpectExecute("builder:BuildSceneGraph()"); - ExpectExecute("scene = builder:GetScene()"); - ExpectExecute("TestExpectTrue(scene ~= nil)"); - ExpectExecute("TestExpectTrue(scene.name == 'unit_scene')"); - ExpectExecute("TestExpectTrue(scene.manifestFilename == 'manifest_filename')"); - ExpectExecute("TestExpectTrue(scene.sourceFilename == 'unit_source_filename')"); - ExpectExecute("TestExpectTrue(tostring(scene.sourceGuid) == '{1F2E6142-B0D8-42C6-A6E5-CD726DAA9EF0}')"); - ExpectExecute("TestExpectTrue(scene:GetOriginalSceneOrientation() == Scene.SceneOrientation_YUp)"); - } - - TEST_F(SceneGraphBehaviorScriptTest, SceneGraph_ScriptContext_AccessMockNodes) - { - ExpectExecute("builder = MockBuilder()"); - ExpectExecute("builder:BuildSceneGraph()"); - ExpectExecute("scene = builder:GetScene()"); - - // instance methods - ExpectExecute("TestExpectTrue(scene.graph ~= nil)"); - ExpectExecute("TestExpectTrue(scene.graph:GetRoot():IsValid())"); - ExpectExecute("TestExpectEquals(scene.graph:GetNodeCount(), 13)"); - ExpectExecute("nodeRoot = scene.graph:GetRoot()"); - ExpectExecute("nodeA = scene.graph:GetNodeChild(nodeRoot); TestExpectTrue(nodeA:IsValid())"); - ExpectExecute("TestExpectTrue(scene.graph:HasNodeContent(nodeA))"); - ExpectExecute("nodeC = scene.graph:GetNodeChild(nodeA); TestExpectTrue(nodeC:IsValid())"); - ExpectExecute("nodeNameC = scene.graph:GetNodeName(nodeC); TestExpectTrue(nodeNameC ~= nil)"); - ExpectExecute("nodeE = scene.graph:GetNodeChild(nodeC); TestExpectTrue(nodeE:IsValid())"); - ExpectExecute("TestExpectTrue(scene.graph:HasNodeSibling(nodeE))"); - ExpectExecute("TestExpectTrue(scene.graph:HasNodeChild(nodeE))"); - ExpectExecute("TestExpectTrue(scene.graph:HasNodeParent(nodeE))"); - ExpectExecute("nodeG = scene.graph:GetNodeChild(nodeE); TestExpectTrue(nodeG:IsValid())"); - ExpectExecute("TestExpectTrue(scene.graph:GetNodeParent(nodeG) == nodeE)"); - ExpectExecute("nodeH = scene.graph:GetNodeSibling(nodeG); TestExpectTrue(nodeH:IsValid())"); - ExpectExecute("TestExpectTrue(scene.graph:GetNodeName(nodeH):GetPath() == 'A.C.E.H')"); - ExpectExecute("nodeB = scene.graph:GetNodeSibling(nodeA); TestExpectTrue(nodeB:IsValid())"); - ExpectExecute("nodeK = scene.graph:GetNodeChild(nodeB); TestExpectTrue(nodeK:IsValid())"); - ExpectExecute("TestExpectTrue(scene.graph:FindWithPath('B.K') == nodeK)"); - ExpectExecute("nodeL = scene.graph:GetNodeChild(nodeK); TestExpectTrue(nodeL:IsValid())"); - ExpectExecute("TestExpectTrue(scene.graph:FindWithRootAndPath(nodeK, 'L') == nodeL)"); - - // static methods - ExpectExecute("TestExpectTrue(scene.graph.IsValidName('A'))"); - ExpectExecute("TestExpectTrue(scene.graph.GetNodeSeperationCharacter() == string.byte('.'))"); - } - - TEST_F(SceneGraphBehaviorScriptTest, SceneGraphNodeIndex_ScriptContext_AccessMockNodes) - { - ExpectExecute("builder = MockBuilder()"); - ExpectExecute("builder:BuildSceneGraph()"); - ExpectExecute("scene = builder:GetScene()"); - ExpectExecute("nodeA = scene.graph:GetNodeChild(scene.graph:GetRoot())"); - ExpectExecute("TestExpectTrue(nodeA:IsValid())"); - ExpectExecute("TestExpectEquals(nodeA:AsNumber(), 1)"); - ExpectExecute("TestExpectEquals(scene.graph:GetRoot():Distance(nodeA), 1)"); - ExpectExecute("TestExpectEquals(nodeA:Distance(scene.graph:GetRoot()), -1)"); - ExpectExecute("TestExpectTrue(nodeA == scene.graph:FindWithPath('A'))"); - } - - TEST_F(SceneGraphBehaviorScriptTest, SceneGraphName_ScriptContext_AccessMockNodes) - { - ExpectExecute("builder = MockBuilder()"); - ExpectExecute("builder:BuildSceneGraph()"); - ExpectExecute("scene = builder:GetScene()"); - ExpectExecute("nodeG = scene.graph:FindWithPath('A.C.E.G')"); - ExpectExecute("nodeNameG = scene.graph:GetNodeName(nodeG)"); - ExpectExecute("TestExpectTrue(nodeNameG:GetPath() == 'A.C.E.G')"); - ExpectExecute("TestExpectTrue(nodeNameG:GetName() == 'G')"); - } - - TEST_F(SceneGraphBehaviorScriptTest, SceneGraphIGraphNode_ScriptContext_AccessMockNodes) - { - ExpectExecute("builder = MockBuilder()"); - ExpectExecute("builder:BuildSceneGraph()"); - ExpectExecute("scene = builder:GetScene()"); - ExpectExecute("nodeG = scene.graph:FindWithPath('A.C.E.G')"); - ExpectExecute("proxy = scene.graph:GetNodeContent(nodeG)"); - ExpectExecute("TestExpectTrue(proxy:CastWithTypeName('MockIGraphObject'))"); - ExpectExecute("value = proxy:Invoke('GetId', vector_any())"); - ExpectExecute("TestExpectEquals(value, 7)"); - ExpectExecute("setIdArgs = vector_any(); setIdArgs:push_back(8);"); - ExpectExecute("proxy:Invoke('SetId', setIdArgs)"); - ExpectExecute("value = proxy:Invoke('GetId', vector_any())"); - ExpectExecute("TestExpectEquals(value, 8)"); - ExpectExecute("addArgs = vector_any(); addArgs:push_back(8); addArgs:push_back(9)"); - ExpectExecute("proxy:Invoke('AddAndSet', addArgs)"); - ExpectExecute("value = proxy:Invoke('GetId', vector_any())"); - ExpectExecute("TestExpectEquals(value, 17)"); - } - - TEST_F(SceneGraphBehaviorScriptTest, GraphObjectProxy_GetClassInfo_Loads) - { - SetupEditorPythonConsoleInterface(); - - ExpectExecute("builder = MockBuilder()"); - ExpectExecute("builder:BuildSceneGraph()"); - ExpectExecute("scene = builder:GetScene()"); - ExpectExecute("nodeG = scene.graph:FindWithPath('A.C.E.G')"); - ExpectExecute("proxy = scene.graph:GetNodeContent(nodeG)"); - ExpectExecute("TestExpectTrue(proxy:CastWithTypeName('MockIGraphObject'))"); - ExpectExecute("info = proxy:GetClassInfo()"); - ExpectExecute("TestExpectTrue(info ~= nil)"); - } - - TEST_F(SceneGraphBehaviorScriptTest, GraphObjectProxy_GetClassInfo_CorrectFormats) - { - SetupEditorPythonConsoleInterface(); - - ExpectExecute("builder = MockBuilder()"); - ExpectExecute("builder:BuildSceneGraph()"); - ExpectExecute("scene = builder:GetScene()"); - ExpectExecute("nodeG = scene.graph:FindWithPath('A.C.E.G')"); - ExpectExecute("proxy = scene.graph:GetNodeContent(nodeG)"); - ExpectExecute("TestExpectTrue(proxy:CastWithTypeName('MockIGraphObject'))"); - ExpectExecute("info = proxy:GetClassInfo()"); - ExpectExecute("TestExpectTrue(info.className == 'MockIGraphObject')"); - ExpectExecute("TestExpectTrue(info.classUuid == '{66A082CC-851D-4E1F-ABBD-45B58A216CFA}')"); - ExpectExecute("TestExpectTrue(info.methodList[1] == 'def GetId(self) -> int')"); - ExpectExecute("TestExpectTrue(info.methodList[2] == 'def SetId(self, arg1: int) -> None')"); - ExpectExecute("TestExpectTrue(info.methodList[3] == 'def AddAndSet(self, arg1: int, arg2: int) -> None')"); - } - - // - // SceneManifestBehaviorScriptTest is meant to test the script abilities of the SceneManifest - // - class SceneManifestBehaviorScriptTest - : public UnitTest::AllocatorsFixture - { - public: - AZStd::unique_ptr m_componentApplication; - AZStd::unique_ptr m_scriptContext; - AZStd::unique_ptr m_behaviorContext; - AZStd::unique_ptr m_serializeContext; - AZStd::unique_ptr m_jsonRegistrationContext; - AZStd::string_view m_jsonMockData = R"JSON('{"values":[{"$type":"MockManifestRule","value":0.1},{"$type":"MockManifestRule","value":2.3},{"$type":"MockManifestRule","value":4.5}]}')JSON"; - - static void TestAssertTrue(bool value) - { - EXPECT_TRUE(value); - } - - void SetUp() override - { - UnitTest::AllocatorsFixture::SetUp(); - - m_serializeContext = AZStd::make_unique(); - MockBuilder::Reflect(m_serializeContext.get()); - MockManifestRule::Reflect(m_serializeContext.get()); - ReflectTypes(m_serializeContext.get()); - - m_behaviorContext = AZStd::make_unique(); - m_behaviorContext->Method("TestAssertTrue", &TestAssertTrue); - AZ::MathReflect(m_behaviorContext.get()); - MockBuilder::Reflect(m_behaviorContext.get()); - MockManifestRule::Reflect(m_behaviorContext.get()); - ReflectBehavior(m_behaviorContext.get()); - - m_jsonRegistrationContext = AZStd::make_unique(); - AZ::JsonSystemComponent::Reflect(m_jsonRegistrationContext.get()); - - m_scriptContext = AZStd::make_unique(); - m_scriptContext->BindTo(m_behaviorContext.get()); - - m_componentApplication = AZStd::make_unique<::testing::NiceMock>(); - - ON_CALL(*m_componentApplication, GetBehaviorContext()).WillByDefault(::testing::Invoke([this]() - { - return this->m_behaviorContext.get(); - })); - - ON_CALL(*m_componentApplication, GetSerializeContext()).WillByDefault(::testing::Invoke([this]() - { - return this->m_serializeContext.get(); - })); - - ON_CALL(*m_componentApplication, GetJsonRegistrationContext()).WillByDefault(::testing::Invoke([this]() - { - return this->m_jsonRegistrationContext.get(); - })); - } - - void TearDown() override - { - m_jsonRegistrationContext->EnableRemoveReflection(); - AZ::JsonSystemComponent::Reflect(m_jsonRegistrationContext.get()); - m_jsonRegistrationContext->DisableRemoveReflection(); - - m_jsonRegistrationContext.reset(); - m_serializeContext.reset(); - m_scriptContext.reset(); - m_behaviorContext.reset(); - m_componentApplication.reset(); - UnitTest::AllocatorsFixture::TearDown(); - } - - void ExpectExecute(AZStd::string_view script) - { - EXPECT_TRUE(m_scriptContext->Execute(script.data())); - } - }; - - TEST_F(SceneManifestBehaviorScriptTest, SceneManifest_ScriptContext_GetDefaultJSON) - { - ExpectExecute("builder = MockBuilder()"); - ExpectExecute("scene = builder:GetScene()"); - ExpectExecute("manifest = scene.manifest:ExportToJson()"); - ExpectExecute(R"JSON(TestAssertTrue(manifest == '{}'))JSON"); - } - - TEST_F(SceneManifestBehaviorScriptTest, SceneManifest_ScriptContext_GetComplexJSON) - { - ExpectExecute("builder = MockBuilder()"); - ExpectExecute("scene = builder:GetScene()"); - ExpectExecute("builder:BuildSceneGraph()"); - ExpectExecute("manifest = scene.manifest:ExportToJson()"); - auto read = AZStd::fixed_string<1024>::format("TestAssertTrue(manifest == %s)", m_jsonMockData.data()); - ExpectExecute(read); - } - - TEST_F(SceneManifestBehaviorScriptTest, SceneManifest_ScriptContext_SetComplexJSON) - { - ExpectExecute("builder = MockBuilder()"); - ExpectExecute("scene = builder:GetScene()"); - ExpectExecute("manifest = scene.manifest:ExportToJson()"); - ExpectExecute(R"JSON(TestAssertTrue(manifest == '{}'))JSON"); - auto load = AZStd::fixed_string<1024>::format("TestAssertTrue(scene.manifest:ImportFromJson(%s))", m_jsonMockData.data()); - ExpectExecute(load); - ExpectExecute("manifest = scene.manifest:ExportToJson()"); - auto read = AZStd::fixed_string<1024>::format("TestAssertTrue(manifest == %s)", m_jsonMockData.data()); - ExpectExecute(read); + serializeContext->Class() + ->Version(1) + ->Field("value", &MockManifestRule::m_value); } } + + private: + double m_value = 0.0; + }; + + struct MockBuilder final + { + AZ_TYPE_INFO(MockBuilder, "{ECF0FB2C-E5C0-4B89-993C-8511A7EF6894}"); + + AZStd::unique_ptr m_scene; + + MockBuilder() + { + m_scene = AZStd::make_unique("unit_scene"); + } + + ~MockBuilder() + { + m_scene.reset(); + } + + void BuildSceneGraph() + { + m_scene->SetManifestFilename("manifest_filename"); + m_scene->SetSource("unit_source_filename", azrtti_typeid()); + + auto& graph = m_scene->GetGraph(); + + /*----------------------------\ + | Root | + | / \ | + | | | | + | A B | + | | /|\ | + | C I J K | + | / | \ \ | + | D E F L | + | / \ | + | G H | + \----------------------------*/ + + //Build up the graph + const auto indexA = graph.AddChild(graph.GetRoot(), "A", AZStd::make_shared(1)); + const auto indexC = graph.AddChild(indexA, "C", AZStd::make_shared(3)); + const auto indexE = graph.AddChild(indexC, "E", AZStd::make_shared(4)); + graph.AddChild(indexC, "D", AZStd::make_shared(5)); + graph.AddChild(indexC, "F", AZStd::make_shared(6)); + graph.AddChild(indexE, "G", AZStd::make_shared(7)); + graph.AddChild(indexE, "H", AZStd::make_shared(8)); + const auto indexB = graph.AddChild(graph.GetRoot(), "B", AZStd::make_shared(2)); + const auto indexK = graph.AddChild(indexB, "K", AZStd::make_shared(2)); + graph.AddChild(indexB, "I", AZStd::make_shared(9)); + graph.AddChild(indexB, "J", AZStd::make_shared(10)); + graph.AddChild(indexK, "L", AZStd::make_shared(12)); + + m_scene->GetManifest().AddEntry(AZStd::make_shared(0.1)); + m_scene->GetManifest().AddEntry(AZStd::make_shared(2.3)); + m_scene->GetManifest().AddEntry(AZStd::make_shared(4.5)); + } + + static void Reflect(ReflectContext* context) + { + BehaviorContext* behaviorContext = azrtti_cast(context); + if (behaviorContext) + { + behaviorContext->Class() + ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common) + ->Attribute(AZ::Script::Attributes::Module, "scene") + ->Method("BuildSceneGraph", [](MockBuilder& self) + { + return self.BuildSceneGraph(); + }) + ->Method("GetScene", [](MockBuilder& self) + { + return self.m_scene.get(); + }); + } + } + }; + + class SceneGraphBehaviorTest + : public ::testing::Test + { + public: + void SetUp() override + { + m_behaviorContext = AZStd::make_unique(); + ReflectBehavior(m_behaviorContext.get()); + } + + void TearDown() override + { + m_behaviorContext.reset(); + } + + AZ::BehaviorClass* GetBehaviorClass(const AZ::TypeId& behaviorClassType) + { + auto entry = m_behaviorContext->m_typeToClassMap.find(behaviorClassType); + return (entry != m_behaviorContext->m_typeToClassMap.end()) ? entry->second : nullptr; + } + + AZ::BehaviorProperty* GetBehaviorProperty(AZ::BehaviorClass& behaviorClass, AZStd::string_view propertyName) + { + auto entry = behaviorClass.m_properties.find(propertyName); + return (entry != behaviorClass.m_properties.end()) ? entry->second : nullptr; + } + + bool HasBehaviorClass(const AZ::TypeId& behaviorClassType) + { + return GetBehaviorClass(behaviorClassType) != nullptr; + } + + bool HasProperty(AZ::BehaviorClass& behaviorClass, AZStd::string_view propertyName, const AZ::TypeId& propertyClassType) + { + AZ::BehaviorProperty* behaviorProperty = GetBehaviorProperty(behaviorClass, propertyName); + if (behaviorProperty) + { + return behaviorProperty->m_getter->GetResult()->m_typeId == propertyClassType; + } + return false; + } + + using ArgList = AZStd::vector; + + bool HasMethodWithInput(AZ::BehaviorClass& behaviorClass, AZStd::string_view methodName, const ArgList& input) + { + auto entry = behaviorClass.m_methods.find(methodName); + if (entry == behaviorClass.m_methods.end()) + { + return false; + } + AZ::BehaviorMethod* method = entry->second; + + const size_t methodArgsCount = method->IsMember() ? method->GetNumArguments() - 1 : method->GetNumArguments(); + if (input.size() != methodArgsCount) + { + return false; + } + + for (size_t argIndex = 0; argIndex < input.size(); ++argIndex) + { + const size_t thisPointerOffset = method->IsMember() ? 1 : 0; + const auto argType = method->GetArgument(argIndex + thisPointerOffset)->m_typeId; + const auto inputType = input[argIndex]; + if (inputType != argType) + { + return false; + } + } + return true; + } + + bool HasMethodWithOutput(AZ::BehaviorClass& behaviorClass, AZStd::string_view methodName, const AZ::TypeId& output, const ArgList& input) + { + auto entry = behaviorClass.m_methods.find(methodName); + if (entry == behaviorClass.m_methods.end()) + { + return false; + } + AZ::BehaviorMethod* method = entry->second; + if (method->HasResult()) + { + if (method->GetResult()->m_typeId != output) + { + return false; + } + } + else + { + return false; + } + return HasMethodWithInput(behaviorClass, methodName, input); + } + + AZStd::unique_ptr m_behaviorContext; + }; + + TEST_F(SceneGraphBehaviorTest, SceneClass_BehaviorContext_Exists) + { + EXPECT_TRUE(HasBehaviorClass(azrtti_typeid())); } -} + + TEST_F(SceneGraphBehaviorTest, SceneClass_BehaviorContext_HasExpectedProperties) + { + AZ::BehaviorClass* behaviorClass = GetBehaviorClass(azrtti_typeid()); + ASSERT_NE(nullptr, behaviorClass); + EXPECT_TRUE(HasProperty(*behaviorClass, "name", azrtti_typeid())); + EXPECT_TRUE(HasProperty(*behaviorClass, "manifestFilename", azrtti_typeid())); + EXPECT_TRUE(HasProperty(*behaviorClass, "sourceFilename", azrtti_typeid())); + EXPECT_TRUE(HasProperty(*behaviorClass, "sourceGuid", azrtti_typeid())); + EXPECT_TRUE(HasProperty(*behaviorClass, "graph", azrtti_typeid())); + EXPECT_TRUE(HasProperty(*behaviorClass, "manifest", azrtti_typeid())); + } + + TEST_F(SceneGraphBehaviorTest, SceneGraphClass_BehaviorContext_Exists) + { + EXPECT_TRUE(HasBehaviorClass(azrtti_typeid())); + EXPECT_TRUE(HasBehaviorClass(azrtti_typeid())); + EXPECT_TRUE(HasBehaviorClass(azrtti_typeid())); + } + + TEST_F(SceneGraphBehaviorTest, SceneGraphClass_BehaviorContext_HasExpectedProperties) + { + using namespace AZ::SceneAPI::Containers; + + AZ::BehaviorClass* behaviorClass = GetBehaviorClass(azrtti_typeid()); + ASSERT_NE(nullptr, behaviorClass); + EXPECT_TRUE(HasMethodWithOutput(*behaviorClass, "GetNodeName", azrtti_typeid(), { azrtti_typeid() })); + EXPECT_TRUE(HasMethodWithOutput(*behaviorClass, "GetRoot", azrtti_typeid(), {})); + EXPECT_TRUE(HasMethodWithOutput(*behaviorClass, "HasNodeContent", azrtti_typeid(), { azrtti_typeid() })); + EXPECT_TRUE(HasMethodWithOutput(*behaviorClass, "HasNodeSibling", azrtti_typeid(), { azrtti_typeid() })); + EXPECT_TRUE(HasMethodWithOutput(*behaviorClass, "HasNodeChild", azrtti_typeid(), { azrtti_typeid() })); + EXPECT_TRUE(HasMethodWithOutput(*behaviorClass, "HasNodeParent", azrtti_typeid(), { azrtti_typeid() })); + EXPECT_TRUE(HasMethodWithOutput(*behaviorClass, "IsNodeEndPoint", azrtti_typeid(), { azrtti_typeid() })); + EXPECT_TRUE(HasMethodWithOutput(*behaviorClass, "GetNodeCount", azrtti_typeid(), {})); + EXPECT_TRUE(HasMethodWithOutput( + *behaviorClass, + "GetNodeParent", + azrtti_typeid(), + { azrtti_typeid(), azrtti_typeid() } + )); + EXPECT_TRUE(HasMethodWithOutput( + *behaviorClass, + "GetNodeSibling", + azrtti_typeid(), + { azrtti_typeid(), azrtti_typeid() } + )); + EXPECT_TRUE(HasMethodWithOutput( + *behaviorClass, + "GetNodeChild", + azrtti_typeid(), + { azrtti_typeid(), azrtti_typeid() } + )); + EXPECT_TRUE(HasMethodWithOutput( + *behaviorClass, + "FindWithPath", + azrtti_typeid(), + { azrtti_typeid(), azrtti_typeid() } + )); + EXPECT_TRUE(HasMethodWithOutput( + *behaviorClass, + "FindWithRootAndPath", + azrtti_typeid(), + { azrtti_typeid(), azrtti_typeid(), azrtti_typeid() } + )); + } + + TEST_F(SceneGraphBehaviorTest, SceneGraphNodeIndexClass_BehaviorContext_HasExpectedProperties) + { + using namespace AZ::SceneAPI::Containers; + + AZ::BehaviorClass* behaviorClass = GetBehaviorClass(azrtti_typeid()); + ASSERT_NE(nullptr, behaviorClass); + EXPECT_TRUE(HasMethodWithOutput(*behaviorClass, "AsNumber", azrtti_typeid(), {})); + EXPECT_TRUE(HasMethodWithOutput(*behaviorClass, "Distance", azrtti_typeid(), { azrtti_typeid() })); + EXPECT_TRUE(HasMethodWithOutput(*behaviorClass, "IsValid", azrtti_typeid(), {})); + EXPECT_TRUE(HasMethodWithOutput(*behaviorClass, "Equal", azrtti_typeid(), { azrtti_typeid() })); + } + + TEST_F(SceneGraphBehaviorTest, SceneGraphNameClass_BehaviorContext_HasExpectedProperties) + { + using namespace AZ::SceneAPI::Containers; + + AZ::BehaviorClass* behaviorClass = GetBehaviorClass(azrtti_typeid()); + ASSERT_NE(nullptr, behaviorClass); + EXPECT_TRUE(HasMethodWithOutput(*behaviorClass, "GetPath", azrtti_typeid(), {})); + EXPECT_TRUE(HasMethodWithOutput(*behaviorClass, "GetName", azrtti_typeid(), {})); + } + + class MockSceneComponentApplication + : public AZ::ComponentApplicationBus::Handler + { + public: + MockSceneComponentApplication() + { + AZ::ComponentApplicationBus::Handler::BusConnect(); + AZ::Interface::Register(this); + } + + ~MockSceneComponentApplication() + { + AZ::Interface::Unregister(this); + AZ::ComponentApplicationBus::Handler::BusDisconnect(); + } + + MOCK_METHOD1(FindEntity, AZ::Entity* (const AZ::EntityId&)); + MOCK_METHOD1(AddEntity, bool(AZ::Entity*)); + MOCK_METHOD0(Destroy, void()); + MOCK_METHOD1(RegisterComponentDescriptor, void(const AZ::ComponentDescriptor*)); + MOCK_METHOD1(UnregisterComponentDescriptor, void(const AZ::ComponentDescriptor*)); + MOCK_METHOD1(RegisterEntityAddedEventHandler, void(AZ::EntityAddedEvent::Handler&)); + MOCK_METHOD1(RegisterEntityRemovedEventHandler, void(AZ::EntityRemovedEvent::Handler&)); + MOCK_METHOD1(RegisterEntityActivatedEventHandler, void(AZ::EntityActivatedEvent::Handler&)); + MOCK_METHOD1(RegisterEntityDeactivatedEventHandler, void(AZ::EntityDeactivatedEvent::Handler&)); + MOCK_METHOD1(SignalEntityActivated, void(AZ::Entity*)); + MOCK_METHOD1(SignalEntityDeactivated, void(AZ::Entity*)); + MOCK_METHOD1(RemoveEntity, bool(AZ::Entity*)); + MOCK_METHOD1(DeleteEntity, bool(const AZ::EntityId&)); + MOCK_METHOD1(GetEntityName, AZStd::string(const AZ::EntityId&)); + MOCK_METHOD1(EnumerateEntities, void(const ComponentApplicationRequests::EntityCallback&)); + MOCK_METHOD0(GetApplication, AZ::ComponentApplication* ()); + MOCK_METHOD0(GetSerializeContext, AZ::SerializeContext* ()); + MOCK_METHOD0(GetJsonRegistrationContext, AZ::JsonRegistrationContext* ()); + MOCK_METHOD0(GetBehaviorContext, AZ::BehaviorContext* ()); + MOCK_CONST_METHOD0(GetAppRoot, const char*()); + MOCK_CONST_METHOD0(GetEngineRoot, const char*()); + MOCK_CONST_METHOD0(GetExecutableFolder, const char* ()); + MOCK_METHOD0(GetDrillerManager, AZ::Debug::DrillerManager* ()); + MOCK_CONST_METHOD1(QueryApplicationType, void(AZ::ApplicationTypeQuery&)); + }; + + class MockEditorPythonConsoleInterface final + : public AzToolsFramework::EditorPythonConsoleInterface + { + public: + MockEditorPythonConsoleInterface() + { + AZ::Interface::Register(this); + } + + ~MockEditorPythonConsoleInterface() + { + AZ::Interface::Unregister(this); + } + + MOCK_CONST_METHOD1(GetModuleList, void(AZStd::vector&)); + MOCK_CONST_METHOD1(GetGlobalFunctionList, void(GlobalFunctionCollection&)); + MOCK_METHOD1(FetchPythonTypeName, AZStd::string(const AZ::BehaviorParameter&)); + }; + + // + // SceneGraphBehaviorScriptTest + // + class SceneGraphBehaviorScriptTest + : public UnitTest::AllocatorsFixture + { + public: + AZStd::unique_ptr m_componentApplication; + AZStd::unique_ptr m_editorPythonConsoleInterface; + AZStd::unique_ptr m_scriptContext; + AZStd::unique_ptr m_behaviorContext; + AZStd::unique_ptr m_serializeContext; + + static void TestExpectTrue(bool value) + { + EXPECT_TRUE(value); + } + + static void TestExpectEquals(AZ::s64 lhs, AZ::s64 rhs) + { + EXPECT_EQ(lhs, rhs); + } + + static void ReflectTestTypes(AZ::ReflectContext* context) + { + AZ::BehaviorContext* behaviorContext = azrtti_cast(context); + if (behaviorContext) + { + behaviorContext->Class() + ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common) + ->Attribute(AZ::Script::Attributes::Module, "scene.graph.test") + ->Method("GetId", [](const DataTypes::MockIGraphObject& self) + { + return self.m_id; + }) + ->Method("SetId", [](DataTypes::MockIGraphObject& self, int value) + { + self.m_id = value; + }) + ->Method("AddAndSet", [](DataTypes::MockIGraphObject& self, int lhs, int rhs) + { + self.m_id = lhs + rhs; + }); + } + } + + void SetUp() override + { + UnitTest::AllocatorsFixture::SetUp(); + + m_serializeContext = AZStd::make_unique(); + + m_behaviorContext = AZStd::make_unique(); + m_behaviorContext->Method("TestExpectTrue", &TestExpectTrue); + m_behaviorContext->Method("TestExpectEquals", &TestExpectEquals); + + AZ::MathReflect(m_behaviorContext.get()); + ReflectBehavior(m_behaviorContext.get()); + ReflectTestTypes(m_behaviorContext.get()); + MockBuilder::Reflect(m_behaviorContext.get()); + + m_scriptContext = AZStd::make_unique(); + m_scriptContext->BindTo(m_behaviorContext.get()); + + m_componentApplication = AZStd::make_unique<::testing::NiceMock>(); + + ON_CALL(*m_componentApplication, GetBehaviorContext()) + .WillByDefault(::testing::Invoke([this]() + { + return this->m_behaviorContext.get(); + })); + + ON_CALL(*m_componentApplication, GetSerializeContext()) + .WillByDefault(::testing::Invoke([this]() + { + return this->m_serializeContext.get(); + })); + + m_editorPythonConsoleInterface = AZStd::make_unique(); + } + + void SetupEditorPythonConsoleInterface() + { + EXPECT_CALL(*m_editorPythonConsoleInterface, FetchPythonTypeName(::testing::_)) + .Times(4) + .WillRepeatedly(::testing::Invoke([](const AZ::BehaviorParameter&) {return "int"; })); + } + + void TearDown() override + { + m_scriptContext.reset(); + m_serializeContext.reset(); + m_behaviorContext.reset(); + + UnitTest::AllocatorsFixture::TearDown(); + } + + void ExpectExecute(AZStd::string_view script) + { + EXPECT_TRUE(m_scriptContext->Execute(script.data())); + } + }; + + TEST_F(SceneGraphBehaviorScriptTest, Scene_ScriptContext_Access) + { + ExpectExecute("builder = MockBuilder()"); + ExpectExecute("builder:BuildSceneGraph()"); + ExpectExecute("scene = builder:GetScene()"); + ExpectExecute("TestExpectTrue(scene ~= nil)"); + ExpectExecute("TestExpectTrue(scene.name == 'unit_scene')"); + ExpectExecute("TestExpectTrue(scene.manifestFilename == 'manifest_filename')"); + ExpectExecute("TestExpectTrue(scene.sourceFilename == 'unit_source_filename')"); + ExpectExecute("TestExpectTrue(tostring(scene.sourceGuid) == '{1F2E6142-B0D8-42C6-A6E5-CD726DAA9EF0}')"); + ExpectExecute("TestExpectTrue(scene:GetOriginalSceneOrientation() == Scene.SceneOrientation_YUp)"); + } + + TEST_F(SceneGraphBehaviorScriptTest, SceneGraph_ScriptContext_AccessMockNodes) + { + ExpectExecute("builder = MockBuilder()"); + ExpectExecute("builder:BuildSceneGraph()"); + ExpectExecute("scene = builder:GetScene()"); + + // instance methods + ExpectExecute("TestExpectTrue(scene.graph ~= nil)"); + ExpectExecute("TestExpectTrue(scene.graph:GetRoot():IsValid())"); + ExpectExecute("TestExpectEquals(scene.graph:GetNodeCount(), 13)"); + ExpectExecute("nodeRoot = scene.graph:GetRoot()"); + ExpectExecute("nodeA = scene.graph:GetNodeChild(nodeRoot); TestExpectTrue(nodeA:IsValid())"); + ExpectExecute("TestExpectTrue(scene.graph:HasNodeContent(nodeA))"); + ExpectExecute("nodeC = scene.graph:GetNodeChild(nodeA); TestExpectTrue(nodeC:IsValid())"); + ExpectExecute("nodeNameC = scene.graph:GetNodeName(nodeC); TestExpectTrue(nodeNameC ~= nil)"); + ExpectExecute("nodeE = scene.graph:GetNodeChild(nodeC); TestExpectTrue(nodeE:IsValid())"); + ExpectExecute("TestExpectTrue(scene.graph:HasNodeSibling(nodeE))"); + ExpectExecute("TestExpectTrue(scene.graph:HasNodeChild(nodeE))"); + ExpectExecute("TestExpectTrue(scene.graph:HasNodeParent(nodeE))"); + ExpectExecute("nodeG = scene.graph:GetNodeChild(nodeE); TestExpectTrue(nodeG:IsValid())"); + ExpectExecute("TestExpectTrue(scene.graph:GetNodeParent(nodeG) == nodeE)"); + ExpectExecute("nodeH = scene.graph:GetNodeSibling(nodeG); TestExpectTrue(nodeH:IsValid())"); + ExpectExecute("TestExpectTrue(scene.graph:GetNodeName(nodeH):GetPath() == 'A.C.E.H')"); + ExpectExecute("nodeB = scene.graph:GetNodeSibling(nodeA); TestExpectTrue(nodeB:IsValid())"); + ExpectExecute("nodeK = scene.graph:GetNodeChild(nodeB); TestExpectTrue(nodeK:IsValid())"); + ExpectExecute("TestExpectTrue(scene.graph:FindWithPath('B.K') == nodeK)"); + ExpectExecute("nodeL = scene.graph:GetNodeChild(nodeK); TestExpectTrue(nodeL:IsValid())"); + ExpectExecute("TestExpectTrue(scene.graph:FindWithRootAndPath(nodeK, 'L') == nodeL)"); + + // static methods + ExpectExecute("TestExpectTrue(scene.graph.IsValidName('A'))"); + ExpectExecute("TestExpectTrue(scene.graph.GetNodeSeperationCharacter() == string.byte('.'))"); + } + + TEST_F(SceneGraphBehaviorScriptTest, SceneGraphNodeIndex_ScriptContext_AccessMockNodes) + { + ExpectExecute("builder = MockBuilder()"); + ExpectExecute("builder:BuildSceneGraph()"); + ExpectExecute("scene = builder:GetScene()"); + ExpectExecute("nodeA = scene.graph:GetNodeChild(scene.graph:GetRoot())"); + ExpectExecute("TestExpectTrue(nodeA:IsValid())"); + ExpectExecute("TestExpectEquals(nodeA:AsNumber(), 1)"); + ExpectExecute("TestExpectEquals(scene.graph:GetRoot():Distance(nodeA), 1)"); + ExpectExecute("TestExpectEquals(nodeA:Distance(scene.graph:GetRoot()), -1)"); + ExpectExecute("TestExpectTrue(nodeA == scene.graph:FindWithPath('A'))"); + } + + TEST_F(SceneGraphBehaviorScriptTest, SceneGraphName_ScriptContext_AccessMockNodes) + { + ExpectExecute("builder = MockBuilder()"); + ExpectExecute("builder:BuildSceneGraph()"); + ExpectExecute("scene = builder:GetScene()"); + ExpectExecute("nodeG = scene.graph:FindWithPath('A.C.E.G')"); + ExpectExecute("nodeNameG = scene.graph:GetNodeName(nodeG)"); + ExpectExecute("TestExpectTrue(nodeNameG:GetPath() == 'A.C.E.G')"); + ExpectExecute("TestExpectTrue(nodeNameG:GetName() == 'G')"); + } + + TEST_F(SceneGraphBehaviorScriptTest, SceneGraphIGraphNode_ScriptContext_AccessMockNodes) + { + ExpectExecute("builder = MockBuilder()"); + ExpectExecute("builder:BuildSceneGraph()"); + ExpectExecute("scene = builder:GetScene()"); + ExpectExecute("nodeG = scene.graph:FindWithPath('A.C.E.G')"); + ExpectExecute("proxy = scene.graph:GetNodeContent(nodeG)"); + ExpectExecute("TestExpectTrue(proxy:CastWithTypeName('MockIGraphObject'))"); + ExpectExecute("value = proxy:Invoke('GetId', vector_any())"); + ExpectExecute("TestExpectEquals(value, 7)"); + ExpectExecute("setIdArgs = vector_any(); setIdArgs:push_back(8);"); + ExpectExecute("proxy:Invoke('SetId', setIdArgs)"); + ExpectExecute("value = proxy:Invoke('GetId', vector_any())"); + ExpectExecute("TestExpectEquals(value, 8)"); + ExpectExecute("addArgs = vector_any(); addArgs:push_back(8); addArgs:push_back(9)"); + ExpectExecute("proxy:Invoke('AddAndSet', addArgs)"); + ExpectExecute("value = proxy:Invoke('GetId', vector_any())"); + ExpectExecute("TestExpectEquals(value, 17)"); + } + + TEST_F(SceneGraphBehaviorScriptTest, GraphObjectProxy_GetClassInfo_Loads) + { + SetupEditorPythonConsoleInterface(); + + ExpectExecute("builder = MockBuilder()"); + ExpectExecute("builder:BuildSceneGraph()"); + ExpectExecute("scene = builder:GetScene()"); + ExpectExecute("nodeG = scene.graph:FindWithPath('A.C.E.G')"); + ExpectExecute("proxy = scene.graph:GetNodeContent(nodeG)"); + ExpectExecute("TestExpectTrue(proxy:CastWithTypeName('MockIGraphObject'))"); + ExpectExecute("info = proxy:GetClassInfo()"); + ExpectExecute("TestExpectTrue(info ~= nil)"); + } + + TEST_F(SceneGraphBehaviorScriptTest, GraphObjectProxy_GetClassInfo_CorrectFormats) + { + SetupEditorPythonConsoleInterface(); + + ExpectExecute("builder = MockBuilder()"); + ExpectExecute("builder:BuildSceneGraph()"); + ExpectExecute("scene = builder:GetScene()"); + ExpectExecute("nodeG = scene.graph:FindWithPath('A.C.E.G')"); + ExpectExecute("proxy = scene.graph:GetNodeContent(nodeG)"); + ExpectExecute("TestExpectTrue(proxy:CastWithTypeName('MockIGraphObject'))"); + ExpectExecute("info = proxy:GetClassInfo()"); + ExpectExecute("TestExpectTrue(info.className == 'MockIGraphObject')"); + ExpectExecute("TestExpectTrue(info.classUuid == '{66A082CC-851D-4E1F-ABBD-45B58A216CFA}')"); + ExpectExecute("TestExpectTrue(info.methodList[1] == 'def GetId(self) -> int')"); + ExpectExecute("TestExpectTrue(info.methodList[2] == 'def SetId(self, arg1: int) -> None')"); + ExpectExecute("TestExpectTrue(info.methodList[3] == 'def AddAndSet(self, arg1: int, arg2: int) -> None')"); + } + + TEST_F(SceneGraphBehaviorScriptTest, ExportProduct_ExpectedClassesAndFields_Work) + { + ExpectExecute("mockAssetType = Uuid.CreateString('{B7AD6A54-963F-4F0F-A70E-1CFC0364BE6B}')"); + ExpectExecute("exportProduct = ExportProduct()"); + ExpectExecute("exportProduct.filename = 'some/file.name'"); + ExpectExecute("exportProduct.sourceId = Uuid.CreateString('{A19F5FDB-C5FB-478F-A0B0-B697D2C10DB5}', 0)"); + ExpectExecute("exportProduct.assetType = mockAssetType"); + ExpectExecute("exportProduct.subId = 10101"); + ExpectExecute("TestExpectEquals(exportProduct.subId, 10101)"); + ExpectExecute("TestExpectEquals(exportProduct.productDependencies:GetSize(), 0)"); + + ExpectExecute("exportProductDep = ExportProduct()"); + ExpectExecute("exportProductDep.filename = 'some/file.dep'"); + ExpectExecute("exportProductDep.sourceId = Uuid.CreateString('{A19F5FDB-C5FB-478F-A0B0-B697D2C10DB5}', 0)"); + ExpectExecute("exportProductDep.assetType = mockAssetType"); + ExpectExecute("exportProductDep.subId = 2"); + + ExpectExecute("exportProductList = ExportProductList()"); + ExpectExecute("exportProductList:AddProduct(exportProduct)"); + ExpectExecute("exportProductList:AddProduct(exportProductDep)"); + ExpectExecute("productList = exportProductList:GetProducts()"); + ExpectExecute("TestExpectEquals(productList:GetSize(), 2)"); + ExpectExecute("exportProductList:AddDependencyToProduct(exportProduct.filename, exportProductDep)"); + ExpectExecute("TestExpectEquals(productList:Front().productDependencies:GetSize(), 1)"); + } + + // + // SceneManifestBehaviorScriptTest is meant to test the script abilities of the SceneManifest + // + class SceneManifestBehaviorScriptTest + : public UnitTest::AllocatorsFixture + { + public: + AZStd::unique_ptr m_componentApplication; + AZStd::unique_ptr m_scriptContext; + AZStd::unique_ptr m_behaviorContext; + AZStd::unique_ptr m_serializeContext; + AZStd::unique_ptr m_jsonRegistrationContext; + AZStd::string_view m_jsonMockData = R"JSON('{"values":[{"$type":"MockManifestRule","value":0.1},{"$type":"MockManifestRule","value":2.3},{"$type":"MockManifestRule","value":4.5}]}')JSON"; + + static void TestAssertTrue(bool value) + { + EXPECT_TRUE(value); + } + + void SetUp() override + { + UnitTest::AllocatorsFixture::SetUp(); + + m_serializeContext = AZStd::make_unique(); + MockBuilder::Reflect(m_serializeContext.get()); + MockManifestRule::Reflect(m_serializeContext.get()); + ReflectTypes(m_serializeContext.get()); + + m_behaviorContext = AZStd::make_unique(); + m_behaviorContext->Method("TestAssertTrue", &TestAssertTrue); + AZ::MathReflect(m_behaviorContext.get()); + MockBuilder::Reflect(m_behaviorContext.get()); + MockManifestRule::Reflect(m_behaviorContext.get()); + ReflectBehavior(m_behaviorContext.get()); + + m_jsonRegistrationContext = AZStd::make_unique(); + AZ::JsonSystemComponent::Reflect(m_jsonRegistrationContext.get()); + + m_scriptContext = AZStd::make_unique(); + m_scriptContext->BindTo(m_behaviorContext.get()); + + m_componentApplication = AZStd::make_unique<::testing::NiceMock>(); + + ON_CALL(*m_componentApplication, GetBehaviorContext()).WillByDefault(::testing::Invoke([this]() + { + return this->m_behaviorContext.get(); + })); + + ON_CALL(*m_componentApplication, GetSerializeContext()).WillByDefault(::testing::Invoke([this]() + { + return this->m_serializeContext.get(); + })); + + ON_CALL(*m_componentApplication, GetJsonRegistrationContext()).WillByDefault(::testing::Invoke([this]() + { + return this->m_jsonRegistrationContext.get(); + })); + } + + void TearDown() override + { + m_jsonRegistrationContext->EnableRemoveReflection(); + AZ::JsonSystemComponent::Reflect(m_jsonRegistrationContext.get()); + m_jsonRegistrationContext->DisableRemoveReflection(); + + m_jsonRegistrationContext.reset(); + m_serializeContext.reset(); + m_scriptContext.reset(); + m_behaviorContext.reset(); + m_componentApplication.reset(); + UnitTest::AllocatorsFixture::TearDown(); + } + + void ExpectExecute(AZStd::string_view script) + { + EXPECT_TRUE(m_scriptContext->Execute(script.data())); + } + }; + + TEST_F(SceneManifestBehaviorScriptTest, SceneManifest_ScriptContext_GetDefaultJSON) + { + ExpectExecute("builder = MockBuilder()"); + ExpectExecute("scene = builder:GetScene()"); + ExpectExecute("manifest = scene.manifest:ExportToJson()"); + ExpectExecute(R"JSON(TestAssertTrue(manifest == '{}'))JSON"); + } + + TEST_F(SceneManifestBehaviorScriptTest, SceneManifest_ScriptContext_GetComplexJSON) + { + ExpectExecute("builder = MockBuilder()"); + ExpectExecute("scene = builder:GetScene()"); + ExpectExecute("builder:BuildSceneGraph()"); + ExpectExecute("manifest = scene.manifest:ExportToJson()"); + auto read = AZStd::fixed_string<1024>::format("TestAssertTrue(manifest == %s)", m_jsonMockData.data()); + ExpectExecute(read); + } + + TEST_F(SceneManifestBehaviorScriptTest, SceneManifest_ScriptContext_SetComplexJSON) + { + ExpectExecute("builder = MockBuilder()"); + ExpectExecute("scene = builder:GetScene()"); + ExpectExecute("manifest = scene.manifest:ExportToJson()"); + ExpectExecute(R"JSON(TestAssertTrue(manifest == '{}'))JSON"); + auto load = AZStd::fixed_string<1024>::format("TestAssertTrue(scene.manifest:ImportFromJson(%s))", m_jsonMockData.data()); + ExpectExecute(load); + ExpectExecute("manifest = scene.manifest:ExportToJson()"); + auto read = AZStd::fixed_string<1024>::format("TestAssertTrue(manifest == %s)", m_jsonMockData.data()); + ExpectExecute(read); + } + +} // namespace AZ::SceneAPI::Containers diff --git a/Code/Tools/SceneAPI/SceneData/Behaviors/ScriptProcessorRuleBehavior.cpp b/Code/Tools/SceneAPI/SceneData/Behaviors/ScriptProcessorRuleBehavior.cpp index 5ae547b577..b7fda1e0dc 100644 --- a/Code/Tools/SceneAPI/SceneData/Behaviors/ScriptProcessorRuleBehavior.cpp +++ b/Code/Tools/SceneAPI/SceneData/Behaviors/ScriptProcessorRuleBehavior.cpp @@ -25,213 +25,319 @@ #include #include #include +#include -namespace AZ +namespace AZ::SceneAPI::Behaviors { - namespace SceneAPI + class EditorPythonConsoleNotificationHandler final + : protected AzToolsFramework::EditorPythonConsoleNotificationBus::Handler { - namespace Behaviors + public: + EditorPythonConsoleNotificationHandler() { - class EditorPythonConsoleNotificationHandler final - : protected AzToolsFramework::EditorPythonConsoleNotificationBus::Handler + BusConnect(); + } + + ~EditorPythonConsoleNotificationHandler() + { + BusDisconnect(); + } + + //////////////////////////////////////////////////////////////////////////////////////////// + // AzToolsFramework::EditorPythonConsoleNotifications + void OnTraceMessage([[maybe_unused]] AZStd::string_view message) override + { + using namespace AZ::SceneAPI::Utilities; + AZ_TracePrintf(LogWindow, "%.*s \n", AZ_STRING_ARG(message)); + } + + void OnErrorMessage([[maybe_unused]] AZStd::string_view message) override + { + using namespace AZ::SceneAPI::Utilities; + AZ_TracePrintf(ErrorWindow, "[ERROR] %.*s \n", AZ_STRING_ARG(message)); + } + + void OnExceptionMessage([[maybe_unused]] AZStd::string_view message) override + { + using namespace AZ::SceneAPI::Utilities; + AZ_TracePrintf(ErrorWindow, "[EXCEPTION] %.*s \n", AZ_STRING_ARG(message)); + } + }; + + using ExportProductList = AZ::SceneAPI::Events::ExportProductList; + + // a event bus to signal during scene building + struct ScriptBuildingNotifications + : public AZ::EBusTraits + { + virtual AZStd::string OnUpdateManifest(Containers::Scene& scene) = 0; + virtual ExportProductList OnPrepareForExport( + const Containers::Scene& scene, + AZStd::string_view outputDirectory, + AZStd::string_view platformIdentifier, + const ExportProductList& productList) = 0; + }; + using ScriptBuildingNotificationBus = AZ::EBus; + + // a back end to handle scene builder events for a script + struct ScriptBuildingNotificationBusHandler final + : public ScriptBuildingNotificationBus::Handler + , public AZ::BehaviorEBusHandler + { + AZ_EBUS_BEHAVIOR_BINDER( + ScriptBuildingNotificationBusHandler, + "{DF2B51DE-A4D0-4139-B5D0-DF185832380D}", + AZ::SystemAllocator, + OnUpdateManifest, + OnPrepareForExport); + + virtual ~ScriptBuildingNotificationBusHandler() = default; + + AZStd::string OnUpdateManifest(Containers::Scene& scene) override + { + AZStd::string result; + CallResult(result, FN_OnUpdateManifest, scene); + return result; + } + + ExportProductList OnPrepareForExport( + const Containers::Scene& scene, + AZStd::string_view outputDirectory, + AZStd::string_view platformIdentifier, + const ExportProductList& productList) override + { + ExportProductList result; + CallResult(result, FN_OnPrepareForExport, scene, outputDirectory, platformIdentifier, productList); + return result; + } + + static void Reflect(AZ::ReflectContext* context) + { + if (AZ::BehaviorContext* behaviorContext = azrtti_cast(context)) { - public: - EditorPythonConsoleNotificationHandler() - { - BusConnect(); - } + behaviorContext->EBus("ScriptBuildingNotificationBus") + ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation) + ->Attribute(AZ::Script::Attributes::Module, "scene") + ->Handler() + ->Event("OnUpdateManifest", &ScriptBuildingNotificationBus::Events::OnUpdateManifest) + ->Event("OnPrepareForExport", &ScriptBuildingNotificationBus::Events::OnPrepareForExport); + } + } + }; - ~EditorPythonConsoleNotificationHandler() - { - BusDisconnect(); - } + struct ScriptProcessorRuleBehavior::ExportEventHandler final + : public AZ::SceneAPI::SceneCore::ExportingComponent + { + using PreExportEventContextFunction = AZStd::function; + PreExportEventContextFunction m_preExportEventContextFunction; - //////////////////////////////////////////////////////////////////////////////////////////// - // AzToolsFramework::EditorPythonConsoleNotifications - void OnTraceMessage([[maybe_unused]] AZStd::string_view message) override - { - using namespace AZ::SceneAPI::Utilities; - AZ_TracePrintf(LogWindow, "%.*s \n", AZ_STRING_ARG(message)); - } + ExportEventHandler(PreExportEventContextFunction preExportEventContextFunction) + : m_preExportEventContextFunction(preExportEventContextFunction) + { + BindToCall(&ExportEventHandler::PrepareForExport); + AZ::SceneAPI::SceneCore::ExportingComponent::Activate(); + } - void OnErrorMessage([[maybe_unused]] AZStd::string_view message) override - { - using namespace AZ::SceneAPI::Utilities; - AZ_TracePrintf(ErrorWindow, "[ERROR] %.*s \n", AZ_STRING_ARG(message)); - } + ~ExportEventHandler() + { + AZ::SceneAPI::SceneCore::ExportingComponent::Deactivate(); + } - void OnExceptionMessage([[maybe_unused]] AZStd::string_view message) override - { - using namespace AZ::SceneAPI::Utilities; - AZ_TracePrintf(ErrorWindow, "[EXCEPTION] %.*s \n", AZ_STRING_ARG(message)); - } - }; + // this allows a Python script to add product assets on "scene export" + Events::ProcessingResult PrepareForExport(Events::PreExportEventContext& context) + { + return m_preExportEventContextFunction(context) ? Events::ProcessingResult::Success : Events::ProcessingResult::Failure; + } + }; - // a event bus to signal during scene building - struct ScriptBuildingNotifications - : public AZ::EBusTraits + void ScriptProcessorRuleBehavior::Activate() + { + Events::AssetImportRequestBus::Handler::BusConnect(); + m_exportEventHandler = AZStd::make_shared([this](Events::PreExportEventContext& context) + { + return this->DoPrepareForExport(context); + }); + } + + void ScriptProcessorRuleBehavior::Deactivate() + { + m_exportEventHandler.reset(); + Events::AssetImportRequestBus::Handler::BusDisconnect(); + UnloadPython(); + } + + bool ScriptProcessorRuleBehavior::LoadPython(const AZ::SceneAPI::Containers::Scene& scene) + { + if (m_editorPythonEventsInterface && !m_scriptFilename.empty()) + { + return true; + } + + // get project folder + auto settingsRegistry = AZ::SettingsRegistry::Get(); + AZ::IO::FixedMaxPath projectPath; + if (!settingsRegistry->Get(projectPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectPath)) + { + return false; + } + + const AZ::SceneAPI::Containers::SceneManifest& manifest = scene.GetManifest(); + auto view = Containers::MakeDerivedFilterView(manifest.GetValueStorage()); + for (const auto& scriptItem : view) + { + AZ::IO::FixedMaxPath scriptFilename(scriptItem.GetScriptFilename()); + if (scriptFilename.empty()) { - virtual AZStd::string OnUpdateManifest(Containers::Scene& scene) = 0; - }; - using ScriptBuildingNotificationBus = AZ::EBus; + AZ_Warning("scene", false, "Skipping an empty script filename in (%s)", scene.GetManifestFilename().c_str()); + continue; + } - // a back end to handle scene builder events for a script - struct ScriptBuildingNotificationBusHandler final - : public ScriptBuildingNotificationBus::Handler - , public AZ::BehaviorEBusHandler + // check for file exist via absolute path + if (!IO::FileIOBase::GetInstance()->Exists(scriptFilename.c_str())) { - AZ_EBUS_BEHAVIOR_BINDER( - ScriptBuildingNotificationBusHandler, - "{DF2B51DE-A4D0-4139-B5D0-DF185832380D}", - AZ::SystemAllocator, - OnUpdateManifest); - - virtual ~ScriptBuildingNotificationBusHandler() = default; - - AZStd::string OnUpdateManifest(Containers::Scene& scene) override + // check for script in the project folder + AZ::IO::FixedMaxPath projectScriptPath = projectPath / scriptFilename; + if (!IO::FileIOBase::GetInstance()->Exists(projectScriptPath.c_str())) { - AZStd::string result; - CallResult(result, FN_OnUpdateManifest, scene); - return result; + AZ_Warning("scene", false, "Skipping a missing script (%s) in manifest file (%s)", + scriptFilename.c_str(), + scene.GetManifestFilename().c_str()); + continue; } + scriptFilename = AZStd::move(projectScriptPath); + } - static void Reflect(AZ::ReflectContext* context) + // lazy load the Python interface + auto editorPythonEventsInterface = AZ::Interface::Get(); + if (editorPythonEventsInterface->IsPythonActive() == false) + { + const bool silenceWarnings = false; + if (editorPythonEventsInterface->StartPython(silenceWarnings) == false) { - if (AZ::BehaviorContext* behaviorContext = azrtti_cast(context)) - { - behaviorContext->EBus("ScriptBuildingNotificationBus") - ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation) - ->Attribute(AZ::Script::Attributes::Module, "scene") - ->Handler() - ->Event("OnUpdateManifest", &ScriptBuildingNotificationBus::Events::OnUpdateManifest); - } + editorPythonEventsInterface = nullptr; } + } + + // both Python and the script need to be ready + if (editorPythonEventsInterface == nullptr || scriptFilename.empty()) + { + AZ_Warning("scene", false,"The scene manifest (%s) attempted to use script(%s) but Python is not enabled;" + "please add the EditorPythonBinding gem & PythonAssetBuilder gem to your project.", + scene.GetManifestFilename().c_str(), scriptFilename.c_str()); + + return false; + } + + m_editorPythonEventsInterface = editorPythonEventsInterface; + m_scriptFilename = scriptFilename.c_str(); + return true; + } + return false; + } + + void ScriptProcessorRuleBehavior::UnloadPython() + { + if (m_editorPythonEventsInterface) + { + const bool silenceWarnings = true; + m_editorPythonEventsInterface->StopPython(silenceWarnings); + m_editorPythonEventsInterface = nullptr; + } + } + + bool ScriptProcessorRuleBehavior::DoPrepareForExport(Events::PreExportEventContext& context) + { + using namespace AzToolsFramework; + + auto executeCallback = [this, &context]() + { + // set up script's hook callback + EditorPythonRunnerRequestBus::Broadcast(&EditorPythonRunnerRequestBus::Events::ExecuteByFilename, + m_scriptFilename.c_str()); + + // call script's callback to allow extra products + ExportProductList extraProducts; + ScriptBuildingNotificationBus::BroadcastResult(extraProducts, &ScriptBuildingNotificationBus::Events::OnPrepareForExport, + context.GetScene(), + context.GetOutputDirectory(), + context.GetPlatformIdentifier(), + context.GetProductList() + ); + + // add new products + for (const auto& product : extraProducts.GetProducts()) + { + context.GetProductList().AddProduct( + product.m_filename, + product.m_id, + product.m_assetType, + product.m_lod, + product.m_subId, + product.m_dependencyFlags); + } + }; + + if (LoadPython(context.GetScene())) + { + EditorPythonConsoleNotificationHandler logger; + m_editorPythonEventsInterface->ExecuteWithLock(executeCallback); + } + + return true; + } + + void ScriptProcessorRuleBehavior::Reflect(ReflectContext* context) + { + ScriptBuildingNotificationBusHandler::Reflect(context); + + SerializeContext* serializeContext = azrtti_cast(context); + if (serializeContext) + { + serializeContext->Class()->Version(1); + } + } + + Events::ProcessingResult ScriptProcessorRuleBehavior::UpdateManifest( + Containers::Scene& scene, + Events::AssetImportRequest::ManifestAction action, + [[maybe_unused]] Events::AssetImportRequest::RequestingApplication requester) + { + using namespace AzToolsFramework; + + if (action != ManifestAction::Update) + { + return Events::ProcessingResult::Ignored; + } + + if (LoadPython(scene)) + { + AZStd::string manifestUpdate; + auto executeCallback = [this, &scene, &manifestUpdate]() + { + EditorPythonRunnerRequestBus::Broadcast(&EditorPythonRunnerRequestBus::Events::ExecuteByFilename, + m_scriptFilename.c_str()); + + ScriptBuildingNotificationBus::BroadcastResult(manifestUpdate, &ScriptBuildingNotificationBus::Events::OnUpdateManifest, + scene); }; - void ScriptProcessorRuleBehavior::Activate() + EditorPythonConsoleNotificationHandler logger; + m_editorPythonEventsInterface->ExecuteWithLock(executeCallback); + + // attempt to load the manifest string back to a JSON-scene-manifest + auto sceneManifestLoader = AZStd::make_unique(); + auto loadOutcome = sceneManifestLoader->LoadFromString(manifestUpdate); + if (loadOutcome.IsSuccess()) { - Events::AssetImportRequestBus::Handler::BusConnect(); + scene.GetManifest().Clear(); + for (size_t entryIndex = 0; entryIndex < sceneManifestLoader->GetEntryCount(); ++entryIndex) + { + scene.GetManifest().AddEntry(sceneManifestLoader->GetValue(entryIndex)); + } + return Events::ProcessingResult::Success; } + } + return Events::ProcessingResult::Ignored; + } - void ScriptProcessorRuleBehavior::Deactivate() - { - Events::AssetImportRequestBus::Handler::BusDisconnect(); - if (m_editorPythonEventsInterface) - { - const bool silenceWarnings = true; - m_editorPythonEventsInterface->StopPython(silenceWarnings); - m_editorPythonEventsInterface = nullptr; - - } - } - - void ScriptProcessorRuleBehavior::Reflect(ReflectContext* context) - { - ScriptBuildingNotificationBusHandler::Reflect(context); - - SerializeContext* serializeContext = azrtti_cast(context); - if (serializeContext) - { - serializeContext->Class()->Version(1); - } - } - - Events::ProcessingResult ScriptProcessorRuleBehavior::UpdateManifest( - Containers::Scene& scene, - Events::AssetImportRequest::ManifestAction action, - [[maybe_unused]] Events::AssetImportRequest::RequestingApplication requester) - { - using namespace AzToolsFramework; - - if (action != ManifestAction::Update) - { - return Events::ProcessingResult::Ignored; - } - - // get project folder - auto settingsRegistry = AZ::SettingsRegistry::Get(); - AZ::IO::FixedMaxPath projectPath; - if (!settingsRegistry->Get(projectPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectPath)) - { - return Events::ProcessingResult::Ignored; - } - - auto& sceneManifest = scene.GetManifest(); - auto view = Containers::MakeDerivedFilterView(sceneManifest.GetValueStorage()); - for (const auto& scriptItem : view) - { - AZ::IO::FixedMaxPath scriptFilename(scriptItem.GetScriptFilename()); - if (scriptFilename.empty()) - { - AZ_Warning("scene", false, "Skipping an empty script filename in (%s)", scene.GetManifestFilename().c_str()); - continue; - } - - // check for file exist via absolute path - if (!IO::FileIOBase::GetInstance()->Exists(scriptFilename.c_str())) - { - // check for script in the project folder - AZ::IO::FixedMaxPath projectScriptPath = projectPath / scriptFilename; - if (!IO::FileIOBase::GetInstance()->Exists(projectScriptPath.c_str())) - { - AZ_Warning("scene", false, "Skipping a missing script (%s) in manifest file (%s)", - scriptFilename.c_str(), - scene.GetManifestFilename().c_str()); - - continue; - } - scriptFilename = AZStd::move(projectScriptPath); - } - - // lazy load the Python interface - if (!m_editorPythonEventsInterface) - { - m_editorPythonEventsInterface = AZ::Interface::Get(); - const bool silenceWarnings = true; - m_editorPythonEventsInterface->StartPython(silenceWarnings); - } - - if (!m_editorPythonEventsInterface && !scriptFilename.empty()) - { - AZ_Warning("scene", false, - "The scene manifest (%s) attempted to use script(%s) but Python is not enabled;" - "please add the EditorPythonBinding gem & PythonAssetBuilder gem to your project.", - scene.GetManifestFilename().c_str(), scriptFilename.c_str()); - - return Events::ProcessingResult::Ignored; - } - - AZStd::string manifestUpdate; - auto executeCallback = [&scene, &scriptFilename, &manifestUpdate]() - { - EditorPythonRunnerRequestBus::Broadcast( - &EditorPythonRunnerRequestBus::Events::ExecuteByFilename, - scriptFilename.c_str()); - - ScriptBuildingNotificationBus::BroadcastResult( - manifestUpdate, - &ScriptBuildingNotificationBus::Events::OnUpdateManifest, - scene); - }; - EditorPythonConsoleNotificationHandler logger; - m_editorPythonEventsInterface->ExecuteWithLock(executeCallback); - - // attempt to load the manifest string back to a JSON-scene-manifest - auto sceneManifestLoader = AZStd::make_unique(); - auto loadOutcome = sceneManifestLoader->LoadFromString(manifestUpdate); - if (loadOutcome.IsSuccess()) - { - sceneManifest.Clear(); - for (size_t entryIndex = 0; entryIndex < sceneManifestLoader->GetEntryCount(); ++entryIndex) - { - sceneManifest.AddEntry(sceneManifestLoader->GetValue(entryIndex)); - } - return Events::ProcessingResult::Success; - } - } - return Events::ProcessingResult::Ignored; - } - - } // namespace Behaviors - } // namespace SceneAPI } // namespace AZ diff --git a/Code/Tools/SceneAPI/SceneData/Behaviors/ScriptProcessorRuleBehavior.h b/Code/Tools/SceneAPI/SceneData/Behaviors/ScriptProcessorRuleBehavior.h index da11614459..9dd1f5b970 100644 --- a/Code/Tools/SceneAPI/SceneData/Behaviors/ScriptProcessorRuleBehavior.h +++ b/Code/Tools/SceneAPI/SceneData/Behaviors/ScriptProcessorRuleBehavior.h @@ -11,40 +11,56 @@ #include #include #include +#include +#include namespace AzToolsFramework { class EditorPythonEventsInterface; } -namespace AZ +namespace AZ::SceneAPI::Events { - namespace SceneAPI + class PreExportEventContext; +} + +namespace AZ::SceneAPI::Containers +{ + class Scene; +} + +namespace AZ::SceneAPI::Behaviors +{ + class SCENE_DATA_CLASS ScriptProcessorRuleBehavior + : public SceneCore::BehaviorComponent + , public Events::AssetImportRequestBus::Handler { - namespace Behaviors - { - class SCENE_DATA_CLASS ScriptProcessorRuleBehavior - : public SceneCore::BehaviorComponent - , public Events::AssetImportRequestBus::Handler - { - public: - AZ_COMPONENT(ScriptProcessorRuleBehavior, "{24054E73-1B92-43B0-AC13-174B2F0E3F66}", SceneCore::BehaviorComponent); + public: + AZ_COMPONENT(ScriptProcessorRuleBehavior, "{24054E73-1B92-43B0-AC13-174B2F0E3F66}", SceneCore::BehaviorComponent); - ~ScriptProcessorRuleBehavior() override = default; + ~ScriptProcessorRuleBehavior() override = default; - SCENE_DATA_API void Activate() override; - SCENE_DATA_API void Deactivate() override; - static void Reflect(ReflectContext* context); + SCENE_DATA_API void Activate() override; + SCENE_DATA_API void Deactivate() override; + static void Reflect(ReflectContext* context); - // AssetImportRequestBus::Handler - SCENE_DATA_API Events::ProcessingResult UpdateManifest( - Containers::Scene& scene, - ManifestAction action, - RequestingApplication requester) override; + // AssetImportRequestBus::Handler + SCENE_DATA_API Events::ProcessingResult UpdateManifest( + Containers::Scene& scene, + ManifestAction action, + RequestingApplication requester) override; - private: - AzToolsFramework::EditorPythonEventsInterface* m_editorPythonEventsInterface = nullptr; - }; - } // namespace SceneData - } // namespace SceneAPI -} // namespace AZ + + protected: + bool LoadPython(const AZ::SceneAPI::Containers::Scene& scene); + void UnloadPython(); + bool DoPrepareForExport(Events::PreExportEventContext& context); + + private: + AzToolsFramework::EditorPythonEventsInterface* m_editorPythonEventsInterface = nullptr; + AZStd::string m_scriptFilename; + + struct ExportEventHandler; + AZStd::shared_ptr m_exportEventHandler; + }; +} // namespace AZ::SceneAPI::Behaviors diff --git a/Gems/Atom/RHI/Code/Include/Atom/RHI/SwapChain.h b/Gems/Atom/RHI/Code/Include/Atom/RHI/SwapChain.h index ae4c0dce57..03b7b8831f 100644 --- a/Gems/Atom/RHI/Code/Include/Atom/RHI/SwapChain.h +++ b/Gems/Atom/RHI/Code/Include/Atom/RHI/SwapChain.h @@ -123,6 +123,11 @@ namespace AZ /// Returns the index of the current image after the swap. virtual uint32_t PresentInternal() = 0; + virtual void SetVerticalSyncIntervalInternal(uint32_t previousVerticalSyncInterval) + { + AZ_UNUSED(previousVerticalSyncInterval); + } + ////////////////////////////////////////////////////////////////////////// SwapChainDescriptor m_descriptor; diff --git a/Gems/Atom/RHI/Code/Source/RHI/SwapChain.cpp b/Gems/Atom/RHI/Code/Source/RHI/SwapChain.cpp index 5d1d938fc8..d0db51ece5 100644 --- a/Gems/Atom/RHI/Code/Source/RHI/SwapChain.cpp +++ b/Gems/Atom/RHI/Code/Source/RHI/SwapChain.cpp @@ -168,7 +168,11 @@ namespace AZ void SwapChain::SetVerticalSyncInterval(uint32_t verticalSyncInterval) { + uint32_t previousVsyncInterval = m_descriptor.m_verticalSyncInterval; + m_descriptor.m_verticalSyncInterval = verticalSyncInterval; + + SetVerticalSyncIntervalInternal(previousVsyncInterval); } const AttachmentId& SwapChain::GetAttachmentId() const diff --git a/Gems/Atom/RHI/Vulkan/Code/Source/RHI/Image.cpp b/Gems/Atom/RHI/Vulkan/Code/Source/RHI/Image.cpp index 7586183b63..61149c19f7 100644 --- a/Gems/Atom/RHI/Vulkan/Code/Source/RHI/Image.cpp +++ b/Gems/Atom/RHI/Vulkan/Code/Source/RHI/Image.cpp @@ -218,7 +218,8 @@ namespace AZ createInfo.extent = extent; createInfo.mipLevels = AZStd::min(descriptor.m_mipLevels, formatProps.maxMipLevels); createInfo.arrayLayers = AZStd::min(descriptor.m_arraySize, formatProps.maxArrayLayers); - createInfo.samples = static_cast(RHI::FilterBits(static_cast(ConvertSampleCount(descriptor.m_multisampleState.m_samples)), formatProps.sampleCounts)); + VkSampleCountFlagBits sampleCountFlagBits = static_cast(RHI::FilterBits(static_cast(ConvertSampleCount(descriptor.m_multisampleState.m_samples)), formatProps.sampleCounts)); + createInfo.samples = (static_cast(sampleCountFlagBits) > 0) ? sampleCountFlagBits : VK_SAMPLE_COUNT_1_BIT; createInfo.tiling = VK_IMAGE_TILING_OPTIMAL; createInfo.usage = GetImageUsageFlags(); createInfo.sharingMode = exclusiveOwnership ? VK_SHARING_MODE_EXCLUSIVE : VK_SHARING_MODE_CONCURRENT; diff --git a/Gems/Atom/RHI/Vulkan/Code/Source/RHI/SwapChain.cpp b/Gems/Atom/RHI/Vulkan/Code/Source/RHI/SwapChain.cpp index d356d61cef..0935e1acd8 100644 --- a/Gems/Atom/RHI/Vulkan/Code/Source/RHI/SwapChain.cpp +++ b/Gems/Atom/RHI/Vulkan/Code/Source/RHI/SwapChain.cpp @@ -56,6 +56,18 @@ namespace AZ m_swapChainBarrier.m_isValid = true; } + void SwapChain::SetVerticalSyncIntervalInternal(uint32_t previousVsyncInterval) + { + uint32_t verticalSyncInterval = GetDescriptor().m_verticalSyncInterval; + if (verticalSyncInterval == 0 || previousVsyncInterval == 0) + { + // The presentation mode may change when transitioning to or from a vsynced presentation mode + // In this case, the swapchain must be recreated. + InvalidateNativeSwapChain(); + BuildNativeSwapChain(GetDescriptor().m_dimensions, verticalSyncInterval); + } + } + void SwapChain::SetNameInternal(const AZStd::string_view& name) { if (IsInitialized() && !name.empty()) @@ -84,7 +96,7 @@ namespace AZ auto& presentationQueue = device.GetCommandQueueContext().GetOrCreatePresentationCommandQueue(*this); m_presentationQueue = &presentationQueue; - result = BuildNativeSwapChain(swapchainDimensions); + result = BuildNativeSwapChain(swapchainDimensions, descriptor.m_verticalSyncInterval); RETURN_RESULT_IF_UNSUCCESSFUL(result); uint32_t imageCount = 0; VkResult vkResult = vkGetSwapchainImagesKHR(device.GetNativeDevice(), m_nativeSwapChain, &imageCount, nullptr); @@ -166,7 +178,7 @@ namespace AZ auto& presentationQueue = device.GetCommandQueueContext().GetOrCreatePresentationCommandQueue(*this); m_presentationQueue = &presentationQueue; - BuildNativeSwapChain(resizeDimensions); + BuildNativeSwapChain(resizeDimensions, GetDescriptor().m_verticalSyncInterval); resizeDimensions.m_imageCount = 0; VkResult vkResult = vkGetSwapchainImagesKHR(device.GetNativeDevice(), m_nativeSwapChain, &resizeDimensions.m_imageCount, nullptr); @@ -256,7 +268,8 @@ namespace AZ info.pImageIndices = &imageIndex; info.pResults = nullptr; - const VkResult result = vkQueuePresentKHR(vulkanQueue->GetNativeQueue(), &info); + VkResult result = vkQueuePresentKHR(vulkanQueue->GetNativeQueue(), &info); + // Resizing window cause recreation of SwapChain after calling this method, // so VK_SUBOPTIMAL_KHR or VK_ERROR_OUT_OF_DATE_KHR should not happen at this point. AZ_Assert(result == VK_SUCCESS || result == VK_SUBOPTIMAL_KHR, "Failed to present swapchain %s", GetName().GetCStr()); @@ -321,9 +334,17 @@ namespace AZ return surfaceFormats[0]; } - VkPresentModeKHR SwapChain::GetSupportedPresentMode() const + VkPresentModeKHR SwapChain::GetSupportedPresentMode(uint32_t verticalSyncInterval) const { AZ_Assert(m_surface, "Surface has not been initialized."); + + if (verticalSyncInterval > 0) + { + // When a non-zero vsync interval is requested, the FIFO presentation mode (always available) + // is usable without needing to query available presentation modes. + return VK_PRESENT_MODE_FIFO_KHR; + } + auto& device = static_cast(GetDevice()); const auto& physicalDevice = static_cast(device.GetPhysicalDevice()); @@ -335,12 +356,12 @@ namespace AZ AZStd::vector supportedModes(modeCount); AssertSuccess(vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice.GetNativePhysicalDevice(), m_surface->GetNativeSurface(), &modeCount, supportedModes.data())); - VkPresentModeKHR preferedModes[] = {VK_PRESENT_MODE_IMMEDIATE_KHR, VK_PRESENT_MODE_MAILBOX_KHR}; - for (VkPresentModeKHR preferedMode : preferedModes) + VkPresentModeKHR preferredModes[] = {VK_PRESENT_MODE_IMMEDIATE_KHR, VK_PRESENT_MODE_MAILBOX_KHR}; + for (VkPresentModeKHR preferredMode : preferredModes) { for (VkPresentModeKHR supportedMode : supportedModes) { - if (supportedMode == preferedMode) + if (supportedMode == preferredMode) { return supportedMode; } @@ -370,7 +391,7 @@ namespace AZ return VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; } - RHI::ResultCode SwapChain::BuildNativeSwapChain(const RHI::SwapChainDimensions& dimensions) + RHI::ResultCode SwapChain::BuildNativeSwapChain(const RHI::SwapChainDimensions& dimensions, uint32_t verticalSyncInterval) { AZ_Assert(m_nativeSwapChain == VK_NULL_HANDLE, "Vulkan's native SwapChain has been initialized already."); auto& device = static_cast(GetDevice()); @@ -421,7 +442,7 @@ namespace AZ createInfo.pQueueFamilyIndices = familyIndices.empty() ? nullptr : familyIndices.data(); createInfo.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; createInfo.compositeAlpha = GetSupportedCompositeAlpha(); - createInfo.presentMode = GetSupportedPresentMode(); + createInfo.presentMode = GetSupportedPresentMode(verticalSyncInterval); createInfo.clipped = VK_FALSE; createInfo.oldSwapchain = VK_NULL_HANDLE; @@ -442,6 +463,7 @@ namespace AZ imageAvailableSemaphore->GetNativeSemaphore(), VK_NULL_HANDLE, acquiredImageIndex); + // Resizing window cause recreation of SwapChain before calling this method, // so VK_SUBOPTIMAL_KHR or VK_ERROR_OUT_OF_DATE_KHR should not happen. AssertSuccess(vkResult); diff --git a/Gems/Atom/RHI/Vulkan/Code/Source/RHI/SwapChain.h b/Gems/Atom/RHI/Vulkan/Code/Source/RHI/SwapChain.h index b77c305e46..475375a5b3 100644 --- a/Gems/Atom/RHI/Vulkan/Code/Source/RHI/SwapChain.h +++ b/Gems/Atom/RHI/Vulkan/Code/Source/RHI/SwapChain.h @@ -49,7 +49,7 @@ namespace AZ const CommandQueue& GetPresentationQueue() const; void QueueBarrier(const VkPipelineStageFlags src, const VkPipelineStageFlags dst, const VkImageMemoryBarrier& imageBarrier); - + private: SwapChain() = default; @@ -65,14 +65,15 @@ namespace AZ RHI::ResultCode InitImageInternal(const RHI::SwapChain::InitImageRequest& request) override; RHI::ResultCode ResizeInternal(const RHI::SwapChainDimensions& dimensions, RHI::SwapChainDimensions* nativeDimensions) override; uint32_t PresentInternal() override; + void SetVerticalSyncIntervalInternal(uint32_t previousVsyncInterval) override; ////////////////////////////////////////////////////////////////////// RHI::ResultCode BuildSurface(const RHI::SwapChainDescriptor& descriptor); bool ValidateSurfaceDimensions(const RHI::SwapChainDimensions& dimensions); VkSurfaceFormatKHR GetSupportedSurfaceFormat(const RHI::Format format) const; - VkPresentModeKHR GetSupportedPresentMode() const; + VkPresentModeKHR GetSupportedPresentMode(uint32_t verticalSyncInterval) const; VkCompositeAlphaFlagBitsKHR GetSupportedCompositeAlpha() const; - RHI::ResultCode BuildNativeSwapChain(const RHI::SwapChainDimensions& dimensions); + RHI::ResultCode BuildNativeSwapChain(const RHI::SwapChainDimensions& dimensions, uint32_t verticalSyncInterval); RHI::ResultCode AcquireNewImage(uint32_t* acquiredImageIndex); void InvalidateSurface(); diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/WindowContext.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/WindowContext.h index fc809c7d39..9f2f40fbbe 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/WindowContext.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/WindowContext.h @@ -71,6 +71,7 @@ namespace AZ // WindowNotificationBus::Handler overrides ... void OnWindowResized(uint32_t width, uint32_t height) override; void OnWindowClosed() override; + void OnVsyncIntervalChanged(uint32_t interval) override; // ExclusiveFullScreenRequestBus::Handler overrides ... bool IsExclusiveFullScreenPreferred() const override; diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/GpuQuery/GpuQuerySystem.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/GpuQuery/GpuQuerySystem.cpp index bf3107cfdf..5d621094ea 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/GpuQuery/GpuQuerySystem.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/GpuQuery/GpuQuerySystem.cpp @@ -116,7 +116,7 @@ namespace AZ { AZ_Assert(IsQueryTypeValid(queryType), "Provided QueryType is invalid"); - return static_cast(m_queryTypeSupport) & static_cast(queryType); + return static_cast(m_queryTypeSupport) & AZ_BIT(static_cast(queryType)); } RPI::QueryPool* GpuQuerySystem::GetQueryPoolByType(RHI::QueryType queryType) diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/WindowContext.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/WindowContext.cpp index 7a5982c10d..16ad7a1155 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/WindowContext.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/WindowContext.cpp @@ -13,6 +13,22 @@ #include +#include +#include + + +void OnVsyncIntervalChanged(uint32_t const& interval) +{ + AzFramework::WindowNotificationBus::Broadcast( + &AzFramework::WindowNotificationBus::Events::OnVsyncIntervalChanged, + AZ::GetClamp(interval, 0u, 4u)); +} + +// NOTE: On change, broadcasts the new requested vsync interval to all windows. +// The value of the vsync interval is constrained between 0 and 4 +// Vsync intervals greater than 1 are not currently supported on the Vulkan RHI (see #2061 for discussion) +AZ_CVAR(uint32_t, rpi_vsync_interval, 0, OnVsyncIntervalChanged, AZ::ConsoleFunctorFlags::Null, "Set swapchain vsync interval"); + namespace AZ { namespace RPI @@ -103,6 +119,14 @@ namespace AZ AzFramework::WindowNotificationBus::Handler::BusDisconnect(m_windowHandle); } + void WindowContext::OnVsyncIntervalChanged(uint32_t interval) + { + if (m_swapChain->GetDescriptor().m_verticalSyncInterval != interval) + { + m_swapChain->SetVerticalSyncInterval(interval); + } + } + bool WindowContext::IsExclusiveFullScreenPreferred() const { return m_swapChain->IsExclusiveFullScreenPreferred(); @@ -135,7 +159,7 @@ namespace AZ RHI::SwapChainDescriptor descriptor; descriptor.m_window = windowHandle; - descriptor.m_verticalSyncInterval = 0; + descriptor.m_verticalSyncInterval = rpi_vsync_interval; descriptor.m_dimensions.m_imageWidth = width; descriptor.m_dimensions.m_imageHeight = height; descriptor.m_dimensions.m_imageCount = 3; diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/RenderViewportWidget.h b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/RenderViewportWidget.h index d8171a83a1..99a3ea4840 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/RenderViewportWidget.h +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/RenderViewportWidget.h @@ -109,7 +109,6 @@ namespace AtomToolsFramework void BeginCursorCapture() override; void EndCursorCapture() override; AzFramework::ScreenPoint ViewportCursorScreenPosition() override; - AZStd::optional PreviousViewportCursorScreenPosition() override; bool IsMouseOver() const override; // AzFramework::WindowRequestBus::Handler ... @@ -160,8 +159,6 @@ namespace AtomToolsFramework AZ::ScriptTimePoint m_time; // Whether the Viewport is currently hiding and capturing the cursor position. bool m_capturingCursor = false; - // The last known position of the mouse cursor, if one is available. - AZStd::optional m_lastCursorPosition; // The viewport settings (e.g. grid snapping, grid size) for this viewport. const AzToolsFramework::ViewportInteraction::ViewportSettings* m_viewportSettings = nullptr; // Maps our internal Qt events into AzFramework InputChannels for our ViewportControllerList. diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/RenderViewportWidget.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/RenderViewportWidget.cpp index 6ab96bbebb..ff10b49483 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/RenderViewportWidget.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/RenderViewportWidget.cpp @@ -16,7 +16,6 @@ #include #include #include -#include #include #include @@ -234,18 +233,6 @@ namespace AtomToolsFramework void RenderViewportWidget::mouseMoveEvent(QMouseEvent* event) { m_mousePosition = event->localPos(); - - if (m_capturingCursor && m_lastCursorPosition.has_value()) - { - AzQtComponents::SetCursorPos(m_lastCursorPosition.value()); - // Even though we just set the cursor position, there are edge cases such as remote desktop that will leave - // the cursor position unchanged. For safety, we re-cache our last cursor position for delta generation. - m_lastCursorPosition = QCursor::pos(); - } - else - { - m_lastCursorPosition = event->globalPos(); - } } void RenderViewportWidget::SendWindowResizeEvent() @@ -420,13 +407,6 @@ namespace AtomToolsFramework return AzToolsFramework::ViewportInteraction::ScreenPointFromQPoint(m_mousePosition.toPoint()); } - AZStd::optional RenderViewportWidget::PreviousViewportCursorScreenPosition() - { - using AzToolsFramework::ViewportInteraction::ScreenPointFromQPoint; - return m_lastCursorPosition.has_value() ? ScreenPointFromQPoint(mapFromGlobal(m_lastCursorPosition.value())) - : AZStd::optional{}; - } - bool RenderViewportWidget::IsMouseOver() const { return m_mouseOver; @@ -434,24 +414,12 @@ namespace AtomToolsFramework void RenderViewportWidget::BeginCursorCapture() { - if (m_capturingCursor) - { - return; - } - - qApp->setOverrideCursor(Qt::BlankCursor); - m_capturingCursor = true; + m_inputChannelMapper->SetCursorCaptureEnabled(true); } void RenderViewportWidget::EndCursorCapture() { - if (!m_capturingCursor) - { - return; - } - - qApp->restoreOverrideCursor(); - m_capturingCursor = false; + m_inputChannelMapper->SetCursorCaptureEnabled(false); } void RenderViewportWidget::SetWindowTitle(const AZStd::string& title) diff --git a/Gems/EMotionFX/Code/EMotionFX/Source/MotionData/NonUniformMotionData.cpp b/Gems/EMotionFX/Code/EMotionFX/Source/MotionData/NonUniformMotionData.cpp index f9e0911d51..5c530e35e3 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Source/MotionData/NonUniformMotionData.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Source/MotionData/NonUniformMotionData.cpp @@ -412,25 +412,24 @@ namespace EMotionFX void NonUniformMotionData::UpdateDuration() { + m_duration = 0.0f; + for (const JointData& jointData : m_jointData) { if (!jointData.m_positionTrack.m_times.empty()) { - m_duration = jointData.m_positionTrack.m_times.back(); - return; + m_duration = AZ::GetMax(m_duration, jointData.m_positionTrack.m_times.back()); } if (!jointData.m_rotationTrack.m_times.empty()) { - m_duration = jointData.m_rotationTrack.m_times.back(); - return; + m_duration = AZ::GetMax(m_duration, jointData.m_rotationTrack.m_times.back()); } #ifndef EMFX_SCALE_DISABLED if (!jointData.m_scaleTrack.m_times.empty()) { - m_duration = jointData.m_scaleTrack.m_times.back(); - return; + m_duration = AZ::GetMax(m_duration, jointData.m_scaleTrack.m_times.back()); } #endif } @@ -439,8 +438,7 @@ namespace EMotionFX { if (!morphData.m_track.m_times.empty()) { - m_duration = morphData.m_track.m_times.back(); - return; + m_duration = AZ::GetMax(m_duration, morphData.m_track.m_times.back()); } } @@ -448,12 +446,9 @@ namespace EMotionFX { if (!floatData.m_track.m_times.empty()) { - m_duration = floatData.m_track.m_times.back(); - return; + m_duration = AZ::GetMax(m_duration, floatData.m_track.m_times.back()); } } - - m_duration = 0.0f; } void NonUniformMotionData::AllocateJointPositionSamples(size_t jointDataIndex, size_t numSamples) diff --git a/Gems/EditorPythonBindings/Code/Source/PythonSystemComponent.cpp b/Gems/EditorPythonBindings/Code/Source/PythonSystemComponent.cpp index a2241b9953..a9e344f4ee 100644 --- a/Gems/EditorPythonBindings/Code/Source/PythonSystemComponent.cpp +++ b/Gems/EditorPythonBindings/Code/Source/PythonSystemComponent.cpp @@ -237,8 +237,8 @@ namespace EditorPythonBindings { ec->Class("PythonSystemComponent", "The Python interpreter") ->ClassElement(AZ::Edit::ClassElements::EditorData, "") - ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("System")) - ->Attribute(AZ::Edit::Attributes::AutoExpand, true) + ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("System")) + ->Attribute(AZ::Edit::Attributes::AutoExpand, true) ; } } @@ -284,10 +284,10 @@ namespace EditorPythonBindings ReleaseFunction m_releaseFunction; }; ReleaseInitalizeWaiterScope scope([this]() - { - m_initalizeWaiter.release(m_initalizeWaiterCount); - m_initalizeWaiterCount = 0; - }); + { + m_initalizeWaiter.release(m_initalizeWaiterCount); + m_initalizeWaiterCount = 0; + }); if (Py_IsInitialized()) { @@ -327,6 +327,11 @@ namespace EditorPythonBindings return result; } + bool PythonSystemComponent::IsPythonActive() + { + return Py_IsInitialized() != 0; + } + void PythonSystemComponent::WaitForInitialization() { m_initalizeWaiterCount++; diff --git a/Gems/EditorPythonBindings/Code/Source/PythonSystemComponent.h b/Gems/EditorPythonBindings/Code/Source/PythonSystemComponent.h index 82f44e674d..8e616ff85b 100644 --- a/Gems/EditorPythonBindings/Code/Source/PythonSystemComponent.h +++ b/Gems/EditorPythonBindings/Code/Source/PythonSystemComponent.h @@ -44,6 +44,7 @@ namespace EditorPythonBindings // AzToolsFramework::EditorPythonEventsInterface bool StartPython(bool silenceWarnings = false) override; bool StopPython(bool silenceWarnings = false) override; + bool IsPythonActive() override; void WaitForInitialization() override; void ExecuteWithLock(AZStd::function executionCallback) override; ////////////////////////////////////////////////////////////////////////