diff --git a/AutomatedTesting/Editor/Scripts/scene_mesh_to_prefab.py b/AutomatedTesting/Editor/Scripts/scene_mesh_to_prefab.py new file mode 100644 index 0000000000..d5a7acfe91 --- /dev/null +++ b/AutomatedTesting/Editor/Scripts/scene_mesh_to_prefab.py @@ -0,0 +1,162 @@ +# +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# +import os, traceback, binascii, sys, json, pathlib +import azlmbr.math +import azlmbr.bus + +# +# SceneAPI Processor +# + + +def log_exception_traceback(): + exc_type, exc_value, exc_tb = sys.exc_info() + data = traceback.format_exception(exc_type, exc_value, exc_tb) + print(str(data)) + +def get_mesh_node_names(sceneGraph): + import azlmbr.scene as sceneApi + import azlmbr.scene.graph + from scene_api import scene_data as sceneData + + meshDataList = [] + node = sceneGraph.get_root() + children = [] + paths = [] + + while node.IsValid(): + # store children to process after siblings + if sceneGraph.has_node_child(node): + children.append(sceneGraph.get_node_child(node)) + + nodeName = sceneData.SceneGraphName(sceneGraph.get_node_name(node)) + paths.append(nodeName.get_path()) + + # store any node that has mesh data content + nodeContent = sceneGraph.get_node_content(node) + if nodeContent.CastWithTypeName('MeshData'): + if sceneGraph.is_node_end_point(node) is False: + if (len(nodeName.get_path())): + meshDataList.append(sceneData.SceneGraphName(sceneGraph.get_node_name(node))) + + # advance to next node + if sceneGraph.has_node_sibling(node): + node = sceneGraph.get_node_sibling(node) + elif children: + node = children.pop() + else: + node = azlmbr.scene.graph.NodeIndex() + + return meshDataList, paths + +def update_manifest(scene): + import json + import uuid, os + import azlmbr.scene as sceneApi + import azlmbr.scene.graph + from scene_api import scene_data as sceneData + + graph = sceneData.SceneGraph(scene.graph) + # Get a list of all the mesh nodes, as well as all the nodes + mesh_name_list, all_node_paths = get_mesh_node_names(graph) + scene_manifest = sceneData.SceneManifest() + + clean_filename = scene.sourceFilename.replace('.', '_') + + # Compute the filename of the scene file + source_basepath = scene.watchFolder + source_relative_path = os.path.dirname(os.path.relpath(clean_filename, source_basepath)) + source_filename_only = os.path.basename(clean_filename) + + created_entities = [] + + # Loop every mesh node in the scene + for activeMeshIndex in range(len(mesh_name_list)): + mesh_name = mesh_name_list[activeMeshIndex] + mesh_path = mesh_name.get_path() + # Create a unique mesh group name using the filename + node name + mesh_group_name = '{}_{}'.format(source_filename_only, mesh_name.get_name()) + # Remove forbidden filename characters from the name since this will become a file on disk later + mesh_group_name = "".join(char for char in mesh_group_name if char not in "|<>:\"/?*\\") + # Add the MeshGroup to the manifest and give it a unique ID + mesh_group = scene_manifest.add_mesh_group(mesh_group_name) + mesh_group['id'] = '{' + str(uuid.uuid5(uuid.NAMESPACE_DNS, source_filename_only + mesh_path)) + '}' + # Set our current node as the only node that is included in this MeshGroup + scene_manifest.mesh_group_select_node(mesh_group, mesh_path) + + # Explicitly remove all other nodes to prevent implicit inclusions + for node in all_node_paths: + if node != mesh_path: + scene_manifest.mesh_group_unselect_node(mesh_group, node) + + # Create an editor entity + entity_id = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "CreateEditorReadyEntity", mesh_group_name) + # Add an EditorMeshComponent to the entity + editor_mesh_component = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "GetOrAddComponentByTypeName", entity_id, "AZ::Render::EditorMeshComponent") + # Set the ModelAsset assetHint to the relative path of the input asset + the name of the MeshGroup we just created + the azmodel extension + # The MeshGroup we created will be output as a product in the asset's path named mesh_group_name.azmodel + # The assetHint will be converted to an AssetId later during prefab loading + json_update = json.dumps({ + "Controller": { "Configuration": { "ModelAsset": { + "assetHint": os.path.join(source_relative_path, mesh_group_name) + ".azmodel" }}} + }); + # Apply the JSON above to the component we created + result = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "UpdateComponentForEntity", entity_id, editor_mesh_component, json_update) + + if not result: + raise RuntimeError("UpdateComponentForEntity failed") + + # Keep track of the entity we set up, we'll add them all to the prefab we're creating later + created_entities.append(entity_id) + + # Create a prefab with all our entities + prefab_filename = source_filename_only + ".prefab" + created_template_id = azlmbr.prefab.PrefabSystemScriptingBus(azlmbr.bus.Broadcast, "CreatePrefab", created_entities, prefab_filename) + + if created_template_id == azlmbr.prefab.InvalidTemplateId: + raise RuntimeError("CreatePrefab {} failed".format(prefab_filename)) + + # Convert the prefab to a JSON string + output = azlmbr.prefab.PrefabLoaderScriptingBus(azlmbr.bus.Broadcast, "SaveTemplateToString", created_template_id) + + if output.IsSuccess(): + jsonString = output.GetValue() + uuid = azlmbr.math.Uuid_CreateRandom().ToString() + jsonResult = json.loads(jsonString) + # Add a PrefabGroup to the manifest and store the JSON on it + scene_manifest.add_prefab_group(source_filename_only, uuid, jsonResult) + else: + raise RuntimeError("SaveTemplateToString failed for template id {}, prefab {}".format(created_template_id, prefab_filename)) + + # Convert the manifest to a JSON string and return it + new_manifest = scene_manifest.export() + + return new_manifest + +sceneJobHandler = None + +def on_update_manifest(args): + try: + scene = args[0] + return update_manifest(scene) + except RuntimeError as err: + print (f'ERROR - {err}') + log_exception_traceback() + + global sceneJobHandler + sceneJobHandler = None + +# try to create SceneAPI handler for processing +try: + import azlmbr.scene as sceneApi + if (sceneJobHandler == None): + sceneJobHandler = sceneApi.ScriptBuildingNotificationBusHandler() + sceneJobHandler.connect() + sceneJobHandler.add_callback('OnUpdateManifest', on_update_manifest) +except: + sceneJobHandler = None diff --git a/AutomatedTesting/Gem/Code/enabled_gems.cmake b/AutomatedTesting/Gem/Code/enabled_gems.cmake index 0d281661b9..30740a489d 100644 --- a/AutomatedTesting/Gem/Code/enabled_gems.cmake +++ b/AutomatedTesting/Gem/Code/enabled_gems.cmake @@ -21,7 +21,6 @@ set(ENABLED_GEMS QtForPython PythonAssetBuilder Metastream - Camera EMotionFX AtomTressFX @@ -53,6 +52,6 @@ set(ENABLED_GEMS AWSCore AWSClientAuth AWSMetrics - + PrefabBuilder AudioSystem ) diff --git a/AutomatedTesting/Gem/PythonTests/Atom/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/Atom/CMakeLists.txt index 0d6fe4c8fa..ff3cd5c465 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/CMakeLists.txt +++ b/AutomatedTesting/Gem/PythonTests/Atom/CMakeLists.txt @@ -6,12 +6,7 @@ # # -################################################################################ -# Atom Renderer: Automated Tests -# Runs EditorPythonBindings (hydra) scripts inside the Editor to verify test results for the Atom renderer. -################################################################################ - -if(PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_BUILD_TESTS_SUPPORTED AND AutomatedTesting IN_LIST LY_PROJECTS) +if(PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_BUILD_TESTS_SUPPORTED) ly_add_pytest( NAME AutomatedTesting::Atom_TestSuite_Main TEST_SUITE main diff --git a/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main.py b/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main.py index 4c5716d365..b148ffdcdb 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main.py @@ -25,6 +25,7 @@ TEST_DIRECTORY = os.path.join(os.path.dirname(__file__), "tests") class TestAtomEditorComponentsMain(object): """Holds tests for Atom components.""" + @pytest.mark.xfail(reason="This test is being marked xfail as it failed during an unrelated development run. See LYN-7530 for more details.") def test_AtomEditorComponents_AddedToEntity(self, request, editor, level, workspace, project, launcher_platform): """ Please review the hydra script run by this test for more specific test info. diff --git a/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main_GPU.py b/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main_GPU.py index 3403d8e9b1..220623af8b 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main_GPU.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main_GPU.py @@ -220,20 +220,18 @@ class TestPerformanceBenchmarkSuite(object): @pytest.mark.system class TestMaterialEditor(object): - @pytest.mark.parametrize("cfg_args", ["-rhi=dx12", "-rhi=Vulkan"]) + @pytest.mark.parametrize("cfg_args,expected_lines", [ + pytest.param("-rhi=dx12", ["Registering dx12 RHI"]), + pytest.param("-rhi=Vulkan", ["Registering vulkan RHI"]) + ]) @pytest.mark.parametrize("exe_file_name", ["MaterialEditor"]) def test_MaterialEditorLaunch_AllRHIOptionsSucceed( - self, request, workspace, project, launcher_platform, generic_launcher, exe_file_name, cfg_args): + self, request, workspace, project, launcher_platform, generic_launcher, exe_file_name, cfg_args, + expected_lines): """ Tests each valid RHI option (Null RHI excluded) can be launched with the MaterialEditor. - Checks for the "Finished loading viewport configurations." success message post launch. + Checks for the specific expected_lines messaging for each RHI type. """ - expected_lines = ["Finished loading viewport configurations."] - unexpected_lines = [ - # "Trace::Assert", - # "Trace::Error", - "Traceback (most recent call last):", - ] hydra.launch_and_validate_results( request, @@ -243,7 +241,7 @@ class TestMaterialEditor(object): run_python="--runpython", timeout=60, expected_lines=expected_lines, - unexpected_lines=unexpected_lines, + unexpected_lines=[], halt_on_unexpected=False, null_renderer=False, cfg_args=[cfg_args], diff --git a/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main_Optimized.py b/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main_Optimized.py index 47b2204d56..90436fbe27 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main_Optimized.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main_Optimized.py @@ -14,33 +14,50 @@ from ly_test_tools.o3de.editor_test import EditorSharedTest, EditorTestSuite @pytest.mark.parametrize("launcher_platform", ['windows_editor']) class TestAutomation(EditorTestSuite): + @pytest.mark.test_case_id("C32078118") class AtomEditorComponents_DecalAdded(EditorSharedTest): from Atom.tests import hydra_AtomEditorComponents_DecalAdded as test_module + @pytest.mark.test_case_id("C32078119") class AtomEditorComponents_DepthOfFieldAdded(EditorSharedTest): from Atom.tests import hydra_AtomEditorComponents_DepthOfFieldAdded as test_module + @pytest.mark.test_case_id("C32078120") class AtomEditorComponents_DirectionalLightAdded(EditorSharedTest): from Atom.tests import hydra_AtomEditorComponents_DirectionalLightAdded as test_module + @pytest.mark.test_case_id("C32078121") class AtomEditorComponents_ExposureControlAdded(EditorSharedTest): from Atom.tests import hydra_AtomEditorComponents_ExposureControlAdded as test_module + @pytest.mark.test_case_id("C32078115") class AtomEditorComponents_GlobalSkylightIBLAdded(EditorSharedTest): from Atom.tests import hydra_AtomEditorComponents_GlobalSkylightIBLAdded as test_module + @pytest.mark.test_case_id("C32078125") class AtomEditorComponents_PhysicalSkyAdded(EditorSharedTest): from Atom.tests import hydra_AtomEditorComponents_PhysicalSkyAdded as test_module + @pytest.mark.test_case_id("C32078131") class AtomEditorComponents_PostFXRadiusWeightModifierAdded(EditorSharedTest): from Atom.tests import ( hydra_AtomEditorComponents_PostFXRadiusWeightModifierAdded as test_module) + @pytest.mark.test_case_id("C32078117") class AtomEditorComponents_LightAdded(EditorSharedTest): from Atom.tests import hydra_AtomEditorComponents_LightAdded as test_module + @pytest.mark.test_case_id("C36525660") class AtomEditorComponents_DisplayMapperAdded(EditorSharedTest): from Atom.tests import hydra_AtomEditorComponents_DisplayMapperAdded as test_module + @pytest.mark.test_case_id("C32078128") + class AtomEditorComponents_ReflectionProbeAdded(EditorSharedTest): + from Atom.tests import hydra_AtomEditorComponents_ReflectionProbeAdded as test_module + + @pytest.mark.test_case_id("C32078124") + class AtomEditorComponents_MeshAdded(EditorSharedTest): + from Atom.tests import hydra_AtomEditorComponents_MeshAdded as test_module + class ShaderAssetBuilder_RecompilesShaderAsChainOfDependenciesChanges(EditorSharedTest): from Atom.tests import hydra_ShaderAssetBuilder_RecompilesShaderAsChainOfDependenciesChanges as test_module diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_MeshAdded.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_MeshAdded.py new file mode 100644 index 0000000000..fbf5c987d1 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_MeshAdded.py @@ -0,0 +1,171 @@ +""" +Copyright (c) Contributors to the Open 3D Engine Project. +For complete copyright and license terms please see the LICENSE at the root of this distribution. + +SPDX-License-Identifier: Apache-2.0 OR MIT +""" + +class Tests: + creation_undo = ( + "UNDO Entity creation success", + "UNDO Entity creation failed") + creation_redo = ( + "REDO Entity creation success", + "REDO Entity creation failed") + mesh_entity_creation = ( + "Mesh Entity successfully created", + "Mesh Entity failed to be created") + mesh_component_added = ( + "Entity has a Mesh component", + "Entity failed to find Mesh component") + mesh_asset_specified = ( + "Mesh asset set", + "Mesh asset not set") + enter_game_mode = ( + "Entered game mode", + "Failed to enter game mode") + exit_game_mode = ( + "Exited game mode", + "Couldn't exit game mode") + is_visible = ( + "Entity is visible", + "Entity was not visible") + is_hidden = ( + "Entity is hidden", + "Entity was not hidden") + entity_deleted = ( + "Entity deleted", + "Entity was not deleted") + deletion_undo = ( + "UNDO deletion success", + "UNDO deletion failed") + deletion_redo = ( + "REDO deletion success", + "REDO deletion failed") + + +def AtomEditorComponents_Mesh_AddedToEntity(): + """ + Summary: + Tests the Mesh component can be added to an entity and has the expected functionality. + + Test setup: + - Wait for Editor idle loop. + - Open the "Base" level. + + Expected Behavior: + The component can be added, used in game mode, hidden/shown, deleted, and has accurate required components. + Creation and deletion undo/redo should also work. + + Test Steps: + 1) Create a Mesh entity with no components. + 2) Add a Mesh component to Mesh entity. + 3) UNDO the entity creation and component addition. + 4) REDO the entity creation and component addition. + 5) Specify the Mesh component asset + 6) Enter/Exit game mode. + 7) Test IsHidden. + 8) Test IsVisible. + 9) Delete Mesh entity. + 10) UNDO deletion. + 11) REDO deletion. + 12) Look for errors. + + :return: None + """ + + import os + + import azlmbr.legacy.general as general + + from editor_python_test_tools.asset_utils import Asset + from editor_python_test_tools.editor_entity_utils import EditorEntity + from editor_python_test_tools.utils import Report, Tracer, TestHelper as helper + + with Tracer() as error_tracer: + # Test setup begins. + # Setup: Wait for Editor idle loop before executing Python hydra scripts then open "Base" level. + helper.init_idle() + helper.open_level("", "Base") + + # Test steps begin. + # 1. Create a Mesh entity with no components. + mesh_name = "Mesh" + mesh_entity = EditorEntity.create_editor_entity(mesh_name) + Report.critical_result(Tests.mesh_entity_creation, mesh_entity.exists()) + + # 2. Add a Mesh component to Mesh entity. + mesh_component = mesh_entity.add_component(mesh_name) + Report.critical_result( + Tests.mesh_component_added, + mesh_entity.has_component(mesh_name)) + + # 3. UNDO the entity creation and component addition. + # -> UNDO component addition. + general.undo() + # -> UNDO naming entity. + general.undo() + # -> UNDO selecting entity. + general.undo() + # -> UNDO entity creation. + general.undo() + general.idle_wait_frames(1) + Report.result(Tests.creation_undo, not mesh_entity.exists()) + + # 4. REDO the entity creation and component addition. + # -> REDO entity creation. + general.redo() + # -> REDO selecting entity. + general.redo() + # -> REDO naming entity. + general.redo() + # -> REDO component addition. + general.redo() + general.idle_wait_frames(1) + Report.result(Tests.creation_redo, mesh_entity.exists()) + + # 5. Set Mesh component asset property + mesh_property_asset = 'Controller|Configuration|Mesh Asset' + model_path = os.path.join('Objects', 'shaderball', 'shaderball_default_1m.azmodel') + model = Asset.find_asset_by_path(model_path) + mesh_component.set_component_property_value(mesh_property_asset, model.id) + Report.result(Tests.mesh_asset_specified, + mesh_component.get_component_property_value(mesh_property_asset) == model.id) + + # 6. Enter/Exit game mode. + helper.enter_game_mode(Tests.enter_game_mode) + general.idle_wait_frames(1) + helper.exit_game_mode(Tests.exit_game_mode) + + # 7. Test IsHidden. + mesh_entity.set_visibility_state(False) + Report.result(Tests.is_hidden, mesh_entity.is_hidden() is True) + + # 8. Test IsVisible. + mesh_entity.set_visibility_state(True) + general.idle_wait_frames(1) + Report.result(Tests.is_visible, mesh_entity.is_visible() is True) + + # 9. Delete Mesh entity. + mesh_entity.delete() + Report.result(Tests.entity_deleted, not mesh_entity.exists()) + + # 10. UNDO deletion. + general.undo() + Report.result(Tests.deletion_undo, mesh_entity.exists()) + + # 11. REDO deletion. + general.redo() + Report.result(Tests.deletion_redo, not mesh_entity.exists()) + + # 12. Look for errors or asserts. + helper.wait_for_condition(lambda: error_tracer.has_errors or error_tracer.has_asserts, 1.0) + for error_info in error_tracer.errors: + Report.info(f"Error: {error_info.filename} {error_info.function} | {error_info.message}") + for assert_info in error_tracer.asserts: + Report.info(f"Assert: {assert_info.filename} {assert_info.function} | {assert_info.message}") + + +if __name__ == "__main__": + from editor_python_test_tools.utils import Report + Report.start_test(AtomEditorComponents_Mesh_AddedToEntity) diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_ReflectionProbeAdded.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_ReflectionProbeAdded.py new file mode 100644 index 0000000000..9b13eb2c7e --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_ReflectionProbeAdded.py @@ -0,0 +1,194 @@ +""" +Copyright (c) Contributors to the Open 3D Engine Project. +For complete copyright and license terms please see the LICENSE at the root of this distribution. + +SPDX-License-Identifier: Apache-2.0 OR MIT +""" + +class Tests: + creation_undo = ( + "UNDO Entity creation success", + "UNDO Entity creation failed") + creation_redo = ( + "REDO Entity creation success", + "REDO Entity creation failed") + reflection_probe_creation = ( + "Reflection Probe Entity successfully created", + "Reflection Probe Entity failed to be created") + reflection_probe_component = ( + "Entity has a Reflection Probe component", + "Entity failed to find Reflection Probe component") + reflection_probe_disabled = ( + "Reflection Probe component disabled", + "Reflection Probe component was not disabled.") + reflection_map_generated = ( + "Reflection Probe cubemap generated", + "Reflection Probe cubemap not generated") + box_shape_component = ( + "Entity has a Box Shape component", + "Entity did not have a Box Shape component") + reflection_probe_enabled = ( + "Reflection Probe component enabled", + "Reflection Probe component was not enabled.") + enter_game_mode = ( + "Entered game mode", + "Failed to enter game mode") + exit_game_mode = ( + "Exited game mode", + "Couldn't exit game mode") + is_visible = ( + "Entity is visible", + "Entity was not visible") + is_hidden = ( + "Entity is hidden", + "Entity was not hidden") + entity_deleted = ( + "Entity deleted", + "Entity was not deleted") + deletion_undo = ( + "UNDO deletion success", + "UNDO deletion failed") + deletion_redo = ( + "REDO deletion success", + "REDO deletion failed") + + +def AtomEditorComponents_ReflectionProbe_AddedToEntity(): + """ + Summary: + Tests the Reflection Probe component can be added to an entity and has the expected functionality. + + Test setup: + - Wait for Editor idle loop. + - Open the "Base" level. + + Expected Behavior: + The component can be added, used in game mode, hidden/shown, deleted, and has accurate required components. + Creation and deletion undo/redo should also work. + + Test Steps: + 1) Create a Reflection Probe entity with no components. + 2) Add a Reflection Probe component to Reflection Probe entity. + 3) UNDO the entity creation and component addition. + 4) REDO the entity creation and component addition. + 5) Verify Reflection Probe component not enabled. + 6) Add Shape component since it is required by the Reflection Probe component. + 7) Verify Reflection Probe component is enabled. + 8) Enter/Exit game mode. + 9) Test IsHidden. + 10) Test IsVisible. + 11) Verify cubemap generation + 12) Delete Reflection Probe entity. + 13) UNDO deletion. + 14) REDO deletion. + 15) Look for errors. + + :return: None + """ + + import azlmbr.legacy.general as general + import azlmbr.math as math + import azlmbr.render as render + + from editor_python_test_tools.editor_entity_utils import EditorEntity + from editor_python_test_tools.utils import Report, Tracer, TestHelper as helper + + with Tracer() as error_tracer: + # Test setup begins. + # Setup: Wait for Editor idle loop before executing Python hydra scripts then open "Base" level. + helper.init_idle() + helper.open_level("", "Base") + + # Test steps begin. + # 1. Create a Reflection Probe entity with no components. + reflection_probe_name = "Reflection Probe" + reflection_probe_entity = EditorEntity.create_editor_entity_at( + math.Vector3(512.0, 512.0, 34.0), reflection_probe_name) + Report.critical_result(Tests.reflection_probe_creation, reflection_probe_entity.exists()) + + # 2. Add a Reflection Probe component to Reflection Probe entity. + reflection_probe_component = reflection_probe_entity.add_component(reflection_probe_name) + Report.critical_result( + Tests.reflection_probe_component, + reflection_probe_entity.has_component(reflection_probe_name)) + + # 3. UNDO the entity creation and component addition. + # -> UNDO component addition. + general.undo() + # -> UNDO naming entity. + general.undo() + # -> UNDO selecting entity. + general.undo() + # -> UNDO entity creation. + general.undo() + general.idle_wait_frames(1) + Report.result(Tests.creation_undo, not reflection_probe_entity.exists()) + + # 4. REDO the entity creation and component addition. + # -> REDO entity creation. + general.redo() + # -> REDO selecting entity. + general.redo() + # -> REDO naming entity. + general.redo() + # -> REDO component addition. + general.redo() + general.idle_wait_frames(1) + Report.result(Tests.creation_redo, reflection_probe_entity.exists()) + + # 5. Verify Reflection Probe component not enabled. + Report.result(Tests.reflection_probe_disabled, not reflection_probe_component.is_enabled()) + + # 6. Add Box Shape component since it is required by the Reflection Probe component. + box_shape = "Box Shape" + reflection_probe_entity.add_component(box_shape) + Report.result(Tests.box_shape_component, reflection_probe_entity.has_component(box_shape)) + + # 7. Verify Reflection Probe component is enabled. + Report.result(Tests.reflection_probe_enabled, reflection_probe_component.is_enabled()) + + # 8. Enter/Exit game mode. + helper.enter_game_mode(Tests.enter_game_mode) + general.idle_wait_frames(1) + helper.exit_game_mode(Tests.exit_game_mode) + + # 9. Test IsHidden. + reflection_probe_entity.set_visibility_state(False) + Report.result(Tests.is_hidden, reflection_probe_entity.is_hidden() is True) + + # 10. Test IsVisible. + reflection_probe_entity.set_visibility_state(True) + general.idle_wait_frames(1) + Report.result(Tests.is_visible, reflection_probe_entity.is_visible() is True) + + # 11. Verify cubemap generation + render.EditorReflectionProbeBus(azlmbr.bus.Event, "BakeReflectionProbe", reflection_probe_entity.id) + Report.result( + Tests.reflection_map_generated, + helper.wait_for_condition( + lambda: reflection_probe_component.get_component_property_value("Cubemap|Baked Cubemap Path") != "", + 20.0)) + + # 12. Delete Reflection Probe entity. + reflection_probe_entity.delete() + Report.result(Tests.entity_deleted, not reflection_probe_entity.exists()) + + # 13. UNDO deletion. + general.undo() + Report.result(Tests.deletion_undo, reflection_probe_entity.exists()) + + # 14. REDO deletion. + general.redo() + Report.result(Tests.deletion_redo, not reflection_probe_entity.exists()) + + # 15. Look for errors or asserts. + helper.wait_for_condition(lambda: error_tracer.has_errors or error_tracer.has_asserts, 1.0) + for error_info in error_tracer.errors: + Report.info(f"Error: {error_info.filename} {error_info.function} | {error_info.message}") + for assert_info in error_tracer.asserts: + Report.info(f"Assert: {assert_info.filename} {assert_info.function} | {assert_info.message}") + + +if __name__ == "__main__": + from editor_python_test_tools.utils import Report + Report.start_test(AtomEditorComponents_ReflectionProbe_AddedToEntity) diff --git a/AutomatedTesting/multiple_mesh_one_material/FBXTestTexture.png b/AutomatedTesting/multiple_mesh_one_material/FBXTestTexture.png new file mode 100644 index 0000000000..4f52364cb3 --- /dev/null +++ b/AutomatedTesting/multiple_mesh_one_material/FBXTestTexture.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:827a63985273229050bf4f63030bcc666f045091fe81cf8157a9ca23b40074b6 +size 3214 diff --git a/AutomatedTesting/multiple_mesh_one_material/multiple_mesh_one_material.fbx b/AutomatedTesting/multiple_mesh_one_material/multiple_mesh_one_material.fbx new file mode 100644 index 0000000000..d9deb899f0 --- /dev/null +++ b/AutomatedTesting/multiple_mesh_one_material/multiple_mesh_one_material.fbx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d8d24963e6e8765205bc79cbe2304fc39f1245ee75249e2834a71c96c3cab824 +size 22700 diff --git a/AutomatedTesting/multiple_mesh_one_material/multiple_mesh_one_material.fbx.assetinfo b/AutomatedTesting/multiple_mesh_one_material/multiple_mesh_one_material.fbx.assetinfo new file mode 100644 index 0000000000..fe18a16cb5 --- /dev/null +++ b/AutomatedTesting/multiple_mesh_one_material/multiple_mesh_one_material.fbx.assetinfo @@ -0,0 +1,8 @@ +{ + "values": [ + { + "$type": "ScriptProcessorRule", + "scriptFilename": "Editor/Scripts/scene_mesh_to_prefab.py" + } + ] +} \ No newline at end of file diff --git a/Code/Editor/Core/LevelEditorMenuHandler.cpp b/Code/Editor/Core/LevelEditorMenuHandler.cpp index ec92622f02..fdde1ac305 100644 --- a/Code/Editor/Core/LevelEditorMenuHandler.cpp +++ b/Code/Editor/Core/LevelEditorMenuHandler.cpp @@ -487,8 +487,6 @@ void LevelEditorMenuHandler::PopulateEditMenu(ActionManager::MenuWrapper& editMe editMenu.AddAction(AzToolsFramework::EditPivot); editMenu.AddAction(AzToolsFramework::EditReset); editMenu.AddAction(AzToolsFramework::EditResetManipulator); - editMenu.AddAction(AzToolsFramework::EditResetLocal); - editMenu.AddAction(AzToolsFramework::EditResetWorld); // Hide Selection editMenu.AddAction(AzToolsFramework::HideSelection); diff --git a/Code/Editor/CryEdit.cpp b/Code/Editor/CryEdit.cpp index 4aa3d22114..abba2bb9c6 100644 --- a/Code/Editor/CryEdit.cpp +++ b/Code/Editor/CryEdit.cpp @@ -2124,6 +2124,8 @@ bool CCryEditApp::FixDanglingSharedMemory(const QString& sharedMemName) const int CCryEditApp::ExitInstance(int exitCode) { + AZ_TracePrintf("Exit", "Called ExitInstance() with exit code: 0x%x", exitCode); + if (m_pEditor) { m_pEditor->OnBeginShutdownSequence(); diff --git a/Code/Editor/EditorModularViewportCameraComposer.cpp b/Code/Editor/EditorModularViewportCameraComposer.cpp index 29cf348ab5..ce4a0a2e33 100644 --- a/Code/Editor/EditorModularViewportCameraComposer.cpp +++ b/Code/Editor/EditorModularViewportCameraComposer.cpp @@ -95,7 +95,8 @@ namespace SandboxEditor cameras.AddCamera(m_firstPersonPanCamera); cameras.AddCamera(m_firstPersonTranslateCamera); cameras.AddCamera(m_firstPersonScrollCamera); - cameras.AddCamera(m_pivotCamera); + cameras.AddCamera(m_firstPersonFocusCamera); + cameras.AddCamera(m_orbitCamera); }); return controller; @@ -111,6 +112,7 @@ namespace SandboxEditor viewportId, &AzToolsFramework::ViewportInteraction::ViewportMouseCursorRequestBus::Events::BeginCursorCapture); } }; + const auto showCursor = [viewportId = m_viewportId] { if (SandboxEditor::CameraCaptureCursorForLook()) @@ -133,7 +135,7 @@ namespace SandboxEditor m_firstPersonRotateCamera->SetActivationEndedFn(showCursor); m_firstPersonPanCamera = AZStd::make_shared( - SandboxEditor::CameraFreePanChannelId(), AzFramework::LookPan, AzFramework::TranslatePivot); + SandboxEditor::CameraFreePanChannelId(), AzFramework::LookPan, AzFramework::TranslatePivotLook); m_firstPersonPanCamera->m_panSpeedFn = [] { @@ -153,7 +155,7 @@ namespace SandboxEditor const auto translateCameraInputChannelIds = BuildTranslateCameraInputChannelIds(); m_firstPersonTranslateCamera = AZStd::make_shared( - translateCameraInputChannelIds, AzFramework::LookTranslation, AzFramework::TranslatePivot); + translateCameraInputChannelIds, AzFramework::LookTranslation, AzFramework::TranslatePivotLook); m_firstPersonTranslateCamera->m_translateSpeedFn = [] { @@ -165,90 +167,111 @@ namespace SandboxEditor return SandboxEditor::CameraBoostMultiplier(); }; - m_firstPersonScrollCamera = AZStd::make_shared(); + m_firstPersonScrollCamera = AZStd::make_shared(); m_firstPersonScrollCamera->m_scrollSpeedFn = [] { return SandboxEditor::CameraScrollSpeed(); }; - m_pivotCamera = AZStd::make_shared(SandboxEditor::CameraPivotChannelId()); + const auto pivotFn = [] + { + // use the manipulator transform as the pivot point + AZStd::optional entityPivot; + AzToolsFramework::EditorTransformComponentSelectionRequestBus::EventResult( + entityPivot, AzToolsFramework::GetEntityContextId(), + &AzToolsFramework::EditorTransformComponentSelectionRequestBus::Events::GetManipulatorTransform); - m_pivotCamera->SetPivotFn( - []([[maybe_unused]] const AZ::Vector3& position, [[maybe_unused]] const AZ::Vector3& direction) + if (entityPivot.has_value()) { - // use the manipulator transform as the pivot point - AZStd::optional entityPivot; - AzToolsFramework::EditorTransformComponentSelectionRequestBus::EventResult( - entityPivot, AzToolsFramework::GetEntityContextId(), - &AzToolsFramework::EditorTransformComponentSelectionRequestBus::Events::GetManipulatorTransform); - - // otherwise just use the identity - return entityPivot.value_or(AZ::Transform::CreateIdentity()).GetTranslation(); + return entityPivot->GetTranslation(); + } + + // otherwise just use the identity + return AZ::Vector3::CreateZero(); + }; + + m_firstPersonFocusCamera = + AZStd::make_shared(SandboxEditor::CameraFocusChannelId(), AzFramework::FocusLook); + + m_firstPersonFocusCamera->SetPivotFn(pivotFn); + + m_orbitCamera = AZStd::make_shared(SandboxEditor::CameraOrbitChannelId()); + + m_orbitCamera->SetPivotFn( + [pivotFn]([[maybe_unused]] const AZ::Vector3& position, [[maybe_unused]] const AZ::Vector3& direction) + { + return pivotFn(); }); - m_pivotRotateCamera = AZStd::make_shared(SandboxEditor::CameraPivotLookChannelId()); + m_orbitRotateCamera = AZStd::make_shared(SandboxEditor::CameraOrbitLookChannelId()); - m_pivotRotateCamera->m_rotateSpeedFn = [] + m_orbitRotateCamera->m_rotateSpeedFn = [] { return SandboxEditor::CameraRotateSpeed(); }; - m_pivotRotateCamera->m_invertYawFn = [] + m_orbitRotateCamera->m_invertYawFn = [] { - return SandboxEditor::CameraPivotYawRotationInverted(); + return SandboxEditor::CameraOrbitYawRotationInverted(); }; - m_pivotTranslateCamera = AZStd::make_shared( - translateCameraInputChannelIds, AzFramework::LookTranslation, AzFramework::TranslateOffset); + m_orbitTranslateCamera = AZStd::make_shared( + translateCameraInputChannelIds, AzFramework::LookTranslation, AzFramework::TranslateOffsetOrbit); - m_pivotTranslateCamera->m_translateSpeedFn = [] + m_orbitTranslateCamera->m_translateSpeedFn = [] { return SandboxEditor::CameraTranslateSpeed(); }; - m_pivotTranslateCamera->m_boostMultiplierFn = [] + m_orbitTranslateCamera->m_boostMultiplierFn = [] { return SandboxEditor::CameraBoostMultiplier(); }; - m_pivotDollyScrollCamera = AZStd::make_shared(); + m_orbitDollyScrollCamera = AZStd::make_shared(); - m_pivotDollyScrollCamera->m_scrollSpeedFn = [] + m_orbitDollyScrollCamera->m_scrollSpeedFn = [] { return SandboxEditor::CameraScrollSpeed(); }; - m_pivotDollyMoveCamera = AZStd::make_shared(SandboxEditor::CameraPivotDollyChannelId()); + m_orbitDollyMoveCamera = AZStd::make_shared(SandboxEditor::CameraOrbitDollyChannelId()); - m_pivotDollyMoveCamera->m_motionSpeedFn = [] + m_orbitDollyMoveCamera->m_motionSpeedFn = [] { return SandboxEditor::CameraDollyMotionSpeed(); }; - m_pivotPanCamera = AZStd::make_shared( - SandboxEditor::CameraPivotPanChannelId(), AzFramework::LookPan, AzFramework::TranslateOffset); + m_orbitPanCamera = AZStd::make_shared( + SandboxEditor::CameraOrbitPanChannelId(), AzFramework::LookPan, AzFramework::TranslateOffsetOrbit); - m_pivotPanCamera->m_panSpeedFn = [] + m_orbitPanCamera->m_panSpeedFn = [] { return SandboxEditor::CameraPanSpeed(); }; - m_pivotPanCamera->m_invertPanXFn = [] + m_orbitPanCamera->m_invertPanXFn = [] { return SandboxEditor::CameraPanInvertedX(); }; - m_pivotPanCamera->m_invertPanYFn = [] + m_orbitPanCamera->m_invertPanYFn = [] { return SandboxEditor::CameraPanInvertedY(); }; - m_pivotCamera->m_pivotCameras.AddCamera(m_pivotRotateCamera); - m_pivotCamera->m_pivotCameras.AddCamera(m_pivotTranslateCamera); - m_pivotCamera->m_pivotCameras.AddCamera(m_pivotDollyScrollCamera); - m_pivotCamera->m_pivotCameras.AddCamera(m_pivotDollyMoveCamera); - m_pivotCamera->m_pivotCameras.AddCamera(m_pivotPanCamera); + m_orbitFocusCamera = + AZStd::make_shared(SandboxEditor::CameraFocusChannelId(), AzFramework::FocusOrbit); + + m_orbitFocusCamera->SetPivotFn(pivotFn); + + m_orbitCamera->m_orbitCameras.AddCamera(m_orbitRotateCamera); + m_orbitCamera->m_orbitCameras.AddCamera(m_orbitTranslateCamera); + m_orbitCamera->m_orbitCameras.AddCamera(m_orbitDollyScrollCamera); + m_orbitCamera->m_orbitCameras.AddCamera(m_orbitDollyMoveCamera); + m_orbitCamera->m_orbitCameras.AddCamera(m_orbitPanCamera); + m_orbitCamera->m_orbitCameras.AddCamera(m_orbitFocusCamera); } void EditorModularViewportCameraComposer::OnEditorModularViewportCameraComposerSettingsChanged() @@ -257,12 +280,14 @@ namespace SandboxEditor m_firstPersonTranslateCamera->SetTranslateCameraInputChannelIds(translateCameraInputChannelIds); m_firstPersonPanCamera->SetPanInputChannelId(SandboxEditor::CameraFreePanChannelId()); m_firstPersonRotateCamera->SetRotateInputChannelId(SandboxEditor::CameraFreeLookChannelId()); - - m_pivotCamera->SetPivotInputChannelId(SandboxEditor::CameraPivotChannelId()); - m_pivotTranslateCamera->SetTranslateCameraInputChannelIds(translateCameraInputChannelIds); - m_pivotPanCamera->SetPanInputChannelId(SandboxEditor::CameraPivotPanChannelId()); - m_pivotRotateCamera->SetRotateInputChannelId(SandboxEditor::CameraPivotLookChannelId()); - m_pivotDollyMoveCamera->SetDollyInputChannelId(SandboxEditor::CameraPivotDollyChannelId()); + m_firstPersonFocusCamera->SetFocusInputChannelId(SandboxEditor::CameraFocusChannelId()); + + m_orbitCamera->SetOrbitInputChannelId(SandboxEditor::CameraOrbitChannelId()); + m_orbitTranslateCamera->SetTranslateCameraInputChannelIds(translateCameraInputChannelIds); + m_orbitPanCamera->SetPanInputChannelId(SandboxEditor::CameraOrbitPanChannelId()); + m_orbitRotateCamera->SetRotateInputChannelId(SandboxEditor::CameraOrbitLookChannelId()); + m_orbitDollyMoveCamera->SetDollyInputChannelId(SandboxEditor::CameraOrbitDollyChannelId()); + m_orbitFocusCamera->SetFocusInputChannelId(SandboxEditor::CameraFocusChannelId()); } void EditorModularViewportCameraComposer::OnViewportViewEntityChanged(const AZ::EntityId& viewEntityId) diff --git a/Code/Editor/EditorModularViewportCameraComposer.h b/Code/Editor/EditorModularViewportCameraComposer.h index e691ca1c89..9cfd6f3554 100644 --- a/Code/Editor/EditorModularViewportCameraComposer.h +++ b/Code/Editor/EditorModularViewportCameraComposer.h @@ -41,13 +41,15 @@ namespace SandboxEditor AZStd::shared_ptr m_firstPersonRotateCamera; AZStd::shared_ptr m_firstPersonPanCamera; AZStd::shared_ptr m_firstPersonTranslateCamera; - AZStd::shared_ptr m_firstPersonScrollCamera; - AZStd::shared_ptr m_pivotCamera; - AZStd::shared_ptr m_pivotRotateCamera; - AZStd::shared_ptr m_pivotTranslateCamera; - AZStd::shared_ptr m_pivotDollyScrollCamera; - AZStd::shared_ptr m_pivotDollyMoveCamera; - AZStd::shared_ptr m_pivotPanCamera; + AZStd::shared_ptr m_firstPersonScrollCamera; + AZStd::shared_ptr m_firstPersonFocusCamera; + AZStd::shared_ptr m_orbitCamera; + AZStd::shared_ptr m_orbitRotateCamera; + AZStd::shared_ptr m_orbitTranslateCamera; + AZStd::shared_ptr m_orbitDollyScrollCamera; + AZStd::shared_ptr m_orbitDollyMoveCamera; + AZStd::shared_ptr m_orbitPanCamera; + AZStd::shared_ptr m_orbitFocusCamera; AzFramework::ViewportId m_viewportId; }; diff --git a/Code/Editor/EditorPreferencesPageViewportCamera.cpp b/Code/Editor/EditorPreferencesPageViewportCamera.cpp index 55b631e1f6..16176bc241 100644 --- a/Code/Editor/EditorPreferencesPageViewportCamera.cpp +++ b/Code/Editor/EditorPreferencesPageViewportCamera.cpp @@ -73,7 +73,7 @@ void CEditorPreferencesPage_ViewportCamera::Reflect(AZ::SerializeContext& serial ->Field("TranslateSmoothing", &CameraMovementSettings::m_translateSmoothing) ->Field("TranslateSmoothness", &CameraMovementSettings::m_translateSmoothness) ->Field("CaptureCursorLook", &CameraMovementSettings::m_captureCursorLook) - ->Field("PivotYawRotationInverted", &CameraMovementSettings::m_pivotYawRotationInverted) + ->Field("OrbitYawRotationInverted", &CameraMovementSettings::m_orbitYawRotationInverted) ->Field("PanInvertedX", &CameraMovementSettings::m_panInvertedX) ->Field("PanInvertedY", &CameraMovementSettings::m_panInvertedY); @@ -86,12 +86,13 @@ void CEditorPreferencesPage_ViewportCamera::Reflect(AZ::SerializeContext& serial ->Field("TranslateUp", &CameraInputSettings::m_translateUpChannelId) ->Field("TranslateDown", &CameraInputSettings::m_translateDownChannelId) ->Field("Boost", &CameraInputSettings::m_boostChannelId) - ->Field("Pivot", &CameraInputSettings::m_pivotChannelId) + ->Field("Orbit", &CameraInputSettings::m_orbitChannelId) ->Field("FreeLook", &CameraInputSettings::m_freeLookChannelId) ->Field("FreePan", &CameraInputSettings::m_freePanChannelId) - ->Field("PivotLook", &CameraInputSettings::m_pivotLookChannelId) - ->Field("PivotDolly", &CameraInputSettings::m_pivotDollyChannelId) - ->Field("PivotPan", &CameraInputSettings::m_pivotPanChannelId); + ->Field("OrbitLook", &CameraInputSettings::m_orbitLookChannelId) + ->Field("OrbitDolly", &CameraInputSettings::m_orbitDollyChannelId) + ->Field("OrbitPan", &CameraInputSettings::m_orbitPanChannelId) + ->Field("Focus", &CameraInputSettings::m_focusChannelId); serialize.Class() ->Version(1) @@ -143,8 +144,8 @@ void CEditorPreferencesPage_ViewportCamera::Reflect(AZ::SerializeContext& serial ->Attribute(AZ::Edit::Attributes::Min, minValue) ->Attribute(AZ::Edit::Attributes::Visibility, &CameraMovementSettings::TranslateSmoothingVisibility) ->DataElement( - AZ::Edit::UIHandlers::CheckBox, &CameraMovementSettings::m_pivotYawRotationInverted, "Camera Pivot Yaw Inverted", - "Inverted yaw rotation while pivoting") + AZ::Edit::UIHandlers::CheckBox, &CameraMovementSettings::m_orbitYawRotationInverted, "Camera Orbit Yaw Inverted", + "Inverted yaw rotation while orbiting") ->DataElement( AZ::Edit::UIHandlers::CheckBox, &CameraMovementSettings::m_panInvertedX, "Invert Pan X", "Invert direction of pan in local X axis") @@ -185,8 +186,8 @@ void CEditorPreferencesPage_ViewportCamera::Reflect(AZ::SerializeContext& serial "Key/button to move the camera more quickly") ->Attribute(AZ::Edit::Attributes::StringList, &GetEditorInputNames) ->DataElement( - AZ::Edit::UIHandlers::ComboBox, &CameraInputSettings::m_pivotChannelId, "Pivot", - "Key/button to begin the camera pivot behavior") + AZ::Edit::UIHandlers::ComboBox, &CameraInputSettings::m_orbitChannelId, "Orbit", + "Key/button to begin the camera orbit behavior") ->Attribute(AZ::Edit::Attributes::StringList, &GetEditorInputNames) ->DataElement( AZ::Edit::UIHandlers::ComboBox, &CameraInputSettings::m_freeLookChannelId, "Free Look", @@ -196,24 +197,27 @@ void CEditorPreferencesPage_ViewportCamera::Reflect(AZ::SerializeContext& serial AZ::Edit::UIHandlers::ComboBox, &CameraInputSettings::m_freePanChannelId, "Free Pan", "Key/button to begin camera free pan") ->Attribute(AZ::Edit::Attributes::StringList, &GetEditorInputNames) ->DataElement( - AZ::Edit::UIHandlers::ComboBox, &CameraInputSettings::m_pivotLookChannelId, "Pivot Look", - "Key/button to begin camera pivot look") + AZ::Edit::UIHandlers::ComboBox, &CameraInputSettings::m_orbitLookChannelId, "Orbit Look", + "Key/button to begin camera orbit look") ->Attribute(AZ::Edit::Attributes::StringList, &GetEditorInputNames) ->DataElement( - AZ::Edit::UIHandlers::ComboBox, &CameraInputSettings::m_pivotDollyChannelId, "Pivot Dolly", - "Key/button to begin camera pivot dolly") + AZ::Edit::UIHandlers::ComboBox, &CameraInputSettings::m_orbitDollyChannelId, "Orbit Dolly", + "Key/button to begin camera orbit dolly") ->Attribute(AZ::Edit::Attributes::StringList, &GetEditorInputNames) ->DataElement( - AZ::Edit::UIHandlers::ComboBox, &CameraInputSettings::m_pivotPanChannelId, "Pivot Pan", - "Key/button to begin camera pivot pan") + AZ::Edit::UIHandlers::ComboBox, &CameraInputSettings::m_orbitPanChannelId, "Orbit Pan", + "Key/button to begin camera orbit pan") + ->Attribute(AZ::Edit::Attributes::StringList, &GetEditorInputNames) + ->DataElement( + AZ::Edit::UIHandlers::ComboBox, &CameraInputSettings::m_focusChannelId, "Focus", "Key/button to focus camera orbit") ->Attribute(AZ::Edit::Attributes::StringList, &GetEditorInputNames); editContext->Class("Viewport Preferences", "Viewport Preferences") ->ClassElement(AZ::Edit::ClassElements::EditorData, "") ->Attribute(AZ::Edit::Attributes::Visibility, AZ_CRC("PropertyVisibility_ShowChildrenOnly", 0xef428f20)) ->DataElement( - AZ::Edit::UIHandlers::Default, &CEditorPreferencesPage_ViewportCamera::m_cameraMovementSettings, - "Camera Movement Settings", "Camera Movement Settings") + AZ::Edit::UIHandlers::Default, &CEditorPreferencesPage_ViewportCamera::m_cameraMovementSettings, "Camera Movement Settings", + "Camera Movement Settings") ->DataElement( AZ::Edit::UIHandlers::Default, &CEditorPreferencesPage_ViewportCamera::m_cameraInputSettings, "Camera Input Settings", "Camera Input Settings"); @@ -264,7 +268,7 @@ void CEditorPreferencesPage_ViewportCamera::OnApply() SandboxEditor::SetCameraTranslateSmoothness(m_cameraMovementSettings.m_translateSmoothness); SandboxEditor::SetCameraTranslateSmoothingEnabled(m_cameraMovementSettings.m_translateSmoothing); SandboxEditor::SetCameraCaptureCursorForLook(m_cameraMovementSettings.m_captureCursorLook); - SandboxEditor::SetCameraPivotYawRotationInverted(m_cameraMovementSettings.m_pivotYawRotationInverted); + SandboxEditor::SetCameraOrbitYawRotationInverted(m_cameraMovementSettings.m_orbitYawRotationInverted); SandboxEditor::SetCameraPanInvertedX(m_cameraMovementSettings.m_panInvertedX); SandboxEditor::SetCameraPanInvertedY(m_cameraMovementSettings.m_panInvertedY); @@ -275,12 +279,13 @@ void CEditorPreferencesPage_ViewportCamera::OnApply() SandboxEditor::SetCameraTranslateUpChannelId(m_cameraInputSettings.m_translateUpChannelId); SandboxEditor::SetCameraTranslateDownChannelId(m_cameraInputSettings.m_translateDownChannelId); SandboxEditor::SetCameraTranslateBoostChannelId(m_cameraInputSettings.m_boostChannelId); - SandboxEditor::SetCameraPivotChannelId(m_cameraInputSettings.m_pivotChannelId); + SandboxEditor::SetCameraOrbitChannelId(m_cameraInputSettings.m_orbitChannelId); SandboxEditor::SetCameraFreeLookChannelId(m_cameraInputSettings.m_freeLookChannelId); SandboxEditor::SetCameraFreePanChannelId(m_cameraInputSettings.m_freePanChannelId); - SandboxEditor::SetCameraPivotLookChannelId(m_cameraInputSettings.m_pivotLookChannelId); - SandboxEditor::SetCameraPivotDollyChannelId(m_cameraInputSettings.m_pivotDollyChannelId); - SandboxEditor::SetCameraPivotPanChannelId(m_cameraInputSettings.m_pivotPanChannelId); + SandboxEditor::SetCameraOrbitLookChannelId(m_cameraInputSettings.m_orbitLookChannelId); + SandboxEditor::SetCameraOrbitDollyChannelId(m_cameraInputSettings.m_orbitDollyChannelId); + SandboxEditor::SetCameraOrbitPanChannelId(m_cameraInputSettings.m_orbitPanChannelId); + SandboxEditor::SetCameraFocusChannelId(m_cameraInputSettings.m_focusChannelId); SandboxEditor::EditorModularViewportCameraComposerNotificationBus::Broadcast( &SandboxEditor::EditorModularViewportCameraComposerNotificationBus::Events::OnEditorModularViewportCameraComposerSettingsChanged); @@ -299,7 +304,7 @@ void CEditorPreferencesPage_ViewportCamera::InitializeSettings() m_cameraMovementSettings.m_translateSmoothness = SandboxEditor::CameraTranslateSmoothness(); m_cameraMovementSettings.m_translateSmoothing = SandboxEditor::CameraTranslateSmoothingEnabled(); m_cameraMovementSettings.m_captureCursorLook = SandboxEditor::CameraCaptureCursorForLook(); - m_cameraMovementSettings.m_pivotYawRotationInverted = SandboxEditor::CameraPivotYawRotationInverted(); + m_cameraMovementSettings.m_orbitYawRotationInverted = SandboxEditor::CameraOrbitYawRotationInverted(); m_cameraMovementSettings.m_panInvertedX = SandboxEditor::CameraPanInvertedX(); m_cameraMovementSettings.m_panInvertedY = SandboxEditor::CameraPanInvertedY(); @@ -310,10 +315,11 @@ void CEditorPreferencesPage_ViewportCamera::InitializeSettings() m_cameraInputSettings.m_translateUpChannelId = SandboxEditor::CameraTranslateUpChannelId().GetName(); m_cameraInputSettings.m_translateDownChannelId = SandboxEditor::CameraTranslateDownChannelId().GetName(); m_cameraInputSettings.m_boostChannelId = SandboxEditor::CameraTranslateBoostChannelId().GetName(); - m_cameraInputSettings.m_pivotChannelId = SandboxEditor::CameraPivotChannelId().GetName(); + m_cameraInputSettings.m_orbitChannelId = SandboxEditor::CameraOrbitChannelId().GetName(); m_cameraInputSettings.m_freeLookChannelId = SandboxEditor::CameraFreeLookChannelId().GetName(); m_cameraInputSettings.m_freePanChannelId = SandboxEditor::CameraFreePanChannelId().GetName(); - m_cameraInputSettings.m_pivotLookChannelId = SandboxEditor::CameraPivotLookChannelId().GetName(); - m_cameraInputSettings.m_pivotDollyChannelId = SandboxEditor::CameraPivotDollyChannelId().GetName(); - m_cameraInputSettings.m_pivotPanChannelId = SandboxEditor::CameraPivotPanChannelId().GetName(); + m_cameraInputSettings.m_orbitLookChannelId = SandboxEditor::CameraOrbitLookChannelId().GetName(); + m_cameraInputSettings.m_orbitDollyChannelId = SandboxEditor::CameraOrbitDollyChannelId().GetName(); + m_cameraInputSettings.m_orbitPanChannelId = SandboxEditor::CameraOrbitPanChannelId().GetName(); + m_cameraInputSettings.m_focusChannelId = SandboxEditor::CameraFocusChannelId().GetName(); } diff --git a/Code/Editor/EditorPreferencesPageViewportCamera.h b/Code/Editor/EditorPreferencesPageViewportCamera.h index 01dc4664f6..fdc86b0f89 100644 --- a/Code/Editor/EditorPreferencesPageViewportCamera.h +++ b/Code/Editor/EditorPreferencesPageViewportCamera.h @@ -54,7 +54,7 @@ private: float m_translateSmoothness; bool m_translateSmoothing; bool m_captureCursorLook; - bool m_pivotYawRotationInverted; + bool m_orbitYawRotationInverted; bool m_panInvertedX; bool m_panInvertedY; @@ -80,12 +80,13 @@ private: AZStd::string m_translateUpChannelId; AZStd::string m_translateDownChannelId; AZStd::string m_boostChannelId; - AZStd::string m_pivotChannelId; + AZStd::string m_orbitChannelId; AZStd::string m_freeLookChannelId; AZStd::string m_freePanChannelId; - AZStd::string m_pivotLookChannelId; - AZStd::string m_pivotDollyChannelId; - AZStd::string m_pivotPanChannelId; + AZStd::string m_orbitLookChannelId; + AZStd::string m_orbitDollyChannelId; + AZStd::string m_orbitPanChannelId; + AZStd::string m_focusChannelId; }; CameraMovementSettings m_cameraMovementSettings; diff --git a/Code/Editor/EditorViewportSettings.cpp b/Code/Editor/EditorViewportSettings.cpp index fe8efecd17..2354c6d63a 100644 --- a/Code/Editor/EditorViewportSettings.cpp +++ b/Code/Editor/EditorViewportSettings.cpp @@ -28,7 +28,7 @@ namespace SandboxEditor constexpr AZStd::string_view CameraRotateSpeedSetting = "/Amazon/Preferences/Editor/Camera/RotateSpeed"; constexpr AZStd::string_view CameraScrollSpeedSetting = "/Amazon/Preferences/Editor/Camera/DollyScrollSpeed"; constexpr AZStd::string_view CameraDollyMotionSpeedSetting = "/Amazon/Preferences/Editor/Camera/DollyMotionSpeed"; - constexpr AZStd::string_view CameraPivotYawRotationInvertedSetting = "/Amazon/Preferences/Editor/Camera/YawRotationInverted"; + constexpr AZStd::string_view CameraOrbitYawRotationInvertedSetting = "/Amazon/Preferences/Editor/Camera/YawRotationInverted"; constexpr AZStd::string_view CameraPanInvertedXSetting = "/Amazon/Preferences/Editor/Camera/PanInvertedX"; constexpr AZStd::string_view CameraPanInvertedYSetting = "/Amazon/Preferences/Editor/Camera/PanInvertedY"; constexpr AZStd::string_view CameraPanSpeedSetting = "/Amazon/Preferences/Editor/Camera/PanSpeed"; @@ -44,12 +44,13 @@ namespace SandboxEditor constexpr AZStd::string_view CameraTranslateUpIdSetting = "/Amazon/Preferences/Editor/Camera/CameraTranslateUpId"; constexpr AZStd::string_view CameraTranslateDownIdSetting = "/Amazon/Preferences/Editor/Camera/CameraTranslateUpDownId"; constexpr AZStd::string_view CameraTranslateBoostIdSetting = "/Amazon/Preferences/Editor/Camera/TranslateBoostId"; - constexpr AZStd::string_view CameraPivotIdSetting = "/Amazon/Preferences/Editor/Camera/PivotId"; + constexpr AZStd::string_view CameraOrbitIdSetting = "/Amazon/Preferences/Editor/Camera/OrbitId"; constexpr AZStd::string_view CameraFreeLookIdSetting = "/Amazon/Preferences/Editor/Camera/FreeLookId"; constexpr AZStd::string_view CameraFreePanIdSetting = "/Amazon/Preferences/Editor/Camera/FreePanId"; - constexpr AZStd::string_view CameraPivotLookIdSetting = "/Amazon/Preferences/Editor/Camera/PivotLookId"; - constexpr AZStd::string_view CameraPivotDollyIdSetting = "/Amazon/Preferences/Editor/Camera/PivotDollyId"; - constexpr AZStd::string_view CameraPivotPanIdSetting = "/Amazon/Preferences/Editor/Camera/PivotPanId"; + constexpr AZStd::string_view CameraOrbitLookIdSetting = "/Amazon/Preferences/Editor/Camera/OrbitLookId"; + constexpr AZStd::string_view CameraOrbitDollyIdSetting = "/Amazon/Preferences/Editor/Camera/OrbitDollyId"; + constexpr AZStd::string_view CameraOrbitPanIdSetting = "/Amazon/Preferences/Editor/Camera/OrbitPanId"; + constexpr AZStd::string_view CameraFocusIdSetting = "/Amazon/Preferences/Editor/Camera/FocusId"; template void SetRegistry(const AZStd::string_view setting, T&& value) @@ -239,14 +240,14 @@ namespace SandboxEditor SetRegistry(CameraDollyMotionSpeedSetting, speed); } - bool CameraPivotYawRotationInverted() + bool CameraOrbitYawRotationInverted() { - return GetRegistry(CameraPivotYawRotationInvertedSetting, false); + return GetRegistry(CameraOrbitYawRotationInvertedSetting, false); } - void SetCameraPivotYawRotationInverted(const bool inverted) + void SetCameraOrbitYawRotationInverted(const bool inverted) { - SetRegistry(CameraPivotYawRotationInvertedSetting, inverted); + SetRegistry(CameraOrbitYawRotationInvertedSetting, inverted); } bool CameraPanInvertedX() @@ -403,14 +404,14 @@ namespace SandboxEditor SetRegistry(CameraTranslateBoostIdSetting, cameraTranslateBoostId); } - AzFramework::InputChannelId CameraPivotChannelId() + AzFramework::InputChannelId CameraOrbitChannelId() { - return AzFramework::InputChannelId(GetRegistry(CameraPivotIdSetting, AZStd::string("keyboard_key_modifier_alt_l")).c_str()); + return AzFramework::InputChannelId(GetRegistry(CameraOrbitIdSetting, AZStd::string("keyboard_key_modifier_alt_l")).c_str()); } - void SetCameraPivotChannelId(AZStd::string_view cameraPivotId) + void SetCameraOrbitChannelId(AZStd::string_view cameraOrbitId) { - SetRegistry(CameraPivotIdSetting, cameraPivotId); + SetRegistry(CameraOrbitIdSetting, cameraOrbitId); } AzFramework::InputChannelId CameraFreeLookChannelId() @@ -433,33 +434,43 @@ namespace SandboxEditor SetRegistry(CameraFreePanIdSetting, cameraFreePanId); } - AzFramework::InputChannelId CameraPivotLookChannelId() + AzFramework::InputChannelId CameraOrbitLookChannelId() { - return AzFramework::InputChannelId(GetRegistry(CameraPivotLookIdSetting, AZStd::string("mouse_button_left")).c_str()); + return AzFramework::InputChannelId(GetRegistry(CameraOrbitLookIdSetting, AZStd::string("mouse_button_left")).c_str()); } - void SetCameraPivotLookChannelId(AZStd::string_view cameraPivotLookId) + void SetCameraOrbitLookChannelId(AZStd::string_view cameraOrbitLookId) { - SetRegistry(CameraPivotLookIdSetting, cameraPivotLookId); + SetRegistry(CameraOrbitLookIdSetting, cameraOrbitLookId); } - AzFramework::InputChannelId CameraPivotDollyChannelId() + AzFramework::InputChannelId CameraOrbitDollyChannelId() { - return AzFramework::InputChannelId(GetRegistry(CameraPivotDollyIdSetting, AZStd::string("mouse_button_right")).c_str()); + return AzFramework::InputChannelId(GetRegistry(CameraOrbitDollyIdSetting, AZStd::string("mouse_button_right")).c_str()); } - void SetCameraPivotDollyChannelId(AZStd::string_view cameraPivotDollyId) + void SetCameraOrbitDollyChannelId(AZStd::string_view cameraOrbitDollyId) { - SetRegistry(CameraPivotDollyIdSetting, cameraPivotDollyId); + SetRegistry(CameraOrbitDollyIdSetting, cameraOrbitDollyId); } - AzFramework::InputChannelId CameraPivotPanChannelId() + AzFramework::InputChannelId CameraOrbitPanChannelId() { - return AzFramework::InputChannelId(GetRegistry(CameraPivotPanIdSetting, AZStd::string("mouse_button_middle")).c_str()); + return AzFramework::InputChannelId(GetRegistry(CameraOrbitPanIdSetting, AZStd::string("mouse_button_middle")).c_str()); } - void SetCameraPivotPanChannelId(AZStd::string_view cameraPivotPanId) + void SetCameraOrbitPanChannelId(AZStd::string_view cameraOrbitPanId) { - SetRegistry(CameraPivotPanIdSetting, cameraPivotPanId); + SetRegistry(CameraOrbitPanIdSetting, cameraOrbitPanId); + } + + AzFramework::InputChannelId CameraFocusChannelId() + { + return AzFramework::InputChannelId(GetRegistry(CameraFocusIdSetting, AZStd::string("keyboard_key_alphanumeric_X")).c_str()); + } + + void SetCameraFocusChannelId(AZStd::string_view cameraFocusId) + { + SetRegistry(CameraFocusIdSetting, cameraFocusId); } } // namespace SandboxEditor diff --git a/Code/Editor/EditorViewportSettings.h b/Code/Editor/EditorViewportSettings.h index d1271017c6..c1394f7404 100644 --- a/Code/Editor/EditorViewportSettings.h +++ b/Code/Editor/EditorViewportSettings.h @@ -71,8 +71,8 @@ namespace SandboxEditor SANDBOX_API float CameraDollyMotionSpeed(); SANDBOX_API void SetCameraDollyMotionSpeed(float speed); - SANDBOX_API bool CameraPivotYawRotationInverted(); - SANDBOX_API void SetCameraPivotYawRotationInverted(bool inverted); + SANDBOX_API bool CameraOrbitYawRotationInverted(); + SANDBOX_API void SetCameraOrbitYawRotationInverted(bool inverted); SANDBOX_API bool CameraPanInvertedX(); SANDBOX_API void SetCameraPanInvertedX(bool inverted); @@ -119,8 +119,8 @@ namespace SandboxEditor SANDBOX_API AzFramework::InputChannelId CameraTranslateBoostChannelId(); SANDBOX_API void SetCameraTranslateBoostChannelId(AZStd::string_view cameraTranslateBoostId); - SANDBOX_API AzFramework::InputChannelId CameraPivotChannelId(); - SANDBOX_API void SetCameraPivotChannelId(AZStd::string_view cameraPivotId); + SANDBOX_API AzFramework::InputChannelId CameraOrbitChannelId(); + SANDBOX_API void SetCameraOrbitChannelId(AZStd::string_view cameraOrbitId); SANDBOX_API AzFramework::InputChannelId CameraFreeLookChannelId(); SANDBOX_API void SetCameraFreeLookChannelId(AZStd::string_view cameraFreeLookId); @@ -128,12 +128,15 @@ namespace SandboxEditor SANDBOX_API AzFramework::InputChannelId CameraFreePanChannelId(); SANDBOX_API void SetCameraFreePanChannelId(AZStd::string_view cameraFreePanId); - SANDBOX_API AzFramework::InputChannelId CameraPivotLookChannelId(); - SANDBOX_API void SetCameraPivotLookChannelId(AZStd::string_view cameraPivotLookId); + SANDBOX_API AzFramework::InputChannelId CameraOrbitLookChannelId(); + SANDBOX_API void SetCameraOrbitLookChannelId(AZStd::string_view cameraOrbitLookId); - SANDBOX_API AzFramework::InputChannelId CameraPivotDollyChannelId(); - SANDBOX_API void SetCameraPivotDollyChannelId(AZStd::string_view cameraPivotDollyId); + SANDBOX_API AzFramework::InputChannelId CameraOrbitDollyChannelId(); + SANDBOX_API void SetCameraOrbitDollyChannelId(AZStd::string_view cameraOrbitDollyId); - SANDBOX_API AzFramework::InputChannelId CameraPivotPanChannelId(); - SANDBOX_API void SetCameraPivotPanChannelId(AZStd::string_view cameraPivotPanId); + SANDBOX_API AzFramework::InputChannelId CameraOrbitPanChannelId(); + SANDBOX_API void SetCameraOrbitPanChannelId(AZStd::string_view cameraOrbitPanId); + + SANDBOX_API AzFramework::InputChannelId CameraFocusChannelId(); + SANDBOX_API void SetCameraFocusChannelId(AZStd::string_view cameraFocusId); } // namespace SandboxEditor diff --git a/Code/Framework/AzCore/AzCore/Asset/AssetJsonSerializer.cpp b/Code/Framework/AzCore/AzCore/Asset/AssetJsonSerializer.cpp index 3a83aa5d1c..3fa3b39ca5 100644 --- a/Code/Framework/AzCore/AzCore/Asset/AssetJsonSerializer.cpp +++ b/Code/Framework/AzCore/AzCore/Asset/AssetJsonSerializer.cpp @@ -173,6 +173,7 @@ namespace AZ if (assetTracker) { + assetTracker->FixUpAsset(*instance); assetTracker->AddAsset(*instance); } @@ -185,7 +186,20 @@ namespace AZ return context.Report(result, message); } - void SerializedAssetTracker::AddAsset(Asset& asset) + void SerializedAssetTracker::SetAssetFixUp(AssetFixUp assetFixUpCallback) + { + m_assetFixUpCallback = AZStd::move(assetFixUpCallback); + } + + void SerializedAssetTracker::FixUpAsset(Asset& asset) + { + if (m_assetFixUpCallback) + { + m_assetFixUpCallback(asset); + } + } + + void SerializedAssetTracker::AddAsset(Asset asset) { m_serializedAssets.emplace_back(asset); } @@ -199,5 +213,6 @@ namespace AZ { return m_serializedAssets; } + } // namespace Data } // namespace AZ diff --git a/Code/Framework/AzCore/AzCore/Asset/AssetJsonSerializer.h b/Code/Framework/AzCore/AzCore/Asset/AssetJsonSerializer.h index 879952c82d..e1b8cda00d 100644 --- a/Code/Framework/AzCore/AzCore/Asset/AssetJsonSerializer.h +++ b/Code/Framework/AzCore/AzCore/Asset/AssetJsonSerializer.h @@ -39,13 +39,18 @@ namespace AZ { public: AZ_RTTI(SerializedAssetTracker, "{1E067091-8C0A-44B1-A455-6E97663F6963}"); + using AssetFixUp = AZStd::function& asset)>; - void AddAsset(Asset& asset); + void SetAssetFixUp(AssetFixUp assetFixUpCallback); + void FixUpAsset(Asset& asset); + + void AddAsset(Asset asset); AZStd::vector>& GetTrackedAssets(); const AZStd::vector>& GetTrackedAssets() const; private: AZStd::vector> m_serializedAssets; + AssetFixUp m_assetFixUpCallback; }; } // namespace Data } // namespace AZ diff --git a/Code/Framework/AzCore/AzCore/Console/Console.cpp b/Code/Framework/AzCore/AzCore/Console/Console.cpp index a1b8a1759d..9f207d9afd 100644 --- a/Code/Framework/AzCore/AzCore/Console/Console.cpp +++ b/Code/Framework/AzCore/AzCore/Console/Console.cpp @@ -476,15 +476,16 @@ namespace AZ // Responsible for using the Json Serialization Issue Callback system // to determine when a JSON Patch or JSON Merge Patch modifies a value - // at a path underneath the IConsole::ConsoleRootCommandKey JSON pointer + // at a path underneath the IConsole::ConsoleRuntimeCommandKey JSON pointer JsonSerializationResult::ResultCode operator()(AZStd::string_view message, JsonSerializationResult::ResultCode result, AZStd::string_view path) { - AZ::IO::PathView consoleRootCommandKey{ IConsole::ConsoleRootCommandKey, AZ::IO::PosixPathSeparator }; + constexpr AZ::IO::PathView consoleRootCommandKey{ IConsole::ConsoleRuntimeCommandKey, AZ::IO::PosixPathSeparator }; + constexpr AZ::IO::PathView consoleAutoexecCommandKey{ IConsole::ConsoleAutoexecCommandKey, AZ::IO::PosixPathSeparator }; AZ::IO::PathView inputKey{ path, AZ::IO::PosixPathSeparator }; if (result.GetTask() == JsonSerializationResult::Tasks::Merge && result.GetProcessing() == JsonSerializationResult::Processing::Completed - && inputKey.IsRelativeTo(consoleRootCommandKey)) + && (inputKey.IsRelativeTo(consoleRootCommandKey) || inputKey.IsRelativeTo(consoleAutoexecCommandKey))) { if (auto type = m_settingsRegistry.GetType(path); type != SettingsRegistryInterface::Type::NoType) { @@ -510,12 +511,24 @@ namespace AZ { using FixedValueString = AZ::SettingsRegistryInterface::FixedValueString; - AZ::IO::PathView consoleRootCommandKey{ IConsole::ConsoleRootCommandKey, AZ::IO::PosixPathSeparator }; + constexpr AZ::IO::PathView consoleRuntimeCommandKey{ IConsole::ConsoleRuntimeCommandKey, AZ::IO::PosixPathSeparator }; + constexpr AZ::IO::PathView consoleAutoexecCommandKey{ IConsole::ConsoleAutoexecCommandKey, AZ::IO::PosixPathSeparator }; AZ::IO::PathView inputKey{ path, AZ::IO::PosixPathSeparator }; - // The ConsoleRootComamndKey is not a command itself so strictly children keys are being examined - if (inputKey.IsRelativeTo(consoleRootCommandKey) && inputKey != consoleRootCommandKey) + + // Abuses the IsRelativeToFuncton function of the path class to extract the console + // command from the settings registry objects + FixedValueString command; + if (inputKey != consoleRuntimeCommandKey && inputKey.IsRelativeTo(consoleRuntimeCommandKey)) + { + command = inputKey.LexicallyRelative(consoleRuntimeCommandKey).Native(); + } + else if (inputKey != consoleAutoexecCommandKey && inputKey.IsRelativeTo(consoleAutoexecCommandKey)) + { + command = inputKey.LexicallyRelative(consoleAutoexecCommandKey).Native(); + } + + if (!command.empty()) { - FixedValueString command = inputKey.LexicallyRelative(consoleRootCommandKey).Native(); ConsoleCommandContainer commandArgs; // Argument string which stores the value from the Settings Registry long enough // to pass into the PerformCommand. The ConsoleCommandContainer stores string_views @@ -603,9 +616,10 @@ namespace AZ void Console::RegisterCommandInvokerWithSettingsRegistry(AZ::SettingsRegistryInterface& settingsRegistry) { - // Make sure the there is a JSON object at the path of AZ::IConsole::ConsoleRootCommandKey + // Make sure the there is a JSON object at the ConsoleRuntimeCommandKey or ConsoleAutoexecKey // So that JSON Patch is able to add values underneath that object (JSON Patch doesn't create intermediate objects) - settingsRegistry.MergeSettings(R"({ "Amazon": { "AzCore": { "Runtime": { "ConsoleCommands": {} } }}})", + settingsRegistry.MergeSettings(R"({ "Amazon": { "AzCore": { "Runtime": { "ConsoleCommands": {} } } })" + R"(,"O3DE": { "Autoexec": { "ConsoleCommands": {} } } })", SettingsRegistryInterface::Format::JsonMergePatch); m_consoleCommandKeyHandler = settingsRegistry.RegisterNotifier(ConsoleCommandKeyNotificationHandler{ settingsRegistry, *this }); diff --git a/Code/Framework/AzCore/AzCore/Console/IConsole.h b/Code/Framework/AzCore/AzCore/Console/IConsole.h index dafc284ce3..73d17ac65a 100644 --- a/Code/Framework/AzCore/AzCore/Console/IConsole.h +++ b/Code/Framework/AzCore/AzCore/Console/IConsole.h @@ -31,7 +31,8 @@ namespace AZ using FunctorVisitor = AZStd::function; - inline static constexpr AZStd::string_view ConsoleRootCommandKey = "/Amazon/AzCore/Runtime/ConsoleCommands"; + inline static constexpr AZStd::string_view ConsoleRuntimeCommandKey = "/Amazon/AzCore/Runtime/ConsoleCommands"; + inline static constexpr AZStd::string_view ConsoleAutoexecCommandKey = "/O3DE/Autoexec/ConsoleCommands"; IConsole() = default; virtual ~IConsole() = default; diff --git a/Code/Framework/AzCore/AzCore/Debug/Trace.cpp b/Code/Framework/AzCore/AzCore/Debug/Trace.cpp index 14504cc282..3edcbaa273 100644 --- a/Code/Framework/AzCore/AzCore/Debug/Trace.cpp +++ b/Code/Framework/AzCore/AzCore/Debug/Trace.cpp @@ -224,6 +224,8 @@ namespace AZ void Debug::Trace::Terminate(int exitCode) { + AZ_TracePrintf("Exit", "Called Terminate() with exit code: 0x%x", exitCode); + AZ::Debug::Trace::PrintCallstack("Exit"); Platform::Terminate(exitCode); } diff --git a/Code/Framework/AzCore/AzCore/EBus/BusImpl.h b/Code/Framework/AzCore/AzCore/EBus/BusImpl.h index 8e655c0525..e2b1c2e92b 100644 --- a/Code/Framework/AzCore/AzCore/EBus/BusImpl.h +++ b/Code/Framework/AzCore/AzCore/EBus/BusImpl.h @@ -160,8 +160,8 @@ namespace AZ /** * Locking primitive that is used when executing events in the event queue. */ - using EventQueueMutexType = typename AZStd::Utils::if_c::value, // if EventQueueMutexType==NullMutex use MutexType otherwise EventQueueMutexType - MutexType, typename Traits::EventQueueMutexType>::type; + using EventQueueMutexType = AZStd::conditional_t::value, // if EventQueueMutexType==NullMutex use MutexType otherwise EventQueueMutexType + MutexType, typename Traits::EventQueueMutexType>; /** * Pointer to an address on the bus. @@ -180,14 +180,22 @@ namespace AZ * `::ExecuteQueuedEvents()`. * By default, the event queue is disabled. */ - static const bool EnableEventQueue = Traits::EnableEventQueue; - static const bool EventQueueingActiveByDefault = Traits::EventQueueingActiveByDefault; - static const bool EnableQueuedReferences = Traits::EnableQueuedReferences; + static constexpr bool EnableEventQueue = Traits::EnableEventQueue; + static constexpr bool EventQueueingActiveByDefault = Traits::EventQueueingActiveByDefault; + static constexpr bool EnableQueuedReferences = Traits::EnableQueuedReferences; /** * True if the EBus supports more than one address. Otherwise, false. */ - static const bool HasId = Traits::AddressPolicy != EBusAddressPolicy::Single; + static constexpr bool HasId = Traits::AddressPolicy != EBusAddressPolicy::Single; + + /** + * Template Lock Guard class that wraps around the Mutex + * The EBus uses for Dispatching Events. + * This is not the EBus Context Mutex if LocklessDispatch is true + */ + template + using DispatchLockGuard = typename Traits::template DispatchLockGuard; }; /** @@ -460,7 +468,7 @@ namespace AZ using BusPtr = typename Traits::BusPtr; /** - * Helper to queue an event by BusIdType only when function queueing is enabled + * Helper to queue an event by BusIdType only when function queueing is enabled * @param id Address ID. Handlers that are connected to this ID will receive the event. * @param func Function pointer of the event to dispatch. * @param args Function arguments that are passed to each handler. @@ -581,7 +589,7 @@ namespace AZ , public EBusBroadcaster , public EBusEventer , public EBusEventEnumerator - , public AZStd::Utils::if_c, EBusNullQueue>::type + , public AZStd::conditional_t, EBusNullQueue> { }; @@ -599,7 +607,7 @@ namespace AZ : public EventDispatcher , public EBusBroadcaster , public EBusBroadcastEnumerator - , public AZStd::Utils::if_c, EBusNullQueue>::type + , public AZStd::conditional_t, EBusNullQueue> { }; diff --git a/Code/Framework/AzCore/AzCore/EBus/EBus.h b/Code/Framework/AzCore/AzCore/EBus/EBus.h index da8c880963..a3dc10b103 100644 --- a/Code/Framework/AzCore/AzCore/EBus/EBus.h +++ b/Code/Framework/AzCore/AzCore/EBus/EBus.h @@ -236,6 +236,17 @@ namespace AZ * code before or after an event. */ using EventProcessingPolicy = EBusEventProcessingPolicy; + + /** + * Template Lock Guard class that wraps around the Mutex + * The EBus Context uses the LockGuard when dispatching + * (either AZStd::scoped_lock or NullLockGuard) + * The IsLocklessDispatch bool is there to defer evaluation of the LocklessDispatch constant + * Otherwise the value above in EBusTraits.h is always used and not the value + * that the derived trait class sets. + */ + template + using DispatchLockGuard = AZStd::conditional_t, AZStd::scoped_lock>; }; namespace Internal @@ -496,6 +507,14 @@ namespace AZ */ static const bool HasId = Traits::AddressPolicy != EBusAddressPolicy::Single; + /** + * Template Lock Guard class that wraps around the Mutex + * The EBus uses for Dispatching Events. + * This is not EBus Context Mutex when LocklessDispatch is set + */ + template + using DispatchLockGuard = typename ImplTraits::template DispatchLockGuard; + ////////////////////////////////////////////////////////////////////////// // Check to help identify common mistakes /// @cond EXCLUDE_DOCS @@ -620,11 +639,11 @@ namespace AZ using ContextMutexType = AZStd::conditional_t, AZStd::shared_mutex, MutexType>; /** - * The scoped lock guard to use (either AZStd::scoped_lock or NullLockGuard + * The scoped lock guard to use * during broadcast/event dispatch. * @see EBusTraits::LocklessDispatch */ - using DispatchLockGuard = AZStd::conditional_t, AZStd::scoped_lock>; + using DispatchLockGuard = DispatchLockGuard; /** * The scoped lock guard to use during connection. Some specialized policies execute handler methods which @@ -704,6 +723,11 @@ namespace AZ static Context& GetOrCreateContext(bool trackCallstack=true); static bool IsInDispatch(Context* context = GetContext(false)); + + /** + * Returns whether the EBus context is in the middle of a dispatch on the current thread + */ + static bool IsInDispatchThisThread(Context* context = GetContext(false)); /// @cond EXCLUDE_DOCS struct RouterCallstackEntry : public CallstackEntry @@ -1208,6 +1232,13 @@ AZ_POP_DISABLE_WARNING return context != nullptr && context->m_dispatches > 0; } + template + bool EBus::IsInDispatchThisThread(Context* context) + { + return context != nullptr && context->s_callstack != nullptr + && context->s_callstack->m_prev != nullptr; + } + //========================================================================= template EBus::RouterCallstackEntry::RouterCallstackEntry(Iterator it, const BusIdType* busId, bool isQueued, bool isReverse) diff --git a/Code/Framework/AzCore/AzCore/Jobs/JobManagerComponent.cpp b/Code/Framework/AzCore/AzCore/Jobs/JobManagerComponent.cpp index 25dfc7a493..6f5ccc93e4 100644 --- a/Code/Framework/AzCore/AzCore/Jobs/JobManagerComponent.cpp +++ b/Code/Framework/AzCore/AzCore/Jobs/JobManagerComponent.cpp @@ -18,6 +18,14 @@ #include #include +#include + +#include + +AZ_CVAR(float, cl_jobThreadsConcurrencyRatio, 0.6f, nullptr, AZ::ConsoleFunctorFlags::Null, "Legacy Job system multiplier on the number of hw threads the machine creates at initialization"); +AZ_CVAR(uint32_t, cl_jobThreadsNumReserved, 2, nullptr, AZ::ConsoleFunctorFlags::Null, "Legacy Job system number of hardware threads that are reserved for O3DE system threads"); +AZ_CVAR(uint32_t, cl_jobThreadsMinNumber, 2, nullptr, AZ::ConsoleFunctorFlags::Null, "Legacy Job system minimum number of worker threads to create after scaling the number of hw threads"); + namespace AZ { //========================================================================= @@ -46,9 +54,10 @@ namespace AZ JobManagerThreadDesc threadDesc; int numberOfWorkerThreads = m_numberOfWorkerThreads; - if (numberOfWorkerThreads <= 0) + if (numberOfWorkerThreads <= 0) // spawn default number of threads { - numberOfWorkerThreads = AZ::GetMin(static_cast(desc.m_workerThreads.capacity()), AZStd::thread::hardware_concurrency()); + uint32_t scaledHardwareThreads = Threading::CalcNumWorkerThreads(cl_jobThreadsConcurrencyRatio, cl_jobThreadsMinNumber, cl_jobThreadsNumReserved); + numberOfWorkerThreads = AZ::GetMin(static_cast(desc.m_workerThreads.capacity()), scaledHardwareThreads); #if (AZ_TRAIT_MAX_JOB_MANAGER_WORKER_THREADS) numberOfWorkerThreads = AZ::GetMin(numberOfWorkerThreads, AZ_TRAIT_MAX_JOB_MANAGER_WORKER_THREADS); #endif // (AZ_TRAIT_MAX_JOB_MANAGER_WORKER_THREADS) diff --git a/Code/Framework/AzCore/AzCore/Jobs/JobManagerDesc.h b/Code/Framework/AzCore/AzCore/Jobs/JobManagerDesc.h index 5594d7aa15..94f84f27b3 100644 --- a/Code/Framework/AzCore/AzCore/Jobs/JobManagerDesc.h +++ b/Code/Framework/AzCore/AzCore/Jobs/JobManagerDesc.h @@ -36,7 +36,7 @@ namespace AZ */ int m_stackSize; - JobManagerThreadDesc(int cpuId = -1, int priority = -100000, int stackSize = -1) + JobManagerThreadDesc(int cpuId = -1, int priority = 0, int stackSize = -1) : m_cpuId(cpuId) , m_priority(priority) , m_stackSize(stackSize) diff --git a/Code/Framework/AzCore/AzCore/Math/Uuid.cpp b/Code/Framework/AzCore/AzCore/Math/Uuid.cpp index d875e28a1e..410d235a37 100644 --- a/Code/Framework/AzCore/AzCore/Math/Uuid.cpp +++ b/Code/Framework/AzCore/AzCore/Math/Uuid.cpp @@ -135,18 +135,12 @@ namespace AZ { stringLength = strlen(uuidString); } - if (stringLength > MaxPermissiveStringSize) - { - if (!skipWarnings) - { - AZ_Warning("Math", false, "Can't create UUID from string length %zu over maximum %zu", stringLength, MaxPermissiveStringSize); - } - return Uuid::CreateNull(); - } + size_t newLength{ 0 }; char createString[MaxPermissiveStringSize]; - for (size_t curPos = 0; curPos < stringLength; ++curPos) + // Loop until we get to the end of the string OR stop once we've accumulated a full UUID string worth of data + for (size_t curPos = 0; curPos < stringLength && newLength < ValidUuidStringLength; ++curPos) { char curChar = uuidString[curPos]; switch (curChar) diff --git a/Code/Framework/AzCore/AzCore/Math/Uuid.h b/Code/Framework/AzCore/AzCore/Math/Uuid.h index ea920e45c8..6b77e7ec3e 100644 --- a/Code/Framework/AzCore/AzCore/Math/Uuid.h +++ b/Code/Framework/AzCore/AzCore/Math/Uuid.h @@ -42,8 +42,9 @@ namespace AZ //VER_AZ_RANDOM_CRC32 = 6, // 0 1 1 0 }; + static constexpr int ValidUuidStringLength = 32; /// Number of characters (data only, no extra formatting) in a valid UUID string static const size_t MaxStringBuffer = 39; /// 32 Uuid + 4 dashes + 2 brackets + 1 terminate - + Uuid() {} Uuid(const char* string, size_t stringLength = 0) { *this = CreateString(string, stringLength); } diff --git a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistry.h b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistry.h index 3dc1be87c5..40022bdc04 100644 --- a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistry.h +++ b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistry.h @@ -204,14 +204,14 @@ namespace AZ [[nodiscard]] virtual PreMergeEventHandler RegisterPreMergeEvent(const PreMergeEventCallback& callback) = 0; //! Register a function that will be called before a file is merged. //! @callback The function to call before a file is merged. - [[nodiscard]] virtual PreMergeEventHandler RegisterPreMergeEvent (PreMergeEventCallback&& callback) = 0; + [[nodiscard]] virtual PreMergeEventHandler RegisterPreMergeEvent(PreMergeEventCallback&& callback) = 0; //! Register a function that will be called after a file is merged. //! @callback The function to call after a file is merged. [[nodiscard]] virtual PostMergeEventHandler RegisterPostMergeEvent(const PostMergeEventCallback& callback) = 0; //! Register a function that will be called after a file is merged. //! @callback The function to call after a file is merged. - [[nodiscard]] virtual PostMergeEventHandler RegisterPostMergeEvent (PostMergeEventCallback&& callback) = 0; + [[nodiscard]] virtual PostMergeEventHandler RegisterPostMergeEvent(PostMergeEventCallback&& callback) = 0; //! Gets the boolean value at the provided path. //! @param result The target to write the result to. diff --git a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryVisitorUtils.cpp b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryVisitorUtils.cpp new file mode 100644 index 0000000000..9456616024 --- /dev/null +++ b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryVisitorUtils.cpp @@ -0,0 +1,136 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include + + +namespace AZ::SettingsRegistryVisitorUtils +{ + // Field Visitor implementation + FieldVisitor::FieldVisitor() = default; + FieldVisitor::FieldVisitor(VisitFieldType visitFieldType) + : m_visitFieldType{ visitFieldType } + { + } + + auto FieldVisitor::Traverse(AZStd::string_view path, AZStd::string_view valueName, + VisitAction action, Type type) -> VisitResponse + { + // A default response skip prevents visiting grand children(depth 2 or lower) + VisitResponse visitResponse = VisitResponse::Skip; + if (action == VisitAction::Begin) + { + // Invoke FieldVisitor override if the root path has been set + if (m_rootPath.has_value()) + { + Visit(path, valueName, type); + } + // To make sure only the direct children are visited(depth 1) + // set the root path once and set the VisitReponsoe + // to Continue to recurse into is fields + if (!m_rootPath.has_value()) + { + bool visitableFieldType{}; + switch (m_visitFieldType) + { + case VisitFieldType::Array: + visitableFieldType = type == Type::Array; + break; + case VisitFieldType::Object: + visitableFieldType = type == Type::Object; + break; + case VisitFieldType::ArrayOrObject: + visitableFieldType = type == Type::Array || type ==Type::Object; + break; + default: + AZ_Error("FieldVisitor", false, "The field visitation type value is invalid"); + break; + } + + if (visitableFieldType) + { + m_rootPath = path; + visitResponse = VisitResponse::Continue; + } + } + } + else if (action == VisitAction::Value) + { + // Invoke FieldVisitor override if the root path has been set + if (m_rootPath.has_value()) + { + Visit(path, valueName, type); + } + } + else if (action == VisitAction::End) + { + // Reset m_rootPath back to null when the root path has finished being visited + if (m_rootPath.has_value() && *m_rootPath == path) + { + m_rootPath = AZStd::nullopt; + } + } + + + return visitResponse; + } + + // Array Visitor implementation + ArrayVisitor::ArrayVisitor() + : FieldVisitor(VisitFieldType::Array) + { + } + + // Object Visitor implementation + ObjectVisitor::ObjectVisitor() + : FieldVisitor(VisitFieldType::Object) + { + } + + // Generic VisitField Callback implemention + template + bool VisitFieldCallback(AZ::SettingsRegistryInterface& settingsRegistry, const VisitorCallback& visitCallback, AZStd::string_view path) + { + struct VisitFieldVisitor + : BaseVisitor + { + using BaseVisitor::Visit; + VisitFieldVisitor(const VisitorCallback& visitCallback) + : m_visitCallback{ visitCallback } + {} + + void Visit(AZStd::string_view path, AZStd::string_view fieldIndex, typename BaseVisitor::Type type) override + { + m_visitCallback(path, fieldIndex, type); + } + + const VisitorCallback& m_visitCallback; + }; + + VisitFieldVisitor visitor{ visitCallback }; + return settingsRegistry.Visit(visitor, path); + } + + // VisitField implementation + bool VisitField(AZ::SettingsRegistryInterface& settingsRegistry, const VisitorCallback& visitCallback, AZStd::string_view path) + { + return VisitFieldCallback(settingsRegistry, visitCallback, path); + } + + // VisitArray implementation + bool VisitArray(AZ::SettingsRegistryInterface& settingsRegistry, const VisitorCallback& visitCallback, AZStd::string_view path) + { + return VisitFieldCallback(settingsRegistry, visitCallback, path); + } + + // VisitObject implementation + bool VisitObject(AZ::SettingsRegistryInterface& settingsRegistry, const VisitorCallback& visitCallback, AZStd::string_view path) + { + return VisitFieldCallback(settingsRegistry, visitCallback, path); + } +} diff --git a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryVisitorUtils.h b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryVisitorUtils.h new file mode 100644 index 0000000000..a45712521f --- /dev/null +++ b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryVisitorUtils.h @@ -0,0 +1,83 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include + + +namespace AZ::SettingsRegistryVisitorUtils +{ + //! Interface for visiting the fields of an array or object + //! To access the values, use the SettingsRegistryInterface Get/GetObject methods + struct FieldVisitor + : public AZ::SettingsRegistryInterface::Visitor + { + using VisitResponse = AZ::SettingsRegistryInterface::VisitResponse; + using VisitAction = AZ::SettingsRegistryInterface::VisitAction; + using Type = AZ::SettingsRegistryInterface::Type; + + FieldVisitor(); + + // Bring the base class visitor functions into scope + using AZ::SettingsRegistryInterface::Visitor::Visit; + virtual void Visit(AZStd::string_view path, AZStd::string_view arrayIndex, Type type) = 0; + + protected: + // VisitFieldType is used for filtering the type of referenced by the root path + enum class VisitFieldType + { + Array, + Object, + ArrayOrObject + }; + FieldVisitor(const VisitFieldType visitFieldType); + private: + VisitResponse Traverse(AZStd::string_view path, AZStd::string_view valueName, + VisitAction action, Type type) override; + + VisitFieldType m_visitFieldType{ VisitFieldType::ArrayOrObject }; + AZStd::optional m_rootPath; + }; + + //! Interface for visiting the fields of an array + //! To access the values, use the SettingsRegistryInterface Get/GetObject methods + struct ArrayVisitor + : public FieldVisitor + { + ArrayVisitor(); + }; + + //! Interface for visiting the fields of an object + //! To access the values, use the SettingsRegistryInterface Get/GetObject methods + struct ObjectVisitor + : public FieldVisitor + { + ObjectVisitor(); + }; + + //! Signature of callback funcition invoked when visiting an element of an array or object + using VisitorCallback = AZStd::function; + + //! Invokes the visitor callback for each element of either the array or object at @path + //! If @path is not an array or object, then no elements are visited + //! This function will not recurse into children of elements + //! @visitCallback functor that is invoked for each array or object element found + bool VisitField(AZ::SettingsRegistryInterface& settingsRegistry, const VisitorCallback& visitCallback, AZStd::string_view path); + //! Invokes the visitor callback for each element of the array at @path + //! If @path is not an array, then no elements are visited + //! This function will not recurse into children of elements + //! @visitCallback functor that is invoked for each array element found + bool VisitArray(AZ::SettingsRegistryInterface& settingsRegistry, const VisitorCallback& visitCallback, AZStd::string_view path); + //! Invokes the visitor callback for each element of the object at @path + //! If @path is not an object, then no elements are visited + //! This function will not recurse into children of elements + //! @visitCallback functor that is invoked for each object element found + bool VisitObject(AZ::SettingsRegistryInterface& settingsRegistry, const VisitorCallback& visitCallback, AZStd::string_view path); +} diff --git a/Code/Framework/AzCore/AzCore/Task/TaskExecutor.cpp b/Code/Framework/AzCore/AzCore/Task/TaskExecutor.cpp index 7da04d7301..4097348798 100644 --- a/Code/Framework/AzCore/AzCore/Task/TaskExecutor.cpp +++ b/Code/Framework/AzCore/AzCore/Task/TaskExecutor.cpp @@ -308,11 +308,11 @@ namespace AZ void TaskExecutor::SetInstance(TaskExecutor* executor) { - if (!executor) + if (!executor) // allow unsetting the executor { s_executor.Reset(); } - else if (!s_executor) // ignore any calls to set after the first (this happens in unit tests that create new system entities) + else if (!s_executor) // ignore any extra executors after the first (this happens during unit tests) { s_executor = AZ::Environment::CreateVariable(s_executorName, executor); } diff --git a/Code/Framework/AzCore/AzCore/Task/TaskGraphSystemComponent.cpp b/Code/Framework/AzCore/AzCore/Task/TaskGraphSystemComponent.cpp index eed461ecb4..56b56e96a1 100644 --- a/Code/Framework/AzCore/AzCore/Task/TaskGraphSystemComponent.cpp +++ b/Code/Framework/AzCore/AzCore/Task/TaskGraphSystemComponent.cpp @@ -11,9 +11,15 @@ #include #include #include +#include +#include // Create a cvar as a central location for experimentation with switching from the Job system to TaskGraph system. AZ_CVAR(bool, cl_activateTaskGraph, false, nullptr, AZ::ConsoleFunctorFlags::Null, "Flag clients of TaskGraph to switch between jobs/taskgraph (Note does not disable task graph system)"); +AZ_CVAR(float, cl_taskGraphThreadsConcurrencyRatio, 1.0f, nullptr, AZ::ConsoleFunctorFlags::Null, "TaskGraph calculate the number of worker threads to spawn by scaling the number of hw threads, value is clamped between 0.0f and 1.0f"); +AZ_CVAR(uint32_t, cl_taskGraphThreadsNumReserved, 2, nullptr, AZ::ConsoleFunctorFlags::Null, "TaskGraph number of hardware threads that are reserved for O3DE system threads. Value is clamped between 0 and the number of logical cores in the system"); +AZ_CVAR(uint32_t, cl_taskGraphThreadsMinNumber, 2, nullptr, AZ::ConsoleFunctorFlags::Null, "TaskGraph minimum number of worker threads to create after scaling the number of hw threads"); + static constexpr uint32_t TaskExecutorServiceCrc = AZ_CRC_CE("TaskExecutorService"); namespace AZ @@ -24,8 +30,8 @@ namespace AZ if (Interface::Get() == nullptr) { - Interface::Register(this); - m_taskExecutor = aznew TaskExecutor(); + Interface::Register(this); // small window that another thread can try to use taskgraph between this line and the set instance. + m_taskExecutor = aznew TaskExecutor(Threading::CalcNumWorkerThreads(cl_taskGraphThreadsConcurrencyRatio, cl_taskGraphThreadsMinNumber, cl_taskGraphThreadsNumReserved)); TaskExecutor::SetInstance(m_taskExecutor); } } diff --git a/Code/Framework/AzCore/AzCore/Threading/ThreadUtils.cpp b/Code/Framework/AzCore/AzCore/Threading/ThreadUtils.cpp new file mode 100644 index 0000000000..93eba9cc3f --- /dev/null +++ b/Code/Framework/AzCore/AzCore/Threading/ThreadUtils.cpp @@ -0,0 +1,25 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include + +namespace AZ::Threading +{ + uint32_t CalcNumWorkerThreads(float workerThreadsRatio, uint32_t minNumWorkerThreads, uint32_t reservedNumThreads) + { + const uint32_t maxHardwareThreads = AZStd::thread::hardware_concurrency(); + const uint32_t numReservedThreads = AZ::GetMin(reservedNumThreads, maxHardwareThreads); // protect against num reserved being bigger than the number of hw threads + const uint32_t maxWorkerThreads = maxHardwareThreads - numReservedThreads; + const float requestedWorkerThreads = AZ::GetClamp(workerThreadsRatio, 0.0f, 1.0f) * static_cast(maxWorkerThreads); + const uint32_t requestedWorkerThreadsRounded = AZStd::lround(requestedWorkerThreads); + const uint32_t numWorkerThreads = AZ::GetMax(minNumWorkerThreads, requestedWorkerThreadsRounded); + return numWorkerThreads; + } +}; diff --git a/Code/Framework/AzCore/AzCore/Threading/ThreadUtils.h b/Code/Framework/AzCore/AzCore/Threading/ThreadUtils.h new file mode 100644 index 0000000000..505d900289 --- /dev/null +++ b/Code/Framework/AzCore/AzCore/Threading/ThreadUtils.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include + +namespace AZ::Threading +{ + //! Calculates the number of worker threads a system should use based on the number of hardware threads a device has. + //! result = max (minNumWorkerThreads, workerThreadsRatio * (num_hardware_threads - reservedNumThreads)) + //! @param workerThreadsRatio scale applied to the calculated maximum number of threads available after reserved threads have been accounted for. Clamped between 0 and 1. + //! @param minNumWorkerThreads minimum value that will be returned. Value is unclamped and can be more than num_hardware_threads. + //! @param reservedNumThreads number of hardware threads to reserve for O3DE system threads. Value clamped to num_hardware_threads. + //! @return number of worker threads for the calling system to allocate + uint32_t CalcNumWorkerThreads(float workerThreadsRatio, uint32_t minNumWorkerThreads, uint32_t reservedNumThreads); +}; diff --git a/Code/Framework/AzCore/AzCore/azcore_files.cmake b/Code/Framework/AzCore/AzCore/azcore_files.cmake index 14579cbf33..6675958247 100644 --- a/Code/Framework/AzCore/AzCore/azcore_files.cmake +++ b/Code/Framework/AzCore/AzCore/azcore_files.cmake @@ -566,6 +566,8 @@ set(FILES Settings/SettingsRegistryMergeUtils.h Settings/SettingsRegistryScriptUtils.cpp Settings/SettingsRegistryScriptUtils.h + Settings/SettingsRegistryVisitorUtils.cpp + Settings/SettingsRegistryVisitorUtils.h State/HSM.cpp State/HSM.h Statistics/NamedRunningStatistic.h @@ -639,6 +641,8 @@ set(FILES Threading/ThreadSafeDeque.inl Threading/ThreadSafeObject.h Threading/ThreadSafeObject.inl + Threading/ThreadUtils.h + Threading/ThreadUtils.cpp Time/ITime.h Time/TimeSystemComponent.cpp Time/TimeSystemComponent.h diff --git a/Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/std/parallel/internal/thread_UnixLike.cpp b/Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/std/parallel/internal/thread_UnixLike.cpp index 079f167408..b615f677f1 100644 --- a/Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/std/parallel/internal/thread_UnixLike.cpp +++ b/Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/std/parallel/internal/thread_UnixLike.cpp @@ -59,6 +59,10 @@ namespace AZStd { priority = desc->m_priority; } + else + { + priority = SCHED_OTHER; + } if (desc->m_name) { name = desc->m_name; diff --git a/Code/Framework/AzCore/Tests/EBus.cpp b/Code/Framework/AzCore/Tests/EBus.cpp index 10216a0485..9acf0f8a05 100644 --- a/Code/Framework/AzCore/Tests/EBus.cpp +++ b/Code/Framework/AzCore/Tests/EBus.cpp @@ -2088,7 +2088,7 @@ namespace UnitTest DisconnectNextHandlerByIdImpl multiHandler2; multiHandler2.BusConnect(DisconnectNextHandlerByIdImpl::firstBusAddress); multiHandler2.BusConnect(DisconnectNextHandlerByIdImpl::secondBusAddress); - + // Set the first handler m_nextHandler field to point to the second handler multiHandler1.m_nextHandler = &multiHandler2; @@ -2807,7 +2807,7 @@ namespace UnitTest AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(m_val % m_maxSleep)); } } - + void DoConnect() override { MyEventGroupBus::Handler::BusConnect(m_id); @@ -2854,7 +2854,7 @@ namespace UnitTest } MyEventGroupBus::Event(id, &MyEventGroupBus::Events::Calculate, i, i * 2, i << 4); - + LocklessConnectorBus::Event(id, &LocklessConnectorBus::Events::DoDisconnect); bool failed = (AZStd::find_if(&sentinel[0], end, [](char s) { return s != 0; }) != end); @@ -2891,7 +2891,7 @@ namespace UnitTest { MyEventGroupImpl() { - + } ~MyEventGroupImpl() override @@ -3614,7 +3614,7 @@ namespace UnitTest { AZStd::this_thread::yield(); } - + EXPECT_GE(AZStd::chrono::system_clock::now(), endTime); }; AZStd::thread connectThread([&connectHandler, &waitHandler]() @@ -3813,7 +3813,7 @@ namespace UnitTest struct LastHandlerDisconnectHandler : public LastHandlerDisconnectBus::Handler { - void OnEvent() override + void OnEvent() override { ++m_numOnEvents; BusDisconnect(); @@ -3854,7 +3854,7 @@ namespace UnitTest struct DisconnectAssertHandler : public DisconnectAssertBus::Handler { - + }; TEST_F(EBus, HandlerDestroyedWithoutDisconnect_Asserts) @@ -3995,6 +3995,191 @@ namespace UnitTest idTestRequest.Disconnect(); } + + // IsInDispatchThisThread + struct IsInThreadDispatchRequests + : AZ::EBusTraits + { + using MutexType = AZStd::recursive_mutex; + }; + + using IsInThreadDispatchBus = AZ::EBus; + + class IsInThreadDispatchHandler + : public IsInThreadDispatchBus::Handler + {}; + + TEST_F(EBus, InvokingIsInThisThread_ReturnsSuccess_OnlyIfThreadIsInDispatch) + { + IsInThreadDispatchHandler handler; + handler.BusConnect(); + + auto ThreadDispatcher = [](IsInThreadDispatchRequests*) + { + EXPECT_TRUE(IsInThreadDispatchBus::IsInDispatchThisThread()); + auto PerThreadBusDispatch = []() + { + EXPECT_FALSE(IsInThreadDispatchBus::IsInDispatchThisThread()); + }; + AZStd::array threads{ AZStd::thread(PerThreadBusDispatch), AZStd::thread(PerThreadBusDispatch) }; + for (AZStd::thread& thread : threads) + { + thread.join(); + } + }; + + static constexpr size_t ThreadDispatcherIterations = 4; + for (size_t iteration = 0; iteration < ThreadDispatcherIterations; ++iteration) + { + EXPECT_FALSE(IsInThreadDispatchBus::IsInDispatchThisThread()); + IsInThreadDispatchBus::Broadcast(ThreadDispatcher); + EXPECT_FALSE(IsInThreadDispatchBus::IsInDispatchThisThread()); + } + } + + // Thread Dispatch Policy + struct ThreadDispatchTestBusTraits + : AZ::EBusTraits + { + using MutexType = AZStd::recursive_mutex; + + struct PostThreadDispatchTestInvoker + { + ~PostThreadDispatchTestInvoker(); + }; + + template + struct ThreadDispatchTestLockGuard + { + ThreadDispatchTestLockGuard(DispatchMutex& contextMutex) + : m_lock{ contextMutex } + {} + ThreadDispatchTestLockGuard(DispatchMutex& contextMutex, AZStd::adopt_lock_t adopt_lock) + : m_lock{ contextMutex, adopt_lock } + {} + ThreadDispatchTestLockGuard(const ThreadDispatchTestLockGuard&) = delete; + ThreadDispatchTestLockGuard& operator=(const ThreadDispatchTestLockGuard&) = delete; + private: + PostThreadDispatchTestInvoker m_threadPolicyInvoker; + using LockType = AZStd::conditional_t, AZStd::scoped_lock>; + LockType m_lock; + }; + + template + using DispatchLockGuard = ThreadDispatchTestLockGuard; + + static inline AZStd::atomic s_threadPostDispatchCalls; + }; + + class ThreadDispatchTestRequests + { + public: + virtual void FirstCall() = 0; + virtual void SecondCall() = 0; + virtual void ThirdCall() = 0; + }; + + using ThreadDispatchTestBus = AZ::EBus; + + ThreadDispatchTestBusTraits::PostThreadDispatchTestInvoker::~PostThreadDispatchTestInvoker() + { + if (!ThreadDispatchTestBus::IsInDispatchThisThread()) + { + ++s_threadPostDispatchCalls; + } + } + + class ThreadDispatchTestHandler + : public ThreadDispatchTestBus::Handler + { + public: + void Connect() + { + ThreadDispatchTestBus::Handler::BusConnect(); + } + void Disconnect() + { + ThreadDispatchTestBus::Handler::BusDisconnect(); + } + + void FirstCall() override + { + ThreadDispatchTestBus::Broadcast(&ThreadDispatchTestBus::Events::SecondCall); + } + void SecondCall() override + { + ThreadDispatchTestBus::Broadcast(&ThreadDispatchTestBus::Events::ThirdCall); + } + void ThirdCall() override + { + } + }; + + template + class EBusParamFixture + : public ScopedAllocatorSetupFixture + , public ::testing::WithParamInterface + {}; + + struct ThreadDispatchParams + { + size_t m_threadCount{}; + size_t m_handlerCount{}; + }; + + using ThreadDispatchParamFixture = EBusParamFixture; + + INSTANTIATE_TEST_CASE_P( + ThreadDispatch, + ThreadDispatchParamFixture, + ::testing::Values( + ThreadDispatchParams{ 1, 1 }, + ThreadDispatchParams{ 2, 1 }, + ThreadDispatchParams{ 1, 2 }, + ThreadDispatchParams{ 2, 2 }, + ThreadDispatchParams{ 16, 8 } + ) + ); + + TEST_P(ThreadDispatchParamFixture, CustomDispatchLockGuard_InvokesPostDispatchFunction_AfterThreadHasFinishedDispatch) + { + ThreadDispatchTestBusTraits::s_threadPostDispatchCalls = 0; + ThreadDispatchParams threadDispatchParams = GetParam(); + AZStd::vector testThreads; + AZStd::vector testHandlers(threadDispatchParams.m_handlerCount); + for (ThreadDispatchTestHandler& testHandler : testHandlers) + { + testHandler.Connect(); + } + + static constexpr size_t DispatchThreadCalls = 3; + const size_t totalThreadDispatchCalls = threadDispatchParams.m_threadCount * DispatchThreadCalls; + + auto DispatchThreadWorker = []() + { + ThreadDispatchTestBus::Broadcast(&ThreadDispatchTestBus::Events::FirstCall); + ThreadDispatchTestBus::Broadcast(&ThreadDispatchTestBus::Events::SecondCall); + ThreadDispatchTestBus::Broadcast(&ThreadDispatchTestBus::Events::ThirdCall); + }; + + for (size_t threadIndex = 0; threadIndex < threadDispatchParams.m_threadCount; ++threadIndex) + { + testThreads.emplace_back(DispatchThreadWorker); + } + + for (AZStd::thread& thread : testThreads) + { + thread.join(); + } + + for (ThreadDispatchTestHandler& testHandler : testHandlers) + { + testHandler.Disconnect(); + } + + EXPECT_EQ(totalThreadDispatchCalls, ThreadDispatchTestBusTraits::s_threadPostDispatchCalls); + ThreadDispatchTestBusTraits::s_threadPostDispatchCalls = 0; + } } // namespace UnitTest #if defined(HAVE_BENCHMARK) @@ -4370,7 +4555,7 @@ namespace Benchmark Bus::ExecuteQueuedEvents(); } s_benchmarkEBusEnv.Disconnect(state); - + } BUS_BENCHMARK_REGISTER_ALL(BM_EBus_ExecuteBroadcast); diff --git a/Code/Framework/AzCore/Tests/SettingsRegistryMergeUtilsTests.cpp b/Code/Framework/AzCore/Tests/Settings/SettingsRegistryMergeUtilsTests.cpp similarity index 100% rename from Code/Framework/AzCore/Tests/SettingsRegistryMergeUtilsTests.cpp rename to Code/Framework/AzCore/Tests/Settings/SettingsRegistryMergeUtilsTests.cpp diff --git a/Code/Framework/AzCore/Tests/SettingsRegistryTests.cpp b/Code/Framework/AzCore/Tests/Settings/SettingsRegistryTests.cpp similarity index 100% rename from Code/Framework/AzCore/Tests/SettingsRegistryTests.cpp rename to Code/Framework/AzCore/Tests/Settings/SettingsRegistryTests.cpp diff --git a/Code/Framework/AzCore/Tests/Settings/SettingsRegistryVisitorUtilsTests.cpp b/Code/Framework/AzCore/Tests/Settings/SettingsRegistryVisitorUtilsTests.cpp new file mode 100644 index 0000000000..15f346ee93 --- /dev/null +++ b/Code/Framework/AzCore/Tests/Settings/SettingsRegistryVisitorUtilsTests.cpp @@ -0,0 +1,196 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + + +#include +#include +#include +#include +#include +#include + +namespace SettingsRegistryVisitorUtilsTests +{ + struct VisitCallbackParams + { + AZStd::string_view m_inputJsonDocument; + using VisitFieldFunction = bool(*)(AZ::SettingsRegistryInterface&, + const AZ::SettingsRegistryVisitorUtils::VisitorCallback&, + AZStd::string_view); + + static inline constexpr size_t MaxFieldCount = 10; + using ObjectFields = AZStd::fixed_vector, MaxFieldCount>; + using ArrayFields = AZStd::fixed_vector; + ObjectFields m_objectFields; + ArrayFields m_arrayFields; + }; + + template + class SettingsRegistryVisitorUtilsParamFixture + : public UnitTest::ScopedAllocatorSetupFixture + , public ::testing::WithParamInterface + { + public: + + void SetUp() override + { + m_registry = AZStd::make_unique(); + } + + void TearDown() override + { + m_registry.reset(); + } + + AZStd::unique_ptr m_registry; + }; + + using SettingsRegistryVisitCallbackFixture = SettingsRegistryVisitorUtilsParamFixture; + + TEST_P(SettingsRegistryVisitCallbackFixture, VisitFunction_VisitFieldsOfArrayType_ReturnsFields) + { + const VisitCallbackParams& visitParams = GetParam(); + + ASSERT_TRUE(m_registry->MergeSettings(visitParams.m_inputJsonDocument, AZ::SettingsRegistryInterface::Format::JsonMergePatch)); + + AZStd::fixed_vector testArrayFields; + auto visitorCallback = [this, &testArrayFields](AZStd::string_view path, AZStd::string_view, AZ::SettingsRegistryInterface::Type) + { + AZStd::string fieldValue; + EXPECT_TRUE(m_registry->Get(fieldValue, path)); + testArrayFields.emplace_back(AZStd::move(fieldValue)); + }; + + AZ::SettingsRegistryVisitorUtils::VisitField(*m_registry, visitorCallback, "/Test/Array"); + + const AZStd::fixed_vector expectedFields{ + visitParams.m_arrayFields.begin(), visitParams.m_arrayFields.end() }; + EXPECT_THAT(testArrayFields, ::testing::ContainerEq(expectedFields)); + } + + TEST_P(SettingsRegistryVisitCallbackFixture, VisitFunction_VisitFieldsOfObjectType_ReturnsFields) + { + const VisitCallbackParams& visitParams = GetParam(); + + ASSERT_TRUE(m_registry->MergeSettings(visitParams.m_inputJsonDocument, AZ::SettingsRegistryInterface::Format::JsonMergePatch)); + + AZStd::fixed_vector, VisitCallbackParams::MaxFieldCount> testObjectFields; + auto visitorCallback = [this, &testObjectFields](AZStd::string_view path, AZStd::string_view fieldName, AZ::SettingsRegistryInterface::Type) + { + AZStd::string fieldValue; + EXPECT_TRUE(m_registry->Get(fieldValue, path)); + testObjectFields.emplace_back(fieldName, AZStd::move(fieldValue)); + }; + + AZ::SettingsRegistryVisitorUtils::VisitField(*m_registry, visitorCallback, "/Test/Object"); + + const AZStd::fixed_vector, VisitCallbackParams::MaxFieldCount> expectedFields{ + visitParams.m_objectFields.begin(), visitParams.m_objectFields.end() }; + EXPECT_THAT(testObjectFields, ::testing::ContainerEq(expectedFields)); + } + + TEST_P(SettingsRegistryVisitCallbackFixture, VisitFunction_VisitArrayOfArrayType_ReturnsFields) + { + const VisitCallbackParams& visitParams = GetParam(); + + ASSERT_TRUE(m_registry->MergeSettings(visitParams.m_inputJsonDocument, AZ::SettingsRegistryInterface::Format::JsonMergePatch)); + + AZStd::fixed_vector testArrayFields; + auto visitorCallback = [this, &testArrayFields](AZStd::string_view path, AZStd::string_view, AZ::SettingsRegistryInterface::Type) + { + AZStd::string fieldValue; + EXPECT_TRUE(m_registry->Get(fieldValue, path)); + testArrayFields.emplace_back(AZStd::move(fieldValue)); + }; + + AZ::SettingsRegistryVisitorUtils::VisitArray(*m_registry, visitorCallback, "/Test/Array"); + + const AZStd::fixed_vector expectedArrayFields{ + visitParams.m_arrayFields.begin(), visitParams.m_arrayFields.end() }; + EXPECT_THAT(testArrayFields, ::testing::ContainerEq(expectedArrayFields)); + } + + TEST_P(SettingsRegistryVisitCallbackFixture, VisitFunction_VisitArrayOfObjectType_ReturnsEmpty) + { + const VisitCallbackParams& visitParams = GetParam(); + + ASSERT_TRUE(m_registry->MergeSettings(visitParams.m_inputJsonDocument, AZ::SettingsRegistryInterface::Format::JsonMergePatch)); + + AZStd::fixed_vector testArrayFields; + auto visitorCallback = [this, &testArrayFields](AZStd::string_view path, AZStd::string_view, AZ::SettingsRegistryInterface::Type) + { + AZStd::string fieldValue; + EXPECT_TRUE(m_registry->Get(fieldValue, path)); + testArrayFields.emplace_back(AZStd::move(fieldValue)); + }; + + AZ::SettingsRegistryVisitorUtils::VisitArray(*m_registry, visitorCallback, "/Test/Object"); + + EXPECT_TRUE(testArrayFields.empty()); + } + + TEST_P(SettingsRegistryVisitCallbackFixture, VisitFunction_VisitObjectOfArrayType_ReturnsEmpty) + { + const VisitCallbackParams& visitParams = GetParam(); + + ASSERT_TRUE(m_registry->MergeSettings(visitParams.m_inputJsonDocument, AZ::SettingsRegistryInterface::Format::JsonMergePatch)); + + AZStd::fixed_vector, VisitCallbackParams::MaxFieldCount> testObjectFields; + auto visitorCallback = [this, &testObjectFields](AZStd::string_view path, AZStd::string_view fieldName, AZ::SettingsRegistryInterface::Type) + { + AZStd::string fieldValue; + EXPECT_TRUE(m_registry->Get(fieldValue, path)); + testObjectFields.emplace_back(fieldName, AZStd::move(fieldValue)); + }; + + AZ::SettingsRegistryVisitorUtils::VisitObject(*m_registry, visitorCallback, "/Test/Array"); + + EXPECT_TRUE(testObjectFields.empty()); + } + + TEST_P(SettingsRegistryVisitCallbackFixture, VisitFunction_VisitObjectOfObjectType_ReturnsFields) + { + const VisitCallbackParams& visitParams = GetParam(); + + ASSERT_TRUE(m_registry->MergeSettings(visitParams.m_inputJsonDocument, AZ::SettingsRegistryInterface::Format::JsonMergePatch)); + + AZStd::fixed_vector, VisitCallbackParams::MaxFieldCount> testObjectFields; + auto visitorCallback = [this, &testObjectFields](AZStd::string_view path, AZStd::string_view fieldName, AZ::SettingsRegistryInterface::Type) + { + AZStd::string fieldValue; + EXPECT_TRUE(m_registry->Get(fieldValue, path)); + testObjectFields.emplace_back(fieldName, AZStd::move(fieldValue)); + }; + + AZ::SettingsRegistryVisitorUtils::VisitObject(*m_registry, visitorCallback, "/Test/Object"); + + const AZStd::fixed_vector, VisitCallbackParams::MaxFieldCount> expectedObjectFields{ + visitParams.m_objectFields.begin(), visitParams.m_objectFields.end() }; + EXPECT_THAT(testObjectFields, ::testing::ContainerEq(expectedObjectFields)); + } + + + INSTANTIATE_TEST_CASE_P( + VisitField, + SettingsRegistryVisitCallbackFixture, + ::testing::Values( + VisitCallbackParams + { + R"({)" "\n" + R"( "Test":)" "\n" + R"( {)" "\n" + R"( "Array": [ "Hello", "World" ],)" "\n" + R"( "Object": { "Foo": "Hello", "Bar": "World"})" "\n" + R"( })" "\n" + R"(})" "\n", + VisitCallbackParams::ObjectFields{{"Foo", "Hello"}, {"Bar", "World"}}, + VisitCallbackParams::ArrayFields{"Hello", "World"} + } + ) + ); +} diff --git a/Code/Framework/AzCore/Tests/UUIDTests.cpp b/Code/Framework/AzCore/Tests/UUIDTests.cpp index a18dc33c4f..64b9048a62 100644 --- a/Code/Framework/AzCore/Tests/UUIDTests.cpp +++ b/Code/Framework/AzCore/Tests/UUIDTests.cpp @@ -247,4 +247,27 @@ namespace UnitTest Uuid right = Uuid::CreateStringPermissive(permissiveStr1); EXPECT_EQ(left, right); } + + TEST_F(UuidTests, CreateStringPermissive_StringWithExtraData_Succeeds) + { + const char uuidStr[] = "{34D44249-E599-4B30-811F-4215C2DEA269}"; + Uuid left = Uuid::CreateString(uuidStr); + + const char permissiveStr[] = "0x34D44249-0xE5994B30-0x811F4215-0xC2DEA269 Hello World"; + Uuid right = Uuid::CreateStringPermissive(permissiveStr); + EXPECT_EQ(left, right); + + } + + TEST_F(UuidTests, CreateStringPermissive_StringWithLotsOfExtraData_Succeeds) + { + const char uuidStr[] = "{34D44249-E599-4B30-811F-4215C2DEA269}"; + Uuid left = Uuid::CreateString(uuidStr); + + const char permissiveStr[] = "0x34D44249-0xE5994B30-0x811F4215-0xC2DEA269 Hello World this is a really long string " + "with lots of extra data to make sure we can parse a long string without failing as long as the uuid is in " + "the beginning of the string then we should succeed"; + Uuid right = Uuid::CreateStringPermissive(permissiveStr); + EXPECT_EQ(left, right); + } } diff --git a/Code/Framework/AzCore/Tests/azcoretests_files.cmake b/Code/Framework/AzCore/Tests/azcoretests_files.cmake index e155594aa0..d738711a4f 100644 --- a/Code/Framework/AzCore/Tests/azcoretests_files.cmake +++ b/Code/Framework/AzCore/Tests/azcoretests_files.cmake @@ -75,11 +75,12 @@ set(FILES Name/NameJsonSerializerTests.cpp Name/NameTests.cpp RTTI/TypeSafeIntegralTests.cpp - SettingsRegistryTests.cpp - SettingsRegistryMergeUtilsTests.cpp Settings/CommandLineTests.cpp + Settings/SettingsRegistryTests.cpp Settings/SettingsRegistryConsoleUtilsTests.cpp + Settings/SettingsRegistryMergeUtilsTests.cpp Settings/SettingsRegistryScriptUtilsTests.cpp + Settings/SettingsRegistryVisitorUtilsTests.cpp Streamer/BlockCacheTests.cpp Streamer/DedicatedCacheTests.cpp Streamer/FullDecompressorTests.cpp diff --git a/Code/Framework/AzFramework/AzFramework/Entity/BehaviorEntity.cpp b/Code/Framework/AzFramework/AzFramework/Entity/BehaviorEntity.cpp index 725f71a3c9..5d82cf488a 100644 --- a/Code/Framework/AzFramework/AzFramework/Entity/BehaviorEntity.cpp +++ b/Code/Framework/AzFramework/AzFramework/Entity/BehaviorEntity.cpp @@ -133,6 +133,8 @@ namespace AzFramework behaviorContext->Class("Entity") ->Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value) ->Attribute(AZ::Script::Attributes::ConstructorOverride, &Internal::BehaviorEntityScriptConstructor) + ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common) + ->Attribute(AZ::Script::Attributes::Module, "entity") ->Constructor() ->Constructor() ->Constructor() diff --git a/Code/Framework/AzFramework/AzFramework/Matchmaking/IMatchmakingRequests.h b/Code/Framework/AzFramework/AzFramework/Matchmaking/IMatchmakingRequests.h new file mode 100644 index 0000000000..22b65f8340 --- /dev/null +++ b/Code/Framework/AzFramework/AzFramework/Matchmaking/IMatchmakingRequests.h @@ -0,0 +1,63 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include +#include + +namespace AzFramework +{ + //! IMatchmakingRequests + //! Pure virtual session interface class to abstract the details of session handling from application code. + class IMatchmakingRequests + { + public: + AZ_RTTI(IMatchmakingRequests, "{BC0B74DA-A448-4F40-9B50-9D73142829D5}"); + + IMatchmakingRequests() = default; + virtual ~IMatchmakingRequests() = default; + + // Registers a player's acceptance or rejection of a proposed matchmaking. + // @param acceptMatchRequest The request of AcceptMatch operation + virtual void AcceptMatch(const AcceptMatchRequest& acceptMatchRequest) = 0; + + // Create a game match for a group of players. + // @param startMatchmakingRequest The request of StartMatchmaking operation + // @return A unique identifier for a matchmaking ticket + virtual AZStd::string StartMatchmaking(const StartMatchmakingRequest& startMatchmakingRequest) = 0; + + // Cancels a matchmaking ticket that is currently being processed. + // @param stopMatchmakingRequest The request of StopMatchmaking operation + virtual void StopMatchmaking(const StopMatchmakingRequest& stopMatchmakingRequest) = 0; + }; + + //! IMatchmakingAsyncRequests + //! Async version of IMatchmakingRequests + class IMatchmakingAsyncRequests + { + public: + AZ_RTTI(ISessionAsyncRequests, "{53513480-2D02-493C-B44E-96AA27F42429}"); + + IMatchmakingAsyncRequests() = default; + virtual ~IMatchmakingAsyncRequests() = default; + + // AcceptMatch Async + // @param acceptMatchRequest The request of AcceptMatch operation + virtual void AcceptMatchAsync(const AcceptMatchRequest& acceptMatchRequest) = 0; + + // StartMatchmaking Async + // @param startMatchmakingRequest The request of StartMatchmaking operation + virtual void StartMatchmakingAsync(const StartMatchmakingRequest& startMatchmakingRequest) = 0; + + // StopMatchmaking Async + // @param stopMatchmakingRequest The request of StopMatchmaking operation + virtual void StopMatchmakingAsync(const StopMatchmakingRequest& stopMatchmakingRequest) = 0; + }; +} // namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/Matchmaking/MatchmakingNotifications.h b/Code/Framework/AzFramework/AzFramework/Matchmaking/MatchmakingNotifications.h new file mode 100644 index 0000000000..ad61971a11 --- /dev/null +++ b/Code/Framework/AzFramework/AzFramework/Matchmaking/MatchmakingNotifications.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include + +namespace AzFramework +{ + //! MatchmakingAsyncRequestNotifications + //! The notifications correspond to matchmaking async requests + class MatchmakingAsyncRequestNotifications + : public AZ::EBusTraits + { + public: + // Safeguard handler for multi-threaded use case + using MutexType = AZStd::recursive_mutex; + + ////////////////////////////////////////////////////////////////////////// + // EBusTraits overrides + static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple; + static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; + ////////////////////////////////////////////////////////////////////////// + + // OnAcceptMatchAsyncComplete is fired once AcceptMatchAsync completes + virtual void OnAcceptMatchAsyncComplete() = 0; + + // OnStartMatchmakingAsyncComplete is fired once StartMatchmakingAsync completes + // @param matchmakingTicketId The unique identifier for the matchmaking ticket + virtual void OnStartMatchmakingAsyncComplete(const AZStd::string& matchmakingTicketId) = 0; + + // OnStopMatchmakingAsyncComplete is fired once StopMatchmakingAsync completes + virtual void OnStopMatchmakingAsyncComplete() = 0; + }; + using MatchmakingAsyncRequestNotificationBus = AZ::EBus; + + //! MatchmakingNotifications + //! The matchmaking notifications to listen for performing required operations + class MatchAcceptanceNotifications + : public AZ::EBusTraits + { + public: + // Safeguard handler for multi-threaded use case + using MutexType = AZStd::recursive_mutex; + + ////////////////////////////////////////////////////////////////////////// + // EBusTraits overrides + static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple; + static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; + ////////////////////////////////////////////////////////////////////////// + + // OnMatchAcceptance is fired when DescribeMatchmaking ticket status is REQUIRES_ACCEPTANCE + virtual void OnMatchAcceptance() = 0; + }; + using MatchAcceptanceNotificationBus = AZ::EBus; +} // namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/Matchmaking/MatchmakingRequests.cpp b/Code/Framework/AzFramework/AzFramework/Matchmaking/MatchmakingRequests.cpp new file mode 100644 index 0000000000..b6c8c55fa4 --- /dev/null +++ b/Code/Framework/AzFramework/AzFramework/Matchmaking/MatchmakingRequests.cpp @@ -0,0 +1,78 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include +#include + +namespace AzFramework +{ + void AcceptMatchRequest::Reflect(AZ::ReflectContext* context) + { + if (auto serializeContext = azrtti_cast(context)) + { + serializeContext->Class() + ->Version(0) + ->Field("acceptMatch", &AcceptMatchRequest::m_acceptMatch) + ->Field("playerIds", &AcceptMatchRequest::m_playerIds) + ->Field("ticketId", &AcceptMatchRequest::m_ticketId); + + if (AZ::EditContext* editContext = serializeContext->GetEditContext()) + { + editContext->Class("AcceptMatchRequest", "The container for AcceptMatch request parameters") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) + ->DataElement(AZ::Edit::UIHandlers::Default, &AcceptMatchRequest::m_acceptMatch, "AcceptMatch", + "Player response to accept or reject match") + ->DataElement(AZ::Edit::UIHandlers::Default, &AcceptMatchRequest::m_playerIds, "PlayerIds", + "A list of unique identifiers for players delivering the response") + ->DataElement(AZ::Edit::UIHandlers::Default, &AcceptMatchRequest::m_ticketId, "TicketId", + "A unique identifier for a matchmaking ticket"); + } + } + } + + void StartMatchmakingRequest::Reflect(AZ::ReflectContext* context) + { + if (auto serializeContext = azrtti_cast(context)) + { + serializeContext->Class() + ->Version(0) + ->Field("ticketId", &StartMatchmakingRequest::m_ticketId); + + if (AZ::EditContext* editContext = serializeContext->GetEditContext()) + { + editContext->Class("StartMatchmakingRequest", "The container for StartMatchmaking request parameters") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) + ->DataElement(AZ::Edit::UIHandlers::Default, &StartMatchmakingRequest::m_ticketId, "TicketId", + "A unique identifier for a matchmaking ticket"); + } + } + } + + void StopMatchmakingRequest::Reflect(AZ::ReflectContext* context) + { + if (auto serializeContext = azrtti_cast(context)) + { + serializeContext->Class() + ->Version(0) + ->Field("ticketId", &StopMatchmakingRequest::m_ticketId); + + if (AZ::EditContext* editContext = serializeContext->GetEditContext()) + { + editContext->Class("StopMatchmakingRequest", "The container for StopMatchmaking request parameters") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) + ->DataElement(AZ::Edit::UIHandlers::Default, &StopMatchmakingRequest::m_ticketId, "TicketId", + "A unique identifier for a matchmaking ticket"); + } + } + } +} // namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/Matchmaking/MatchmakingRequests.h b/Code/Framework/AzFramework/AzFramework/Matchmaking/MatchmakingRequests.h new file mode 100644 index 0000000000..58e335696f --- /dev/null +++ b/Code/Framework/AzFramework/AzFramework/Matchmaking/MatchmakingRequests.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include + +namespace AZ +{ + class ReflectContext; +} + +namespace AzFramework +{ + //! AcceptMatchRequest + //! The container for AcceptMatch request parameters. + struct AcceptMatchRequest + { + AZ_RTTI(AcceptMatchRequest, "{AD289D76-CEE2-424F-847E-E62AA83B7D79}"); + static void Reflect(AZ::ReflectContext* context); + + AcceptMatchRequest() = default; + virtual ~AcceptMatchRequest() = default; + + // Player response to accept or reject match + bool m_acceptMatch; + // A list of unique identifiers for players delivering the response + AZStd::vector m_playerIds; + // A unique identifier for a matchmaking ticket + AZStd::string m_ticketId; + }; + + //! StartMatchmakingRequest + //! The container for StartMatchmaking request parameters. + struct StartMatchmakingRequest + { + AZ_RTTI(StartMatchmakingRequest, "{70B47776-E8E7-4993-BEC3-5CAEC3D48E47}"); + static void Reflect(AZ::ReflectContext* context); + + StartMatchmakingRequest() = default; + virtual ~StartMatchmakingRequest() = default; + + // A unique identifier for a matchmaking ticket + AZStd::string m_ticketId; + }; + + //! StopMatchmakingRequest + //! The container for StopMatchmaking request parameters. + struct StopMatchmakingRequest + { + AZ_RTTI(StopMatchmakingRequest, "{6132E293-65EF-4DC2-A8A0-00269697229D}"); + static void Reflect(AZ::ReflectContext* context); + + StopMatchmakingRequest() = default; + virtual ~StopMatchmakingRequest() = default; + + // A unique identifier for a matchmaking ticket + AZStd::string m_ticketId; + }; +} // namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/Session/ISessionRequests.h b/Code/Framework/AzFramework/AzFramework/Session/ISessionRequests.h index 0323686442..bdc3fe1444 100644 --- a/Code/Framework/AzFramework/AzFramework/Session/ISessionRequests.h +++ b/Code/Framework/AzFramework/AzFramework/Session/ISessionRequests.h @@ -10,98 +10,11 @@ #include #include -#include #include -#include +#include namespace AzFramework { - struct SessionConfig; - - //! CreateSessionRequest - //! The container for CreateSession request parameters. - struct CreateSessionRequest - { - AZ_RTTI(CreateSessionRequest, "{E39C2A45-89C9-4CFB-B337-9734DC798930}"); - static void Reflect(AZ::ReflectContext* context); - - CreateSessionRequest() = default; - virtual ~CreateSessionRequest() = default; - - // A unique identifier for a player or entity creating the session. - AZStd::string m_creatorId; - - // A collection of custom properties for a session. - AZStd::unordered_map m_sessionProperties; - - // A descriptive label that is associated with a session. - AZStd::string m_sessionName; - - // The maximum number of players that can be connected simultaneously to the session. - uint64_t m_maxPlayer = 0; - }; - - //! SearchSessionsRequest - //! The container for SearchSessions request parameters. - struct SearchSessionsRequest - { - AZ_RTTI(SearchSessionsRequest, "{B49207A8-8549-4ADB-B7D9-D7A4932F9B4B}"); - static void Reflect(AZ::ReflectContext* context); - - SearchSessionsRequest() = default; - virtual ~SearchSessionsRequest() = default; - - // String containing the search criteria for the session search. If no filter expression is included, the request returns results - // for all active sessions. - AZStd::string m_filterExpression; - - // Instructions on how to sort the search results. If no sort expression is included, the request returns results in random order. - AZStd::string m_sortExpression; - - // The maximum number of results to return. - uint8_t m_maxResult = 0; - - // A token that indicates the start of the next sequential page of results. - AZStd::string m_nextToken; - }; - - //! SearchSessionsResponse - //! The container for SearchSession request results. - struct SearchSessionsResponse - { - AZ_RTTI(SearchSessionsResponse, "{F93DE7DC-D381-4E08-8A3B-0B08F7C38714}"); - static void Reflect(AZ::ReflectContext* context); - - SearchSessionsResponse() = default; - virtual ~SearchSessionsResponse() = default; - - // A collection of sessions that match the search criteria and sorted in specific order. - AZStd::vector m_sessionConfigs; - - // A token that indicates the start of the next sequential page of results. - AZStd::string m_nextToken; - }; - - //! JoinSessionRequest - //! The container for JoinSession request parameters. - struct JoinSessionRequest - { - AZ_RTTI(JoinSessionRequest, "{519769E8-3CDE-4385-A0D7-24DBB3685657}"); - static void Reflect(AZ::ReflectContext* context); - - JoinSessionRequest() = default; - virtual ~JoinSessionRequest() = default; - - // A unique identifier for the session. - AZStd::string m_sessionId; - - // A unique identifier for a player. Player IDs are developer-defined. - AZStd::string m_playerId; - - // Developer-defined information related to a player. - AZStd::string m_playerData; - }; - //! ISessionRequests //! Pure virtual session interface class to abstract the details of session handling from application code. class ISessionRequests diff --git a/Code/Framework/AzFramework/AzFramework/Session/ISessionRequests.cpp b/Code/Framework/AzFramework/AzFramework/Session/SessionRequests.cpp similarity index 98% rename from Code/Framework/AzFramework/AzFramework/Session/ISessionRequests.cpp rename to Code/Framework/AzFramework/AzFramework/Session/SessionRequests.cpp index 5536d9a47d..a7ffaa2491 100644 --- a/Code/Framework/AzFramework/AzFramework/Session/ISessionRequests.cpp +++ b/Code/Framework/AzFramework/AzFramework/Session/SessionRequests.cpp @@ -6,9 +6,10 @@ * */ +#include #include #include -#include +#include #include namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/Session/SessionRequests.h b/Code/Framework/AzFramework/AzFramework/Session/SessionRequests.h new file mode 100644 index 0000000000..1ae018e1ef --- /dev/null +++ b/Code/Framework/AzFramework/AzFramework/Session/SessionRequests.h @@ -0,0 +1,107 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include +#include + +namespace AZ +{ + class ReflectContext; +} + +namespace AzFramework +{ + struct SessionConfig; + + //! CreateSessionRequest + //! The container for CreateSession request parameters. + struct CreateSessionRequest + { + AZ_RTTI(CreateSessionRequest, "{E39C2A45-89C9-4CFB-B337-9734DC798930}"); + static void Reflect(AZ::ReflectContext* context); + + CreateSessionRequest() = default; + virtual ~CreateSessionRequest() = default; + + // A unique identifier for a player or entity creating the session. + AZStd::string m_creatorId; + + // A collection of custom properties for a session. + AZStd::unordered_map m_sessionProperties; + + // A descriptive label that is associated with a session. + AZStd::string m_sessionName; + + // The maximum number of players that can be connected simultaneously to the session. + uint64_t m_maxPlayer = 0; + }; + + //! SearchSessionsRequest + //! The container for SearchSessions request parameters. + struct SearchSessionsRequest + { + AZ_RTTI(SearchSessionsRequest, "{B49207A8-8549-4ADB-B7D9-D7A4932F9B4B}"); + static void Reflect(AZ::ReflectContext* context); + + SearchSessionsRequest() = default; + virtual ~SearchSessionsRequest() = default; + + // String containing the search criteria for the session search. If no filter expression is included, the request returns results + // for all active sessions. + AZStd::string m_filterExpression; + + // Instructions on how to sort the search results. If no sort expression is included, the request returns results in random order. + AZStd::string m_sortExpression; + + // The maximum number of results to return. + uint8_t m_maxResult = 0; + + // A token that indicates the start of the next sequential page of results. + AZStd::string m_nextToken; + }; + + //! SearchSessionsResponse + //! The container for SearchSession request results. + struct SearchSessionsResponse + { + AZ_RTTI(SearchSessionsResponse, "{F93DE7DC-D381-4E08-8A3B-0B08F7C38714}"); + static void Reflect(AZ::ReflectContext* context); + + SearchSessionsResponse() = default; + virtual ~SearchSessionsResponse() = default; + + // A collection of sessions that match the search criteria and sorted in specific order. + AZStd::vector m_sessionConfigs; + + // A token that indicates the start of the next sequential page of results. + AZStd::string m_nextToken; + }; + + //! JoinSessionRequest + //! The container for JoinSession request parameters. + struct JoinSessionRequest + { + AZ_RTTI(JoinSessionRequest, "{519769E8-3CDE-4385-A0D7-24DBB3685657}"); + static void Reflect(AZ::ReflectContext* context); + + JoinSessionRequest() = default; + virtual ~JoinSessionRequest() = default; + + // A unique identifier for the session. + AZStd::string m_sessionId; + + // A unique identifier for a player. Player IDs are developer-defined. + AZStd::string m_playerId; + + // Developer-defined information related to a player. + AZStd::string m_playerData; + }; +} // namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp b/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp index a3eb4d8329..ddee63e191 100644 --- a/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp +++ b/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp @@ -533,8 +533,8 @@ namespace AzFramework m_translateCameraInputChannelIds = translateCameraInputChannelIds; } - PivotCameraInput::PivotCameraInput(const InputChannelId& pivotChannelId) - : m_pivotChannelId(pivotChannelId) + OrbitCameraInput::OrbitCameraInput(const InputChannelId& orbitChannelId) + : m_orbitChannelId(orbitChannelId) { m_pivotFn = []([[maybe_unused]] const AZ::Vector3& position, [[maybe_unused]] const AZ::Vector3& direction) { @@ -542,11 +542,11 @@ namespace AzFramework }; } - bool PivotCameraInput::HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, const float scrollDelta) + bool OrbitCameraInput::HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, const float scrollDelta) { if (const auto* input = AZStd::get_if(&event)) { - if (input->m_channelId == m_pivotChannelId) + if (input->m_channelId == m_orbitChannelId) { if (input->m_state == InputChannel::State::Began) { @@ -561,13 +561,13 @@ namespace AzFramework if (Active()) { - return m_pivotCameras.HandleEvents(event, cursorDelta, scrollDelta); + return m_orbitCameras.HandleEvents(event, cursorDelta, scrollDelta); } return !Idle(); } - Camera PivotCameraInput::StepCamera( + Camera OrbitCameraInput::StepCamera( const Camera& targetCamera, const ScreenVector& cursorDelta, const float scrollDelta, const float deltaTime) { Camera nextCamera = targetCamera; @@ -581,12 +581,12 @@ namespace AzFramework if (Active()) { MovePivotDetached(nextCamera, m_pivotFn(targetCamera.Translation(), targetCamera.Rotation().GetBasisY())); - nextCamera = m_pivotCameras.StepCamera(nextCamera, cursorDelta, scrollDelta, deltaTime); + nextCamera = m_orbitCameras.StepCamera(nextCamera, cursorDelta, scrollDelta, deltaTime); } if (Ending()) { - m_pivotCameras.Reset(); + m_orbitCameras.Reset(); nextCamera.m_pivot = nextCamera.Translation(); nextCamera.m_offset = AZ::Vector3::CreateZero(); @@ -595,12 +595,12 @@ namespace AzFramework return nextCamera; } - void PivotCameraInput::SetPivotInputChannelId(const InputChannelId& pivotChanneId) + void OrbitCameraInput::SetOrbitInputChannelId(const InputChannelId& orbitChanneId) { - m_pivotChannelId = pivotChanneId; + m_orbitChannelId = orbitChanneId; } - PivotDollyScrollCameraInput::PivotDollyScrollCameraInput() + OrbitDollyScrollCameraInput::OrbitDollyScrollCameraInput() { m_scrollSpeedFn = []() constexpr { @@ -608,7 +608,7 @@ namespace AzFramework }; } - bool PivotDollyScrollCameraInput::HandleEvents( + bool OrbitDollyScrollCameraInput::HandleEvents( const InputEvent& event, [[maybe_unused]] const ScreenVector& cursorDelta, [[maybe_unused]] const float scrollDelta) { if (const auto* scroll = AZStd::get_if(&event)) @@ -619,36 +619,45 @@ namespace AzFramework return !Idle(); } - static Camera PivotDolly(const Camera& targetCamera, const float delta) + static Camera OrbitDolly(const Camera& targetCamera, const float delta) { Camera nextCamera = targetCamera; - const auto pivotDirection = targetCamera.m_offset.GetNormalized(); - nextCamera.m_offset -= pivotDirection * delta; - const auto pivotDot = targetCamera.m_offset.Dot(nextCamera.m_offset); - const auto distance = nextCamera.m_offset.GetLength() * AZ::GetSign(pivotDot); + // handle case where pivot and offset may be the same to begin with + // choose negative y-axis for offset to default to moving the camera backwards from the pivot (standard centered pivot behavior) + const auto pivotDirection = [&targetCamera] + { + if (const auto offsetLength = targetCamera.m_offset.GetLength(); AZ::IsCloseMag(offsetLength, 0.0f)) + { + return -AZ::Vector3::CreateAxisY(); + } + else + { + return targetCamera.m_offset / offsetLength; + } + }(); - const auto minDistance = 0.01f; - if (distance < minDistance || pivotDot < 0.0f) + nextCamera.m_offset -= pivotDirection * delta; + if (pivotDirection.Dot(nextCamera.m_offset) < 0.0f) { - nextCamera.m_offset = pivotDirection * minDistance; + nextCamera.m_offset = pivotDirection * 0.001f; } return nextCamera; } - Camera PivotDollyScrollCameraInput::StepCamera( + Camera OrbitDollyScrollCameraInput::StepCamera( const Camera& targetCamera, [[maybe_unused]] const ScreenVector& cursorDelta, const float scrollDelta, [[maybe_unused]] const float deltaTime) { - const auto nextCamera = PivotDolly(targetCamera, aznumeric_cast(scrollDelta) * m_scrollSpeedFn()); + const auto nextCamera = OrbitDolly(targetCamera, aznumeric_cast(scrollDelta) * m_scrollSpeedFn()); EndActivation(); return nextCamera; } - PivotDollyMotionCameraInput::PivotDollyMotionCameraInput(const InputChannelId& dollyChannelId) + OrbitDollyMotionCameraInput::OrbitDollyMotionCameraInput(const InputChannelId& dollyChannelId) : m_dollyChannelId(dollyChannelId) { m_motionSpeedFn = []() constexpr @@ -657,28 +666,28 @@ namespace AzFramework }; } - bool PivotDollyMotionCameraInput::HandleEvents( + bool OrbitDollyMotionCameraInput::HandleEvents( const InputEvent& event, [[maybe_unused]] const ScreenVector& cursorDelta, [[maybe_unused]] const float scrollDelta) { HandleActivationEvents(event, m_dollyChannelId, cursorDelta, m_clickDetector, *this); return CameraInputUpdatingAfterMotion(*this); } - Camera PivotDollyMotionCameraInput::StepCamera( + Camera OrbitDollyMotionCameraInput::StepCamera( const Camera& targetCamera, const ScreenVector& cursorDelta, [[maybe_unused]] const float scrollDelta, [[maybe_unused]] const float deltaTime) { - return PivotDolly(targetCamera, aznumeric_cast(cursorDelta.m_y) * m_motionSpeedFn()); + return OrbitDolly(targetCamera, aznumeric_cast(cursorDelta.m_y) * m_motionSpeedFn()); } - void PivotDollyMotionCameraInput::SetDollyInputChannelId(const InputChannelId& dollyChannelId) + void OrbitDollyMotionCameraInput::SetDollyInputChannelId(const InputChannelId& dollyChannelId) { m_dollyChannelId = dollyChannelId; } - ScrollTranslationCameraInput::ScrollTranslationCameraInput() + LookScrollTranslationCameraInput::LookScrollTranslationCameraInput() { m_scrollSpeedFn = []() constexpr { @@ -686,7 +695,7 @@ namespace AzFramework }; } - bool ScrollTranslationCameraInput::HandleEvents( + bool LookScrollTranslationCameraInput::HandleEvents( const InputEvent& event, [[maybe_unused]] const ScreenVector& cursorDelta, [[maybe_unused]] const float scrollDelta) { if (const auto* scroll = AZStd::get_if(&event)) @@ -697,7 +706,7 @@ namespace AzFramework return !Idle(); } - Camera ScrollTranslationCameraInput::StepCamera( + Camera LookScrollTranslationCameraInput::StepCamera( const Camera& targetCamera, [[maybe_unused]] const ScreenVector& cursorDelta, const float scrollDelta, @@ -771,6 +780,73 @@ namespace AzFramework return camera; } + FocusCameraInput::FocusCameraInput(const InputChannelId& focusChannelId, FocusOffsetFn offsetFn) + : m_focusChannelId(focusChannelId) + , m_offsetFn(offsetFn) + { + } + + bool FocusCameraInput::HandleEvents( + const InputEvent& event, [[maybe_unused]] const ScreenVector& cursorDelta, [[maybe_unused]] float scrollDelta) + { + if (const auto* input = AZStd::get_if(&event)) + { + if (input->m_channelId == m_focusChannelId && input->m_state == InputChannel::State::Began) + { + BeginActivation(); + } + } + + return !Idle(); + } + + Camera FocusCameraInput::StepCamera( + const Camera& targetCamera, + [[maybe_unused]] const ScreenVector& cursorDelta, + [[maybe_unused]] float scrollDelta, + [[maybe_unused]] float deltaTime) + { + if (Beginning()) + { + // as the camera starts, record the camera we would like to end up as + m_nextCamera.m_offset = m_offsetFn(m_pivotFn().GetDistance(targetCamera.Translation())); + const auto angles = + EulerAngles(AZ::Matrix3x3::CreateFromMatrix3x4(AZ::Matrix3x4::CreateLookAt(targetCamera.Translation(), m_pivotFn()))); + m_nextCamera.m_pitch = angles.GetX(); + m_nextCamera.m_yaw = angles.GetZ(); + m_nextCamera.m_pivot = targetCamera.m_pivot; + } + + // end the behavior when the camera is in alignment + if (AZ::IsCloseMag(targetCamera.m_pitch, m_nextCamera.m_pitch) && AZ::IsCloseMag(targetCamera.m_yaw, m_nextCamera.m_yaw)) + { + EndActivation(); + } + + return m_nextCamera; + } + + void FocusCameraInput::SetPivotFn(PivotFn pivotFn) + { + m_pivotFn = AZStd::move(pivotFn); + } + + void FocusCameraInput::SetFocusInputChannelId(const InputChannelId& focusChannelId) + { + m_focusChannelId = focusChannelId; + } + + bool CustomCameraInput::HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, const float scrollDelta) + { + return m_handleEventsFn(*this, event, cursorDelta, scrollDelta); + } + + Camera CustomCameraInput::StepCamera( + const Camera& targetCamera, const ScreenVector& cursorDelta, const float scrollDelta, const float deltaTime) + { + return m_stepCameraFn(*this, targetCamera, cursorDelta, scrollDelta, deltaTime); + } + InputEvent BuildInputEvent(const InputChannel& inputChannel, const WindowSize& windowSize) { const auto& inputChannelId = inputChannel.GetInputChannelId(); diff --git a/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.h b/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.h index c44d951292..2b7cc3ea9e 100644 --- a/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.h +++ b/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.h @@ -30,8 +30,11 @@ namespace AzFramework AZ::Vector3 EulerAngles(const AZ::Matrix3x3& orientation); //! A simple camera representation using spherical coordinates as input (pitch, yaw, pivot and offset). - //! The cameras transform and view can be obtained through accessor functions that use the internal + //! The camera's transform and view can be obtained through accessor functions that use the internal //! spherical coordinates to calculate the position and orientation. + //! @note Modifying m_pivot directly and leaving m_offset as zero will produce a free look camera effect, giving + //! m_offset a value (e.g. in negative Y only) will produce an orbit camera effect, modifying X and Z of m_offset + //! will further alter the camera translation in relation to m_pivot so it appears off center. struct Camera { AZ::Vector3 m_pivot = AZ::Vector3::CreateZero(); //!< Pivot point to rotate about (modified in world space). @@ -291,7 +294,7 @@ namespace AzFramework Cameras m_cameras; //!< Represents a collection of camera inputs that together provide a camera controller. private: - ScreenVector m_motionDelta; //!< The delta used for look/pivot/pan (rotation + translation) - two dimensional. + ScreenVector m_motionDelta; //!< The delta used for look/orbit/pan (rotation + translation) - two dimensional. CursorState m_cursorState; //!< The current and previous position of the cursor (used to calculate movement delta). float m_scrollDelta = 0.0f; //!< The delta used for dolly/movement (translation) - one dimensional. bool m_handlingEvents = false; //!< Is the camera system currently handling events (events are consumed and not propagated). @@ -316,7 +319,7 @@ namespace AzFramework return AZStd::fmod(yaw + AZ::Constants::TwoPi, AZ::Constants::TwoPi); } - //! A camera input to handle motion deltas that can rotate or pivot the camera. + //! A camera input to handle motion deltas that can change the orientation of the camera (update pitch and yaw). class RotateCameraInput : public CameraInput { public: @@ -348,15 +351,16 @@ namespace AzFramework //! PanAxes build function that will return a pair of pan axes depending on the camera orientation. using PanAxesFn = AZStd::function; - //! PanAxes to use while in 'look' camera behavior (free look). + //! PanAxes to use while in 'look' or 'orbit' camera behavior. inline PanAxes LookPan(const Camera& camera) { const AZ::Matrix3x3 orientation = camera.Rotation(); return { orientation.GetBasisX(), orientation.GetBasisZ() }; } - //! PanAxes to use while in 'pivot' camera behavior. - inline PanAxes PivotPan(const Camera& camera) + //! Optional PanAxes to use while in 'orbit' camera behavior. + //! @note This will move the camera in the local X/Y plane instead of usual X/Z plane. + inline PanAxes OrbitPan(const Camera& camera) { const AZ::Matrix3x3 orientation = camera.Rotation(); @@ -370,14 +374,23 @@ namespace AzFramework return { basisX, basisY }; } + //! TranslationDeltaFn is used by PanCameraInput and TranslateCameraInput + //! @note Choose the appropriate function if the behavior should be operating as a free look camera (TranslatePivotLook) + //! or an orbit camera (TranslateOffsetOrbit). using TranslationDeltaFn = AZStd::function; - inline void TranslatePivot(Camera& camera, const AZ::Vector3& delta) + //! Update the pivot camera position. + //! @note delta will need to have been transformed to world space, e.g. To move the camera right, (1, 0, 0) must + //! first be transformed by the orientation of the camera before being applied to m_pivot. + inline void TranslatePivotLook(Camera& camera, const AZ::Vector3& delta) { camera.m_pivot += delta; } - inline void TranslateOffset(Camera& camera, const AZ::Vector3& delta) + //! Update the offset camera position. + //! @note delta still needs to be transformed to world space (as with TranslatePivotLook) but internally this is undone + //! to be performed in local space when being applied to m_offset. + inline void TranslateOffsetOrbit(Camera& camera, const AZ::Vector3& delta) { camera.m_offset += camera.View().TransformVector(delta); } @@ -409,7 +422,7 @@ namespace AzFramework //! Axes to use while translating the camera. using TranslationAxesFn = AZStd::function; - //! TranslationAxes to use while in 'look' camera behavior (free look). + //! TranslationAxes to use while in 'look' or 'orbit' camera behavior. inline AZ::Matrix3x3 LookTranslation(const Camera& camera) { const AZ::Matrix3x3 orientation = camera.Rotation(); @@ -421,8 +434,8 @@ namespace AzFramework return AZ::Matrix3x3::CreateFromColumns(basisX, basisY, basisZ); } - //! TranslationAxes to use while in 'pivot' camera behavior. - inline AZ::Matrix3x3 PivotTranslation(const Camera& camera) + //! Optional TranslationAxes to use while in 'orbit' camera behavior. + inline AZ::Matrix3x3 OrbitTranslation(const Camera& camera) { const AZ::Matrix3x3 orientation = camera.Rotation(); @@ -535,11 +548,11 @@ namespace AzFramework bool m_boost = false; //!< Is the translation speed currently being multiplied/scaled upwards. }; - //! A camera input to handle discrete scroll events that can modify the camera pivot distance. - class PivotDollyScrollCameraInput : public CameraInput + //! A camera input to handle discrete scroll events that can modify the camera offset. + class OrbitDollyScrollCameraInput : public CameraInput { public: - PivotDollyScrollCameraInput(); + OrbitDollyScrollCameraInput(); // CameraInput overrides ... bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override; @@ -548,11 +561,11 @@ namespace AzFramework AZStd::function m_scrollSpeedFn; }; - //! A camera input to handle motion deltas that can modify the camera pivot distance. - class PivotDollyMotionCameraInput : public CameraInput + //! A camera input to handle motion deltas that can modify the camera offset. + class OrbitDollyMotionCameraInput : public CameraInput { public: - explicit PivotDollyMotionCameraInput(const InputChannelId& dollyChannelId); + explicit OrbitDollyMotionCameraInput(const InputChannelId& dollyChannelId); // CameraInput overrides ... bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override; @@ -569,10 +582,10 @@ namespace AzFramework }; //! A camera input to handle discrete scroll events that can scroll (translate) the camera along its forward axis. - class ScrollTranslationCameraInput : public CameraInput + class LookScrollTranslationCameraInput : public CameraInput { public: - ScrollTranslationCameraInput(); + LookScrollTranslationCameraInput(); // CameraInput overrides ... bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override; @@ -583,40 +596,96 @@ namespace AzFramework //! A camera input that doubles as its own set of camera inputs. //! It is 'exclusive', so does not overlap with other sibling camera inputs - it runs its own set of camera inputs as 'children'. - class PivotCameraInput : public CameraInput + class OrbitCameraInput : public CameraInput { public: using PivotFn = AZStd::function; - explicit PivotCameraInput(const InputChannelId& pivotChannelId); + explicit OrbitCameraInput(const InputChannelId& orbitChannelId); // CameraInput overrides ... bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override; Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) override; bool Exclusive() const override; - void SetPivotInputChannelId(const InputChannelId& pivotChanneId); + void SetOrbitInputChannelId(const InputChannelId& orbitChanneId); - Cameras m_pivotCameras; //!< The camera inputs to run when this camera input is active (only these will run as it is exclusive). + Cameras m_orbitCameras; //!< The camera inputs to run when this camera input is active (only these will run as it is exclusive). //! Override the default behavior for how a pivot point is calculated. void SetPivotFn(PivotFn pivotFn); private: - InputChannelId m_pivotChannelId; //!< Input channel to begin the pivot camera input. - PivotFn m_pivotFn; //!< The pivot position to use for this pivot camera (how is the pivot point calculated/retrieved). + InputChannelId m_orbitChannelId; //!< Input channel to begin the orbit camera input. + PivotFn m_pivotFn; //!< The pivot position to use for this orbit camera (how is the pivot point calculated/retrieved). }; - inline void PivotCameraInput::SetPivotFn(PivotFn pivotFn) + inline void OrbitCameraInput::SetPivotFn(PivotFn pivotFn) { m_pivotFn = AZStd::move(pivotFn); } - inline bool PivotCameraInput::Exclusive() const + inline bool OrbitCameraInput::Exclusive() const { return true; } + //! Callback to use for FocusCameraInput when a free look camera is being used. + //! @note This is when offset is zero. + inline AZ::Vector3 FocusLook(float) + { + return AZ::Vector3::CreateZero(); + } + + //! Callback to use for FocusCameraInput when a orbit camera is being used. + //! @note This is when offset is non zero. + inline AZ::Vector3 FocusOrbit(const float length) + { + return AZ::Vector3::CreateAxisY(-length); + } + + using FocusOffsetFn = AZStd::function; + + //! A focus behavior to align the camera view to the position returned by the pivot function. + //! @note This only alters the camera orientation, the translation is unaffected. + class FocusCameraInput : public CameraInput + { + public: + using PivotFn = AZStd::function; + + FocusCameraInput(const InputChannelId& focusChannelId, FocusOffsetFn offsetFn); + + // CameraInput overrides ... + bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override; + Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) override; + + //! Override the default behavior for how a pivot point is calculated. + void SetPivotFn(PivotFn pivotFn); + + void SetFocusInputChannelId(const InputChannelId& focusChannelId); + + private: + InputChannelId m_focusChannelId; //!< Input channel to begin the focus camera input. + Camera m_nextCamera; + PivotFn m_pivotFn; + FocusOffsetFn m_offsetFn; + }; + + //! Provides a CameraInput type that can be implemented without needing to create a new type deriving from CameraInput. + //! This can be very useful for specific use cases that are less generally applicable. + class CustomCameraInput : public CameraInput + { + public: + // CameraInput overrides ... + bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override; + Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) override; + + //! HandleEvents delegates directly to m_handleEventsFn. + AZStd::function m_handleEventsFn; + //! StepCamera delegates directly to m_stepCameraFn. + AZStd::function m_stepCameraFn; + }; + //! Map from a generic InputChannel event to a camera specific InputEvent. InputEvent BuildInputEvent(const InputChannel& inputChannel, const WindowSize& windowSize); } // namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/azframework_files.cmake b/Code/Framework/AzFramework/AzFramework/azframework_files.cmake index e752e5ae04..c5616d84c0 100644 --- a/Code/Framework/AzFramework/AzFramework/azframework_files.cmake +++ b/Code/Framework/AzFramework/AzFramework/azframework_files.cmake @@ -167,6 +167,10 @@ set(FILES Logging/MissingAssetLogger.cpp Logging/MissingAssetLogger.h Logging/MissingAssetNotificationBus.h + Matchmaking/IMatchmakingRequests.h + Matchmaking/MatchmakingRequests.cpp + Matchmaking/MatchmakingRequests.h + Matchmaking/MatchmakingNotifications.h Scene/Scene.h Scene/Scene.inl Scene/Scene.cpp @@ -181,8 +185,9 @@ set(FILES Script/ScriptRemoteDebugging.cpp Script/ScriptRemoteDebugging.h Session/ISessionHandlingRequests.h - Session/ISessionRequests.cpp Session/ISessionRequests.h + Session/SessionRequests.cpp + Session/SessionRequests.h Session/SessionConfig.cpp Session/SessionConfig.h Session/SessionNotifications.h diff --git a/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbInputDeviceKeyboard.cpp b/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbInputDeviceKeyboard.cpp index c971c3b793..de326df200 100644 --- a/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbInputDeviceKeyboard.cpp +++ b/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbInputDeviceKeyboard.cpp @@ -20,6 +20,15 @@ namespace AzFramework { + // xcb-xkb does not provide a generic event type, so we define our own. + // These fields are enough to get to the xkbType field, which can then be + // read to typecast the event to the right concrete type. + struct XcbXkbGenericEventT + { + uint8_t response_type; + uint8_t xkbType; + }; + XcbInputDeviceKeyboard::XcbInputDeviceKeyboard(InputDeviceKeyboard& inputDevice) : InputDeviceKeyboard::Implementation(inputDevice) { @@ -39,19 +48,22 @@ namespace AzFramework return; } - XcbStdFreePtr xkbUseExtensionReply{ - xcb_xkb_use_extension_reply(connection, xcb_xkb_use_extension(connection, 1, 0), nullptr) - }; - if (!xkbUseExtensionReply) + int initializeXkbExtensionSuccess = xkb_x11_setup_xkb_extension( + connection, + 1, + 0, + XKB_X11_SETUP_XKB_EXTENSION_NO_FLAGS, + nullptr, + nullptr, + &m_xkbEventCode, + nullptr + ); + + if (!initializeXkbExtensionSuccess) { AZ_Warning("ApplicationLinux", false, "Failed to initialize the xkb extension"); return; } - if (!xkbUseExtensionReply->supported) - { - AZ_Warning("ApplicationLinux", false, "The X server does not support the xkb extension"); - return; - } m_coreDeviceId = xkb_x11_get_core_keyboard_device_id(connection); @@ -59,6 +71,43 @@ namespace AzFramework m_xkbKeymap.reset(xkb_x11_keymap_new_from_device(m_xkbContext.get(), connection, m_coreDeviceId, XKB_KEYMAP_COMPILE_NO_FLAGS)); m_xkbState.reset(xkb_x11_state_new_from_device(m_xkbKeymap.get(), connection, m_coreDeviceId)); + const uint16_t affectMap = + XCB_XKB_MAP_PART_KEY_TYPES + | XCB_XKB_MAP_PART_KEY_SYMS + | XCB_XKB_MAP_PART_MODIFIER_MAP + | XCB_XKB_MAP_PART_EXPLICIT_COMPONENTS + | XCB_XKB_MAP_PART_KEY_ACTIONS + | XCB_XKB_MAP_PART_KEY_BEHAVIORS + | XCB_XKB_MAP_PART_VIRTUAL_MODS + | XCB_XKB_MAP_PART_VIRTUAL_MOD_MAP + ; + + const uint16_t selectedEvents = + XCB_XKB_EVENT_TYPE_NEW_KEYBOARD_NOTIFY + | XCB_XKB_EVENT_TYPE_MAP_NOTIFY + | XCB_XKB_EVENT_TYPE_STATE_NOTIFY + ; + + XcbStdFreePtr error{xcb_request_check( + connection, + xcb_xkb_select_events( + connection, + /* deviceSpec = */ XCB_XKB_ID_USE_CORE_KBD, + /* affectWhich = */ selectedEvents, + /* clear = */ 0, + /* selectAll = */ selectedEvents, + /* affectMap = */ affectMap, + /* map = */ affectMap, + /* details = */ nullptr + ) + )}; + + if (error) + { + AZ_Warning("ApplicationLinux", false, "failed to select notify events from XKB"); + return; + } + m_initialized = true; } @@ -70,15 +119,17 @@ namespace AzFramework bool XcbInputDeviceKeyboard::HasTextEntryStarted() const { - return false; + return m_hasTextEntryStarted; } void XcbInputDeviceKeyboard::TextEntryStart(const InputDeviceKeyboard::VirtualKeyboardOptions& options) { + m_hasTextEntryStarted = true; } void XcbInputDeviceKeyboard::TextEntryStop() { + m_hasTextEntryStarted = false; } void XcbInputDeviceKeyboard::TickInputDevice() @@ -93,30 +144,45 @@ namespace AzFramework return; } - switch (event->response_type & ~0x80) - { - case XCB_KEY_PRESS: + const auto responseType = event->response_type & ~0x80; + if (responseType == XCB_KEY_PRESS) { - auto* keyPress = reinterpret_cast(event); + const auto* keyPress = reinterpret_cast(event); + { + auto text = TextFromKeycode(m_xkbState.get(), keyPress->detail); + if (!text.empty()) + { + QueueRawTextEvent(AZStd::move(text)); + } + } - const InputChannelId* key = InputChannelFromKeyEvent(keyPress->detail); - if (key) + if (const InputChannelId* key = InputChannelFromKeyEvent(keyPress->detail)) { QueueRawKeyEvent(*key, true); } - break; } - case XCB_KEY_RELEASE: + else if (responseType == XCB_KEY_RELEASE) { - auto* keyRelease = reinterpret_cast(event); + const auto* keyRelease = reinterpret_cast(event); const InputChannelId* key = InputChannelFromKeyEvent(keyRelease->detail); if (key) { QueueRawKeyEvent(*key, false); } - break; } + else if (responseType == m_xkbEventCode) + { + const auto* xkbEvent = reinterpret_cast(event); + switch (xkbEvent->xkbType) + { + case XCB_XKB_STATE_NOTIFY: + { + const auto* stateNotifyEvent = reinterpret_cast(event); + UpdateState(stateNotifyEvent); + break; + } + } } } @@ -268,4 +334,34 @@ namespace AzFramework default: return nullptr; } } + + AZStd::string XcbInputDeviceKeyboard::TextFromKeycode(xkb_state* state, xkb_keycode_t code) + { + // Find out how much of a buffer we need + const size_t size = xkb_state_key_get_utf8(state, code, nullptr, 0); + if (!size) + { + return {}; + } + // xkb_state_key_get_utf8 will null-terminate the resulting string, and + // will truncate the result to `size - 1` if there is not enough space + // for the null byte. The first call returns the size of the resulting + // string without including the null byte. AZStd::string internally + // includes space for the null byte, but that is not included in its + // `size()`. xkb_state_key_get_utf8 will always set `buf[size - 1] = + // 0`, so add 1 to `chars.size()` to include that internal null byte in + // the string. + AZStd::string chars; + chars.resize_no_construct(size); + xkb_state_key_get_utf8(state, code, chars.data(), chars.size() + 1); + return chars; + } + + void XcbInputDeviceKeyboard::UpdateState(const xcb_xkb_state_notify_event_t* state) + { + if (m_initialized) + { + xkb_state_update_mask(m_xkbState.get(), state->baseMods, state->latchedMods, state->lockedMods, state->baseGroup, state->latchedGroup, state->lockedGroup); + } + } } // namespace AzFramework diff --git a/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbInputDeviceKeyboard.h b/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbInputDeviceKeyboard.h index de65941a67..00383abf6c 100644 --- a/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbInputDeviceKeyboard.h +++ b/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbInputDeviceKeyboard.h @@ -13,6 +13,8 @@ #include #include +struct xcb_xkb_state_notify_event_t; + namespace AzFramework { class XcbInputDeviceKeyboard @@ -37,10 +39,16 @@ namespace AzFramework private: [[nodiscard]] const InputChannelId* InputChannelFromKeyEvent(xcb_keycode_t code) const; + static AZStd::string TextFromKeycode(xkb_state* state, xkb_keycode_t code); + + void UpdateState(const xcb_xkb_state_notify_event_t* state); + XcbUniquePtr m_xkbContext; XcbUniquePtr m_xkbKeymap; XcbUniquePtr m_xkbState; int m_coreDeviceId{-1}; + uint8_t m_xkbEventCode{0}; bool m_initialized{false}; + bool m_hasTextEntryStarted{false}; }; } // namespace AzFramework diff --git a/Code/Framework/AzFramework/Tests/CameraInputTests.cpp b/Code/Framework/AzFramework/Tests/CameraInputTests.cpp index 1e0c805587..a89fb0bd84 100644 --- a/Code/Framework/AzFramework/Tests/CameraInputTests.cpp +++ b/Code/Framework/AzFramework/Tests/CameraInputTests.cpp @@ -53,31 +53,31 @@ namespace UnitTest }; m_firstPersonTranslateCamera = AZStd::make_shared( - m_translateCameraInputChannelIds, AzFramework::LookTranslation, AzFramework::TranslatePivot); + m_translateCameraInputChannelIds, AzFramework::LookTranslation, AzFramework::TranslatePivotLook); - m_pivotCamera = AZStd::make_shared(m_pivotChannelId); - m_pivotCamera->SetPivotFn( + m_orbitCamera = AZStd::make_shared(m_orbitChannelId); + m_orbitCamera->SetPivotFn( [this](const AZ::Vector3&, const AZ::Vector3&) { return m_pivot; }); - auto pivotRotateCamera = AZStd::make_shared(AzFramework::InputDeviceMouse::Button::Left); + auto orbitRotateCamera = AZStd::make_shared(AzFramework::InputDeviceMouse::Button::Left); // set rotate speed to be a value that will scale motion delta (pixels moved) by a thousandth. - pivotRotateCamera->m_rotateSpeedFn = []() + orbitRotateCamera->m_rotateSpeedFn = []() { return 0.001f; }; - auto pivotTranslateCamera = AZStd::make_shared( - m_translateCameraInputChannelIds, AzFramework::PivotTranslation, AzFramework::TranslateOffset); + auto orbitTranslateCamera = AZStd::make_shared( + m_translateCameraInputChannelIds, AzFramework::OrbitTranslation, AzFramework::TranslateOffsetOrbit); - m_pivotCamera->m_pivotCameras.AddCamera(pivotRotateCamera); - m_pivotCamera->m_pivotCameras.AddCamera(pivotTranslateCamera); + m_orbitCamera->m_orbitCameras.AddCamera(orbitRotateCamera); + m_orbitCamera->m_orbitCameras.AddCamera(orbitTranslateCamera); m_cameraSystem->m_cameras.AddCamera(m_firstPersonRotateCamera); m_cameraSystem->m_cameras.AddCamera(m_firstPersonTranslateCamera); - m_cameraSystem->m_cameras.AddCamera(m_pivotCamera); + m_cameraSystem->m_cameras.AddCamera(m_orbitCamera); // these tests rely on using motion delta, not cursor positions (default is true) AzFramework::ed_cameraSystemUseCursor = false; @@ -87,7 +87,7 @@ namespace UnitTest { AzFramework::ed_cameraSystemUseCursor = true; - m_pivotCamera.reset(); + m_orbitCamera.reset(); m_firstPersonRotateCamera.reset(); m_firstPersonTranslateCamera.reset(); @@ -97,11 +97,11 @@ namespace UnitTest AllocatorsTestFixture::TearDown(); } - AzFramework::InputChannelId m_pivotChannelId = AzFramework::InputChannelId("keyboard_key_modifier_alt_l"); + AzFramework::InputChannelId m_orbitChannelId = AzFramework::InputChannelId("keyboard_key_modifier_alt_l"); AzFramework::TranslateCameraInputChannelIds m_translateCameraInputChannelIds; AZStd::shared_ptr m_firstPersonRotateCamera; AZStd::shared_ptr m_firstPersonTranslateCamera; - AZStd::shared_ptr m_pivotCamera; + AZStd::shared_ptr m_orbitCamera; AZ::Vector3 m_pivot = AZ::Vector3::CreateZero(); //! This is approximately Pi/2 * 1000 - this can be used to rotate the camera 90 degrees (pitch or yaw based @@ -109,17 +109,17 @@ namespace UnitTest inline static const int PixelMotionDelta = 1570; }; - TEST_F(CameraInputFixture, BeginAndEndPivotCameraInputConsumesCorrectEvents) + TEST_F(CameraInputFixture, BeginAndEndOrbitCameraInputConsumesCorrectEvents) { - // begin pivot camera + // begin orbit camera const bool consumed1 = HandleEventAndUpdate(AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceKeyboard::Key::ModifierAltL, AzFramework::InputChannel::State::Began }); - // begin listening for pivot rotate (click detector) - event is not consumed + // begin listening for orbit rotate (click detector) - event is not consumed const bool consumed2 = HandleEventAndUpdate( AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceMouse::Button::Left, AzFramework::InputChannel::State::Began }); - // begin pivot rotate (mouse has moved sufficient distance to initiate) + // begin orbit rotate (mouse has moved sufficient distance to initiate) const bool consumed3 = HandleEventAndUpdate(AzFramework::HorizontalMotionEvent{ 5 }); - // end pivot (mouse up) - event is not consumed + // end orbit (mouse up) - event is not consumed const bool consumed4 = HandleEventAndUpdate( AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceMouse::Button::Left, AzFramework::InputChannel::State::Ended }); @@ -260,10 +260,10 @@ namespace UnitTest EXPECT_TRUE(activationEnded); } - TEST_F(CameraInputFixture, PivotCameraInputHandlesLookAtPointAndSelfAtSamePositionWhenPivoting) + TEST_F(CameraInputFixture, OrbitCameraInputHandlesLookAtPointAndSelfAtSamePositionWhenOrbiting) { // create pathological lookAtFn that just returns the same position as the camera - m_pivotCamera->SetPivotFn( + m_orbitCamera->SetPivotFn( [](const AZ::Vector3& position, [[maybe_unused]] const AZ::Vector3& direction) { return position; @@ -275,7 +275,7 @@ namespace UnitTest AZ::Transform::CreateFromQuaternionAndTranslation( AZ::Quaternion::CreateFromEulerAnglesDegrees(AZ::Vector3(0.0f, 0.0f, 90.0f)), expectedCameraPosition)); - HandleEventAndUpdate(AzFramework::DiscreteInputEvent{ m_pivotChannelId, AzFramework::InputChannel::State::Began }); + HandleEventAndUpdate(AzFramework::DiscreteInputEvent{ m_orbitChannelId, AzFramework::InputChannel::State::Began }); // verify the camera yaw has not changed and pivot point matches the expected camera position using ::testing::FloatNear; @@ -321,14 +321,14 @@ namespace UnitTest EXPECT_THAT(m_camera.m_offset, IsClose(AZ::Vector3::CreateZero())); } - TEST_F(CameraInputFixture, PivotRotateCameraInputRotatesPitchOffsetByNinetyDegreesWithRequiredPixelDelta) + TEST_F(CameraInputFixture, OrbitRotateCameraInputRotatesPitchOffsetByNinetyDegreesWithRequiredPixelDelta) { const auto cameraStartingPosition = AZ::Vector3::CreateAxisY(-20.0f); m_targetCamera.m_pivot = cameraStartingPosition; m_pivot = AZ::Vector3::CreateAxisY(-10.0f); - HandleEventAndUpdate(AzFramework::DiscreteInputEvent{ m_pivotChannelId, AzFramework::InputChannel::State::Began }); + HandleEventAndUpdate(AzFramework::DiscreteInputEvent{ m_orbitChannelId, AzFramework::InputChannel::State::Began }); HandleEventAndUpdate( AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceMouse::Button::Left, AzFramework::InputChannel::State::Began }); HandleEventAndUpdate(AzFramework::VerticalMotionEvent{ PixelMotionDelta }); @@ -344,14 +344,14 @@ namespace UnitTest EXPECT_THAT(m_camera.Translation(), IsCloseTolerance(expectedCameraEndingPosition, 0.01f)); } - TEST_F(CameraInputFixture, PivotRotateCameraInputRotatesYawOffsetByNinetyDegreesWithRequiredPixelDelta) + TEST_F(CameraInputFixture, OrbitRotateCameraInputRotatesYawOffsetByNinetyDegreesWithRequiredPixelDelta) { const auto cameraStartingPosition = AZ::Vector3(15.0f, -20.0f, 0.0f); m_targetCamera.m_pivot = cameraStartingPosition; m_pivot = AZ::Vector3(10.0f, -10.0f, 0.0f); - HandleEventAndUpdate(AzFramework::DiscreteInputEvent{ m_pivotChannelId, AzFramework::InputChannel::State::Began }); + HandleEventAndUpdate(AzFramework::DiscreteInputEvent{ m_orbitChannelId, AzFramework::InputChannel::State::Began }); HandleEventAndUpdate( AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceMouse::Button::Left, AzFramework::InputChannel::State::Began }); HandleEventAndUpdate(AzFramework::HorizontalMotionEvent{ -PixelMotionDelta }); diff --git a/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/Matchers.h b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/Matchers.h new file mode 100644 index 0000000000..77acb1c3e4 --- /dev/null +++ b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/Matchers.h @@ -0,0 +1,17 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include + +inline testing::PolymorphicMatcher> StrEq(const AZStd::string& str) +{ + return ::testing::MakePolymorphicMatcher(testing::internal::StrEqualityMatcher(str, true, true)); +} diff --git a/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/MockXcbInterface.cpp b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/MockXcbInterface.cpp index 64c3967fdc..b15809a4c6 100644 --- a/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/MockXcbInterface.cpp +++ b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/MockXcbInterface.cpp @@ -28,6 +28,10 @@ xcb_generic_event_t* xcb_poll_for_event(xcb_connection_t* c) { return MockXcbInterface::Instance()->xcb_poll_for_event(c); } +xcb_generic_error_t* xcb_request_check(xcb_connection_t* c, xcb_void_cookie_t cookie) +{ + return MockXcbInterface::Instance()->xcb_request_check(c, cookie); +} // ---------------------------------------------------------------------------- // xcb-xkb @@ -39,6 +43,10 @@ xcb_xkb_use_extension_reply_t* xcb_xkb_use_extension_reply(xcb_connection_t* c, { return MockXcbInterface::Instance()->xcb_xkb_use_extension_reply(c, cookie, e); } +xcb_void_cookie_t xcb_xkb_select_events(xcb_connection_t* c, xcb_xkb_device_spec_t deviceSpec, uint16_t affectWhich, uint16_t clear, uint16_t selectAll, uint16_t affectMap, uint16_t map, const void* details) +{ + return MockXcbInterface::Instance()->xcb_xkb_select_events(c, deviceSpec, affectWhich, clear, selectAll, affectMap, map, details); +} // ---------------------------------------------------------------------------- // xkb-x11 @@ -46,7 +54,7 @@ int32_t xkb_x11_get_core_keyboard_device_id(xcb_connection_t* connection) { return MockXcbInterface::Instance()->xkb_x11_get_core_keyboard_device_id(connection); } -struct xkb_keymap* xkb_x11_keymap_new_from_device(struct xkb_context* context, xcb_connection_t* connection, int32_t device_id, enum xkb_keymap_compile_flags flags) +xkb_keymap* xkb_x11_keymap_new_from_device(xkb_context* context, xcb_connection_t* connection, int32_t device_id, xkb_keymap_compile_flags flags) { return MockXcbInterface::Instance()->xkb_x11_keymap_new_from_device(context, connection, device_id, flags); } @@ -54,28 +62,58 @@ xkb_state* xkb_x11_state_new_from_device(xkb_keymap* keymap, xcb_connection_t* c { return MockXcbInterface::Instance()->xkb_x11_state_new_from_device(keymap, connection, device_id); } +int xkb_x11_setup_xkb_extension( + xcb_connection_t* connection, + uint16_t major_xkb_version, + uint16_t minor_xkb_version, + xkb_x11_setup_xkb_extension_flags flags, + uint16_t* major_xkb_version_out, + uint16_t* minor_xkb_version_out, + uint8_t* base_event_out, + uint8_t* base_error_out) +{ + return MockXcbInterface::Instance()->xkb_x11_setup_xkb_extension( + connection, major_xkb_version, minor_xkb_version, flags, major_xkb_version_out, minor_xkb_version_out, base_event_out, + base_error_out); +} // ---------------------------------------------------------------------------- // xkbcommon -xkb_context* xkb_context_new(enum xkb_context_flags flags) +xkb_context* xkb_context_new(xkb_context_flags flags) { return MockXcbInterface::Instance()->xkb_context_new(flags); } -void xkb_context_unref(xkb_context *context) +void xkb_context_unref(xkb_context* context) { return MockXcbInterface::Instance()->xkb_context_unref(context); } -void xkb_keymap_unref(xkb_keymap *keymap) +void xkb_keymap_unref(xkb_keymap* keymap) { return MockXcbInterface::Instance()->xkb_keymap_unref(keymap); } -void xkb_state_unref(xkb_state *state) +void xkb_state_unref(xkb_state* state) { return MockXcbInterface::Instance()->xkb_state_unref(state); } -xkb_keysym_t xkb_state_key_get_one_sym(xkb_state *state, xkb_keycode_t key) +xkb_keysym_t xkb_state_key_get_one_sym(xkb_state* state, xkb_keycode_t key) { return MockXcbInterface::Instance()->xkb_state_key_get_one_sym(state, key); } +int xkb_state_key_get_utf8(xkb_state* state, xkb_keycode_t key, char* buffer, size_t size) +{ + return MockXcbInterface::Instance()->xkb_state_key_get_utf8(state, key, buffer, size); +} +xkb_state_component xkb_state_update_mask( + xkb_state* state, + xkb_mod_mask_t depressed_mods, + xkb_mod_mask_t latched_mods, + xkb_mod_mask_t locked_mods, + xkb_layout_index_t depressed_layout, + xkb_layout_index_t latched_layout, + xkb_layout_index_t locked_layout) +{ + return MockXcbInterface::Instance()->xkb_state_update_mask( + state, depressed_mods, latched_mods, locked_mods, depressed_layout, latched_layout, locked_layout); +} } diff --git a/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/MockXcbInterface.h b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/MockXcbInterface.h index ecda005830..b57751344e 100644 --- a/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/MockXcbInterface.h +++ b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/MockXcbInterface.h @@ -17,6 +17,7 @@ #include #undef explicit #include +#include #include "Printers.h" @@ -35,6 +36,7 @@ struct xkb_keymap struct xkb_state { + xkb_mod_mask_t m_modifiers{}; }; class MockXcbInterface @@ -48,7 +50,7 @@ public: MockXcbInterface(MockXcbInterface&&) = delete; MockXcbInterface& operator=(const MockXcbInterface&) = delete; MockXcbInterface& operator=(MockXcbInterface&&) = delete; - ~MockXcbInterface() + virtual ~MockXcbInterface() { self = nullptr; } @@ -59,22 +61,27 @@ public: MOCK_CONST_METHOD2(xcb_connect, xcb_connection_t*(const char* displayname, int* screenp)); MOCK_CONST_METHOD1(xcb_disconnect, void(xcb_connection_t* c)); MOCK_CONST_METHOD1(xcb_poll_for_event, xcb_generic_event_t*(xcb_connection_t* c)); + MOCK_CONST_METHOD2(xcb_request_check, xcb_generic_error_t*(xcb_connection_t* c, xcb_void_cookie_t cookie)); // xcb-xkb MOCK_CONST_METHOD3(xcb_xkb_use_extension, xcb_xkb_use_extension_cookie_t(xcb_connection_t* c, uint16_t wantedMajor, uint16_t wantedMinor)); MOCK_CONST_METHOD3(xcb_xkb_use_extension_reply, xcb_xkb_use_extension_reply_t*(xcb_connection_t* c, xcb_xkb_use_extension_cookie_t cookie, xcb_generic_error_t** e)); + MOCK_CONST_METHOD8(xcb_xkb_select_events, xcb_void_cookie_t(xcb_connection_t* c, xcb_xkb_device_spec_t deviceSpec, uint16_t affectWhich, uint16_t clear, uint16_t selectAll, uint16_t affectMap, uint16_t map, const void* details)); // xkb-x11 MOCK_CONST_METHOD1(xkb_x11_get_core_keyboard_device_id, int32_t(xcb_connection_t* connection)); MOCK_CONST_METHOD4(xkb_x11_keymap_new_from_device, xkb_keymap*(xkb_context* context, xcb_connection_t* connection, int32_t device_id, xkb_keymap_compile_flags flags)); MOCK_CONST_METHOD3(xkb_x11_state_new_from_device, xkb_state*(xkb_keymap* keymap, xcb_connection_t* connection, int32_t device_id)); + MOCK_CONST_METHOD8(xkb_x11_setup_xkb_extension, int(xcb_connection_t* connection, uint16_t major_xkb_version, uint16_t minor_xkb_version, xkb_x11_setup_xkb_extension_flags flags, uint16_t* major_xkb_version_out, uint16_t* minor_xkb_version_out, uint8_t* base_event_out, uint8_t* base_error_out)); // xkbcommon MOCK_CONST_METHOD1(xkb_context_new, xkb_context*(xkb_context_flags flags)); MOCK_CONST_METHOD1(xkb_context_unref, void(xkb_context* context)); MOCK_CONST_METHOD1(xkb_keymap_unref, void(xkb_keymap* keymap)); MOCK_CONST_METHOD1(xkb_state_unref, void(xkb_state* state)); - MOCK_CONST_METHOD2(xkb_state_key_get_one_sym, xkb_keysym_t(xkb_state *state, xkb_keycode_t key)); + MOCK_CONST_METHOD2(xkb_state_key_get_one_sym, xkb_keysym_t(xkb_state* state, xkb_keycode_t key)); + MOCK_CONST_METHOD4(xkb_state_key_get_utf8, int(xkb_state* state, xkb_keycode_t key, char* buffer, size_t size)); + MOCK_CONST_METHOD7(xkb_state_update_mask, xkb_state_component(xkb_state* state, xkb_mod_mask_t depressed_mods, xkb_mod_mask_t latched_mods, xkb_mod_mask_t locked_mods, xkb_layout_index_t depressed_layout, xkb_layout_index_t latched_layout, xkb_layout_index_t locked_layout)); private: static inline MockXcbInterface* self = nullptr; diff --git a/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/XcbBaseTestFixture.cpp b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/XcbBaseTestFixture.cpp new file mode 100644 index 0000000000..937cb04e14 --- /dev/null +++ b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/XcbBaseTestFixture.cpp @@ -0,0 +1,25 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include "XcbBaseTestFixture.h" + +namespace AzFramework +{ + void XcbBaseTestFixture::SetUp() + { + using testing::Return; + using testing::_; + + testing::Test::SetUp(); + + EXPECT_CALL(m_interface, xcb_connect(_, _)) + .WillOnce(Return(&m_connection)); + EXPECT_CALL(m_interface, xcb_disconnect(&m_connection)) + .Times(1); + } +} // namespace AzFramework diff --git a/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/XcbBaseTestFixture.h b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/XcbBaseTestFixture.h new file mode 100644 index 0000000000..8e9b008fc1 --- /dev/null +++ b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/XcbBaseTestFixture.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include + +#include "MockXcbInterface.h" + +namespace AzFramework +{ + // Sets up mock behavior for the xcb library, providing an xcb_connection_t that is returned from a call to xcb_connect + class XcbBaseTestFixture + : public testing::Test + { + public: + void SetUp() override; + + protected: + testing::NiceMock m_interface; + xcb_connection_t m_connection{}; + }; +} // namespace AzFramework diff --git a/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/XcbInputDeviceKeyboardTests.cpp b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/XcbInputDeviceKeyboardTests.cpp index 1b494c9525..3abbcdc564 100644 --- a/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/XcbInputDeviceKeyboardTests.cpp +++ b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/XcbInputDeviceKeyboardTests.cpp @@ -6,6 +6,7 @@ * */ +#include #include #include @@ -13,8 +14,11 @@ #include #include +#include #include "MockXcbInterface.h" +#include "Matchers.h" #include "Actions.h" +#include "XcbBaseTestFixture.h" template xcb_generic_event_t MakeEvent(T event) @@ -24,26 +28,126 @@ xcb_generic_event_t MakeEvent(T event) namespace AzFramework { - TEST(XcbInputDeviceKeyboard, InputChannelsUpdateStateFromXcbEvents) + // Sets up default behavior for mock keyboard responses to xcb methods + class XcbInputDeviceKeyboardTests + : public XcbBaseTestFixture { - using testing::Return; - using testing::Eq; - using testing::_; - MockXcbInterface interface; + void SetUp() override + { + using testing::Return; + using testing::SetArgPointee; + using testing::_; + + XcbBaseTestFixture::SetUp(); + EXPECT_CALL(m_interface, xkb_context_new(XKB_CONTEXT_NO_FLAGS)) + .WillOnce(Return(&m_xkbContext)); + EXPECT_CALL(m_interface, xkb_context_unref(&m_xkbContext)) + .Times(1); + + EXPECT_CALL(m_interface, xkb_x11_keymap_new_from_device(&m_xkbContext, &m_connection, s_coreDeviceId, XKB_KEYMAP_COMPILE_NO_FLAGS)) + .WillOnce(Return(&m_xkbKeymap)); + EXPECT_CALL(m_interface, xkb_keymap_unref(&m_xkbKeymap)) + .Times(1); + + EXPECT_CALL(m_interface, xkb_x11_state_new_from_device(&m_xkbKeymap, &m_connection, s_coreDeviceId)) + .WillOnce(Return(&m_xkbState)); + EXPECT_CALL(m_interface, xkb_state_unref(&m_xkbState)) + .Times(1); + + ON_CALL(m_interface, xkb_x11_setup_xkb_extension(&m_connection, 1, 0, XKB_X11_SETUP_XKB_EXTENSION_NO_FLAGS, _, _, _, _)) + .WillByDefault(DoAll( + SetArgPointee<6>(s_xkbEventCode), // Set the "base_event_out" argument to the xkbEventCode, the value to identify XKB events + Return(1) + )); + + constexpr unsigned int xcbXkbSelectEventsSequence = 342; + ON_CALL(m_interface, xcb_xkb_select_events(&m_connection, _, _, _, _, _, _, _)) + .WillByDefault(Return(xcb_void_cookie_t{/*.sequence = */ xcbXkbSelectEventsSequence})); + ON_CALL(m_interface, xcb_request_check(&m_connection, testing::Field(&xcb_void_cookie_t::sequence, testing::Eq(xcbXkbSelectEventsSequence)))) + .WillByDefault(Return(nullptr)); // indicates success + + ON_CALL(m_interface, xkb_x11_get_core_keyboard_device_id(&m_connection)) + .WillByDefault(Return(s_coreDeviceId)); - xcb_connection_t connection{}; - xkb_context xkbContext{}; - xkb_keymap xkbKeymap{}; - xkb_state xkbState{}; - const int32_t coreDeviceId{1}; + ON_CALL(m_interface, xkb_state_key_get_one_sym(&m_xkbState, s_keycodeForAKey)) + .WillByDefault(Return(XKB_KEY_a)) + ; + ON_CALL(m_interface, xkb_state_key_get_one_sym(&m_xkbState, s_keycodeForShiftLKey)) + .WillByDefault(Return(XKB_KEY_Shift_L)) + ; + + ON_CALL(m_interface, xkb_state_update_mask(&m_xkbState, _, _, _, _, _, _)) + .WillByDefault(testing::Invoke(this, &XcbInputDeviceKeyboardTests::UpdateStateMask)); + + ON_CALL(m_interface, xkb_state_key_get_utf8(&m_xkbState, s_keycodeForAKey, nullptr, 0)) + .WillByDefault(Return(1)); + ON_CALL(m_interface, xkb_state_key_get_utf8(m_matchesStateWithoutShift, s_keycodeForAKey, _, 2)) + .WillByDefault(DoAll( + SetArgPointee<2>('a'), + Return(1) + )); + ON_CALL(m_interface, xkb_state_key_get_utf8(m_matchesStateWithShift, s_keycodeForAKey, _, 2)) + .WillByDefault(DoAll( + SetArgPointee<2>('A'), + Return(1) + )); + + ON_CALL(m_interface, xkb_state_key_get_utf8(&m_xkbState, s_keycodeForShiftLKey, nullptr, 0)) + .WillByDefault(Return(0)); + } + + private: + xkb_state_component UpdateStateMask( + xkb_state* state, + xkb_mod_mask_t depressed_mods, + xkb_mod_mask_t latched_mods, + xkb_mod_mask_t locked_mods, + xkb_layout_index_t depressed_layout, + xkb_layout_index_t latched_layout, + xkb_layout_index_t locked_layout) + { + state->m_modifiers = depressed_mods | locked_mods; + return {}; + } + + protected: + xkb_context m_xkbContext{}; + xkb_keymap m_xkbKeymap{}; + xkb_state m_xkbState{}; + const testing::Matcher m_matchesStateWithoutShift = testing::AllOf(&m_xkbState, testing::Field(&xkb_state::m_modifiers, 0)); + const testing::Matcher m_matchesStateWithShift = testing::AllOf(&m_xkbState, testing::Field(&xkb_state::m_modifiers, XCB_MOD_MASK_SHIFT)); + + static constexpr int32_t s_coreDeviceId{1}; + static constexpr uint8_t s_xkbEventCode{85}; + + static constexpr xcb_keycode_t s_keycodeForAKey{38}; + static constexpr xcb_keycode_t s_keycodeForShiftLKey{50}; + }; + + class InputTextNotificationListener + : public InputTextNotificationBus::Handler + { + public: + InputTextNotificationListener() + { + BusConnect(); + } + MOCK_METHOD2(OnInputTextEvent, void(const AZStd::string& /*textUTF8*/, bool& /*o_hasBeenConsumed*/)); + }; - constexpr xcb_keycode_t keycodeForAKey = 38; + TEST_F(XcbInputDeviceKeyboardTests, InputChannelsUpdateStateFromXcbEvents) + { + using testing::DoAll; + using testing::Eq; + using testing::Return; + using testing::SetArgPointee; + using testing::_; const AZStd::array events { MakeEvent(xcb_key_press_event_t{ /*.response_type = */ XCB_KEY_PRESS, - /*.detail = */ keycodeForAKey, + /*.detail = */ s_keycodeForAKey, /*.sequence = */ 0, /*.time = */ 0, /*.root = */ 0, @@ -59,7 +163,7 @@ namespace AzFramework }), MakeEvent(xcb_key_release_event_t{ /*.response_type = */ XCB_KEY_RELEASE, - /*.detail = */ keycodeForAKey, + /*.detail = */ s_keycodeForAKey, /*.sequence = */ 0, /*.time = */ 0, /*.root = */ 0, @@ -75,51 +179,20 @@ namespace AzFramework }), }; - EXPECT_CALL(interface, xcb_connect(_, _)) - .WillOnce(Return(&connection)); - EXPECT_CALL(interface, xcb_disconnect(&connection)) - .Times(1); - - EXPECT_CALL(interface, xkb_context_new(XKB_CONTEXT_NO_FLAGS)) - .WillOnce(Return(&xkbContext)); - EXPECT_CALL(interface, xkb_context_unref(&xkbContext)) - .Times(1); - - EXPECT_CALL(interface, xkb_x11_keymap_new_from_device(&xkbContext, &connection, coreDeviceId, XKB_KEYMAP_COMPILE_NO_FLAGS)) - .WillOnce(Return(&xkbKeymap)); - EXPECT_CALL(interface, xkb_keymap_unref(&xkbKeymap)) - .Times(1); - - EXPECT_CALL(interface, xkb_x11_state_new_from_device(&xkbKeymap, &connection, coreDeviceId)) - .WillOnce(Return(&xkbState)); - EXPECT_CALL(interface, xkb_state_unref(&xkbState)) - .Times(1); - - EXPECT_CALL(interface, xcb_xkb_use_extension(&connection, 1, 0)); - EXPECT_CALL(interface, xcb_xkb_use_extension_reply(&connection, _, _)) - .WillOnce(ReturnMalloc( - /* .response_type =*/static_cast(XCB_XKB_USE_EXTENSION), - /* .supported =*/ static_cast(1)) - ); - EXPECT_CALL(interface, xkb_x11_get_core_keyboard_device_id(&connection)) - .WillRepeatedly(Return(coreDeviceId)); - // Set the expectations for the events that will be generated // nullptr entries represent when the event queue is empty, and will cause // PumpSystemEventLoopUntilEmpty to return // event pointers are freed by the calling code, so we malloc new copies // here - EXPECT_CALL(interface, xcb_poll_for_event(&connection)) + EXPECT_CALL(m_interface, xcb_poll_for_event(&m_connection)) .WillOnce(ReturnMalloc(events[0])) .WillOnce(Return(nullptr)) .WillOnce(ReturnMalloc(events[1])) .WillOnce(Return(nullptr)) ; - EXPECT_CALL(interface, xkb_state_key_get_one_sym(&xkbState, keycodeForAKey)) - .WillOnce(Return(XKB_KEY_a)) - .WillOnce(Return(XKB_KEY_a)) - ; + EXPECT_CALL(m_interface, xkb_state_key_get_one_sym(&m_xkbState, s_keycodeForAKey)) + .Times(2); Application application; application.Start({}, {}); @@ -142,4 +215,219 @@ namespace AzFramework application.Stop(); } + + TEST_F(XcbInputDeviceKeyboardTests, TextEnteredFromXcbKeyPressEvents) + { + using testing::DoAll; + using testing::Eq; + using testing::Return; + using testing::SetArgPointee; + using testing::_; + + // press a + // release a + // press shift + // press a + // release a + // release shift + const AZStd::array events + { + MakeEvent(xcb_key_press_event_t{ + /*.response_type = */ XCB_KEY_PRESS, + /*.detail = */ s_keycodeForAKey, + /*.sequence = */ 0, + /*.time = */ 0, + /*.root = */ 0, + /*.event = */ 0, + /*.child = */ 0, + /*.root_x = */ 0, + /*.root_y = */ 0, + /*.event_x = */ 0, + /*.event_y = */ 0, + /*.state = */ 0, + /*.same_screen = */ 0, + /*.pad0 = */ 0 + }), + MakeEvent(xcb_key_release_event_t{ + /*.response_type = */ XCB_KEY_RELEASE, + /*.detail = */ s_keycodeForAKey, + /*.sequence = */ 0, + /*.time = */ 0, + /*.root = */ 0, + /*.event = */ 0, + /*.child = */ 0, + /*.root_x = */ 0, + /*.root_y = */ 0, + /*.event_x = */ 0, + /*.event_y = */ 0, + /*.state = */ 0, + /*.same_screen = */ 0, + /*.pad0 = */ 0 + }), + // Pressing a modifier key will generate a key press event followed + // by a state notify event + MakeEvent(xcb_key_press_event_t{ + /*.response_type = */ XCB_KEY_PRESS, + /*.detail = */ s_keycodeForShiftLKey, + /*.sequence = */ 0, + /*.time = */ 0, + /*.root = */ 0, + /*.event = */ 0, + /*.child = */ 0, + /*.root_x = */ 0, + /*.root_y = */ 0, + /*.event_x = */ 0, + /*.event_y = */ 0, + /*.state = */ 0, + /*.same_screen = */ 0, + /*.pad0 = */ 0 + }), + MakeEvent(xcb_xkb_state_notify_event_t{ + /*.response_type = */ s_xkbEventCode, + /*.xkbType = */ XCB_XKB_STATE_NOTIFY, + /*.sequence = */ 0, + /*.time = */ 0, + /*.deviceID = */ s_coreDeviceId, + /*.mods = */ XCB_MOD_MASK_SHIFT, + /*.baseMods = */ XCB_MOD_MASK_SHIFT, + /*.latchedMods = */ 0, + /*.lockedMods = */ 0, + /*.group = */ 0, + /*.baseGroup = */ 0, + /*.latchedGroup = */ 0, + /*.lockedGroup = */ 0, + /*.compatState = */ XCB_MOD_MASK_SHIFT, + /*.grabMods = */ XCB_MOD_MASK_SHIFT, + /*.compatGrabMods = */ XCB_MOD_MASK_SHIFT, + /*.lookupMods = */ XCB_MOD_MASK_SHIFT, + /*.compatLoockupMods = */ XCB_MOD_MASK_SHIFT, + /*.ptrBtnState = */ 0, + /*.changed = */ 0, + /*.keycode = */ s_keycodeForShiftLKey, + /*.eventType = */ XCB_KEY_PRESS, + /*.requestMajor = */ 0, + /*.requestMinor = */ 0, + }), + MakeEvent(xcb_key_press_event_t{ + /*.response_type = */ XCB_KEY_PRESS, + /*.detail = */ s_keycodeForAKey, + /*.sequence = */ 0, + /*.time = */ 0, + /*.root = */ 0, + /*.event = */ 0, + /*.child = */ 0, + /*.root_x = */ 0, + /*.root_y = */ 0, + /*.event_x = */ 0, + /*.event_y = */ 0, + /*.state = */ 0, + /*.same_screen = */ 0, + /*.pad0 = */ 0 + }), + MakeEvent(xcb_key_release_event_t{ + /*.response_type = */ XCB_KEY_RELEASE, + /*.detail = */ s_keycodeForAKey, + /*.sequence = */ 0, + /*.time = */ 0, + /*.root = */ 0, + /*.event = */ 0, + /*.child = */ 0, + /*.root_x = */ 0, + /*.root_y = */ 0, + /*.event_x = */ 0, + /*.event_y = */ 0, + /*.state = */ 0, + /*.same_screen = */ 0, + /*.pad0 = */ 0 + }), + MakeEvent(xcb_key_release_event_t{ + /*.response_type = */ XCB_KEY_RELEASE, + /*.detail = */ s_keycodeForShiftLKey, + /*.sequence = */ 0, + /*.time = */ 0, + /*.root = */ 0, + /*.event = */ 0, + /*.child = */ 0, + /*.root_x = */ 0, + /*.root_y = */ 0, + /*.event_x = */ 0, + /*.event_y = */ 0, + /*.state = */ 0, + /*.same_screen = */ 0, + /*.pad0 = */ 0 + }), + MakeEvent(xcb_xkb_state_notify_event_t{ + /*.response_type = */ s_xkbEventCode, + /*.xkbType = */ XCB_XKB_STATE_NOTIFY, + /*.sequence = */ 0, + /*.time = */ 0, + /*.deviceID = */ s_coreDeviceId, + /*.mods = */ 0, + /*.baseMods = */ 0, + /*.latchedMods = */ 0, + /*.lockedMods = */ 0, + /*.group = */ 0, + /*.baseGroup = */ 0, + /*.latchedGroup = */ 0, + /*.lockedGroup = */ 0, + /*.compatState = */ 0, + /*.grabMods = */ 0, + /*.compatGrabMods = */ 0, + /*.lookupMods = */ 0, + /*.compatLoockupMods = */ 0, + /*.ptrBtnState = */ 0, + /*.changed = */ 0, + /*.keycode = */ s_keycodeForShiftLKey, + /*.eventType = */ XCB_KEY_RELEASE, + /*.requestMajor = */ 0, + /*.requestMinor = */ 0, + }), + }; + + // Set the expectations for the events that will be generated + // nullptr entries represent when the event queue is empty, and will cause + // PumpSystemEventLoopUntilEmpty to return + // event pointers are freed by the calling code, so we malloc new copies + // here + EXPECT_CALL(m_interface, xcb_poll_for_event(&m_connection)) + .WillOnce(ReturnMalloc(events[0])) // press a + .WillOnce(Return(nullptr)) + .WillOnce(ReturnMalloc(events[1])) // release a + .WillOnce(Return(nullptr)) + .WillOnce(ReturnMalloc(events[2])) // press shift + .WillOnce(ReturnMalloc(events[3])) // state notify shift is down + .WillOnce(ReturnMalloc(events[4])) // press a + .WillOnce(Return(nullptr)) + .WillOnce(ReturnMalloc(events[5])) // release a + .WillOnce(ReturnMalloc(events[6])) // release shift + .WillOnce(ReturnMalloc(events[7])) // state notify shift is up + .WillRepeatedly(Return(nullptr)) + ; + + EXPECT_CALL(m_interface, xkb_state_key_get_utf8(&m_xkbState, s_keycodeForAKey, nullptr, 0)) + .Times(2); + EXPECT_CALL(m_interface, xkb_state_key_get_utf8(m_matchesStateWithoutShift, s_keycodeForAKey, _, 2)) + .Times(1); + EXPECT_CALL(m_interface, xkb_state_key_get_utf8(m_matchesStateWithShift, s_keycodeForAKey, _, 2)) + .Times(1); + + EXPECT_CALL(m_interface, xkb_state_key_get_utf8(&m_xkbState, s_keycodeForShiftLKey, nullptr, 0)) + .Times(1); + + InputTextNotificationListener textListener; + EXPECT_CALL(textListener, OnInputTextEvent(StrEq("a"), _)).Times(1); + EXPECT_CALL(textListener, OnInputTextEvent(StrEq("A"), _)).Times(1); + + Application application; + application.Start({}, {}); + + for (int i = 0; i < 4; ++i) + { + application.PumpSystemEventLoopUntilEmpty(); + application.TickSystem(); + application.Tick(); + } + + application.Stop(); + } } // namespace AzFramework diff --git a/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/azframework_xcb_tests_files.cmake b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/azframework_xcb_tests_files.cmake index aebd28222b..762d5d74fe 100644 --- a/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/azframework_xcb_tests_files.cmake +++ b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/azframework_xcb_tests_files.cmake @@ -9,9 +9,12 @@ set(FILES Actions.h Main.cpp + Matchers.h MockXcbInterface.cpp MockXcbInterface.h Printers.cpp Printers.h + XcbBaseTestFixture.cpp + XcbBaseTestFixture.h XcbInputDeviceKeyboardTests.cpp ) diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/FileDialog.cpp b/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/FileDialog.cpp index d2d773ff93..3061ceedd5 100644 --- a/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/FileDialog.cpp +++ b/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/FileDialog.cpp @@ -6,10 +6,11 @@ * */ +#include #include #include -#include +#include namespace AzQtComponents { @@ -24,7 +25,12 @@ namespace AzQtComponents // Trigger Qt's save filename dialog // If filePath isn't empty, it means we are prompting again because the filename was invalid, // so pass it instead of the directory so the filename is pre-filled in for the user - filePath = QFileDialog::getSaveFileName(parent, caption, (filePath.isEmpty()) ? dir : filePath, filter, selectedFilter, options); + QString localSelectedFilter; + filePath = QFileDialog::getSaveFileName(parent, caption, (filePath.isEmpty()) ? dir : filePath, filter, &localSelectedFilter, options); + if (selectedFilter) + { + *selectedFilter = localSelectedFilter; + } if (!filePath.isEmpty()) { @@ -32,15 +38,39 @@ namespace AzQtComponents QString fileName = fileInfo.fileName(); // Check if the filename has any invalid characters - QRegExp validFileNameRegex("^[a-zA-Z0-9_\\-./]*$"); - shouldPromptAgain = !validFileNameRegex.exactMatch(fileName); + QRegularExpression validFileNameRegex("^[a-zA-Z0-9_\\-./]*$"); + QRegularExpressionMatch validFileNameMatch = validFileNameRegex.match(fileName); // If the filename had invalid characters, then show a warning message and then we will re-prompt the save filename dialog - if (shouldPromptAgain) + if (!validFileNameMatch.hasMatch()) { QMessageBox::warning(parent, QObject::tr("Invalid filename"), QObject::tr("O3DE assets are restricted to alphanumeric characters, hyphens (-), underscores (_), and dots (.)\n\n%1").arg(fileName)); + shouldPromptAgain = true; + continue; + } + else + { + shouldPromptAgain = false; + } +#if AZ_TRAIT_AZQTCOMPONENTS_FILE_DIALOG_APPLY_MISSING_EXTENSION + // If a filter was selected, then make sure that the resulting filename ends with that extension. On systems that use the default QFileDialog, + // the extension is not guaranteed to be set in the resulting filename + if (FileDialog::ApplyMissingExtension(localSelectedFilter, filePath)) + { + // If an extension had to be applied, then the file dialog did not handle the case of overwriting existing files. + // We need to check that condition before we proceed + QFileInfo updatedFilePath(filePath); + + if (updatedFilePath.exists()) + { + QMessageBox::StandardButton overwriteSelection = QMessageBox::question(parent, + QObject::tr("File exists"), + QObject::tr("%1 exists. Do you want to overwrite the existing file?").arg(updatedFilePath.fileName())); + shouldPromptAgain = (overwriteSelection == QMessageBox::No); + } } +#endif // AZ_TRAIT_AZQTCOMPONENTS_FILE_DIALOG_APPLY_MISSING_EXTENSION } else { @@ -51,4 +81,56 @@ namespace AzQtComponents return filePath; } + + bool FileDialog::ApplyMissingExtension(const QString& selectedFilter, QString& filePath) + { + if (selectedFilter.isEmpty()) + { + return false; + } + + // According to the QT documentation for QFileDialog, the selected filter will come in the form + // ( .. ) + // + // For example: + // "Images (*.gif *.png *.jpg)" + // + // Extract the contents of the (s) inside the parenthesis and split them based on a whitespace or comma + const QRegularExpression filterContent(".*\\((?[^\\)]+)\\)"); + QRegularExpressionMatch filterContentMatch = filterContent.match(selectedFilter); + if (!filterContentMatch.hasMatch()) + { + return false; + } + QString filterExtensionsString = filterContentMatch.captured("filters"); + QStringList filterExtensionsFull = filterExtensionsString.split(" ", Qt::SkipEmptyParts); + if (filterExtensionsFull.length() <= 0) + { + return false; + } + + // If there are multiple suffixes in the selected filter, then default to the first one if a suffix needs to be appended + QString defaultSuffix = filterExtensionsFull[0].mid(1); + + // Iterate through the filter patterns to see if the current filename matches + QFileInfo fileInfo(filePath); + bool extensionNeeded = true; + for (const QString& filterExtensionFull : filterExtensionsFull) + { + QString wildcardExpression = QRegularExpression::wildcardToRegularExpression(filterExtensionFull); + QRegularExpression filterPattern(wildcardExpression, AZ_TRAIT_AZQTCOMPONENTS_FILE_DIALOG_FILTER_CASE_SENSITIVITY); + QRegularExpressionMatch filterPatternMatch = filterPattern.match(fileInfo.fileName()); + if (filterPatternMatch.hasMatch()) + { + // The filename matches one of the filter patterns already, the extension does not need to be added to the filename + extensionNeeded = false; + } + } + if (extensionNeeded) + { + // If the current (if any) suffix does not match, automatically add the default suffix for the selected filter + filePath.append(defaultSuffix); + } + return extensionNeeded; + } } // namespace AzQtComponents diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/FileDialog.h b/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/FileDialog.h index 6b63404949..9f27431400 100644 --- a/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/FileDialog.h +++ b/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/FileDialog.h @@ -24,6 +24,12 @@ namespace AzQtComponents static QString GetSaveFileName(QWidget* parent = nullptr, const QString& caption = QString(), const QString& dir = QString(), const QString& filter = QString(), QString* selectedFilter = nullptr, QFileDialog::Options options = QFileDialog::Options()); + + //! Helper method that parses a selected filter from Qt's QFileDialog::getSaveFileName and applies the + //! selected filter's extension to the filePath if it doesnt already have the extension. This is needed + //! on platforms that do not have a default file dialog (These platforms uses Qt's custom file dialog which will + //! not apply the filter's extension automatically on user entered filenames) + static bool ApplyMissingExtension(const QString& selectedFilter, QString& filePath); }; } // namespace AzQtComponents diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Platform/Linux/platform_linux_files.cmake b/Code/Framework/AzQtComponents/AzQtComponents/Platform/Linux/platform_linux_files.cmake index 7f204e5022..ec71d7b508 100644 --- a/Code/Framework/AzQtComponents/AzQtComponents/Platform/Linux/platform_linux_files.cmake +++ b/Code/Framework/AzQtComponents/AzQtComponents/Platform/Linux/platform_linux_files.cmake @@ -12,4 +12,6 @@ set(FILES ../../Utilities/QtWindowUtilities_linux.cpp ../../Utilities/ScreenGrabber_linux.cpp ../../../Platform/Linux/AzQtComponents/Components/StyledDockWidget_Linux.cpp + ../../../Platform/Linux/AzQtComponents/AzQtComponents_Traits_Linux.h + ../../../Platform/Linux/AzQtComponents/AzQtComponents_Traits_Platform.h ) diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Platform/Mac/platform_mac_files.cmake b/Code/Framework/AzQtComponents/AzQtComponents/Platform/Mac/platform_mac_files.cmake index 50084d7a1e..18f9479289 100644 --- a/Code/Framework/AzQtComponents/AzQtComponents/Platform/Mac/platform_mac_files.cmake +++ b/Code/Framework/AzQtComponents/AzQtComponents/Platform/Mac/platform_mac_files.cmake @@ -12,4 +12,6 @@ set(FILES ../../Utilities/QtWindowUtilities_mac.mm ../../Utilities/ScreenGrabber_mac.mm ../../../Platform/Mac/AzQtComponents/Components/StyledDockWidget_Mac.cpp + ../../../Platform/Mac/AzQtComponents/AzQtComponents_Traits_Mac.h + ../../../Platform/Mac/AzQtComponents/AzQtComponents_Traits_Platform.h ) diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Platform/Windows/platform_windows_files.cmake b/Code/Framework/AzQtComponents/AzQtComponents/Platform/Windows/platform_windows_files.cmake index 37d1d0f390..ca4145ef35 100644 --- a/Code/Framework/AzQtComponents/AzQtComponents/Platform/Windows/platform_windows_files.cmake +++ b/Code/Framework/AzQtComponents/AzQtComponents/Platform/Windows/platform_windows_files.cmake @@ -17,4 +17,6 @@ set(FILES ../../Components/TitleBarOverdrawScreenHandler_win.h ../../Components/TitleBarOverdrawScreenHandler_win.cpp ../../../Platform/Windows/AzQtComponents/Components/StyledDockWidget_Windows.cpp + ../../../Platform/Windows/AzQtComponents/AzQtComponents_Traits_Windows.h + ../../../Platform/Windows/AzQtComponents/AzQtComponents_Traits_Platform.h ) diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Tests/FileDialogTests.cpp b/Code/Framework/AzQtComponents/AzQtComponents/Tests/FileDialogTests.cpp new file mode 100644 index 0000000000..e3dc4b4f0d --- /dev/null +++ b/Code/Framework/AzQtComponents/AzQtComponents/Tests/FileDialogTests.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include + +TEST(AzQtComponents, ApplyMissingExtension_UpdateMissingExtension_Success) +{ + const QString textFiler{"Text Files (*.txt)"}; + QString testPath{"testFile"}; + bool result = AzQtComponents::FileDialog::ApplyMissingExtension(textFiler, testPath); + EXPECT_TRUE(result); + EXPECT_STRCASEEQ("testFile.txt", testPath.toUtf8().constData()); +} + +TEST(AzQtComponents, ApplyMissingExtension_NoUpdateExistingExtension_Success) +{ + const QString textFiler{"Text Files (*.txt)"}; + QString testPath{"testFile.txt"}; + bool result = AzQtComponents::FileDialog::ApplyMissingExtension(textFiler, testPath); + EXPECT_FALSE(result); + EXPECT_STRCASEEQ("testFile.txt", testPath.toUtf8().constData()); +} + +TEST(AzQtComponents, ApplyMissingExtension_UpdateMissingExtensionMultipleExtensionFilter_Success) +{ + const QString textFiler{"Image Files (*.jpg *.bmp *.png)"}; + QString testPath{"testFile"}; + bool result = AzQtComponents::FileDialog::ApplyMissingExtension(textFiler, testPath); + EXPECT_TRUE(result); + EXPECT_STRCASEEQ("testFile.jpg", testPath.toUtf8().constData()); +} + +TEST(AzQtComponents, ApplyMissingExtension_NoUpdateMissingExtensionMultipleExtensionFilter_Success) +{ + const QString textFiler{"Image Files (*.jpg *.bmp *.png)"}; + QString testPath{"testFile.png"}; + bool result = AzQtComponents::FileDialog::ApplyMissingExtension(textFiler, testPath); + EXPECT_FALSE(result); + EXPECT_STRCASEEQ("testFile.png", testPath.toUtf8().constData()); +} + +TEST(AzQtComponents, ApplyMissingExtension_NoUpdateMissingExtensionEmptyFilter_Success) +{ + const QString textFiler{""}; + QString testPath{"testFile"}; + bool result = AzQtComponents::FileDialog::ApplyMissingExtension(textFiler, testPath); + EXPECT_FALSE(result); + EXPECT_STRCASEEQ("testFile", testPath.toUtf8().constData()); +} + +TEST(AzQtComponents, ApplyMissingExtension_NoUpdateMissingExtensionInvalidFilter_Success) +{ + const QString textFiler{"Bad Filter!!"}; + QString testPath{"testFile"}; + bool result = AzQtComponents::FileDialog::ApplyMissingExtension(textFiler, testPath); + EXPECT_FALSE(result); + EXPECT_STRCASEEQ("testFile", testPath.toUtf8().constData()); +} diff --git a/Code/Framework/AzQtComponents/AzQtComponents/azqtcomponents_testing_files.cmake b/Code/Framework/AzQtComponents/AzQtComponents/azqtcomponents_testing_files.cmake index 2611063305..b4ab487c79 100644 --- a/Code/Framework/AzQtComponents/AzQtComponents/azqtcomponents_testing_files.cmake +++ b/Code/Framework/AzQtComponents/AzQtComponents/azqtcomponents_testing_files.cmake @@ -9,6 +9,7 @@ set(FILES Tests/AzQtComponentTests.cpp Tests/ColorControllerTests.cpp + Tests/FileDialogTests.cpp Tests/FloatToStringConversionTests.cpp Tests/HexParsingTests.cpp Tests/StyleSheetCacheTests.cpp diff --git a/Code/Framework/AzQtComponents/CMakeLists.txt b/Code/Framework/AzQtComponents/CMakeLists.txt index dd217a7be2..a24b68bcd7 100644 --- a/Code/Framework/AzQtComponents/CMakeLists.txt +++ b/Code/Framework/AzQtComponents/CMakeLists.txt @@ -10,6 +10,8 @@ if(NOT PAL_TRAIT_BUILD_HOST_TOOLS) return() endif() +ly_get_list_relative_pal_filename(pal_dir ${CMAKE_CURRENT_LIST_DIR}/Platform/${PAL_PLATFORM_NAME}) + ly_add_target( NAME AzQtComponents SHARED NAMESPACE AZ @@ -26,6 +28,7 @@ ly_add_target( AzQtComponents PUBLIC . + ${pal_dir} COMPILE_DEFINITIONS PRIVATE AZ_QT_COMPONENTS_EXPORT_SYMBOLS @@ -53,6 +56,7 @@ ly_add_target( . AzQtComponents AzQtComponents/Gallery + ${pal_dir} BUILD_DEPENDENCIES PRIVATE 3rdParty::Qt::Svg @@ -86,6 +90,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) PRIVATE Tests AzQtComponents + ${pal_dir} BUILD_DEPENDENCIES PRIVATE AZ::AzQtComponents diff --git a/Code/Framework/AzQtComponents/Platform/Linux/AzQtComponents/AzQtComponents_Traits_Linux.h b/Code/Framework/AzQtComponents/Platform/Linux/AzQtComponents/AzQtComponents_Traits_Linux.h new file mode 100644 index 0000000000..685ac4dfed --- /dev/null +++ b/Code/Framework/AzQtComponents/Platform/Linux/AzQtComponents/AzQtComponents_Traits_Linux.h @@ -0,0 +1,11 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#pragma once + +#define AZ_TRAIT_AZQTCOMPONENTS_FILE_DIALOG_APPLY_MISSING_EXTENSION 1 +#define AZ_TRAIT_AZQTCOMPONENTS_FILE_DIALOG_FILTER_CASE_SENSITIVITY QRegularExpression::NoPatternOption diff --git a/Code/Framework/AzQtComponents/Platform/Linux/AzQtComponents/AzQtComponents_Traits_Platform.h b/Code/Framework/AzQtComponents/Platform/Linux/AzQtComponents/AzQtComponents_Traits_Platform.h new file mode 100644 index 0000000000..101fe3a494 --- /dev/null +++ b/Code/Framework/AzQtComponents/Platform/Linux/AzQtComponents/AzQtComponents_Traits_Platform.h @@ -0,0 +1,10 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#pragma once + +#include diff --git a/Code/Framework/AzQtComponents/Platform/Mac/AzQtComponents/AzQtComponents_Traits_Mac.h b/Code/Framework/AzQtComponents/Platform/Mac/AzQtComponents/AzQtComponents_Traits_Mac.h new file mode 100644 index 0000000000..1cb91ec09e --- /dev/null +++ b/Code/Framework/AzQtComponents/Platform/Mac/AzQtComponents/AzQtComponents_Traits_Mac.h @@ -0,0 +1,11 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#pragma once + +#define AZ_TRAIT_AZQTCOMPONENTS_FILE_DIALOG_APPLY_MISSING_EXTENSION 0 +#define AZ_TRAIT_AZQTCOMPONENTS_FILE_DIALOG_FILTER_CASE_SENSITIVITY QRegularExpression::NoPatternOption diff --git a/Code/Framework/AzQtComponents/Platform/Mac/AzQtComponents/AzQtComponents_Traits_Platform.h b/Code/Framework/AzQtComponents/Platform/Mac/AzQtComponents/AzQtComponents_Traits_Platform.h new file mode 100644 index 0000000000..9b285476c1 --- /dev/null +++ b/Code/Framework/AzQtComponents/Platform/Mac/AzQtComponents/AzQtComponents_Traits_Platform.h @@ -0,0 +1,10 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#pragma once + +#include diff --git a/Code/Framework/AzQtComponents/Platform/Windows/AzQtComponents/AzQtComponents_Traits_Platform.h b/Code/Framework/AzQtComponents/Platform/Windows/AzQtComponents/AzQtComponents_Traits_Platform.h new file mode 100644 index 0000000000..2c3efc5e6c --- /dev/null +++ b/Code/Framework/AzQtComponents/Platform/Windows/AzQtComponents/AzQtComponents_Traits_Platform.h @@ -0,0 +1,10 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#pragma once + +#include diff --git a/Code/Framework/AzQtComponents/Platform/Windows/AzQtComponents/AzQtComponents_Traits_Windows.h b/Code/Framework/AzQtComponents/Platform/Windows/AzQtComponents/AzQtComponents_Traits_Windows.h new file mode 100644 index 0000000000..edba0043b4 --- /dev/null +++ b/Code/Framework/AzQtComponents/Platform/Windows/AzQtComponents/AzQtComponents_Traits_Windows.h @@ -0,0 +1,11 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#pragma once + +#define AZ_TRAIT_AZQTCOMPONENTS_FILE_DIALOG_APPLY_MISSING_EXTENSION 0 +#define AZ_TRAIT_AZQTCOMPONENTS_FILE_DIALOG_FILTER_CASE_SENSITIVITY QRegularExpression::CaseInsensitiveOption diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Application/ToolsApplication.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Application/ToolsApplication.cpp index c54735fe91..fbd066ec6e 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Application/ToolsApplication.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Application/ToolsApplication.cpp @@ -69,6 +69,7 @@ #include #include #include +#include #include AZ_PUSH_DISABLE_WARNING(4251, "-Wunknown-warning-option") // 4251: 'QFileInfo::d_ptr': class 'QSharedDataPointer' needs to have dll-interface to be used by clients of class 'QFileInfo' @@ -271,7 +272,8 @@ namespace AzToolsFramework azrtti_typeid(), azrtti_typeid(), azrtti_typeid(), - azrtti_typeid() + azrtti_typeid(), + azrtti_typeid() }); return components; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/AssetEditor/AssetEditorWidget.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/AssetEditor/AssetEditorWidget.cpp index 8856989911..a159882f72 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/AssetEditor/AssetEditorWidget.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/AssetEditor/AssetEditorWidget.cpp @@ -410,7 +410,7 @@ namespace AzToolsFramework filter.append(ext); if (i < n - 1) { - filter.append(", "); + filter.append(" "); } } filter.append(")"); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/AzToolsFrameworkModule.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/AzToolsFrameworkModule.cpp index 05d67d9d71..d2a88df544 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/AzToolsFrameworkModule.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/AzToolsFrameworkModule.cpp @@ -53,6 +53,7 @@ #include #include #include +#include AZ_DEFINE_BUDGET(AzToolsFramework); @@ -71,6 +72,7 @@ namespace AzToolsFramework Components::EditorSelectionAccentSystemComponent::CreateDescriptor(), EditorEntityContextComponent::CreateDescriptor(), EditorEntityFixupComponent::CreateDescriptor(), + EntityUtilityComponent::CreateDescriptor(), ContainerEntitySystemComponent::CreateDescriptor(), FocusModeSystemComponent::CreateDescriptor(), SliceMetadataEntityContextComponent::CreateDescriptor(), diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/EntityUtilityComponent.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/EntityUtilityComponent.cpp new file mode 100644 index 0000000000..7fb998f012 --- /dev/null +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/EntityUtilityComponent.cpp @@ -0,0 +1,351 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace AzToolsFramework +{ + void ComponentDetails::Reflect(AZ::ReflectContext* context) + { + if (auto serializeContext = azrtti_cast(context)) + { + serializeContext->Class() + ->Field("TypeInfo", &ComponentDetails::m_typeInfo) + ->Field("BaseClasses", &ComponentDetails::m_baseClasses); + + serializeContext->RegisterGenericType>(); + } + + if (AZ::BehaviorContext* behaviorContext = azrtti_cast(context)) + { + behaviorContext->Class() + ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common) + ->Attribute(AZ::Script::Attributes::Module, "entity") + ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All) + ->Property("TypeInfo", BehaviorValueProperty(&ComponentDetails::m_typeInfo)) + ->Property("BaseClasses", BehaviorValueProperty(&ComponentDetails::m_baseClasses)) + ->Method("__repr__", [](const ComponentDetails& obj) + { + std::ostringstream result; + bool first = true; + + for (const auto& baseClass : obj.m_baseClasses) + { + if (!first) + { + result << ", "; + } + + first = false; + result << baseClass.c_str(); + } + + return AZStd::string::format("%s, Base Classes: <%s>", obj.m_typeInfo.c_str(), result.str().c_str()); + }) + ->Attribute(AZ::Script::Attributes::Operator, AZ::Script::Attributes::OperatorType::ToString); + } + } + + AZ::EntityId EntityUtilityComponent::CreateEditorReadyEntity(const AZStd::string& entityName) + { + auto* newEntity = m_entityContext->CreateEntity(entityName.c_str()); + + if (!newEntity) + { + AZ_Error("EditorEntityUtility", false, "Failed to create new entity %s", entityName.c_str()); + return AZ::EntityId(); + } + + AzToolsFramework::EditorEntityContextRequestBus::Broadcast( + &AzToolsFramework::EditorEntityContextRequestBus::Events::AddRequiredComponents, *newEntity); + + newEntity->Init(); + auto newEntityId = newEntity->GetId(); + + m_createdEntities.emplace_back(newEntityId); + + return newEntityId; + } + + AZ::TypeId GetComponentTypeIdFromName(const AZStd::string& typeName) + { + // Try to create a TypeId first. We won't show any warnings if this fails as the input might be a class name instead + AZ::TypeId typeId = AZ::TypeId::CreateStringPermissive(typeName.data()); + + // If the typeId is null, try a lookup by class name + if (typeId.IsNull()) + { + AZ::SerializeContext* serializeContext = nullptr; + AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext); + + auto typeNameCrc = AZ::Crc32(typeName.data()); + auto typeUuidList = serializeContext->FindClassId(typeNameCrc); + + // TypeId is invalid or class name is invalid + if (typeUuidList.empty()) + { + AZ_Error("EntityUtilityComponent", false, "Provided type %s is either an invalid TypeId or does not match any class names", typeName.c_str()); + return AZ::TypeId::CreateNull(); + } + + typeId = typeUuidList[0]; + } + + return typeId; + } + + AZ::Component* FindComponentHelper(AZ::EntityId entityId, const AZ::TypeId& typeId, AZ::ComponentId componentId, bool createComponent = false) + { + AZ::Entity* entity = nullptr; + AZ::ComponentApplicationBus::BroadcastResult(entity, &AZ::ComponentApplicationBus::Events::FindEntity, entityId); + + if (!entity) + { + AZ_Error("EntityUtilityComponent", false, "Invalid entityId %s", entityId.ToString().c_str()); + return nullptr; + } + + AZ::Component* component = nullptr; + if (componentId != AZ::InvalidComponentId) + { + component = entity->FindComponent(componentId); + } + else + { + component = entity->FindComponent(typeId); + } + + if (!component && createComponent) + { + component = entity->CreateComponent(typeId); + } + + if (!component) + { + AZ_Error( + "EntityUtilityComponent", false, "Failed to find component (%s) on entity %s (%s)", + componentId != AZ::InvalidComponentId ? AZStd::to_string(componentId).c_str() + : typeId.ToString().c_str(), + entityId.ToString().c_str(), + entity->GetName().c_str()); + return nullptr; + } + + return component; + } + + AzFramework::BehaviorComponentId EntityUtilityComponent::GetOrAddComponentByTypeName(AZ::EntityId entityId, const AZStd::string& typeName) + { + AZ::TypeId typeId = GetComponentTypeIdFromName(typeName); + + if (typeId.IsNull()) + { + return AzFramework::BehaviorComponentId(AZ::InvalidComponentId); + } + + AZ::Component* component = FindComponentHelper(entityId, typeId, AZ::InvalidComponentId, true); + + return component ? AzFramework::BehaviorComponentId(component->GetId()) : + AzFramework::BehaviorComponentId(AZ::InvalidComponentId); + } + + bool EntityUtilityComponent::UpdateComponentForEntity(AZ::EntityId entityId, AzFramework::BehaviorComponentId componentId, const AZStd::string& json) + { + if (!componentId.IsValid()) + { + AZ_Error("EntityUtilityComponent", false, "Invalid componentId passed to UpdateComponentForEntity"); + return false; + } + + AZ::Component* component = FindComponentHelper(entityId, AZ::TypeId::CreateNull(), componentId); + + if (!component) + { + return false; + } + + using namespace AZ::JsonSerializationResult; + + AZ::JsonDeserializerSettings settings = AZ::JsonDeserializerSettings{}; + settings.m_reporting = []([[maybe_unused]] AZStd::string_view message, ResultCode result, AZStd::string_view) -> auto + { + if (result.GetProcessing() == Processing::Halted) + { + AZ_Error("EntityUtilityComponent", false, "JSON %s\n", message.data()); + } + else if (result.GetOutcome() > Outcomes::PartialDefaults) + { + AZ_Warning("EntityUtilityComponent", false, "JSON %s\n", message.data()); + } + return result; + }; + + rapidjson::Document doc; + doc.Parse(json.data(), json.size()); + ResultCode resultCode = AZ::JsonSerialization::Load(*component, doc, settings); + + return resultCode.GetProcessing() != Processing::Halted; + } + + AZStd::string EntityUtilityComponent::GetComponentDefaultJson(const AZStd::string& typeName) + { + AZ::TypeId typeId = GetComponentTypeIdFromName(typeName); + + if (typeId.IsNull()) + { + // GetComponentTypeIdFromName already does error handling + return ""; + } + + AZ::SerializeContext* serializeContext = nullptr; + AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext); + + const AZ::SerializeContext::ClassData* classData = serializeContext->FindClassData(typeId); + + if (!classData) + { + AZ_Error("EntityUtilityComponent", false, "Failed to find ClassData for typeId %s (%s)", typeId.ToString().c_str(), typeName.c_str()); + return ""; + } + + void* component = classData->m_factory->Create("Component"); + rapidjson::Document document; + AZ::JsonSerializerSettings settings; + settings.m_keepDefaults = true; + + auto resultCode = AZ::JsonSerialization::Store(document, document.GetAllocator(), component, nullptr, typeId, settings); + + // Clean up the allocated component ASAP, we don't need it anymore + classData->m_factory->Destroy(component); + + if (resultCode.GetProcessing() == AZ::JsonSerializationResult::Processing::Halted) + { + AZ_Error("EntityUtilityComponent", false, "Failed to serialize component to json (%s): %s", + typeName.c_str(), resultCode.ToString(typeName).c_str()) + return ""; + } + + AZStd::string jsonString; + AZ::Outcome outcome = AZ::JsonSerializationUtils::WriteJsonString(document, jsonString); + + if (!outcome.IsSuccess()) + { + AZ_Error("EntityUtilityComponent", false, "Failed to write component json to string: %s", outcome.GetError().c_str()); + return ""; + } + + return jsonString; + } + + AZStd::vector EntityUtilityComponent::FindMatchingComponents(const AZStd::string& searchTerm) + { + AZ::SerializeContext* serializeContext = nullptr; + AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext); + + if (m_typeInfo.empty()) + { + serializeContext->EnumerateDerived( + [this, serializeContext](const AZ::SerializeContext::ClassData* classData, const AZ::Uuid& /*typeId*/) + { + auto& typeInfo = m_typeInfo.emplace_back(classData->m_typeId, classData->m_name, AZStd::vector{}); + + serializeContext->EnumerateBase( + [&typeInfo](const AZ::SerializeContext::ClassData* classData, const AZ::Uuid&) + { + if (classData) + { + AZStd::get<2>(typeInfo).emplace_back(classData->m_name); + } + return true; + }, + classData->m_typeId); + + return true; + }); + } + + AZStd::vector matches; + + for (const auto& [typeId, typeName, baseClasses] : m_typeInfo) + { + if (AZStd::wildcard_match(searchTerm, typeName)) + { + ComponentDetails details; + details.m_typeInfo = AZStd::string::format("%s %s", typeId.ToString().c_str(), typeName.c_str()); + details.m_baseClasses = baseClasses; + + matches.emplace_back(AZStd::move(details)); + } + } + + return matches; + } + + void EntityUtilityComponent::ResetEntityContext() + { + for (AZ::EntityId entityId : m_createdEntities) + { + m_entityContext->DestroyEntityById(entityId); + } + + m_createdEntities.clear(); + m_entityContext->ResetContext(); + } + + void EntityUtilityComponent::Reflect(AZ::ReflectContext* context) + { + ComponentDetails::Reflect(context); + + if (auto* serializeContext = azrtti_cast(context)) + { + serializeContext->Class(); + } + + if (auto* behaviorContext = azrtti_cast(context)) + { + behaviorContext->ConstantProperty("InvalidComponentId", BehaviorConstant(AZ::InvalidComponentId)) + ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common) + ->Attribute(AZ::Script::Attributes::Category, "Entity") + ->Attribute(AZ::Script::Attributes::Module, "entity"); + + behaviorContext->EBus("EntityUtilityBus") + ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common) + ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All) + ->Attribute(AZ::Script::Attributes::Category, "Entity") + ->Attribute(AZ::Script::Attributes::Module, "entity") + ->Event("CreateEditorReadyEntity", &EntityUtilityBus::Events::CreateEditorReadyEntity) + ->Event("GetOrAddComponentByTypeName", &EntityUtilityBus::Events::GetOrAddComponentByTypeName) + ->Event("UpdateComponentForEntity", &EntityUtilityBus::Events::UpdateComponentForEntity) + ->Event("FindMatchingComponents", &EntityUtilityBus::Events::FindMatchingComponents) + ->Event("GetComponentDefaultJson", &EntityUtilityBus::Events::GetComponentDefaultJson) + ; + } + } + + void EntityUtilityComponent::Activate() + { + m_entityContext = AZStd::make_unique(UtilityEntityContextId); + m_entityContext->InitContext(); + EntityUtilityBus::Handler::BusConnect(); + } + + void EntityUtilityComponent::Deactivate() + { + EntityUtilityBus::Handler::BusDisconnect(); + m_entityContext = nullptr; + } +} diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/EntityUtilityComponent.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/EntityUtilityComponent.h new file mode 100644 index 0000000000..af7c353163 --- /dev/null +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/EntityUtilityComponent.h @@ -0,0 +1,90 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace AzToolsFramework +{ + struct ComponentDetails + { + AZ_TYPE_INFO(AzToolsFramework::ComponentDetails, "{107D8379-4AD4-4547-BEE1-184B120F23E9}"); + + static void Reflect(AZ::ReflectContext* context); + + AZStd::string m_typeInfo; + AZStd::vector m_baseClasses; + }; + + // This ebus is intended to provide behavior-context friendly APIs to create and manage entities + struct EntityUtilityTraits : AZ::EBusTraits + { + AZ_RTTI(AzToolsFramework::EntityUtilityTraits, "{A6305CAE-C825-43F9-A44D-E503910912AF}"); + + virtual ~EntityUtilityTraits() = default; + + static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; + static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; + + // Creates an entity with the default editor components attached and initializes the entity + virtual AZ::EntityId CreateEditorReadyEntity(const AZStd::string& entityName) = 0; + + virtual AzFramework::BehaviorComponentId GetOrAddComponentByTypeName(AZ::EntityId entity, const AZStd::string& typeName) = 0; + + virtual bool UpdateComponentForEntity(AZ::EntityId entity, AzFramework::BehaviorComponentId component, const AZStd::string& json) = 0; + + // Gets a JSON string containing describing the default serialization state of the specified component + virtual AZStd::string GetComponentDefaultJson(const AZStd::string& typeName) = 0; + + // Returns a list of matching component type names. Supports wildcard search terms + virtual AZStd::vector FindMatchingComponents(const AZStd::string& searchTerm) = 0; + + virtual void ResetEntityContext() = 0; + }; + + using EntityUtilityBus = AZ::EBus; + + struct EntityUtilityComponent : AZ::Component + , EntityUtilityBus::Handler + { + inline const static AZ::Uuid UtilityEntityContextId = AZ::Uuid("{9C277B88-E79E-4F8A-BAFF-A4C175BD565F}"); + + AZ_COMPONENT(EntityUtilityComponent, "{47205907-A0EA-4FFF-A620-04D20C04A379}"); + + AZ::EntityId CreateEditorReadyEntity(const AZStd::string& entityName) override; + AzFramework::BehaviorComponentId GetOrAddComponentByTypeName(AZ::EntityId entity, const AZStd::string& typeName) override; + bool UpdateComponentForEntity(AZ::EntityId entity, AzFramework::BehaviorComponentId component, const AZStd::string& json) override; + AZStd::string GetComponentDefaultJson(const AZStd::string& typeName) override; + AZStd::vector FindMatchingComponents(const AZStd::string& searchTerm) override; + void ResetEntityContext() override; + + static void Reflect(AZ::ReflectContext* context); + + protected: + void Activate() override; + void Deactivate() override; + + // Our own entity context. This API is intended mostly for use in Asset Builders where there is no editor context + // Additionally, an entity context is needed when using the Behavior Entity class + AZStd::unique_ptr m_entityContext; + + // TypeId, TypeName, Vector + AZStd::vector>> m_typeInfo; + + // Keep track of the entities we create so they can be reset + AZStd::vector m_createdEntities; + }; +}; // namespace AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeInterface.h b/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeInterface.h index 75e3bab60f..5455e8d773 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeInterface.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeInterface.h @@ -10,6 +10,7 @@ #include #include +#include #include #include diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabDomUtils.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabDomUtils.cpp index f60759f77e..ea0fa55256 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabDomUtils.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabDomUtils.cpp @@ -224,6 +224,7 @@ namespace AzToolsFramework return false; } + AZ::Data::SerializedAssetTracker* assetTracker = settings.m_metadata.Find(); referencedAssets = AZStd::move(assetTracker->GetTrackedAssets()); @@ -245,6 +246,30 @@ namespace AzToolsFramework entityIdMapper.SetEntityIdGenerationApproach(InstanceEntityIdMapper::EntityIdGenerationApproach::Random); } + // some assets may come in from the JSON serialzier with no AssetID, but have an asset hint + // this attempts to fix up the assets using the assetHint field + auto fixUpInvalidAssets = [](AZ::Data::Asset& asset) + { + if (!asset.GetId().IsValid() && !asset.GetHint().empty()) + { + AZ::Data::AssetId assetId; + AZ::Data::AssetCatalogRequestBus::BroadcastResult( + assetId, + &AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath, + asset.GetHint().c_str(), + AZ::Data::s_invalidAssetType, + false); + + if (assetId.IsValid()) + { + asset.Create(assetId, true); + } + } + }; + + auto tracker = AZ::Data::SerializedAssetTracker{}; + tracker.SetAssetFixUp(fixUpInvalidAssets); + AZ::JsonDeserializerSettings settings; // The InstanceEntityIdMapper is registered twice because it's used in several places during deserialization where one is // specific for the InstanceEntityIdMapper and once for the generic JsonEntityIdMapper. Because the Json Serializer's meta @@ -252,16 +277,17 @@ namespace AzToolsFramework settings.m_metadata.Add(static_cast(&entityIdMapper)); settings.m_metadata.Add(&entityIdMapper); settings.m_metadata.Create(newlyAddedEntities); + settings.m_metadata.Add(tracker); AZStd::string scratchBuffer; auto issueReportingCallback = [&scratchBuffer]( - AZStd::string_view message, AZ::JsonSerializationResult::ResultCode result, - AZStd::string_view path) -> AZ::JsonSerializationResult::ResultCode + AZStd::string_view message, AZ::JsonSerializationResult::ResultCode result, + AZStd::string_view path) -> AZ::JsonSerializationResult::ResultCode { return Internal::JsonIssueReporter(scratchBuffer, message, result, path); }; settings.m_reporting = AZStd::move(issueReportingCallback); - + AZ::JsonSerializationResult::ResultCode result = AZ::JsonSerialization::Load(instance, prefabDom, settings); AZ::Data::AssetManager::Instance().ResumeAssetRelease(); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabLoader.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabLoader.cpp index 09439724cd..07bda45c8a 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabLoader.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabLoader.cpp @@ -50,17 +50,19 @@ namespace AzToolsFramework auto settingsRegistry = AZ::SettingsRegistry::Get(); AZ_Assert(settingsRegistry, "Settings registry is not set"); - + [[maybe_unused]] bool result = settingsRegistry->Get(m_projectPathWithOsSeparator.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectPath); AZ_Warning("Prefab", result, "Couldn't retrieve project root path"); m_projectPathWithSlashSeparator = AZ::IO::Path(m_projectPathWithOsSeparator.Native(), '/').MakePreferred(); AZ::Interface::Register(this); + m_scriptingPrefabLoader.Connect(this); } void PrefabLoader::UnregisterPrefabLoaderInterface() { + m_scriptingPrefabLoader.Disconnect(); AZ::Interface::Unregister(this); } @@ -568,7 +570,7 @@ namespace AzToolsFramework (pathStr.find_first_of(AZ_FILESYSTEM_INVALID_CHARACTERS) == AZStd::string::npos) && (pathStr.back() != '\\' && pathStr.back() != '/'); } - + AZ::IO::Path PrefabLoader::GetFullPath(AZ::IO::PathView path) { AZ::IO::Path pathWithOSSeparator = AZ::IO::Path(path).MakePreferred(); @@ -596,26 +598,38 @@ namespace AzToolsFramework { // The asset system provided us with a valid root folder and relative path, so return it. fullPath = AZ::IO::Path(rootFolder) / assetInfo.m_relativePath; + return fullPath; } else { - // If for some reason the Asset system couldn't provide a relative path, provide some fallback logic. + // attempt to find the absolute from the Cache folder + AZStd::string assetRootFolder; + if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr) + { + settingsRegistry->Get(assetRootFolder, AZ::SettingsRegistryMergeUtils::FilePathKey_CacheRootFolder); + } + fullPath = AZ::IO::Path(assetRootFolder) / path; + if (fullPath.IsAbsolute() && AZ::IO::SystemFile::Exists(fullPath.c_str())) + { + return fullPath; + } + } - // Check to see if the AssetProcessor is ready. If it *is* and we didn't get a path, print an error then follow - // the fallback logic. If it's *not* ready, we're probably either extremely early in a tool startup flow or inside - // a unit test, so just execute the fallback logic without an error. - [[maybe_unused]] bool assetProcessorReady = false; - AzFramework::AssetSystemRequestBus::BroadcastResult( - assetProcessorReady, &AzFramework::AssetSystemRequestBus::Events::AssetProcessorIsReady); + // If for some reason the Asset system couldn't provide a relative path, provide some fallback logic. - AZ_Error( - "Prefab", !assetProcessorReady, "Full source path for '%.*s' could not be determined. Using fallback logic.", - AZ_STRING_ARG(path.Native())); + // Check to see if the AssetProcessor is ready. If it *is* and we didn't get a path, print an error then follow + // the fallback logic. If it's *not* ready, we're probably either extremely early in a tool startup flow or inside + // a unit test, so just execute the fallback logic without an error. + [[maybe_unused]] bool assetProcessorReady = false; + AzFramework::AssetSystemRequestBus::BroadcastResult( + assetProcessorReady, &AzFramework::AssetSystemRequestBus::Events::AssetProcessorIsReady); - // If a relative path was passed in, make it relative to the project root. - fullPath = AZ::IO::Path(m_projectPathWithOsSeparator).Append(pathWithOSSeparator); - } + AZ_Error( + "Prefab", !assetProcessorReady, "Full source path for '%.*s' could not be determined. Using fallback logic.", + AZ_STRING_ARG(path.Native())); + // If a relative path was passed in, make it relative to the project root. + fullPath = AZ::IO::Path(m_projectPathWithOsSeparator).Append(pathWithOSSeparator); return fullPath; } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabLoader.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabLoader.h index 15f201e027..da2c3f3161 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabLoader.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabLoader.h @@ -15,6 +15,7 @@ #include #include #include +#include namespace AZ { @@ -114,6 +115,7 @@ namespace AzToolsFramework void SetSaveAllPrefabsPreference(SaveAllPrefabsPreference saveAllPrefabsPreference) override; private: + /** * Copies the template dom provided and manipulates it into the proper format to be saved to disk. * @param templateRef The template whose dom we want to transform into the proper format to be saved to disk. @@ -177,6 +179,7 @@ namespace AzToolsFramework AZStd::optional> StoreTemplateIntoFileFormat(TemplateId templateId); PrefabSystemComponentInterface* m_prefabSystemComponentInterface = nullptr; + ScriptingPrefabLoader m_scriptingPrefabLoader; AZ::IO::Path m_projectPathWithOsSeparator; AZ::IO::Path m_projectPathWithSlashSeparator; }; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabLoaderInterface.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabLoaderInterface.h index a428f8a7b9..b4586ad5bf 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabLoaderInterface.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabLoaderInterface.h @@ -99,7 +99,6 @@ namespace AzToolsFramework // Generates a new path static AZ::IO::Path GeneratePath(); }; - } // namespace Prefab } // namespace AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabLoaderScriptingBus.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabLoaderScriptingBus.h new file mode 100644 index 0000000000..6e9d64b147 --- /dev/null +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabLoaderScriptingBus.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace AzToolsFramework +{ + namespace Prefab + { + // Ebus for script-friendly APIs for the prefab loader + struct PrefabLoaderScriptingTraits : AZ::EBusTraits + { + AZ_TYPE_INFO(PrefabLoaderScriptingTraits, "{C344B7D8-8299-48C9-8450-26E1332EA011}"); + + virtual ~PrefabLoaderScriptingTraits() = default; + + static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; + static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; + + /** + * Saves a Prefab Template into the provided output string. + * Converts Prefab Template form into .prefab form by collapsing nested Template info + * into a source path and patches. + * @param templateId Id of the template to be saved + * @return Will contain the serialized template json on success + */ + virtual AZ::Outcome SaveTemplateToString(TemplateId templateId) = 0; + }; + + using PrefabLoaderScriptingBus = AZ::EBus; + + } // namespace Prefab +} // namespace AzToolsFramework + diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.cpp index 896e40d929..b1fdf9784d 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -36,12 +37,14 @@ namespace AzToolsFramework m_instanceToTemplatePropagator.RegisterInstanceToTemplateInterface(); m_prefabPublicHandler.RegisterPrefabPublicHandlerInterface(); m_prefabPublicRequestHandler.Connect(); + m_prefabSystemScriptingHandler.Connect(this); AZ::SystemTickBus::Handler::BusConnect(); } void PrefabSystemComponent::Deactivate() { AZ::SystemTickBus::Handler::BusDisconnect(); + m_prefabSystemScriptingHandler.Disconnect(); m_prefabPublicRequestHandler.Disconnect(); m_prefabPublicHandler.UnregisterPrefabPublicHandlerInterface(); m_instanceToTemplatePropagator.UnregisterInstanceToTemplateInterface(); @@ -58,13 +61,24 @@ namespace AzToolsFramework AzToolsFramework::Prefab::PrefabConversionUtils::EditorInfoRemover::Reflect(context); PrefabPublicRequestHandler::Reflect(context); PrefabLoader::Reflect(context); + PrefabSystemScriptingHandler::Reflect(context); - AZ::SerializeContext* serialize = azrtti_cast(context); - if (serialize) + if (AZ::SerializeContext* serialize = azrtti_cast(context)) { serialize->Class()->Version(1); } + if (AZ::BehaviorContext* behaviorContext = azrtti_cast(context)) + { + + behaviorContext->EBus("PrefabLoaderScriptingBus") + ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common) + ->Attribute(AZ::Script::Attributes::Module, "prefab") + ->Attribute(AZ::Script::Attributes::Category, "Prefab") + ->Event("SaveTemplateToString", &PrefabLoaderScriptingBus::Events::SaveTemplateToString); + ; + } + AZ::JsonRegistrationContext* jsonRegistration = azrtti_cast(context); if (jsonRegistration) { @@ -145,7 +159,7 @@ namespace AzToolsFramework newInstance->SetTemplateId(newTemplateId); } } - + void PrefabSystemComponent::PropagateTemplateChanges(TemplateId templateId, bool immediate, InstanceOptionalReference instanceToExclude) { UpdatePrefabInstances(templateId, immediate, instanceToExclude); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.h index f640eb2f1b..480bb83121 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.h @@ -27,6 +27,7 @@ #include #include #include +#include namespace AZ { @@ -219,7 +220,7 @@ namespace AzToolsFramework const AZStd::vector& entities, AZStd::vector>&& instancesToConsume, AZ::IO::PathView filePath, AZStd::unique_ptr containerEntity = nullptr, InstanceOptionalReference parent = AZStd::nullopt, bool shouldCreateLinks = true) override; - + PrefabDom& FindTemplateDom(TemplateId templateId) override; /** @@ -244,7 +245,7 @@ namespace AzToolsFramework private: AZ_DISABLE_COPY_MOVE(PrefabSystemComponent); - + /** * Builds a new Prefab Template out of entities and instances and returns the first instance comprised of * these entities and instances. @@ -412,6 +413,8 @@ namespace AzToolsFramework // Handler of the public Prefab requests. PrefabPublicRequestHandler m_prefabPublicRequestHandler; + + PrefabSystemScriptingHandler m_prefabSystemScriptingHandler; }; } // namespace Prefab } // namespace AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponentInterface.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponentInterface.h index 54ca951841..66d85ccee9 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponentInterface.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponentInterface.h @@ -78,8 +78,7 @@ namespace AzToolsFramework AZStd::unique_ptr containerEntity = nullptr, InstanceOptionalReference parent = AZStd::nullopt, bool shouldCreateLinks = true) = 0; }; - - + } // namespace Prefab } // namespace AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemScriptingBus.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemScriptingBus.h new file mode 100644 index 0000000000..f83bd2c6f1 --- /dev/null +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemScriptingBus.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace AzToolsFramework +{ + namespace Prefab + { + // Bus that exposes a script-friendly interface to the PrefabSystemComponent + struct PrefabSystemScriptingEbusTraits : AZ::EBusTraits + { + using MutexType = AZ::NullMutex; + static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; + static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; + + virtual TemplateId CreatePrefabTemplate( + const AZStd::vector& entityIds, const AZStd::string& filePath) = 0; + }; + + using PrefabSystemScriptingBus = AZ::EBus; + + } // namespace Prefab +} // namespace AzToolsFramework + diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemScriptingHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemScriptingHandler.cpp new file mode 100644 index 0000000000..d34e2cfc92 --- /dev/null +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemScriptingHandler.cpp @@ -0,0 +1,75 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include +#include +#include + +namespace AzToolsFramework::Prefab +{ + void PrefabSystemScriptingHandler::Reflect(AZ::ReflectContext* context) + { + if (auto behaviorContext = azrtti_cast(context)) + { + behaviorContext->ConstantProperty("InvalidTemplateId", BehaviorConstant(InvalidTemplateId)) + ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common) + ->Attribute(AZ::Script::Attributes::Module, "prefab") + ->Attribute(AZ::Script::Attributes::Category, "Prefab"); + + behaviorContext->EBus("PrefabSystemScriptingBus") + ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common) + ->Attribute(AZ::Script::Attributes::Module, "prefab") + ->Attribute(AZ::Script::Attributes::Category, "Prefab") + ->Event("CreatePrefab", &PrefabSystemScriptingBus::Events::CreatePrefabTemplate); + } + } + + void PrefabSystemScriptingHandler::Connect(PrefabSystemComponentInterface* prefabSystemComponentInterface) + { + AZ_Assert(prefabSystemComponentInterface != nullptr, "prefabSystemComponentInterface must not be null"); + m_prefabSystemComponentInterface = prefabSystemComponentInterface; + PrefabSystemScriptingBus::Handler::BusConnect(); + } + + void PrefabSystemScriptingHandler::Disconnect() + { + PrefabSystemScriptingBus::Handler::BusDisconnect(); + } + + TemplateId PrefabSystemScriptingHandler::CreatePrefabTemplate(const AZStd::vector& entityIds, const AZStd::string& filePath) + { + AZStd::vector entities; + + for (const auto& entityId : entityIds) + { + AZ::Entity* entity = nullptr; + AZ::ComponentApplicationBus::BroadcastResult(entity, &AZ::ComponentApplicationBus::Events::FindEntity, entityId); + + AZ_Warning( + "PrefabSystemComponent", entity, "EntityId %s was not found and will not be added to the prefab", + entityId.ToString().c_str()); + + if (entity) + { + entities.push_back(entity); + } + } + + auto prefab = m_prefabSystemComponentInterface->CreatePrefab(entities, {}, AZ::IO::PathView(AZStd::string_view(filePath))); + + if (!prefab) + { + AZ_Error("PrefabSystemComponenent", false, "Failed to create prefab %s", filePath.c_str()); + return InvalidTemplateId; + } + + return prefab->GetTemplateId(); + } +} diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemScriptingHandler.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemScriptingHandler.h new file mode 100644 index 0000000000..809690bf35 --- /dev/null +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemScriptingHandler.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once +#include + +namespace AzToolsFramework +{ + namespace Prefab + { + class PrefabSystemScriptingHandler + : PrefabSystemScriptingBus::Handler + { + public: + static void Reflect(AZ::ReflectContext* context); + + PrefabSystemScriptingHandler() = default; + + void Connect(PrefabSystemComponentInterface* prefabSystemComponentInterface); + void Disconnect(); + + private: + AZ_DISABLE_COPY(PrefabSystemScriptingHandler); + + ////////////////////////////////////////////////////////////////////////// + // PrefabSystemScriptingBus implementation + TemplateId CreatePrefabTemplate(const AZStd::vector& entityIds, const AZStd::string& filePath) override; + ////////////////////////////////////////////////////////////////////////// + + PrefabSystemComponentInterface* m_prefabSystemComponentInterface = nullptr; + }; + } // namespace Prefab +} // namespace AzToolsFramework + diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Procedural/ProceduralPrefabAsset.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Procedural/ProceduralPrefabAsset.cpp new file mode 100644 index 0000000000..465d83e94b --- /dev/null +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Procedural/ProceduralPrefabAsset.cpp @@ -0,0 +1,144 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#include +#include +#include +#include +#include + +namespace AZ::Prefab +{ + static constexpr const char s_useProceduralPrefabsKey[] = "/O3DE/Preferences/Prefabs/UseProceduralPrefabs"; + + // ProceduralPrefabAsset + + ProceduralPrefabAsset::ProceduralPrefabAsset(const AZ::Data::AssetId& assetId) + : AZ::Data::AssetData(assetId) + , m_templateId(AzToolsFramework::Prefab::InvalidTemplateId) + { + } + + void ProceduralPrefabAsset::Reflect(AZ::ReflectContext* context) + { + PrefabDomData::Reflect(context); + + if (auto* serializeContext = azrtti_cast(context); serializeContext != nullptr) + { + serializeContext->Class() + ->Version(1) + ->Field("Template Name", &ProceduralPrefabAsset::m_templateName) + ->Field("Template ID", &ProceduralPrefabAsset::m_templateId); + } + } + + const AZStd::string& ProceduralPrefabAsset::GetTemplateName() const + { + return m_templateName; + } + + void ProceduralPrefabAsset::SetTemplateName(AZStd::string templateName) + { + m_templateName = AZStd::move(templateName); + } + + AzToolsFramework::Prefab::TemplateId ProceduralPrefabAsset::GetTemplateId() const + { + return m_templateId; + } + + void ProceduralPrefabAsset::SetTemplateId(AzToolsFramework::Prefab::TemplateId templateId) + { + m_templateId = templateId; + } + + bool ProceduralPrefabAsset::UseProceduralPrefabs() + { + bool useProceduralPrefabs = false; + bool result = AZ::SettingsRegistry::Get()->GetObject(useProceduralPrefabs, s_useProceduralPrefabsKey); + return result && useProceduralPrefabs; + } + + // PrefabDomData + + void PrefabDomData::Reflect(AZ::ReflectContext* context) + { + if (auto* jsonContext = azrtti_cast(context)) + { + jsonContext->Serializer()->HandlesType(); + } + + AZ::SerializeContext* serializeContext = azrtti_cast(context); + if (serializeContext) + { + serializeContext->Class() + ->Version(1); + } + } + + void PrefabDomData::CopyValue(const rapidjson::Value& inputValue) + { + m_prefabDom.CopyFrom(inputValue, m_prefabDom.GetAllocator()); + } + + const AzToolsFramework::Prefab::PrefabDom& PrefabDomData::GetValue() const + { + return m_prefabDom; + } + + // PrefabDomDataJsonSerializer + + AZ::JsonSerializationResult::Result PrefabDomDataJsonSerializer::Load( + void* outputValue, + [[maybe_unused]] const AZ::Uuid& outputValueTypeId, + const rapidjson::Value& inputValue, + AZ::JsonDeserializerContext& context) + { + AZ_Assert(outputValueTypeId == azrtti_typeid(), + "PrefabDomDataJsonSerializer Load against output typeID that was not PrefabDomData"); + AZ_Assert(outputValue, "PrefabDomDataJsonSerializer Load against null output"); + + namespace JSR = AZ::JsonSerializationResult; + JSR::ResultCode result(JSR::Tasks::ReadField); + + if (inputValue.IsObject() == false) + { + result.Combine(context.Report(JSR::Tasks::ReadField, JSR::Outcomes::Missing, "Missing object")); + return context.Report(result, "Prefab should be an object."); + } + + if (inputValue.MemberCount() < 1) + { + result.Combine(context.Report(JSR::Tasks::ReadField, JSR::Outcomes::Missing, "Missing members")); + return context.Report(result, "Prefab should have multiple members."); + } + + auto* outputVariable = reinterpret_cast(outputValue); + outputVariable->CopyValue(inputValue); + return context.Report(result, "Loaded procedural prefab"); + } + + AZ::JsonSerializationResult::Result PrefabDomDataJsonSerializer::Store( + rapidjson::Value& outputValue, + const void* inputValue, + [[maybe_unused]] const void* defaultValue, + [[maybe_unused]] const AZ::Uuid& valueTypeId, + AZ::JsonSerializerContext& context) + { + AZ_Assert(inputValue, "Input value for PrefabDomDataJsonSerializer can't be null."); + AZ_Assert(azrtti_typeid() == valueTypeId, + "Unable to Serialize because the provided type is not PrefabGroup::PrefabDomData."); + + const PrefabDomData* prefabDomData = reinterpret_cast(inputValue); + + namespace JSR = AZ::JsonSerializationResult; + JSR::ResultCode result(JSR::Tasks::WriteValue); + outputValue.SetObject(); + outputValue.CopyFrom(prefabDomData->GetValue(), context.GetJsonAllocator()); + return context.Report(result, "Stored procedural prefab"); + } +} diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Procedural/ProceduralPrefabAsset.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Procedural/ProceduralPrefabAsset.h new file mode 100644 index 0000000000..dd57389b7b --- /dev/null +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Procedural/ProceduralPrefabAsset.h @@ -0,0 +1,90 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#pragma once + +#include +#include +#include +#include + +namespace AZ::Prefab +{ + //! A wrapper around the JSON DOM type so that the assets can read in and write out + //! JSON directly since Prefabs are JSON serialized entity-component data + class PrefabDomData final + { + public: + AZ_RTTI(PrefabDomData, "{C73A3360-D772-4D41-9118-A039BF9340C1}"); + AZ_CLASS_ALLOCATOR(PrefabDomData, AZ::SystemAllocator, 0); + + PrefabDomData() = default; + ~PrefabDomData() = default; + + static void Reflect(AZ::ReflectContext* context); + + void CopyValue(const rapidjson::Value& inputValue); + const AzToolsFramework::Prefab::PrefabDom& GetValue() const; + + private: + AzToolsFramework::Prefab::PrefabDom m_prefabDom; + }; + + //! Registered to help read/write JSON for the PrefabDomData::m_prefabDom + class PrefabDomDataJsonSerializer final + : public AZ::BaseJsonSerializer + { + public: + AZ_RTTI(PrefabDomDataJsonSerializer, "{9FC48652-A00B-4EFA-8FD9-345A8E625439}", BaseJsonSerializer); + AZ_CLASS_ALLOCATOR(PrefabDomDataJsonSerializer, AZ::SystemAllocator, 0); + + ~PrefabDomDataJsonSerializer() override = default; + + AZ::JsonSerializationResult::Result Load( + void* outputValue, + const AZ::Uuid& outputValueTypeId, + const rapidjson::Value& inputValue, + AZ::JsonDeserializerContext& context) override; + + AZ::JsonSerializationResult::Result Store( + rapidjson::Value& outputValue, + const void* inputValue, + const void* defaultValue, + const AZ::Uuid& valueTypeId, + AZ::JsonSerializerContext& context) override; + }; + + //! An asset type to register templates into the Prefab system so that they + //! can instantiate like Authored Prefabs + class ProceduralPrefabAsset + : public AZ::Data::AssetData + { + public: + AZ_CLASS_ALLOCATOR(ProceduralPrefabAsset, AZ::SystemAllocator, 0); + AZ_RTTI(ProceduralPrefabAsset, "{9B7C8459-471E-4EAD-A363-7990CC4065A9}", AZ::Data::AssetData); + + static bool UseProceduralPrefabs(); + + ProceduralPrefabAsset(const AZ::Data::AssetId& assetId = AZ::Data::AssetId()); + ~ProceduralPrefabAsset() override = default; + ProceduralPrefabAsset(const ProceduralPrefabAsset& rhs) = delete; + ProceduralPrefabAsset& operator=(const ProceduralPrefabAsset& rhs) = delete; + + const AZStd::string& GetTemplateName() const; + void SetTemplateName(AZStd::string templateName); + + AzToolsFramework::Prefab::TemplateId GetTemplateId() const; + void SetTemplateId(AzToolsFramework::Prefab::TemplateId templateId); + + static void Reflect(AZ::ReflectContext* context); + + private: + AZStd::string m_templateName; + AzToolsFramework::Prefab::TemplateId m_templateId; + }; + +} diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/ScriptingPrefabLoader.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/ScriptingPrefabLoader.cpp new file mode 100644 index 0000000000..bee8a8bc3b --- /dev/null +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/ScriptingPrefabLoader.cpp @@ -0,0 +1,37 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include + +namespace AzToolsFramework::Prefab +{ + void ScriptingPrefabLoader::Connect(PrefabLoaderInterface* prefabLoaderInterface) + { + AZ_Assert(prefabLoaderInterface, "prefabLoaderInterface must not be null"); + + m_prefabLoaderInterface = prefabLoaderInterface; + PrefabLoaderScriptingBus::Handler::BusConnect(); + } + + void ScriptingPrefabLoader::Disconnect() + { + PrefabLoaderScriptingBus::Handler::BusDisconnect(); + } + + AZ::Outcome ScriptingPrefabLoader::SaveTemplateToString(TemplateId templateId) + { + AZStd::string json; + + if (m_prefabLoaderInterface->SaveTemplateToString(templateId, json)) + { + return AZ::Success(json); + } + + return AZ::Failure(); + } +} // namespace AzToolsFramework::Prefab diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/ScriptingPrefabLoader.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/ScriptingPrefabLoader.h new file mode 100644 index 0000000000..ec6219909e --- /dev/null +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/ScriptingPrefabLoader.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include + +namespace AzToolsFramework +{ + namespace Prefab + { + /** + * The Scripting Prefab Loader handles scripting-friendly API requests for the prefab loader + */ + class ScriptingPrefabLoader + : private PrefabLoaderScriptingBus::Handler + { + public: + AZ_CLASS_ALLOCATOR(ScriptingPrefabLoader, AZ::SystemAllocator, 0); + AZ_RTTI(ScriptingPrefabLoader, "{ABC3C989-4D4F-41E7-B25B-B0FEF97177E6}"); + + void Connect(PrefabLoaderInterface* prefabLoaderInterface); + void Disconnect(); + + private: + + ////////////////////////////////////////////////////////////////////////// + // PrefabLoaderRequestBus implementation + AZ::Outcome SaveTemplateToString(TemplateId templateId) override; + ////////////////////////////////////////////////////////////////////////// + + PrefabLoaderInterface* m_prefabLoaderInterface = nullptr; + }; + } // namespace Prefab +} // namespace AzToolsFramework + diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/ScriptEditorComponent.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/ScriptEditorComponent.cpp index be29af5c0f..dd156e0e40 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/ScriptEditorComponent.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/ScriptEditorComponent.cpp @@ -1025,7 +1025,7 @@ namespace AzToolsFramework ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/LuaScript.svg") ->Attribute(AZ::Edit::Attributes::PrimaryAssetType, AZ::AzTypeInfo::Uuid()) ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/Script.png") - ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/lua-script/") + ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/scripting/lua-script/") ->DataElement("AssetRef", &ScriptEditorComponent::m_scriptAsset, "Script", "Which script to use") ->Attribute(AZ::Edit::Attributes::ChangeNotify, &ScriptEditorComponent::ScriptHasChanged) ->Attribute("BrowseIcon", ":/stylesheet/img/UI20/browse-edit-select-files.svg") diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp index 07dcd2ab20..d4b227c4ff 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -25,6 +26,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -218,6 +222,16 @@ namespace AzToolsFramework instantiateAction, &QAction::triggered, instantiateAction, [] { ContextMenu_InstantiatePrefab(); }); } + // Instantiate Procedural Prefab + if (AZ::Prefab::ProceduralPrefabAsset::UseProceduralPrefabs()) + { + QAction* action = menu->addAction(QObject::tr("Instantiate Procedural Prefab...")); + action->setToolTip(QObject::tr("Instantiates a procedural prefab file in a prefab.")); + + QObject::connect( + action, &QAction::triggered, action, [] { ContextMenu_InstantiateProceduralPrefab(); }); + } + menu->addSeparator(); bool itemWasShown = false; @@ -435,6 +449,38 @@ namespace AzToolsFramework } } + void PrefabIntegrationManager::ContextMenu_InstantiateProceduralPrefab() + { + AZStd::string prefabAssetPath; + bool hasUserForProceduralPrefabAsset = QueryUserForProceduralPrefabAsset(prefabAssetPath); + + if (hasUserForProceduralPrefabAsset) + { + AZ::EntityId parentId; + AZ::Vector3 position = AZ::Vector3::CreateZero(); + + EntityIdList selectedEntities; + ToolsApplicationRequestBus::BroadcastResult(selectedEntities, &ToolsApplicationRequests::GetSelectedEntities); + if (selectedEntities.size() == 1) + { + parentId = selectedEntities.front(); + AZ::TransformBus::EventResult(position, parentId, &AZ::TransformInterface::GetWorldTranslation); + } + else + { + // otherwise return since it needs to be inside an authored prefab + return; + } + + // Instantiating from context menu always puts the instance at the root level + auto createPrefabOutcome = s_prefabPublicInterface->InstantiatePrefab(prefabAssetPath, parentId, position); + if (!createPrefabOutcome.IsSuccess()) + { + WarnUserOfError("Prefab Instantiation Error", createPrefabOutcome.GetError()); + } + } + } + void PrefabIntegrationManager::ContextMenu_EditPrefab(AZ::EntityId containerEntity) { s_prefabFocusInterface->FocusOnOwningPrefab(containerEntity); @@ -690,6 +736,33 @@ namespace AzToolsFramework return true; } + bool PrefabIntegrationManager::QueryUserForProceduralPrefabAsset(AZStd::string& outPrefabAssetPath) + { + using namespace AzToolsFramework; + auto selection = AssetBrowser::AssetSelectionModel::AssetTypeSelection(azrtti_typeid()); + EditorRequests::Bus::Broadcast(&AzToolsFramework::EditorRequests::BrowseForAssets, selection); + + if (!selection.IsValid()) + { + return false; + } + + auto product = azrtti_cast(selection.GetResult()); + if (product == nullptr) + { + return false; + } + + outPrefabAssetPath = product->GetRelativePath(); + + auto asset = AZ::Data::AssetManager::Instance().GetAsset( + product->GetAssetId(), + azrtti_typeid(), + AZ::Data::AssetLoadBehavior::Default); + + return asset.BlockUntilLoadComplete() != AZ::Data::AssetData::AssetStatus::Error; + } + void PrefabIntegrationManager::WarnUserOfError(AZStd::string_view title, AZStd::string_view message) { QWidget* activeWindow = QApplication::activeWindow(); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.h b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.h index 44e30b2013..696c05991c 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.h @@ -91,6 +91,7 @@ namespace AzToolsFramework // Context menu item handlers static void ContextMenu_CreatePrefab(AzToolsFramework::EntityIdList selectedEntities); static void ContextMenu_InstantiatePrefab(); + static void ContextMenu_InstantiateProceduralPrefab(); static void ContextMenu_EditPrefab(AZ::EntityId containerEntity); static void ContextMenu_SavePrefab(AZ::EntityId containerEntity); static void ContextMenu_DeleteSelected(); @@ -101,6 +102,7 @@ namespace AzToolsFramework const AZStd::string& suggestedName, const char* initialTargetDirectory, AZ::u32 prefabUserSettingsId, QWidget* activeWindow, AZStd::string& outPrefabName, AZStd::string& outPrefabFilePath); static bool QueryUserForPrefabFilePath(AZStd::string& outPrefabFilePath); + static bool QueryUserForProceduralPrefabAsset(AZStd::string& outPrefabAssetPath); static void WarnUserOfError(AZStd::string_view title, AZStd::string_view message); // Path and filename generation diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp index 4c1fefc622..7015a25ce1 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp @@ -103,10 +103,6 @@ namespace AzToolsFramework static const char* const ResetEntityTransformDesc = "Reset transform based on manipulator mode"; static const char* const ResetManipulatorTitle = "Reset Manipulator"; static const char* const ResetManipulatorDesc = "Reset the manipulator to recenter it on the selected entity"; - static const char* const ResetTransformLocalTitle = "Reset Transform (Local)"; - static const char* const ResetTransformLocalDesc = "Reset transform to local space"; - static const char* const ResetTransformWorldTitle = "Reset Transform (World)"; - static const char* const ResetTransformWorldDesc = "Reset transform to world space"; static const char* const EntityBoxSelectUndoRedoDesc = "Box Select Entities"; static const char* const EntityDeselectUndoRedoDesc = "Deselect Entity"; @@ -2424,45 +2420,9 @@ namespace AzToolsFramework AddAction( m_actions, { QKeySequence(Qt::CTRL + Qt::Key_R) }, EditResetManipulator, ResetManipulatorTitle, ResetManipulatorDesc, - AZStd::bind(AZStd::mem_fn(&EditorTransformComponentSelection::DelegateClearManipulatorOverride), this)); - - AddAction( - m_actions, { QKeySequence(Qt::ALT + Qt::Key_R) }, EditResetLocal, ResetTransformLocalTitle, ResetTransformLocalDesc, - [this]() - { - switch (m_mode) - { - case Mode::Rotation: - ResetOrientationForSelectedEntitiesLocal(); - break; - case Mode::Scale: - CopyScaleToSelectedEntitiesIndividualWorld(1.0f); - break; - case Mode::Translation: - // do nothing - break; - } - }); - - AddAction( - m_actions, { QKeySequence(Qt::SHIFT + Qt::Key_R) }, EditResetWorld, ResetTransformWorldTitle, ResetTransformWorldDesc, - [this]() + [this] { - switch (m_mode) - { - case Mode::Rotation: - { - // begin an undo batch so operations inside CopyOrientation... and - // DelegateClear... are grouped into a single undo/redo - ScopedUndoBatch undoBatch{ ResetTransformWorldTitle }; - CopyOrientationToSelectedEntitiesIndividual(AZ::Quaternion::CreateIdentity()); - ClearManipulatorOrientationOverride(); - } - break; - case Mode::Scale: - case Mode::Translation: - break; - } + DelegateClearManipulatorOverride(); }); AddAction( diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelectionRequestBus.h b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelectionRequestBus.h index d0c106a15e..35f5b0ba99 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelectionRequestBus.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelectionRequestBus.h @@ -31,8 +31,6 @@ namespace AzToolsFramework constexpr inline AZ::Crc32 EditPivot = AZ_CRC_CE("com.o3de.action.editortransform.editpivot"); constexpr inline AZ::Crc32 EditReset = AZ_CRC_CE("com.o3de.action.editortransform.editreset"); constexpr inline AZ::Crc32 EditResetManipulator = AZ_CRC_CE("com.o3de.action.editortransform.editresetmanipulator"); - constexpr inline AZ::Crc32 EditResetLocal = AZ_CRC_CE("com.o3de.action.editortransform.editresetlocal"); - constexpr inline AZ::Crc32 EditResetWorld = AZ_CRC_CE("com.o3de.action.editortransform.editresetworld"); constexpr inline AZ::Crc32 ViewportUiVisible = AZ_CRC_CE("com.o3de.action.editortransform.viewportuivisible"); //@} diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake b/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake index d6a5651353..37559564db 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake @@ -154,6 +154,8 @@ set(FILES Entity/SliceEditorEntityOwnershipService.h Entity/SliceEditorEntityOwnershipService.cpp Entity/SliceEditorEntityOwnershipServiceBus.h + Entity/EntityUtilityComponent.h + Entity/EntityUtilityComponent.cpp Fingerprinting/TypeFingerprinter.h Fingerprinting/TypeFingerprinter.cpp FocusMode/FocusModeInterface.h @@ -648,9 +650,15 @@ set(FILES Prefab/PrefabLoader.h Prefab/PrefabLoader.cpp Prefab/PrefabLoaderInterface.h + Prefab/PrefabLoaderScriptingBus.h + Prefab/ScriptingPrefabLoader.h + Prefab/ScriptingPrefabLoader.cpp Prefab/PrefabSystemComponent.h Prefab/PrefabSystemComponent.cpp Prefab/PrefabSystemComponentInterface.h + Prefab/PrefabSystemScriptingBus.h + Prefab/PrefabSystemScriptingHandler.h + Prefab/PrefabSystemScriptingHandler.cpp Prefab/Instance/Instance.h Prefab/Instance/Instance.cpp Prefab/Instance/InstanceSerializer.h @@ -673,6 +681,8 @@ set(FILES Prefab/Instance/TemplateInstanceMapperInterface.h Prefab/Link/Link.h Prefab/Link/Link.cpp + Prefab/Procedural/ProceduralPrefabAsset.h + Prefab/Procedural/ProceduralPrefabAsset.cpp Prefab/PrefabPublicHandler.h Prefab/PrefabPublicHandler.cpp Prefab/PrefabPublicInterface.h diff --git a/Code/Framework/AzToolsFramework/Tests/Entity/EntityUtilityComponentTests.cpp b/Code/Framework/AzToolsFramework/Tests/Entity/EntityUtilityComponentTests.cpp new file mode 100644 index 0000000000..fce44d4833 --- /dev/null +++ b/Code/Framework/AzToolsFramework/Tests/Entity/EntityUtilityComponentTests.cpp @@ -0,0 +1,252 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include + +#include +#include +#include +#include +#include + +namespace UnitTest +{ + // Global variables for communicating between Lua test code and C++ + AZ::EntityId g_globalEntityId = AZ::EntityId{}; + AZStd::string g_globalString = ""; + AzFramework::BehaviorComponentId g_globalComponentId = {}; + AZStd::vector g_globalComponentDetails = {}; + bool g_globalBool = false; + + class EntityUtilityComponentTests + : public ToolsApplicationFixture + { + void InitProperties() + { + AZ::ComponentApplicationRequests* componentApplicationRequests = AZ::Interface::Get(); + + ASSERT_NE(componentApplicationRequests, nullptr); + + auto behaviorContext = componentApplicationRequests->GetBehaviorContext(); + + ASSERT_NE(behaviorContext, nullptr); + + behaviorContext->Property("g_globalEntityId", BehaviorValueProperty(&g_globalEntityId)); + behaviorContext->Property("g_globalString", BehaviorValueProperty(&g_globalString)); + behaviorContext->Property("g_globalComponentId", BehaviorValueProperty(&g_globalComponentId)); + behaviorContext->Property("g_globalBool", BehaviorValueProperty(&g_globalBool)); + behaviorContext->Property("g_globalComponentDetails", BehaviorValueProperty(&g_globalComponentDetails)); + + g_globalEntityId = AZ::EntityId{}; + g_globalString = AZStd::string{}; + g_globalComponentId = AzFramework::BehaviorComponentId{}; + g_globalBool = false; + g_globalComponentDetails = AZStd::vector{}; + } + + void SetUpEditorFixtureImpl() override + { + InitProperties(); + } + + void TearDownEditorFixtureImpl() override + { + g_globalString.set_capacity(0); // Free all memory + g_globalComponentDetails.set_capacity(0); + } + }; + + TEST_F(EntityUtilityComponentTests, CreateEntity) + { + AZ::ScriptContext sc; + auto behaviorContext = AZ::Interface::Get()->GetBehaviorContext(); + + sc.BindTo(behaviorContext); + sc.Execute(R"LUA( + g_globalEntityId = EntityUtilityBus.Broadcast.CreateEditorReadyEntity("test") + my_entity = Entity(g_globalEntityId) + g_globalString = my_entity:GetName() + )LUA"); + + EXPECT_NE(g_globalEntityId, AZ::EntityId{}); + EXPECT_STREQ(g_globalString.c_str(), "test"); + + AZ::Entity* entity = AZ::Interface::Get()->FindEntity(g_globalEntityId); + + ASSERT_NE(entity, nullptr); + + // Test cleaning up, make sure the entity is destroyed + AzToolsFramework::EntityUtilityBus::Broadcast(&AzToolsFramework::EntityUtilityBus::Events::ResetEntityContext); + + entity = AZ::Interface::Get()->FindEntity(g_globalEntityId); + + ASSERT_EQ(entity, nullptr); + } + + TEST_F(EntityUtilityComponentTests, CreateEntityEmptyName) + { + AZ::ScriptContext sc; + auto behaviorContext = AZ::Interface::Get()->GetBehaviorContext(); + + sc.BindTo(behaviorContext); + sc.Execute(R"LUA( + g_globalEntityId = EntityUtilityBus.Broadcast.CreateEditorReadyEntity("") + )LUA"); + + EXPECT_NE(g_globalEntityId, AZ::EntityId{}); + + AZ::Entity* entity = AZ::Interface::Get()->FindEntity(g_globalEntityId); + + ASSERT_NE(entity, nullptr); + } + + TEST_F(EntityUtilityComponentTests, FindComponent) + { + AZ::ScriptContext sc; + auto behaviorContext = AZ::Interface::Get()->GetBehaviorContext(); + + sc.BindTo(behaviorContext); + sc.Execute(R"LUA( + ent_id = EntityUtilityBus.Broadcast.CreateEditorReadyEntity("test") + g_globalComponentId = EntityUtilityBus.Broadcast.GetOrAddComponentByTypeName(ent_id, "27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0 TransformComponent") + )LUA"); + + EXPECT_TRUE(g_globalComponentId.IsValid()); + } + + TEST_F(EntityUtilityComponentTests, InvalidComponentName) + { + AZ::ScriptContext sc; + auto behaviorContext = AZ::Interface::Get()->GetBehaviorContext(); + + sc.BindTo(behaviorContext); + AZ_TEST_START_TRACE_SUPPRESSION; + sc.Execute(R"LUA( + ent_id = EntityUtilityBus.Broadcast.CreateEditorReadyEntity("test") + g_globalComponentId = EntityUtilityBus.Broadcast.GetOrAddComponentByTypeName(ent_id, "ThisIsNotAComponent-Error") + )LUA"); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); + EXPECT_FALSE(g_globalComponentId.IsValid()); + } + + TEST_F(EntityUtilityComponentTests, InvalidComponentId) + { + AZ::ScriptContext sc; + auto behaviorContext = AZ::Interface::Get()->GetBehaviorContext(); + + sc.BindTo(behaviorContext); + AZ_TEST_START_TRACE_SUPPRESSION; + sc.Execute(R"LUA( + ent_id = EntityUtilityBus.Broadcast.CreateEditorReadyEntity("test") + g_globalComponentId = EntityUtilityBus.Broadcast.GetOrAddComponentByTypeName(ent_id, "{1234-hello-world-this-is-not-an-id}") + )LUA"); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); // Should get 1 error stating the type id is not valid + EXPECT_FALSE(g_globalComponentId.IsValid()); + } + + TEST_F(EntityUtilityComponentTests, CreateComponent) + { + AZ::ScriptContext sc; + auto behaviorContext = AZ::Interface::Get()->GetBehaviorContext(); + + sc.BindTo(behaviorContext); + sc.Execute(R"LUA( + ent_id = EntityUtilityBus.Broadcast.CreateEditorReadyEntity("test") + g_globalComponentId = EntityUtilityBus.Broadcast.GetOrAddComponentByTypeName(ent_id, "ScriptEditorComponent") + )LUA"); + + EXPECT_TRUE(g_globalComponentId.IsValid()); + } + + TEST_F(EntityUtilityComponentTests, UpdateComponent) + { + AZ::ScriptContext sc; + auto behaviorContext = AZ::Interface::Get()->GetBehaviorContext(); + + sc.BindTo(behaviorContext); + sc.Execute(R"LUA( + g_globalEntityId = EntityUtilityBus.Broadcast.CreateEditorReadyEntity("test") + comp_id = EntityUtilityBus.Broadcast.GetOrAddComponentByTypeName(g_globalEntityId, "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent") + json_update = [[ + { + "Transform Data": { "Rotate": [0.0, 0.1, 180.0] } + } + ]] + g_globalBool = EntityUtilityBus.Broadcast.UpdateComponentForEntity(g_globalEntityId, comp_id, json_update); + )LUA"); + + EXPECT_TRUE(g_globalBool); + EXPECT_NE(g_globalEntityId, AZ::EntityId(AZ::EntityId::InvalidEntityId)); + + AZ::Entity* entity = AZ::Interface::Get()->FindEntity(g_globalEntityId); + + auto* transformComponent = entity->FindComponent(); + + ASSERT_NE(transformComponent, nullptr); + + AZ::Vector3 localRotation = transformComponent->GetLocalRotationQuaternion().GetEulerDegrees(); + + EXPECT_EQ(localRotation, AZ::Vector3(.0f, 0.1f, 180.0f)); + } + + TEST_F(EntityUtilityComponentTests, GetComponentJson) + { + AZ::ScriptContext sc; + auto behaviorContext = AZ::Interface::Get()->GetBehaviorContext(); + + sc.BindTo(behaviorContext); + sc.Execute(R"LUA( + g_globalString = EntityUtilityBus.Broadcast.GetComponentDefaultJson("ScriptEditorComponent") + )LUA"); + + EXPECT_STRNE(g_globalString.c_str(), ""); + } + + TEST_F(EntityUtilityComponentTests, GetComponentJsonDoesNotExist) + { + AZ::ScriptContext sc; + auto behaviorContext = AZ::Interface::Get()->GetBehaviorContext(); + + sc.BindTo(behaviorContext); + AZ_TEST_START_TRACE_SUPPRESSION; + sc.Execute(R"LUA( + g_globalString = EntityUtilityBus.Broadcast.GetComponentDefaultJson("404") + )LUA"); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); // 1 error: Failed to find component id for type name 404 + + EXPECT_STREQ(g_globalString.c_str(), ""); + } + + TEST_F(EntityUtilityComponentTests, SearchComponents) + { + AZ::ScriptContext sc; + auto behaviorContext = AZ::Interface::Get()->GetBehaviorContext(); + + sc.BindTo(behaviorContext); + sc.Execute(R"LUA( + g_globalComponentDetails = EntityUtilityBus.Broadcast.FindMatchingComponents("Transform*") + )LUA"); + + // There should be 2 transform components + EXPECT_EQ(g_globalComponentDetails.size(), 2); + } + + TEST_F(EntityUtilityComponentTests, SearchComponentsNotFound) + { + AZ::ScriptContext sc; + auto behaviorContext = AZ::Interface::Get()->GetBehaviorContext(); + + sc.BindTo(behaviorContext); + sc.Execute(R"LUA( + g_globalComponentDetails = EntityUtilityBus.Broadcast.FindMatchingComponents("404") + )LUA"); + + EXPECT_EQ(g_globalComponentDetails.size(), 0); + } +} diff --git a/Code/Framework/AzToolsFramework/Tests/LogLines.cpp b/Code/Framework/AzToolsFramework/Tests/LogLines.cpp index 5af6513720..1c69e855ee 100644 --- a/Code/Framework/AzToolsFramework/Tests/LogLines.cpp +++ b/Code/Framework/AzToolsFramework/Tests/LogLines.cpp @@ -25,7 +25,7 @@ namespace UnitTest R"X(Executing RC.EXE: '"E:\lyengine\dev\windows\bin\profile\rc.exe" "E:/Directory/File.tga")X", R"X(Executing RC.EXE with working directory : '')X", R"X(ResourceCompiler 64 - bit DEBUG)X", - R"X(Platform support : PC, PowerVR, etc2Comp)X", + R"X(Platform support : PC, PowerVR)X", R"X(Version 1.1.8.6 Nov 5 2018 13 : 28 : 28)X" }; diff --git a/Code/Framework/AzToolsFramework/Tests/Prefab/PrefabScriptingTests.cpp b/Code/Framework/AzToolsFramework/Tests/Prefab/PrefabScriptingTests.cpp new file mode 100644 index 0000000000..7e61c50629 --- /dev/null +++ b/Code/Framework/AzToolsFramework/Tests/Prefab/PrefabScriptingTests.cpp @@ -0,0 +1,173 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include + +#include +#include +#include + +namespace UnitTest +{ + TemplateId g_globalTemplateId = {}; + AZStd::string g_globalPrefabString = ""; + + class PrefabScriptingTest : public PrefabTestFixture + { + void InitProperties() const + { + AZ::ComponentApplicationRequests* componentApplicationRequests = AZ::Interface::Get(); + + ASSERT_NE(componentApplicationRequests, nullptr); + + auto behaviorContext = componentApplicationRequests->GetBehaviorContext(); + + ASSERT_NE(behaviorContext, nullptr); + + behaviorContext->Property("g_globalTemplateId", BehaviorValueProperty(&g_globalTemplateId)); + behaviorContext->Property("g_globalPrefabString", BehaviorValueProperty(&g_globalPrefabString)); + + g_globalTemplateId = TemplateId{}; + g_globalPrefabString = AZStd::string{}; + } + + void SetUpEditorFixtureImpl() override + { + InitProperties(); + } + + void TearDownEditorFixtureImpl() override + { + g_globalPrefabString.set_capacity(0); // Free all memory + } + }; + + TEST_F(PrefabScriptingTest, PrefabScripting_CreatePrefab) + { + AZ::ScriptContext sc; + auto behaviorContext = AZ::Interface::Get()->GetBehaviorContext(); + + sc.BindTo(behaviorContext); + sc.Execute(R"LUA( + my_id = EntityUtilityBus.Broadcast.CreateEditorReadyEntity("test") + entities = vector_EntityId() + entities:push_back(my_id) + g_globalTemplateId = PrefabSystemScriptingBus.Broadcast.CreatePrefab(entities, "test.prefab") + )LUA"); + + EXPECT_NE(g_globalTemplateId, TemplateId{}); + + auto prefabSystemComponentInterface = AZ::Interface::Get(); + + ASSERT_NE(prefabSystemComponentInterface, nullptr); + + TemplateReference templateRef = prefabSystemComponentInterface->FindTemplate(g_globalTemplateId); + + EXPECT_TRUE(templateRef); + } + + TEST_F(PrefabScriptingTest, PrefabScripting_CreatePrefab_NoEntities) + { + AZ::ScriptContext sc; + auto behaviorContext = AZ::Interface::Get()->GetBehaviorContext(); + + sc.BindTo(behaviorContext); + sc.Execute(R"LUA( + my_id = EntityUtilityBus.Broadcast.CreateEditorReadyEntity("test") + entities = vector_EntityId() + g_globalTemplateId = PrefabSystemScriptingBus.Broadcast.CreatePrefab(entities, "test.prefab") + )LUA"); + + EXPECT_NE(g_globalTemplateId, TemplateId{}); + + auto prefabSystemComponentInterface = AZ::Interface::Get(); + + ASSERT_NE(prefabSystemComponentInterface, nullptr); + + TemplateReference templateRef = prefabSystemComponentInterface->FindTemplate(g_globalTemplateId); + + EXPECT_TRUE(templateRef); + } + + TEST_F(PrefabScriptingTest, PrefabScripting_CreatePrefab_NoPath) + { + AZ::ScriptContext sc; + auto behaviorContext = AZ::Interface::Get()->GetBehaviorContext(); + + sc.BindTo(behaviorContext); + AZ_TEST_START_TRACE_SUPPRESSION; + sc.Execute(R"LUA( + my_id = EntityUtilityBus.Broadcast.CreateEditorReadyEntity("test") + entities = vector_EntityId() + template_id = PrefabSystemScriptingBus.Broadcast.CreatePrefab(entities, "") + )LUA"); + /* + error: PrefabSystemComponent::CreateTemplateFromInstance - Attempted to create a prefab template from an instance without a source file path. Unable to proceed. + error: Failed to create a Template associated with file path during CreatePrefab. + error: Failed to create prefab + */ + AZ_TEST_STOP_TRACE_SUPPRESSION(3); + } + + TEST_F(PrefabScriptingTest, PrefabScripting_SaveToString) + { + AZ::ScriptContext sc; + auto behaviorContext = AZ::Interface::Get()->GetBehaviorContext(); + + sc.BindTo(behaviorContext); + sc.Execute(R"LUA( + my_id = EntityUtilityBus.Broadcast.CreateEditorReadyEntity("test") + entities = vector_EntityId() + entities:push_back(my_id) + template_id = PrefabSystemScriptingBus.Broadcast.CreatePrefab(entities, "test.prefab") + my_result = PrefabLoaderScriptingBus.Broadcast.SaveTemplateToString(template_id) + + if my_result:IsSuccess() then + g_globalPrefabString = my_result:GetValue() + end + )LUA"); + + auto prefabSystemComponentInterface = AZ::Interface::Get(); + prefabSystemComponentInterface->RemoveAllTemplates(); + + EXPECT_STRNE(g_globalPrefabString.c_str(), ""); + TemplateId templateFromString = AZ::Interface::Get()->LoadTemplateFromString(g_globalPrefabString); + + EXPECT_NE(templateFromString, InvalidTemplateId); + + // Create another entity for comparison purposes + AZ::EntityId entityId; + AzToolsFramework::EntityUtilityBus::BroadcastResult( + entityId, &AzToolsFramework::EntityUtilityBus::Events::CreateEditorReadyEntity, "test"); + + AZ::Entity* testEntity = AZ::Interface::Get()->FindEntity(entityId); + + // Instantiate the prefab we saved + AZStd::unique_ptr instance = prefabSystemComponentInterface->InstantiatePrefab(templateFromString); + + EXPECT_NE(instance, nullptr); + + AZStd::vector loadedEntities; + + // Get the entities from the instance + instance->GetConstEntities( + [&loadedEntities](const AZ::Entity& entity) + { + loadedEntities.push_back(&entity); + return true; + }); + + // Make sure the instance has an entity with the same number of components as our test entity + EXPECT_EQ(loadedEntities.size(), 1); + EXPECT_EQ(loadedEntities[0]->GetComponents().size(), testEntity->GetComponents().size()); + + g_globalPrefabString.set_capacity(0); // Free all memory + } + +} diff --git a/Code/Framework/AzToolsFramework/Tests/Prefab/ProceduralPrefabAssetTests.cpp b/Code/Framework/AzToolsFramework/Tests/Prefab/ProceduralPrefabAssetTests.cpp new file mode 100644 index 0000000000..0b9e73bbac --- /dev/null +++ b/Code/Framework/AzToolsFramework/Tests/Prefab/ProceduralPrefabAssetTests.cpp @@ -0,0 +1,135 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include + +#include +#include +#include +#include +#include + +namespace UnitTest +{ + class ProceduralPrefabAssetTest + : public PrefabTestFixture + { + void SetUpEditorFixtureImpl() override + { + AZ::ComponentApplicationRequests* componentApplicationRequests = AZ::Interface::Get(); + ASSERT_NE(componentApplicationRequests, nullptr); + + auto* behaviorContext = componentApplicationRequests->GetBehaviorContext(); + ASSERT_NE(behaviorContext, nullptr); + + auto* jsonRegistrationContext = componentApplicationRequests->GetJsonRegistrationContext(); + ASSERT_NE(jsonRegistrationContext, nullptr); + + auto* serializeContext = componentApplicationRequests->GetSerializeContext(); + ASSERT_NE(serializeContext, nullptr); + + AZ::Prefab::ProceduralPrefabAsset::Reflect(serializeContext); + AZ::Prefab::ProceduralPrefabAsset::Reflect(behaviorContext); + AZ::Prefab::ProceduralPrefabAsset::Reflect(jsonRegistrationContext); + } + + void TearDownEditorFixtureImpl() override + { + AZ::ComponentApplicationRequests* componentApplicationRequests = AZ::Interface::Get(); + componentApplicationRequests->GetJsonRegistrationContext()->EnableRemoveReflection(); + AZ::Prefab::ProceduralPrefabAsset::Reflect(componentApplicationRequests->GetJsonRegistrationContext()); + } + }; + + TEST_F(ProceduralPrefabAssetTest, ReflectContext_AccessMethods_Works) + { + AZ::ComponentApplicationRequests* componentApplicationRequests = AZ::Interface::Get(); + + auto* serializeContext = componentApplicationRequests->GetSerializeContext(); + EXPECT_TRUE(!serializeContext->CreateAny(azrtti_typeid()).empty()); + EXPECT_TRUE(!serializeContext->CreateAny(azrtti_typeid()).empty()); + + auto* jsonRegistrationContext = componentApplicationRequests->GetJsonRegistrationContext(); + EXPECT_TRUE(jsonRegistrationContext->GetSerializerForSerializerType(azrtti_typeid())); + } + + TEST_F(ProceduralPrefabAssetTest, ProceduralPrefabAsset_AccessMethods_Works) + { + const auto templateId = TemplateId(1); + const auto prefabString = "fake.prefab"; + + AZ::Prefab::ProceduralPrefabAsset asset{}; + asset.SetTemplateId(templateId); + EXPECT_EQ(asset.GetTemplateId(), templateId); + + asset.SetTemplateName(prefabString); + EXPECT_EQ(asset.GetTemplateName(), prefabString); + } + + TEST_F(ProceduralPrefabAssetTest, PrefabDomData_AccessMethods_Works) + { + AzToolsFramework::Prefab::PrefabDom dom; + dom.SetObject(); + dom.AddMember("boolValue", true, dom.GetAllocator()); + + AZ::Prefab::PrefabDomData prefabDomData; + prefabDomData.CopyValue(dom); + + const AzToolsFramework::Prefab::PrefabDom& result = prefabDomData.GetValue(); + EXPECT_TRUE(result.HasMember("boolValue")); + EXPECT_TRUE(result.FindMember("boolValue")->value.GetBool()); + } + + TEST_F(ProceduralPrefabAssetTest, PrefabDomDataJsonSerializer_Load_Works) + { + AZ::Prefab::PrefabDomData prefabDomData; + + AzToolsFramework::Prefab::PrefabDom dom; + dom.SetObject(); + dom.AddMember("member", "value", dom.GetAllocator()); + + AZ::Prefab::PrefabDomDataJsonSerializer prefabDomDataJsonSerializer; + AZ::JsonDeserializerSettings settings; + settings.m_reporting = [](auto, auto, auto) + { + AZ::JsonSerializationResult::ResultCode result(AZ::JsonSerializationResult::Tasks::ReadField); + return result; + }; + AZ::JsonDeserializerContext context{ settings }; + + auto result = prefabDomDataJsonSerializer.Load(&prefabDomData, azrtti_typeid(prefabDomData), dom, context); + EXPECT_EQ(result.GetResultCode().GetOutcome(), AZ::JsonSerializationResult::Outcomes::DefaultsUsed); + EXPECT_TRUE(prefabDomData.GetValue().HasMember("member")); + EXPECT_STREQ(prefabDomData.GetValue().FindMember("member")->value.GetString(), "value"); + } + + TEST_F(ProceduralPrefabAssetTest, PrefabDomDataJsonSerializer_Store_Works) + { + AzToolsFramework::Prefab::PrefabDom dom; + dom.SetObject(); + dom.AddMember("member", "value", dom.GetAllocator()); + + AZ::Prefab::PrefabDomData prefabDomData; + prefabDomData.CopyValue(dom); + + AZ::Prefab::PrefabDomDataJsonSerializer prefabDomDataJsonSerializer; + AzToolsFramework::Prefab::PrefabDom outputValue; + AZ::JsonSerializerSettings settings; + settings.m_reporting = [](auto, auto, auto) + { + AZ::JsonSerializationResult::ResultCode result(AZ::JsonSerializationResult::Tasks::WriteValue); + return result; + }; + AZ::JsonSerializerContext context{ settings, outputValue.GetAllocator() }; + auto result = prefabDomDataJsonSerializer.Store(outputValue, &prefabDomData, nullptr, azrtti_typeid(prefabDomData), context); + EXPECT_EQ(result.GetResultCode().GetOutcome(), AZ::JsonSerializationResult::Outcomes::DefaultsUsed); + EXPECT_TRUE(outputValue.HasMember("member")); + EXPECT_STREQ(outputValue.FindMember("member")->value.GetString(), "value"); + } +} diff --git a/Code/Framework/AzToolsFramework/Tests/aztoolsframeworktests_files.cmake b/Code/Framework/AzToolsFramework/Tests/aztoolsframeworktests_files.cmake index 11ea5a0b38..87d522c4a6 100644 --- a/Code/Framework/AzToolsFramework/Tests/aztoolsframeworktests_files.cmake +++ b/Code/Framework/AzToolsFramework/Tests/aztoolsframeworktests_files.cmake @@ -27,6 +27,7 @@ set(FILES Entity/EditorEntityHelpersTests.cpp Entity/EditorEntitySearchComponentTests.cpp Entity/EditorEntitySelectionTests.cpp + Entity/EntityUtilityComponentTests.cpp EntityIdQLabelTests.cpp EntityInspectorTests.cpp EntityOwnershipService/EntityOwnershipServiceTestFixture.cpp @@ -91,6 +92,8 @@ set(FILES Prefab/SpawnableSortEntitiesTestFixture.cpp Prefab/SpawnableSortEntitiesTestFixture.h Prefab/SpawnableSortEntitiesTests.cpp + Prefab/PrefabScriptingTests.cpp + Prefab/ProceduralPrefabAssetTests.cpp PropertyIntCtrlCommonTests.h PropertyIntSliderCtrlTests.cpp PropertyIntSpinCtrlTests.cpp diff --git a/Code/Legacy/CrySystem/DebugCallStack.cpp b/Code/Legacy/CrySystem/DebugCallStack.cpp index 8018e46d69..19fe4ce8df 100644 --- a/Code/Legacy/CrySystem/DebugCallStack.cpp +++ b/Code/Legacy/CrySystem/DebugCallStack.cpp @@ -222,6 +222,9 @@ void UpdateFPExceptionsMaskForThreads() ////////////////////////////////////////////////////////////////////////// int DebugCallStack::handleException(EXCEPTION_POINTERS* exception_pointer) { + AZ_TracePrintf("Exit", "Exception with exit code: 0x%x", exception_pointer->ExceptionRecord->ExceptionCode); + AZ::Debug::Trace::PrintCallstack("Exit"); + if (gEnv == NULL) { return EXCEPTION_EXECUTE_HANDLER; diff --git a/Code/Legacy/CrySystem/LevelSystem/SpawnableLevelSystem.cpp b/Code/Legacy/CrySystem/LevelSystem/SpawnableLevelSystem.cpp index a82c4a6914..b2b67c3b75 100644 --- a/Code/Legacy/CrySystem/LevelSystem/SpawnableLevelSystem.cpp +++ b/Code/Legacy/CrySystem/LevelSystem/SpawnableLevelSystem.cpp @@ -22,22 +22,33 @@ #include #include #include +#include #include #include namespace LegacyLevelSystem { + constexpr AZStd::string_view DeferredLoadLevelKey = "/O3DE/Runtime/SpawnableLevelSystem/DeferredLoadLevel"; //------------------------------------------------------------------------ static void LoadLevel(const AZ::ConsoleCommandContainer& arguments) { AZ_Error("SpawnableLevelSystem", !arguments.empty(), "LoadLevel requires a level file name to be provided."); AZ_Error("SpawnableLevelSystem", arguments.size() == 1, "LoadLevel requires a single level file name to be provided."); - if (!arguments.empty() && gEnv->pSystem && gEnv->pSystem->GetILevelSystem() && !gEnv->IsEditor()) + if (!arguments.empty() && gEnv && gEnv->pSystem && gEnv->pSystem->GetILevelSystem() && !gEnv->IsEditor()) { gEnv->pSystem->GetILevelSystem()->LoadLevel(arguments[0].data()); } + else if (!arguments.empty()) + { + // The SpawnableLevelSystem isn't available yet. + // Defer the level load until later by storing it in the SettingsRegistry + if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr) + { + settingsRegistry->Set(DeferredLoadLevelKey, arguments.front()); + } + } } //------------------------------------------------------------------------ @@ -45,7 +56,7 @@ namespace LegacyLevelSystem { AZ_Warning("SpawnableLevelSystem", !arguments.empty(), "UnloadLevel doesn't use any arguments."); - if (gEnv->pSystem && gEnv->pSystem->GetILevelSystem() && !gEnv->IsEditor()) + if (gEnv && gEnv->pSystem && gEnv->pSystem->GetILevelSystem() && !gEnv->IsEditor()) { gEnv->pSystem->GetILevelSystem()->UnloadLevel(); } @@ -73,6 +84,24 @@ namespace LegacyLevelSystem } AzFramework::RootSpawnableNotificationBus::Handler::BusConnect(); + + // If there were LoadLevel command invocations before the creation of the level system + // then those invocations were queued. + // load the last level in the queue, since only one level can be loaded at a time + if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr) + { + if (AZ::SettingsRegistryInterface::FixedValueString deferredLevelName; + settingsRegistry->Get(deferredLevelName, DeferredLoadLevelKey) && !deferredLevelName.empty()) + { + // since this is the constructor any derived classes vtables aren't setup yet + // call this class LoadLevel function + AZ_TracePrintf("SpawnableLevelSystem", "The Level System is now available." + " Loading level %s which could not be loaded earlier\n", deferredLevelName.c_str()); + SpawnableLevelSystem::LoadLevel(deferredLevelName.c_str()); + // Delete the key with the deferred level name + settingsRegistry->Remove(DeferredLoadLevelKey); + } + } } //------------------------------------------------------------------------ @@ -173,7 +202,7 @@ namespace LegacyLevelSystem } // Make sure a spawnable level exists that matches levelname - AZStd::string validLevelName = ""; + AZStd::string validLevelName; AZ::Data::AssetId rootSpawnableAssetId; AZ::Data::AssetCatalogRequestBus::BroadcastResult( rootSpawnableAssetId, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath, levelName, nullptr, false); diff --git a/Code/Tools/AssetProcessor/AssetBuilder/AssetBuilderApplication.cpp b/Code/Tools/AssetProcessor/AssetBuilder/AssetBuilderApplication.cpp index 9c9c517020..da2edcc51f 100644 --- a/Code/Tools/AssetProcessor/AssetBuilder/AssetBuilderApplication.cpp +++ b/Code/Tools/AssetProcessor/AssetBuilder/AssetBuilderApplication.cpp @@ -36,6 +36,7 @@ #include #include #include +#include namespace AssetBuilder { @@ -80,6 +81,7 @@ AZ::ComponentTypeList AssetBuilderApplication::GetRequiredSystemComponents() con azrtti_typeid(), azrtti_typeid(), azrtti_typeid(), + azrtti_typeid(), }); return components; diff --git a/Code/Tools/AssetProcessor/native/ui/AssetTreeFilterModel.cpp b/Code/Tools/AssetProcessor/native/ui/AssetTreeFilterModel.cpp index 50a472c44d..c33fc59d97 100644 --- a/Code/Tools/AssetProcessor/native/ui/AssetTreeFilterModel.cpp +++ b/Code/Tools/AssetProcessor/native/ui/AssetTreeFilterModel.cpp @@ -62,7 +62,10 @@ namespace AssetProcessor { searchStr = searchStr.mid(0, subidPos); } - AZ::Uuid filterAsUuid = AZ::Uuid::CreateStringPermissive(searchStr.toUtf8(), 0); + + // Cap the string to some reasonable length, we don't want to try parsing an entire book + size_t cappedStringLength = searchStr.length() > 60 ? 60 : searchStr.length(); + AZ::Uuid filterAsUuid = AZ::Uuid::CreateStringPermissive(searchStr.toUtf8(), cappedStringLength); return DescendantMatchesFilter(*assetTreeItem, filter, filterAsUuid); } diff --git a/Code/Tools/SceneAPI/SceneCore/Containers/GraphObjectProxy.cpp b/Code/Tools/SceneAPI/SceneCore/Containers/GraphObjectProxy.cpp index ab260eba4d..1efec493c1 100644 --- a/Code/Tools/SceneAPI/SceneCore/Containers/GraphObjectProxy.cpp +++ b/Code/Tools/SceneAPI/SceneCore/Containers/GraphObjectProxy.cpp @@ -194,8 +194,8 @@ namespace AZ if (baseClass) { m_behaviorClass = behaviorClass; + return true; } - return true; } return false; } diff --git a/Code/Tools/SceneAPI/SceneCore/Containers/Scene.cpp b/Code/Tools/SceneAPI/SceneCore/Containers/Scene.cpp index b6c7eef90f..31b74405d2 100644 --- a/Code/Tools/SceneAPI/SceneCore/Containers/Scene.cpp +++ b/Code/Tools/SceneAPI/SceneCore/Containers/Scene.cpp @@ -47,6 +47,16 @@ namespace AZ return m_sourceGuid; } + void Scene::SetWatchFolder(const AZStd::string& watchFolder) + { + m_watchFolder = watchFolder; + } + + const AZStd::string& Scene::GetWatchFolder() const + { + return m_watchFolder; + } + void Scene::SetManifestFilename(const AZStd::string& name) { m_manifestFilename = name; @@ -111,6 +121,7 @@ namespace AZ ->Property("sourceGuid", BehaviorValueGetter(&Scene::m_sourceGuid), nullptr) ->Property("graph", BehaviorValueGetter(&Scene::m_graph), nullptr) ->Property("manifest", BehaviorValueGetter(&Scene::m_manifest), nullptr) + ->Property("watchFolder", BehaviorValueGetter(&Scene::m_watchFolder), nullptr) ->Constant("SceneOrientation_YUp", BehaviorConstant(SceneOrientation::YUp)) ->Constant("SceneOrientation_ZUp", BehaviorConstant(SceneOrientation::ZUp)) ->Constant("SceneOrientation_XUp", BehaviorConstant(SceneOrientation::XUp)) diff --git a/Code/Tools/SceneAPI/SceneCore/Containers/Scene.h b/Code/Tools/SceneAPI/SceneCore/Containers/Scene.h index 49320c4592..0185c23178 100644 --- a/Code/Tools/SceneAPI/SceneCore/Containers/Scene.h +++ b/Code/Tools/SceneAPI/SceneCore/Containers/Scene.h @@ -34,6 +34,9 @@ namespace AZ const AZStd::string& GetSourceFilename() const; const Uuid& GetSourceGuid() const; + void SetWatchFolder(const AZStd::string& watchFolder); + const AZStd::string& GetWatchFolder() const; + void SetManifestFilename(const AZStd::string& name); void SetManifestFilename(AZStd::string&& name); const AZStd::string& GetManifestFilename() const; @@ -59,6 +62,7 @@ namespace AZ AZStd::string m_name; AZStd::string m_manifestFilename; AZStd::string m_sourceFilename; + AZStd::string m_watchFolder; Uuid m_sourceGuid; SceneGraph m_graph; SceneManifest m_manifest; diff --git a/Code/Tools/SceneAPI/SceneCore/Containers/SceneGraph.cpp b/Code/Tools/SceneAPI/SceneCore/Containers/SceneGraph.cpp index cae0dc45d5..3d00dccec3 100644 --- a/Code/Tools/SceneAPI/SceneCore/Containers/SceneGraph.cpp +++ b/Code/Tools/SceneAPI/SceneCore/Containers/SceneGraph.cpp @@ -97,7 +97,7 @@ namespace AZ GraphObjectProxy* proxy = aznew GraphObjectProxy(graphObject); return proxy; } - return nullptr; + return aznew GraphObjectProxy(nullptr); }); } } diff --git a/Code/Tools/SceneAPI/SceneCore/Containers/SceneManifest.cpp b/Code/Tools/SceneAPI/SceneCore/Containers/SceneManifest.cpp index 664c5f1272..8e3f06b4cb 100644 --- a/Code/Tools/SceneAPI/SceneCore/Containers/SceneManifest.cpp +++ b/Code/Tools/SceneAPI/SceneCore/Containers/SceneManifest.cpp @@ -36,8 +36,7 @@ namespace AZ { namespace Containers { - //! Protects from allocating too much memory. The choice of a 5MB threshold is arbitrary. - const size_t MaxSceneManifestFileSizeInBytes = 5 * 1024 * 1024; + const char ErrorWindowName[] = "SceneManifest"; diff --git a/Code/Tools/SceneAPI/SceneCore/Containers/SceneManifest.h b/Code/Tools/SceneAPI/SceneCore/Containers/SceneManifest.h index 4386d4fd8c..f3838096ad 100644 --- a/Code/Tools/SceneAPI/SceneCore/Containers/SceneManifest.h +++ b/Code/Tools/SceneAPI/SceneCore/Containers/SceneManifest.h @@ -41,6 +41,8 @@ namespace AZ AZ_RTTI(SceneManifest, "{9274AD17-3212-4651-9F3B-7DCCB080E467}"); + static constexpr size_t MaxSceneManifestFileSizeInBytes = AZStd::numeric_limits::max(); + virtual ~SceneManifest(); static AZStd::shared_ptr SceneManifestConstDataConverter( diff --git a/Code/Tools/SceneAPI/SceneCore/Events/AssetImportRequest.cpp b/Code/Tools/SceneAPI/SceneCore/Events/AssetImportRequest.cpp index a182e4f02d..c98fa63916 100644 --- a/Code/Tools/SceneAPI/SceneCore/Events/AssetImportRequest.cpp +++ b/Code/Tools/SceneAPI/SceneCore/Events/AssetImportRequest.cpp @@ -6,6 +6,7 @@ * */ +#include #include #include #include @@ -73,6 +74,10 @@ namespace AZ { } + void AssetImportRequest::GetGeneratedManifestExtension(AZStd::string& /*result*/) + { + } + void AssetImportRequest::GetSupportedFileExtensions(AZStd::unordered_set& /*extensions*/) { } @@ -103,14 +108,34 @@ namespace AZ AZ_UNUSED(value); } + void AssetImportRequest::GetManifestDependencyPaths(AZStd::vector&) + { + } + AZStd::shared_ptr AssetImportRequest::LoadSceneFromVerifiedPath(const AZStd::string& assetFilePath, const Uuid& sourceGuid, - RequestingApplication requester, const Uuid& loadingComponentUuid) + RequestingApplication requester, const Uuid& loadingComponentUuid) { AZStd::string sceneName; AzFramework::StringFunc::Path::GetFileName(assetFilePath.c_str(), sceneName); AZStd::shared_ptr scene = AZStd::make_shared(AZStd::move(sceneName)); AZ_Assert(scene, "Unable to create new scene for asset importing."); + Data::AssetInfo assetInfo; + AZStd::string watchFolder; + bool result = false; + AzToolsFramework::AssetSystemRequestBus::BroadcastResult(result, &AzToolsFramework::AssetSystemRequestBus::Events::GetSourceInfoBySourceUUID, sourceGuid, assetInfo, watchFolder); + + if (result) + { + scene->SetWatchFolder(watchFolder); + } + else + { + AZ_Error( + "AssetImportRequest", false, "Failed to get watch folder for source %s", + sourceGuid.ToString().c_str()); + } + // Unique pointer, will deactivate and clean up once going out of scope. SceneCore::EntityConstructor::EntityPointer loaders = SceneCore::EntityConstructor::BuildEntity("Scene Loading", loadingComponentUuid); diff --git a/Code/Tools/SceneAPI/SceneCore/Events/AssetImportRequest.h b/Code/Tools/SceneAPI/SceneCore/Events/AssetImportRequest.h index 8b6e119f99..a2446c5ff8 100644 --- a/Code/Tools/SceneAPI/SceneCore/Events/AssetImportRequest.h +++ b/Code/Tools/SceneAPI/SceneCore/Events/AssetImportRequest.h @@ -78,6 +78,8 @@ namespace AZ virtual void GetSupportedFileExtensions(AZStd::unordered_set& extensions); //! Gets the file extension for the manifest. virtual void GetManifestExtension(AZStd::string& result); + //! Gets the file extension for the generated manifest. + virtual void GetGeneratedManifestExtension(AZStd::string& result); //! Before asset loading starts this is called to allow for any required initialization. virtual ProcessingResult PrepareForAssetLoading(Containers::Scene& scene, RequestingApplication requester); @@ -97,6 +99,23 @@ namespace AZ // Get scene processing project setting: UseCustomNormal virtual void AreCustomNormalsUsed(bool & value); + /*! + Optional method for reporting source file dependencies that may exist in the scene manifest + Paths is a vector of JSON Path strings, relative to the IRule object + For example, the following path: /scriptFilename + Would match with this manifest: + + { + "values": [ + { + "$type": "Test", + "scriptFilename": "file.py" + } + ] + } + */ + virtual void GetManifestDependencyPaths(AZStd::vector& paths); + //! Utility function to load an asset and manifest from file by using the EBus functions above. //! @param assetFilePath The absolute path to the source file (not the manifest). //! @param sourceGuid The guid assigned to the source file (not the manifest). diff --git a/Code/Tools/SceneAPI/SceneCore/Import/ManifestImportRequestHandler.cpp b/Code/Tools/SceneAPI/SceneCore/Import/ManifestImportRequestHandler.cpp index 39045da5b9..f7d3f073b9 100644 --- a/Code/Tools/SceneAPI/SceneCore/Import/ManifestImportRequestHandler.cpp +++ b/Code/Tools/SceneAPI/SceneCore/Import/ManifestImportRequestHandler.cpp @@ -56,8 +56,14 @@ namespace AZ result = s_extension; } + void ManifestImportRequestHandler::GetGeneratedManifestExtension(AZStd::string& result) + { + result = s_extension; + result.append(s_generated); + } + Events::LoadingResult ManifestImportRequestHandler::LoadAsset(Containers::Scene& scene, const AZStd::string& path, - const Uuid& /*guid*/, RequestingApplication /*requester*/) + const Uuid& /*guid*/, RequestingApplication /*requester*/) { AZStd::string manifestPath = path + s_extension; diff --git a/Code/Tools/SceneAPI/SceneCore/Import/ManifestImportRequestHandler.h b/Code/Tools/SceneAPI/SceneCore/Import/ManifestImportRequestHandler.h index 7659f6908f..00c802cb6b 100644 --- a/Code/Tools/SceneAPI/SceneCore/Import/ManifestImportRequestHandler.h +++ b/Code/Tools/SceneAPI/SceneCore/Import/ManifestImportRequestHandler.h @@ -31,6 +31,7 @@ namespace AZ static void Reflect(ReflectContext* context); void GetManifestExtension(AZStd::string& result) override; + void GetGeneratedManifestExtension(AZStd::string& result) override; Events::LoadingResult LoadAsset(Containers::Scene& scene, const AZStd::string& path, const Uuid& guid, RequestingApplication requester) override; diff --git a/Code/Tools/SceneAPI/SceneData/Behaviors/ScriptProcessorRuleBehavior.cpp b/Code/Tools/SceneAPI/SceneData/Behaviors/ScriptProcessorRuleBehavior.cpp index 2e6503d195..8d413b6038 100644 --- a/Code/Tools/SceneAPI/SceneData/Behaviors/ScriptProcessorRuleBehavior.cpp +++ b/Code/Tools/SceneAPI/SceneData/Behaviors/ScriptProcessorRuleBehavior.cpp @@ -19,6 +19,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -99,6 +102,7 @@ namespace AZ::SceneAPI::Behaviors { AZStd::string result; CallResult(result, FN_OnUpdateManifest, scene); + ScriptBuildingNotificationBusHandler::BusDisconnect(); return result; } @@ -110,6 +114,7 @@ namespace AZ::SceneAPI::Behaviors { ExportProductList result; CallResult(result, FN_OnPrepareForExport, scene, outputDirectory, platformIdentifier, productList); + ScriptBuildingNotificationBusHandler::BusDisconnect(); return result; } @@ -168,21 +173,9 @@ namespace AZ::SceneAPI::Behaviors UnloadPython(); } - bool ScriptProcessorRuleBehavior::LoadPython(const AZ::SceneAPI::Containers::Scene& scene) + bool ScriptProcessorRuleBehavior::LoadPython(const AZ::SceneAPI::Containers::Scene& scene, AZStd::string& scriptPath) { - 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; - } - + int scriptDiscoveryAttempts = 0; const AZ::SceneAPI::Containers::SceneManifest& manifest = scene.GetManifest(); auto view = Containers::MakeDerivedFilterView(manifest.GetValueStorage()); for (const auto& scriptItem : view) @@ -194,9 +187,21 @@ namespace AZ::SceneAPI::Behaviors continue; } + ++scriptDiscoveryAttempts; + // check for file exist via absolute path if (!IO::FileIOBase::GetInstance()->Exists(scriptFilename.c_str())) { + // get project folder + auto settingsRegistry = AZ::SettingsRegistry::Get(); + AZ::IO::FixedMaxPath projectPath; + if (!settingsRegistry->Get(projectPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectPath)) + { + AZ_Error("scene", false, "With (%s) could not find Project Path during script discovery.", + scene.GetManifestFilename().c_str()); + return false; + } + // check for script in the project folder AZ::IO::FixedMaxPath projectScriptPath = projectPath / scriptFilename; if (!IO::FileIOBase::GetInstance()->Exists(projectScriptPath.c_str())) @@ -209,32 +214,47 @@ namespace AZ::SceneAPI::Behaviors scriptFilename = AZStd::move(projectScriptPath); } - // lazy load the Python interface - auto editorPythonEventsInterface = AZ::Interface::Get(); - if (editorPythonEventsInterface->IsPythonActive() == false) - { - const bool silenceWarnings = false; - if (editorPythonEventsInterface->StartPython(silenceWarnings) == false) - { - editorPythonEventsInterface = nullptr; - } - } + scriptPath = scriptFilename.c_str(); + break; + } - // 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()); + if (scriptPath.empty()) + { + AZ_Warning("scene", scriptDiscoveryAttempts == 0, + "The scene manifest (%s) attempted to use script rule, but no script file path could be found.", + scene.GetManifestFilename().c_str()); + return false; + } + + // already prepared the Python VM? + if (m_editorPythonEventsInterface) + { + return true; + } - return false; + // lazy load the Python interface + auto editorPythonEventsInterface = AZ::Interface::Get(); + if (editorPythonEventsInterface->IsPythonActive() == false) + { + const bool silenceWarnings = false; + if (editorPythonEventsInterface->StartPython(silenceWarnings) == false) + { + editorPythonEventsInterface = nullptr; } + } - m_editorPythonEventsInterface = editorPythonEventsInterface; - m_scriptFilename = scriptFilename.c_str(); - return true; + // both Python and the script need to be ready + if (editorPythonEventsInterface == nullptr) + { + AZ_Warning("scene", false, + "The scene manifest (%s) attempted to prepare Python but Python can not start", + scene.GetManifestFilename().c_str()); + + return false; } - return false; + + m_editorPythonEventsInterface = editorPythonEventsInterface; + return true; } void ScriptProcessorRuleBehavior::UnloadPython() @@ -251,11 +271,13 @@ namespace AZ::SceneAPI::Behaviors { using namespace AzToolsFramework; - auto executeCallback = [this, &context]() + AZStd::string scriptPath; + + auto executeCallback = [&context, &scriptPath]() { // set up script's hook callback EditorPythonRunnerRequestBus::Broadcast(&EditorPythonRunnerRequestBus::Events::ExecuteByFilename, - m_scriptFilename.c_str()); + scriptPath.c_str()); // call script's callback to allow extra products ExportProductList extraProducts; @@ -279,7 +301,7 @@ namespace AZ::SceneAPI::Behaviors } }; - if (LoadPython(context.GetScene())) + if (LoadPython(context.GetScene(), scriptPath)) { EditorPythonConsoleNotificationHandler logger; m_editorPythonEventsInterface->ExecuteWithLock(executeCallback); @@ -306,23 +328,19 @@ namespace AZ::SceneAPI::Behaviors { using namespace AzToolsFramework; - // This behavior persists on the same AssetBuilder. Clear the script file name so that if - // this builder processes a scene file with a script file name, and then later processes - // a scene without a script file name, it won't run the old script on the new scene. - m_scriptFilename.clear(); - if (action != ManifestAction::Update) { return Events::ProcessingResult::Ignored; } - if (LoadPython(scene)) + AZStd::string scriptPath; + if (LoadPython(scene, scriptPath)) { AZStd::string manifestUpdate; - auto executeCallback = [this, &scene, &manifestUpdate]() + auto executeCallback = [&scene, &manifestUpdate, &scriptPath]() { EditorPythonRunnerRequestBus::Broadcast(&EditorPythonRunnerRequestBus::Events::ExecuteByFilename, - m_scriptFilename.c_str()); + scriptPath.c_str()); ScriptBuildingNotificationBus::BroadcastResult(manifestUpdate, &ScriptBuildingNotificationBus::Events::OnUpdateManifest, scene); @@ -331,6 +349,9 @@ namespace AZ::SceneAPI::Behaviors EditorPythonConsoleNotificationHandler logger; m_editorPythonEventsInterface->ExecuteWithLock(executeCallback); + EntityUtilityBus::Broadcast(&EntityUtilityBus::Events::ResetEntityContext); + AZ::Interface::Get()->RemoveAllTemplates(); + // attempt to load the manifest string back to a JSON-scene-manifest auto sceneManifestLoader = AZStd::make_unique(); auto loadOutcome = sceneManifestLoader->LoadFromString(manifestUpdate); @@ -347,4 +368,8 @@ namespace AZ::SceneAPI::Behaviors return Events::ProcessingResult::Ignored; } + void ScriptProcessorRuleBehavior::GetManifestDependencyPaths(AZStd::vector& paths) + { + paths.emplace_back("/scriptFilename"); + } } // namespace AZ diff --git a/Code/Tools/SceneAPI/SceneData/Behaviors/ScriptProcessorRuleBehavior.h b/Code/Tools/SceneAPI/SceneData/Behaviors/ScriptProcessorRuleBehavior.h index fa70a12b0d..a9ddf88df9 100644 --- a/Code/Tools/SceneAPI/SceneData/Behaviors/ScriptProcessorRuleBehavior.h +++ b/Code/Tools/SceneAPI/SceneData/Behaviors/ScriptProcessorRuleBehavior.h @@ -37,6 +37,7 @@ namespace AZ::SceneAPI::Behaviors , public Events::AssetImportRequestBus::Handler { public: + AZ_COMPONENT(ScriptProcessorRuleBehavior, "{24054E73-1B92-43B0-AC13-174B2F0E3F66}", SceneCore::BehaviorComponent); ~ScriptProcessorRuleBehavior() override = default; @@ -44,22 +45,21 @@ namespace AZ::SceneAPI::Behaviors 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; - + SCENE_DATA_API void GetManifestDependencyPaths(AZStd::vector& paths) override; protected: - bool LoadPython(const AZ::SceneAPI::Containers::Scene& scene); + bool LoadPython(const AZ::SceneAPI::Containers::Scene& scene, AZStd::string& scriptPath); void UnloadPython(); bool DoPrepareForExport(Events::PreExportEventContext& context); private: AzToolsFramework::EditorPythonEventsInterface* m_editorPythonEventsInterface = nullptr; - AZStd::string m_scriptFilename; struct ExportEventHandler; AZStd::shared_ptr m_exportEventHandler; diff --git a/Code/Tools/Standalone/CMakeLists.txt b/Code/Tools/Standalone/CMakeLists.txt index 78047dd6cf..12bdb8f453 100644 --- a/Code/Tools/Standalone/CMakeLists.txt +++ b/Code/Tools/Standalone/CMakeLists.txt @@ -38,3 +38,5 @@ ly_add_target( PRIVATE STANDALONETOOLS_ENABLE_LUA_IDE ) + +ly_add_dependencies(Editor LuaIDE) diff --git a/Code/Tools/Standalone/Source/LUA/LUAEditorMainWindow.cpp b/Code/Tools/Standalone/Source/LUA/LUAEditorMainWindow.cpp index c88ea59bdb..33d8373487 100644 --- a/Code/Tools/Standalone/Source/LUA/LUAEditorMainWindow.cpp +++ b/Code/Tools/Standalone/Source/LUA/LUAEditorMainWindow.cpp @@ -395,7 +395,7 @@ namespace LUAEditor void LUAEditorMainWindow::OnLuaDocumentation() { - QDesktopServices::openUrl(QUrl("http://docs.aws.amazon.com/lumberyard/latest/developerguide/lua-scripting-intro.html")); + QDesktopServices::openUrl(QUrl("https://o3de.org/docs/user-guide/scripting/lua/")); } void LUAEditorMainWindow::OnMenuCloseCurrentWindow() diff --git a/Gems/AWSClientAuth/Code/Source/AWSClientAuthSystemComponent.cpp b/Gems/AWSClientAuth/Code/Source/AWSClientAuthSystemComponent.cpp index 198f29ebf7..dbde19aad6 100644 --- a/Gems/AWSClientAuth/Code/Source/AWSClientAuthSystemComponent.cpp +++ b/Gems/AWSClientAuth/Code/Source/AWSClientAuthSystemComponent.cpp @@ -33,7 +33,7 @@ namespace AWSClientAuth AZ::SerializeContext* serialize = azrtti_cast(context); if (serialize) { - serialize->Class()->Version(0); + serialize->Class()->Version(1); if (AZ::EditContext* ec = serialize->GetEditContext()) { @@ -55,26 +55,44 @@ namespace AWSClientAuth ->Enum<(int)ProviderNameEnum::AWSCognitoIDP>("ProviderNameEnum_AWSCognitoIDP") ->Enum<(int)ProviderNameEnum::LoginWithAmazon>("ProviderNameEnum_LoginWithAmazon") ->Enum<(int)ProviderNameEnum::Google>("ProviderNameEnum_Google"); - behaviorContext->EBus("AuthenticationProviderRequestBus") ->Attribute(AZ::Script::Attributes::Category, SerializeComponentName) ->Event("Initialize", &AuthenticationProviderScriptCanvasRequestBus::Events::Initialize) - ->Event("IsSignedIn", &AuthenticationProviderScriptCanvasRequestBus::Events::IsSignedIn) - ->Event("GetAuthenticationTokens", &AuthenticationProviderScriptCanvasRequestBus::Events::GetAuthenticationTokens) + ->Event("IsSignedIn", &AuthenticationProviderScriptCanvasRequestBus::Events::IsSignedIn, + { { { "Provider name", "The identity provider name" } } }) + ->Event("GetAuthenticationTokens", &AuthenticationProviderScriptCanvasRequestBus::Events::GetAuthenticationTokens, + { { { "Provider name", "The identity provider name" } } }) ->Event( "PasswordGrantSingleFactorSignInAsync", - &AuthenticationProviderScriptCanvasRequestBus::Events::PasswordGrantSingleFactorSignInAsync) + &AuthenticationProviderScriptCanvasRequestBus::Events::PasswordGrantSingleFactorSignInAsync, + { { { "Provider name", "The identity provider" }, { "Username", "The client's username" }, { "Password", "The client's password" } } }) ->Event( "PasswordGrantMultiFactorSignInAsync", - &AuthenticationProviderScriptCanvasRequestBus::Events::PasswordGrantMultiFactorSignInAsync) + &AuthenticationProviderScriptCanvasRequestBus::Events::PasswordGrantMultiFactorSignInAsync, + { { { "Provider name", "The identity provider name" }, + { "Username", "The client's username" }, + { "Password", "The client's password" } } }) ->Event( "PasswordGrantMultiFactorConfirmSignInAsync", - &AuthenticationProviderScriptCanvasRequestBus::Events::PasswordGrantMultiFactorConfirmSignInAsync) - ->Event("DeviceCodeGrantSignInAsync", &AuthenticationProviderScriptCanvasRequestBus::Events::DeviceCodeGrantSignInAsync) - ->Event("DeviceCodeGrantConfirmSignInAsync", &AuthenticationProviderScriptCanvasRequestBus::Events::DeviceCodeGrantConfirmSignInAsync) - ->Event("RefreshTokensAsync", &AuthenticationProviderScriptCanvasRequestBus::Events::RefreshTokensAsync) - ->Event("GetTokensWithRefreshAsync", &AuthenticationProviderScriptCanvasRequestBus::Events::GetTokensWithRefreshAsync) - ->Event("SignOut", &AuthenticationProviderScriptCanvasRequestBus::Events::SignOut); + &AuthenticationProviderScriptCanvasRequestBus::Events::PasswordGrantMultiFactorConfirmSignInAsync, + { { { "Provider name", "The identity provider name" }, + { "Username", "The client's username" }, + { "Confirmation code", "The client's confirmation code" } } }) + ->Event( + "DeviceCodeGrantSignInAsync", &AuthenticationProviderScriptCanvasRequestBus::Events::DeviceCodeGrantSignInAsync, + { { { "Provider name", "The identity provider name" } } }) + ->Event( + "DeviceCodeGrantConfirmSignInAsync", + &AuthenticationProviderScriptCanvasRequestBus::Events::DeviceCodeGrantConfirmSignInAsync, + { { { "Provider name", "The identity provider name" } } }) + ->Event( + "RefreshTokensAsync", &AuthenticationProviderScriptCanvasRequestBus::Events::RefreshTokensAsync, + { { { "Provider name", "The identity provider name" } } }) + ->Event("GetTokensWithRefreshAsync", &AuthenticationProviderScriptCanvasRequestBus::Events::GetTokensWithRefreshAsync, + { { { "Provider name", "The identity provider name" } } }) + ->Event( + "SignOut", &AuthenticationProviderScriptCanvasRequestBus::Events::SignOut, + { { { "Provider name", "The identity provider name" } } }); behaviorContext->EBus("AWSCognitoAuthorizationRequestBus") ->Attribute(AZ::Script::Attributes::Category, SerializeComponentName) diff --git a/Gems/AWSCore/Code/CMakeLists.txt b/Gems/AWSCore/Code/CMakeLists.txt index 808436b7fd..7559f4720b 100644 --- a/Gems/AWSCore/Code/CMakeLists.txt +++ b/Gems/AWSCore/Code/CMakeLists.txt @@ -6,18 +6,18 @@ # # -ly_get_list_relative_pal_filename(pal_editor_include_dir ${CMAKE_CURRENT_LIST_DIR}/Include/Private/Editor/Platform/${PAL_PLATFORM_NAME}) -ly_get_list_relative_pal_filename(pal_cafile_include_dir ${CMAKE_CURRENT_LIST_DIR}/Source/Framework/Platform/${PAL_PLATFORM_NAME}) +ly_get_list_relative_pal_filename(pal_dir ${CMAKE_CURRENT_LIST_DIR}/Platform/${PAL_PLATFORM_NAME}) ly_add_target( NAME AWSCore.Static STATIC NAMESPACE Gem FILES_CMAKE awscore_files.cmake - ${pal_cafile_include_dir}/platform_${PAL_PLATFORM_NAME_LOWERCASE}_files.cmake + ${pal_dir}/platform_${PAL_PLATFORM_NAME_LOWERCASE}_files.cmake INCLUDE_DIRECTORIES PUBLIC Include/Public + ${pal_dir} PRIVATE Include/Private BUILD_DEPENDENCIES @@ -65,11 +65,11 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS) NAMESPACE Gem FILES_CMAKE awscore_editor_files.cmake - ${pal_editor_include_dir}/platform_${PAL_PLATFORM_NAME_LOWERCASE}_files.cmake + ${pal_dir}/platform_${PAL_PLATFORM_NAME_LOWERCASE}_editor_files.cmake INCLUDE_DIRECTORIES PRIVATE Include/Private - ${pal_editor_include_dir} + ${pal_dir} PUBLIC Include/Public BUILD_DEPENDENCIES @@ -164,12 +164,12 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) NAMESPACE Gem FILES_CMAKE awscore_editor_tests_files.cmake - ${pal_editor_include_dir}/platform_${PAL_PLATFORM_NAME_LOWERCASE}_files.cmake - Tests/Editor/Platform/${PAL_PLATFORM_NAME}/awscore_editor_tests_${PAL_PLATFORM_NAME_LOWERCASE}_files.cmake + ${pal_dir}/platform_${PAL_PLATFORM_NAME_LOWERCASE}_editor_files.cmake + ${pal_dir}/platform_${PAL_PLATFORM_NAME_LOWERCASE}_editor_tests_files.cmake INCLUDE_DIRECTORIES PRIVATE Include/Private - ${pal_editor_include_dir} + ${pal_dir} Include/Public Tests COMPILE_DEFINITIONS diff --git a/Gems/AWSCore/Code/Include/Private/AWSCoreModule.h b/Gems/AWSCore/Code/Include/Private/AWSCoreModule.h index ae5b424e69..f03579a0dd 100644 --- a/Gems/AWSCore/Code/Include/Private/AWSCoreModule.h +++ b/Gems/AWSCore/Code/Include/Private/AWSCoreModule.h @@ -24,7 +24,7 @@ namespace AWSCore /** * Add required SystemComponents to the SystemEntity. */ - virtual AZ::ComponentTypeList GetRequiredSystemComponents() const override; + AZ::ComponentTypeList GetRequiredSystemComponents() const override; }; } diff --git a/Gems/AWSCore/Code/Include/Private/Configuration/AWSCoreConfiguration.h b/Gems/AWSCore/Code/Include/Private/Configuration/AWSCoreConfiguration.h index 9834f9ca38..dd9f22f04d 100644 --- a/Gems/AWSCore/Code/Include/Private/Configuration/AWSCoreConfiguration.h +++ b/Gems/AWSCore/Code/Include/Private/Configuration/AWSCoreConfiguration.h @@ -42,7 +42,7 @@ namespace AWSCore AWSCoreConfiguration(); - ~AWSCoreConfiguration() = default; + ~AWSCoreConfiguration() override = default; void ActivateConfig(); void DeactivateConfig(); diff --git a/Gems/AWSCore/Code/Include/Private/Credential/AWSCVarCredentialHandler.h b/Gems/AWSCore/Code/Include/Private/Credential/AWSCVarCredentialHandler.h index 542177dcc4..388f8eefd5 100644 --- a/Gems/AWSCore/Code/Include/Private/Credential/AWSCVarCredentialHandler.h +++ b/Gems/AWSCore/Code/Include/Private/Credential/AWSCVarCredentialHandler.h @@ -21,7 +21,7 @@ namespace AWSCore { public: AWSCVarCredentialHandler() = default; - ~AWSCVarCredentialHandler() = default; + ~AWSCVarCredentialHandler() override = default; //! Activate handler and its credentials provider, make sure activation //! invoked after AWSNativeSDK init to avoid memory leak diff --git a/Gems/AWSCore/Code/Include/Private/Credential/AWSDefaultCredentialHandler.h b/Gems/AWSCore/Code/Include/Private/Credential/AWSDefaultCredentialHandler.h index 7f021f5748..068addfc24 100644 --- a/Gems/AWSCore/Code/Include/Private/Credential/AWSDefaultCredentialHandler.h +++ b/Gems/AWSCore/Code/Include/Private/Credential/AWSDefaultCredentialHandler.h @@ -23,7 +23,7 @@ namespace AWSCore { public: AWSDefaultCredentialHandler(); - ~AWSDefaultCredentialHandler() = default; + ~AWSDefaultCredentialHandler() override = default; //! Activate handler and its credentials provider, make sure activation //! invoked after AWSNativeSDK init to avoid memory leak diff --git a/Gems/AWSCore/Code/Include/Private/Editor/Attribution/AWSCoreAttributionConsentDialog.h b/Gems/AWSCore/Code/Include/Private/Editor/Attribution/AWSCoreAttributionConsentDialog.h index c2194ccfb4..8a3445c742 100644 --- a/Gems/AWSCore/Code/Include/Private/Editor/Attribution/AWSCoreAttributionConsentDialog.h +++ b/Gems/AWSCore/Code/Include/Private/Editor/Attribution/AWSCoreAttributionConsentDialog.h @@ -15,13 +15,12 @@ namespace AWSCore { //! Defines AWSCoreAttributionConsent QT dialog as QT message box. - class AWSCoreAttributionConsentDialog : - public QMessageBox + class AWSCoreAttributionConsentDialog + : public QMessageBox { public: AZ_CLASS_ALLOCATOR(AWSCoreAttributionConsentDialog, AZ::SystemAllocator, 0); AWSCoreAttributionConsentDialog(); - virtual ~AWSCoreAttributionConsentDialog() = default; - + ~AWSCoreAttributionConsentDialog() override = default; }; } // namespace AWSCore diff --git a/Gems/AWSCore/Code/Include/Private/Editor/Attribution/AWSCoreAttributionConstant.h b/Gems/AWSCore/Code/Include/Private/Editor/Attribution/AWSCoreAttributionConstant.h index e8a034e8e9..49c533cef5 100644 --- a/Gems/AWSCore/Code/Include/Private/Editor/Attribution/AWSCoreAttributionConstant.h +++ b/Gems/AWSCore/Code/Include/Private/Editor/Attribution/AWSCoreAttributionConstant.h @@ -18,4 +18,4 @@ namespace AWSCore static constexpr char AwsAttributionAttributeKeyActiveAWSGems[] = "aws_gems"; static constexpr char AwsAttributionAttributeKeyTimestamp[] = "timestamp"; -} // namespace AWSCOre +} // namespace AWSCore diff --git a/Gems/AWSCore/Code/Include/Private/Editor/UI/AWSCoreEditorMenu.h b/Gems/AWSCore/Code/Include/Private/Editor/UI/AWSCoreEditorMenu.h index 39e96517a7..62f91d36ef 100644 --- a/Gems/AWSCore/Code/Include/Private/Editor/UI/AWSCoreEditorMenu.h +++ b/Gems/AWSCore/Code/Include/Private/Editor/UI/AWSCoreEditorMenu.h @@ -34,7 +34,7 @@ namespace AWSCore "Failed to launch Resource Mapping Tool, please check logs for details."; AWSCoreEditorMenu(const QString& text); - ~AWSCoreEditorMenu(); + ~AWSCoreEditorMenu() override; private: QAction* AddExternalLinkAction(const AZStd::string& name, const AZStd::string& url, const AZStd::string& icon = ""); diff --git a/Gems/AWSCore/Code/Include/Private/ResourceMapping/AWSResourceMappingManager.h b/Gems/AWSCore/Code/Include/Private/ResourceMapping/AWSResourceMappingManager.h index fbc37622bf..27ff275630 100644 --- a/Gems/AWSCore/Code/Include/Private/ResourceMapping/AWSResourceMappingManager.h +++ b/Gems/AWSCore/Code/Include/Private/ResourceMapping/AWSResourceMappingManager.h @@ -71,7 +71,7 @@ namespace AWSCore }; AWSResourceMappingManager(); - ~AWSResourceMappingManager() = default; + ~AWSResourceMappingManager() override = default; void ActivateManager(); void DeactivateManager(); diff --git a/Gems/AWSCore/Code/Include/Public/Framework/AWSApiClientJobConfig.h b/Gems/AWSCore/Code/Include/Public/Framework/AWSApiClientJobConfig.h index e991b2af7e..d14509550e 100644 --- a/Gems/AWSCore/Code/Include/Public/Framework/AWSApiClientJobConfig.h +++ b/Gems/AWSCore/Code/Include/Public/Framework/AWSApiClientJobConfig.h @@ -64,9 +64,6 @@ namespace AWSCore /// Initialize an AwsApiClientJobConfig object. /// - /// \param DefaultConfigType - the type of the config object from which - /// default values will be taken. - /// /// \param defaultConfig - the config object that provides values when /// no override has been set in this object. The default is nullptr, which /// will cause a default value to be used. @@ -83,10 +80,10 @@ namespace AWSCore } } - virtual ~AwsApiClientJobConfig() = default; + ~AwsApiClientJobConfig() override = default; /// Gets a client initialized used currently applied settings. If - /// any settings change after first use, code must call + /// any settings change after first use, code must call /// ApplySettings before those changes will take effect. std::shared_ptr GetClient() override { @@ -112,7 +109,7 @@ namespace AWSCore } else { - // If no explict credenitals are provided then AWS C++ SDK will perform standard search + // If no explicit credentials are provided then AWS C++ SDK will perform standard search return std::make_shared(Aws::Auth::AWSCredentials(), GetClientConfiguration()); } } diff --git a/Gems/AWSCore/Code/Include/Public/Framework/AWSApiJob.h b/Gems/AWSCore/Code/Include/Public/Framework/AWSApiJob.h index 00949011cb..93fc75feed 100644 --- a/Gems/AWSCore/Code/Include/Public/Framework/AWSApiJob.h +++ b/Gems/AWSCore/Code/Include/Public/Framework/AWSApiJob.h @@ -14,14 +14,12 @@ namespace AWSCore { - - /// Base class for all AWS jobs. Primarily exists so that + /// Base class for all AWS jobs. Primarily exists so that /// AwsApiJob::s_config can be used for settings that apply to /// all AWS jobs. class AwsApiJob : public AZ::Job { - public: // To use a different allocator, extend this class and use this macro. AZ_CLASS_ALLOCATOR(AwsApiJob, AZ::SystemAllocator, 0); @@ -33,11 +31,10 @@ namespace AWSCore protected: AwsApiJob(bool isAutoDelete, IConfig* config = GetDefaultConfig()); - virtual ~AwsApiJob(); + ~AwsApiJob() override = default; /// Used for error messages. static const char* COMPONENT_DISPLAY_NAME; - }; } // namespace AWSCore diff --git a/Gems/AWSCore/Code/Include/Public/Framework/AWSApiJobConfig.h b/Gems/AWSCore/Code/Include/Public/Framework/AWSApiJobConfig.h index 9d8b0a5e93..516b42c0dd 100644 --- a/Gems/AWSCore/Code/Include/Public/Framework/AWSApiJobConfig.h +++ b/Gems/AWSCore/Code/Include/Public/Framework/AWSApiJobConfig.h @@ -13,12 +13,15 @@ #include #include +#include + // The AWS Native SDK AWSAllocator triggers a warning due to accessing members of std::allocator directly. // AWSAllocator.h(70): warning C4996: 'std::allocator::pointer': warning STL4010: Various members of std::allocator are deprecated in C++17. // Use std::allocator_traits instead of accessing these members directly. // You can define _SILENCE_CXX17_OLD_ALLOCATOR_MEMBERS_DEPRECATION_WARNING or _SILENCE_ALL_CXX17_DEPRECATION_WARNINGS to acknowledge that you have received this warning. AZ_PUSH_DISABLE_WARNING(4251 4996, "-Wunknown-warning-option") +#include #include #include #include @@ -93,9 +96,6 @@ namespace AWSCore /// Initialize an AwsApiClientJobConfig object. /// - /// \param DefaultConfigType - the type of the config object from which - /// default values will be taken. - /// /// \param defaultConfig - the config object that provides values when /// no override has been set in this object. The default is nullptr, which /// will cause a default value to be used. @@ -136,10 +136,14 @@ namespace AWSCore Override> writeRateLimiter; Override> readRateLimiter; Override httpLibOverride; +#if AWSCORE_BACKWARD_INCOMPATIBLE_CHANGE + Override followRedirects; +#else Override followRedirects; +#endif Override caFile; - /// Applys settings changes made after first use. + /// Applies settings changes made after first use. virtual void ApplySettings(); ////////////////////////////////////////////////////////////////////////// @@ -210,7 +214,7 @@ namespace AWSCore : protected AWSCoreNotificationsBus::Handler { public: - ~AwsApiJobConfigHolder() + ~AwsApiJobConfigHolder() override { AWSCoreNotificationsBus::Handler::BusDisconnect(); } diff --git a/Gems/AWSCore/Code/Include/Public/Framework/AWSApiRequestJob.h b/Gems/AWSCore/Code/Include/Public/Framework/AWSApiRequestJob.h index c189e94f22..80ac6657ed 100644 --- a/Gems/AWSCore/Code/Include/Public/Framework/AWSApiRequestJob.h +++ b/Gems/AWSCore/Code/Include/Public/Framework/AWSApiRequestJob.h @@ -145,10 +145,10 @@ namespace AWSCore AWS_API_REQUEST_TRAITS_TEMPLATE_DEFINITION_HELPER typename AWS_API_REQUEST_TRAITS_TEMPLATE_INSTANCE_HELPER::AsyncFunctionType AWS_API_REQUEST_TRAITS_TEMPLATE_INSTANCE_HELPER::AsyncFunction = _AsyncFunction; - /// Macro that simplifies the declaration of an AwsRequstJob that has a result. + /// Macro that simplifies the declaration of an AwsRequestJob that has a result. #define AWS_API_REQUEST_JOB(SERVICE, REQUEST) AWSCore::AwsApiRequestJob -/// Macro that simplifies the declaration of an AwsRequstJob that has no result. +/// Macro that simplifies the declaration of an AwsRequestJob that has no result. #define AWS_API_REQUEST_JOB_NO_RESULT(SERVICE, REQUEST) AWSCore::AwsApiRequestJob /// An Az::Job that that executes a specific AWS request. @@ -257,7 +257,7 @@ namespace AWSCore /// of request data until your running on the job's worker thread, /// instead of setting the request data before calling Start. /// - /// \param true if the request should be made. + /// \return true if the request should be made. virtual bool PrepareRequest() { return true; diff --git a/Gems/AWSCore/Code/Include/Public/Framework/HttpClientComponent.h b/Gems/AWSCore/Code/Include/Public/Framework/HttpClientComponent.h index f936e7f107..48b59cc56a 100644 --- a/Gems/AWSCore/Code/Include/Public/Framework/HttpClientComponent.h +++ b/Gems/AWSCore/Code/Include/Public/Framework/HttpClientComponent.h @@ -39,7 +39,7 @@ namespace AWSCore : public AZ::ComponentBus { public: - virtual ~HttpClientComponentNotifications() {} + ~HttpClientComponentNotifications() override = default; virtual void OnHttpRequestSuccess(int responseCode, AZStd::string responseBody) {} virtual void OnHttpRequestFailure(int responseCode) {} }; @@ -55,7 +55,7 @@ namespace AWSCore { public: AZ_COMPONENT(HttpClientComponent, "{23ECDBDF-129A-4670-B9B4-1E0B541ACD61}"); - virtual ~HttpClientComponent() = default; + ~HttpClientComponent() override = default; void Init() override; void Activate() override; diff --git a/Gems/AWSCore/Code/Include/Public/Framework/HttpRequestJob.h b/Gems/AWSCore/Code/Include/Public/Framework/HttpRequestJob.h index 60bb0ffd34..b277340be9 100644 --- a/Gems/AWSCore/Code/Include/Public/Framework/HttpRequestJob.h +++ b/Gems/AWSCore/Code/Include/Public/Framework/HttpRequestJob.h @@ -178,7 +178,7 @@ namespace AWSCore }; /// Override to process the response to the HTTP request before callbacks are fired. - /// WARNING: This gets called on the job's thread, so observe thread safety precations. + /// WARNING: This gets called on the job's thread, so observe thread safety precautions. virtual void ProcessResponse(const std::shared_ptr& response) { AZ_UNUSED(response); diff --git a/Gems/AWSCore/Code/Include/Public/Framework/JsonObjectHandler.h b/Gems/AWSCore/Code/Include/Public/Framework/JsonObjectHandler.h index 57eabe3593..e86db54bbf 100644 --- a/Gems/AWSCore/Code/Include/Public/Framework/JsonObjectHandler.h +++ b/Gems/AWSCore/Code/Include/Public/Framework/JsonObjectHandler.h @@ -29,24 +29,24 @@ namespace AWSCore Ch Peek() const { int c = m_is.peek(); - return c == std::char_traits::eof() ? '\0' : (Ch)c; + return c == std::char_traits::eof() ? '\0' : static_cast(c); } Ch Take() { int c = m_is.get(); - return c == std::char_traits::eof() ? '\0' : (Ch)c; + return c == std::char_traits::eof() ? '\0' : static_cast(c); } size_t Tell() const { - return (size_t)m_is.tellg(); + return static_cast(m_is.tellg()); } Ch* PutBegin() { AZ_Assert(false, "Not Implemented"); - return 0; + return nullptr; } void Put(Ch) diff --git a/Gems/AWSCore/Code/Include/Public/Framework/JsonWriter.h b/Gems/AWSCore/Code/Include/Public/Framework/JsonWriter.h index 8b3f4d55b2..261907b7ec 100644 --- a/Gems/AWSCore/Code/Include/Public/Framework/JsonWriter.h +++ b/Gems/AWSCore/Code/Include/Public/Framework/JsonWriter.h @@ -161,7 +161,7 @@ namespace AWSCore } /// Write JSON format content directly to the writer's output stream. - /// This can be used to efficently output static content. + /// This can be used to efficiently output static content. bool WriteJson(const Ch* json) { if (json) @@ -182,7 +182,7 @@ namespace AWSCore } /// Write an object. The object can implement a WriteJson function - /// or you can provide an GobalWriteJson template function + /// or you can provide an GlobalWriteJson template function /// specialization. template bool Object(const ObjectType& obj) diff --git a/Gems/AWSCore/Code/Include/Public/Framework/RequestBuilder.h b/Gems/AWSCore/Code/Include/Public/Framework/RequestBuilder.h index 1e4b7c34c2..c12e8b1d5c 100644 --- a/Gems/AWSCore/Code/Include/Public/Framework/RequestBuilder.h +++ b/Gems/AWSCore/Code/Include/Public/Framework/RequestBuilder.h @@ -36,7 +36,7 @@ namespace AWSCore class RequestBuilder { public: - RequestBuilder() = default; + RequestBuilder(); /// Converts the provided object to JSON and sends it as the /// body of the request. The object can implement the following diff --git a/Gems/AWSCore/Code/Include/Public/Framework/ServiceClientJobConfig.h b/Gems/AWSCore/Code/Include/Public/Framework/ServiceClientJobConfig.h index 9082498e96..89141bb492 100644 --- a/Gems/AWSCore/Code/Include/Public/Framework/ServiceClientJobConfig.h +++ b/Gems/AWSCore/Code/Include/Public/Framework/ServiceClientJobConfig.h @@ -20,7 +20,7 @@ namespace AWSCore { public: - virtual const AZStd::string GetServiceUrl() = 0; + virtual AZStd::string GetServiceUrl() = 0; }; /// Encapsulates what code needs to know about a service in order to @@ -81,9 +81,6 @@ namespace AWSCore /// Initialize an ServiceClientJobConfig object. /// - /// \param DefaultConfigType - the type of the config object from which - /// default values will be taken. - /// /// \param defaultConfig - the config object that provides values when /// no override has been set in this object. The default is nullptr, which /// will cause a default value to be used. @@ -102,7 +99,7 @@ namespace AWSCore /// This implementation assumes the caller will cache this value as /// needed. See it's use in ServiceRequestJobConfig. - const AZStd::string GetServiceUrl() override + AZStd::string GetServiceUrl() override { if (endpointOverride.has_value()) { diff --git a/Gems/AWSCore/Code/Include/Public/Framework/ServiceRequestJob.h b/Gems/AWSCore/Code/Include/Public/Framework/ServiceRequestJob.h index cd0686c2d0..c24387a7ba 100644 --- a/Gems/AWSCore/Code/Include/Public/Framework/ServiceRequestJob.h +++ b/Gems/AWSCore/Code/Include/Public/Framework/ServiceRequestJob.h @@ -119,7 +119,7 @@ namespace AWSCore Error error; /// Determines if the AWS credentials, as supplied by the credentialsProvider from - /// the ServiceReqestJobConfig object (which defaults to the user's credentials), + /// the ServiceRequestJobConfig object (which defaults to the user's credentials), /// are used to sign the request. The default is true. Override this and return false /// if calling a public API and want to avoid the overhead of signing requests. bool UseAWSCredentials() { @@ -565,13 +565,11 @@ namespace AWSCore } AZStd::string requestContent; - AZStd::string responseContent; - - std::istreambuf_iterator eos; std::shared_ptr requestStream = response->GetOriginatingRequest().GetContentBody(); if (requestStream) { + std::istreambuf_iterator eos; requestStream->clear(); requestStream->seekg(0); requestContent = AZStd::string{ std::istreambuf_iterator(*requestStream.get()),eos }; @@ -584,7 +582,7 @@ namespace AWSCore Aws::IOStream& responseStream = response->GetResponseBody(); responseStream.clear(); responseStream.seekg(0); - responseContent = AZStd::string{ std::istreambuf_iterator(responseStream),responseEos }; + AZStd::string responseContent = AZStd::string{ std::istreambuf_iterator(responseStream), responseEos }; responseContent = EscapePercentCharsInString(responseContent); responseStream.seekg(0); diff --git a/Gems/AWSCore/Code/Include/Public/Framework/ServiceRequestJobConfig.h b/Gems/AWSCore/Code/Include/Public/Framework/ServiceRequestJobConfig.h index 10b14ed5af..6c4e884f49 100644 --- a/Gems/AWSCore/Code/Include/Public/Framework/ServiceRequestJobConfig.h +++ b/Gems/AWSCore/Code/Include/Public/Framework/ServiceRequestJobConfig.h @@ -44,9 +44,6 @@ namespace AWSCore /// Initialize an ServiceRequestJobConfig object. /// - /// \param DefaultConfigType - the type of the config object from which - /// default values will be taken. - /// /// \param defaultConfig - the config object that provides values when /// no override has been set in this object. The default is nullptr, which /// will cause a default value to be used. diff --git a/Gems/AWSCore/Code/Include/Private/Editor/Platform/Android/AWSCoreEditor_Traits_Android.h b/Gems/AWSCore/Code/Platform/Android/AWSCoreEditor_Traits_Android.h similarity index 100% rename from Gems/AWSCore/Code/Include/Private/Editor/Platform/Android/AWSCoreEditor_Traits_Android.h rename to Gems/AWSCore/Code/Platform/Android/AWSCoreEditor_Traits_Android.h diff --git a/Gems/AWSCore/Code/Include/Private/Editor/Platform/Android/AWSCoreEditor_Traits_Platform.h b/Gems/AWSCore/Code/Platform/Android/AWSCoreEditor_Traits_Platform.h similarity index 100% rename from Gems/AWSCore/Code/Include/Private/Editor/Platform/Android/AWSCoreEditor_Traits_Platform.h rename to Gems/AWSCore/Code/Platform/Android/AWSCoreEditor_Traits_Platform.h diff --git a/Gems/AWSCore/Code/Platform/Android/AWSCore_Traits_Android.h b/Gems/AWSCore/Code/Platform/Android/AWSCore_Traits_Android.h new file mode 100644 index 0000000000..2cacfb0d34 --- /dev/null +++ b/Gems/AWSCore/Code/Platform/Android/AWSCore_Traits_Android.h @@ -0,0 +1,10 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#pragma once + +#define AWSCORE_BACKWARD_INCOMPATIBLE_CHANGE 1 diff --git a/Gems/AWSCore/Code/Platform/Android/AWSCore_Traits_Platform.h b/Gems/AWSCore/Code/Platform/Android/AWSCore_Traits_Platform.h new file mode 100644 index 0000000000..6beeb3442d --- /dev/null +++ b/Gems/AWSCore/Code/Platform/Android/AWSCore_Traits_Platform.h @@ -0,0 +1,10 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#pragma once + +#include diff --git a/Gems/AWSCore/Code/Source/Framework/Platform/Android/GetCertsPath_Android.cpp b/Gems/AWSCore/Code/Platform/Android/GetCertsPath_Android.cpp similarity index 97% rename from Gems/AWSCore/Code/Source/Framework/Platform/Android/GetCertsPath_Android.cpp rename to Gems/AWSCore/Code/Platform/Android/GetCertsPath_Android.cpp index 9dbacce2dd..557e29c3b6 100644 --- a/Gems/AWSCore/Code/Source/Framework/Platform/Android/GetCertsPath_Android.cpp +++ b/Gems/AWSCore/Code/Platform/Android/GetCertsPath_Android.cpp @@ -1,33 +1,33 @@ -/* - * Copyright (c) Contributors to the Open 3D Engine Project. - * For complete copyright and license terms please see the LICENSE at the root of this distribution. - * - * SPDX-License-Identifier: Apache-2.0 OR MIT - * - */ - -#include -// The AWS Native SDK AWSAllocator triggers a warning due to accessing members of std::allocator directly. -// AWSAllocator.h(70): warning C4996: 'std::allocator::pointer': warning STL4010: Various members of std::allocator are deprecated in -// C++17. Use std::allocator_traits instead of accessing these members directly. You can define -// _SILENCE_CXX17_OLD_ALLOCATOR_MEMBERS_DEPRECATION_WARNING or _SILENCE_ALL_CXX17_DEPRECATION_WARNINGS to acknowledge that you have received -// this warning. -AZ_PUSH_DISABLE_WARNING(4251 4996, "-Wunknown-warning-option") -#include -AZ_POP_DISABLE_WARNING -#include -#include - -namespace AWSCore -{ - namespace Platform - { - Aws::String GetCaCertBundlePath() - { - AZStd::string publicStoragePath = AZ::Android::Utils::GetAppPublicStoragePath(); - publicStoragePath.append("/certificates/aws/cacert.pem"); - - return publicStoragePath.c_str(); - } - } // namespace Platform -} +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +// The AWS Native SDK AWSAllocator triggers a warning due to accessing members of std::allocator directly. +// AWSAllocator.h(70): warning C4996: 'std::allocator::pointer': warning STL4010: Various members of std::allocator are deprecated in +// C++17. Use std::allocator_traits instead of accessing these members directly. You can define +// _SILENCE_CXX17_OLD_ALLOCATOR_MEMBERS_DEPRECATION_WARNING or _SILENCE_ALL_CXX17_DEPRECATION_WARNINGS to acknowledge that you have received +// this warning. +AZ_PUSH_DISABLE_WARNING(4251 4996, "-Wunknown-warning-option") +#include +AZ_POP_DISABLE_WARNING +#include +#include + +namespace AWSCore +{ + namespace Platform + { + Aws::String GetCaCertBundlePath() + { + AZStd::string publicStoragePath = AZ::Android::Utils::GetAppPublicStoragePath(); + publicStoragePath.append("/certificates/aws/cacert.pem"); + + return publicStoragePath.c_str(); + } + } // namespace Platform +} diff --git a/Gems/AWSCore/Code/Include/Private/Editor/Platform/Android/platform_android_files.cmake b/Gems/AWSCore/Code/Platform/Android/platform_android_editor_files.cmake similarity index 100% rename from Gems/AWSCore/Code/Include/Private/Editor/Platform/Android/platform_android_files.cmake rename to Gems/AWSCore/Code/Platform/Android/platform_android_editor_files.cmake diff --git a/Gems/AWSCore/Code/Tests/Editor/Platform/Linux/awscore_editor_tests_linux_files.cmake b/Gems/AWSCore/Code/Platform/Android/platform_android_editor_tests_files.cmake similarity index 100% rename from Gems/AWSCore/Code/Tests/Editor/Platform/Linux/awscore_editor_tests_linux_files.cmake rename to Gems/AWSCore/Code/Platform/Android/platform_android_editor_tests_files.cmake diff --git a/Gems/AWSCore/Code/Platform/Android/platform_android_files.cmake b/Gems/AWSCore/Code/Platform/Android/platform_android_files.cmake new file mode 100644 index 0000000000..e5b02beeac --- /dev/null +++ b/Gems/AWSCore/Code/Platform/Android/platform_android_files.cmake @@ -0,0 +1,13 @@ +# +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# + +set(FILES + AWSCore_Traits_Platform.h + AWSCore_Traits_Android.h + GetCertsPath_Android.cpp +) diff --git a/Gems/AWSCore/Code/Source/Framework/Platform/Common/GetCertsPath_Null.cpp b/Gems/AWSCore/Code/Platform/Common/GetCertsPath_Null.cpp similarity index 97% rename from Gems/AWSCore/Code/Source/Framework/Platform/Common/GetCertsPath_Null.cpp rename to Gems/AWSCore/Code/Platform/Common/GetCertsPath_Null.cpp index 5731327fb2..45bb45000f 100644 --- a/Gems/AWSCore/Code/Source/Framework/Platform/Common/GetCertsPath_Null.cpp +++ b/Gems/AWSCore/Code/Platform/Common/GetCertsPath_Null.cpp @@ -1,28 +1,28 @@ -/* - * Copyright (c) Contributors to the Open 3D Engine Project. - * For complete copyright and license terms please see the LICENSE at the root of this distribution. - * - * SPDX-License-Identifier: Apache-2.0 OR MIT - * - */ - -#include -// The AWS Native SDK AWSAllocator triggers a warning due to accessing members of std::allocator directly. -// AWSAllocator.h(70): warning C4996: 'std::allocator::pointer': warning STL4010: Various members of std::allocator are deprecated in -// C++17. Use std::allocator_traits instead of accessing these members directly. You can define -// _SILENCE_CXX17_OLD_ALLOCATOR_MEMBERS_DEPRECATION_WARNING or _SILENCE_ALL_CXX17_DEPRECATION_WARNINGS to acknowledge that you have received -// this warning. -AZ_PUSH_DISABLE_WARNING(4251 4996, "-Wunknown-warning-option") -#include -AZ_POP_DISABLE_WARNING - -namespace AWSCore -{ - namespace Platform - { - Aws::String GetCaCertBundlePath() - { - return ""; // no-op - } - } // namespace Platform -} // namespace GridMate +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +// The AWS Native SDK AWSAllocator triggers a warning due to accessing members of std::allocator directly. +// AWSAllocator.h(70): warning C4996: 'std::allocator::pointer': warning STL4010: Various members of std::allocator are deprecated in +// C++17. Use std::allocator_traits instead of accessing these members directly. You can define +// _SILENCE_CXX17_OLD_ALLOCATOR_MEMBERS_DEPRECATION_WARNING or _SILENCE_ALL_CXX17_DEPRECATION_WARNINGS to acknowledge that you have received +// this warning. +AZ_PUSH_DISABLE_WARNING(4251 4996, "-Wunknown-warning-option") +#include +AZ_POP_DISABLE_WARNING + +namespace AWSCore +{ + namespace Platform + { + Aws::String GetCaCertBundlePath() + { + return ""; // no-op + } + } // namespace Platform +} // namespace GridMate diff --git a/Gems/AWSCore/Code/Include/Private/Editor/Platform/Linux/AWSCoreEditor_Traits_Linux.h b/Gems/AWSCore/Code/Platform/Linux/AWSCoreEditor_Traits_Linux.h similarity index 100% rename from Gems/AWSCore/Code/Include/Private/Editor/Platform/Linux/AWSCoreEditor_Traits_Linux.h rename to Gems/AWSCore/Code/Platform/Linux/AWSCoreEditor_Traits_Linux.h diff --git a/Gems/AWSCore/Code/Include/Private/Editor/Platform/Linux/AWSCoreEditor_Traits_Platform.h b/Gems/AWSCore/Code/Platform/Linux/AWSCoreEditor_Traits_Platform.h similarity index 100% rename from Gems/AWSCore/Code/Include/Private/Editor/Platform/Linux/AWSCoreEditor_Traits_Platform.h rename to Gems/AWSCore/Code/Platform/Linux/AWSCoreEditor_Traits_Platform.h diff --git a/Gems/AWSCore/Code/Platform/Linux/AWSCore_Traits_Linux.h b/Gems/AWSCore/Code/Platform/Linux/AWSCore_Traits_Linux.h new file mode 100644 index 0000000000..d7b1f32461 --- /dev/null +++ b/Gems/AWSCore/Code/Platform/Linux/AWSCore_Traits_Linux.h @@ -0,0 +1,10 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#pragma once + +#define AWSCORE_BACKWARD_INCOMPATIBLE_CHANGE 0 diff --git a/Gems/AWSCore/Code/Platform/Linux/AWSCore_Traits_Platform.h b/Gems/AWSCore/Code/Platform/Linux/AWSCore_Traits_Platform.h new file mode 100644 index 0000000000..d6ef9ceb4d --- /dev/null +++ b/Gems/AWSCore/Code/Platform/Linux/AWSCore_Traits_Platform.h @@ -0,0 +1,10 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#pragma once + +#include diff --git a/Gems/AWSCore/Code/Include/Private/Editor/Platform/Linux/platform_linux_files.cmake b/Gems/AWSCore/Code/Platform/Linux/platform_linux_editor_files.cmake similarity index 100% rename from Gems/AWSCore/Code/Include/Private/Editor/Platform/Linux/platform_linux_files.cmake rename to Gems/AWSCore/Code/Platform/Linux/platform_linux_editor_files.cmake diff --git a/Gems/AWSCore/Code/Tests/Editor/Platform/Mac/awscore_editor_tests_mac_files.cmake b/Gems/AWSCore/Code/Platform/Linux/platform_linux_editor_tests_files.cmake similarity index 100% rename from Gems/AWSCore/Code/Tests/Editor/Platform/Mac/awscore_editor_tests_mac_files.cmake rename to Gems/AWSCore/Code/Platform/Linux/platform_linux_editor_tests_files.cmake diff --git a/Gems/AWSCore/Code/Source/Framework/Platform/Windows/platform_windows_files.cmake b/Gems/AWSCore/Code/Platform/Linux/platform_linux_files.cmake similarity index 82% rename from Gems/AWSCore/Code/Source/Framework/Platform/Windows/platform_windows_files.cmake rename to Gems/AWSCore/Code/Platform/Linux/platform_linux_files.cmake index 0abbd1adb8..1661434740 100644 --- a/Gems/AWSCore/Code/Source/Framework/Platform/Windows/platform_windows_files.cmake +++ b/Gems/AWSCore/Code/Platform/Linux/platform_linux_files.cmake @@ -7,5 +7,7 @@ # set(FILES + AWSCore_Traits_Platform.h + AWSCore_Traits_Linux.h ../Common/GetCertsPath_Null.cpp ) diff --git a/Gems/AWSCore/Code/Include/Private/Editor/Platform/Mac/AWSCoreEditor_Traits_Mac.h b/Gems/AWSCore/Code/Platform/Mac/AWSCoreEditor_Traits_Mac.h similarity index 100% rename from Gems/AWSCore/Code/Include/Private/Editor/Platform/Mac/AWSCoreEditor_Traits_Mac.h rename to Gems/AWSCore/Code/Platform/Mac/AWSCoreEditor_Traits_Mac.h diff --git a/Gems/AWSCore/Code/Include/Private/Editor/Platform/Mac/AWSCoreEditor_Traits_Platform.h b/Gems/AWSCore/Code/Platform/Mac/AWSCoreEditor_Traits_Platform.h similarity index 100% rename from Gems/AWSCore/Code/Include/Private/Editor/Platform/Mac/AWSCoreEditor_Traits_Platform.h rename to Gems/AWSCore/Code/Platform/Mac/AWSCoreEditor_Traits_Platform.h diff --git a/Gems/AWSCore/Code/Platform/Mac/AWSCore_Traits_Mac.h b/Gems/AWSCore/Code/Platform/Mac/AWSCore_Traits_Mac.h new file mode 100644 index 0000000000..d7b1f32461 --- /dev/null +++ b/Gems/AWSCore/Code/Platform/Mac/AWSCore_Traits_Mac.h @@ -0,0 +1,10 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#pragma once + +#define AWSCORE_BACKWARD_INCOMPATIBLE_CHANGE 0 diff --git a/Gems/AWSCore/Code/Platform/Mac/AWSCore_Traits_Platform.h b/Gems/AWSCore/Code/Platform/Mac/AWSCore_Traits_Platform.h new file mode 100644 index 0000000000..2d33c834f9 --- /dev/null +++ b/Gems/AWSCore/Code/Platform/Mac/AWSCore_Traits_Platform.h @@ -0,0 +1,10 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#pragma once + +#include diff --git a/Gems/AWSCore/Code/Include/Private/Editor/Platform/Mac/platform_mac_files.cmake b/Gems/AWSCore/Code/Platform/Mac/platform_mac_editor_files.cmake similarity index 100% rename from Gems/AWSCore/Code/Include/Private/Editor/Platform/Mac/platform_mac_files.cmake rename to Gems/AWSCore/Code/Platform/Mac/platform_mac_editor_files.cmake diff --git a/Gems/AWSCore/Code/Source/Framework/Platform/Android/platform_android_files.cmake b/Gems/AWSCore/Code/Platform/Mac/platform_mac_editor_tests_files.cmake similarity index 84% rename from Gems/AWSCore/Code/Source/Framework/Platform/Android/platform_android_files.cmake rename to Gems/AWSCore/Code/Platform/Mac/platform_mac_editor_tests_files.cmake index 278e4a1c9d..07f96644ab 100644 --- a/Gems/AWSCore/Code/Source/Framework/Platform/Android/platform_android_files.cmake +++ b/Gems/AWSCore/Code/Platform/Mac/platform_mac_editor_tests_files.cmake @@ -6,6 +6,5 @@ # # -set(FILES - GetCertsPath_Android.cpp +set(FILES ) diff --git a/Gems/AWSCore/Code/Source/Framework/Platform/Mac/platform_mac_files.cmake b/Gems/AWSCore/Code/Platform/Mac/platform_mac_files.cmake similarity index 82% rename from Gems/AWSCore/Code/Source/Framework/Platform/Mac/platform_mac_files.cmake rename to Gems/AWSCore/Code/Platform/Mac/platform_mac_files.cmake index 0abbd1adb8..3e5e99cc05 100644 --- a/Gems/AWSCore/Code/Source/Framework/Platform/Mac/platform_mac_files.cmake +++ b/Gems/AWSCore/Code/Platform/Mac/platform_mac_files.cmake @@ -7,5 +7,7 @@ # set(FILES + AWSCore_Traits_Platform.h + AWSCore_Traits_Mac.h ../Common/GetCertsPath_Null.cpp ) diff --git a/Gems/AWSCore/Code/Include/Private/Editor/Platform/Windows/AWSCoreEditor_Traits_Platform.h b/Gems/AWSCore/Code/Platform/Windows/AWSCoreEditor_Traits_Platform.h similarity index 100% rename from Gems/AWSCore/Code/Include/Private/Editor/Platform/Windows/AWSCoreEditor_Traits_Platform.h rename to Gems/AWSCore/Code/Platform/Windows/AWSCoreEditor_Traits_Platform.h diff --git a/Gems/AWSCore/Code/Include/Private/Editor/Platform/Windows/AWSCoreEditor_Traits_Windows.h b/Gems/AWSCore/Code/Platform/Windows/AWSCoreEditor_Traits_Windows.h similarity index 100% rename from Gems/AWSCore/Code/Include/Private/Editor/Platform/Windows/AWSCoreEditor_Traits_Windows.h rename to Gems/AWSCore/Code/Platform/Windows/AWSCoreEditor_Traits_Windows.h diff --git a/Gems/AWSCore/Code/Platform/Windows/AWSCore_Traits_Platform.h b/Gems/AWSCore/Code/Platform/Windows/AWSCore_Traits_Platform.h new file mode 100644 index 0000000000..f6ccf10b88 --- /dev/null +++ b/Gems/AWSCore/Code/Platform/Windows/AWSCore_Traits_Platform.h @@ -0,0 +1,10 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#pragma once + +#include diff --git a/Gems/AWSCore/Code/Platform/Windows/AWSCore_Traits_Windows.h b/Gems/AWSCore/Code/Platform/Windows/AWSCore_Traits_Windows.h new file mode 100644 index 0000000000..d7b1f32461 --- /dev/null +++ b/Gems/AWSCore/Code/Platform/Windows/AWSCore_Traits_Windows.h @@ -0,0 +1,10 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#pragma once + +#define AWSCORE_BACKWARD_INCOMPATIBLE_CHANGE 0 diff --git a/Gems/AWSCore/Code/Include/Private/Editor/Platform/Windows/platform_windows_files.cmake b/Gems/AWSCore/Code/Platform/Windows/platform_windows_editor_files.cmake similarity index 100% rename from Gems/AWSCore/Code/Include/Private/Editor/Platform/Windows/platform_windows_files.cmake rename to Gems/AWSCore/Code/Platform/Windows/platform_windows_editor_files.cmake diff --git a/Gems/AWSCore/Code/Platform/Windows/platform_windows_editor_tests_files.cmake b/Gems/AWSCore/Code/Platform/Windows/platform_windows_editor_tests_files.cmake new file mode 100644 index 0000000000..3b87d2f3bb --- /dev/null +++ b/Gems/AWSCore/Code/Platform/Windows/platform_windows_editor_tests_files.cmake @@ -0,0 +1,19 @@ +# +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# + +set(FILES + ../../Tests/Editor/AWSCoreEditorSystemComponentTest.cpp + ../../Tests/Editor/Attribution/AWSCoreAttributionManagerTest.cpp + ../../Tests/Editor/Attribution/AWSCoreAttributionMetricTest.cpp + ../../Tests/Editor/Attribution/AWSCoreAttributionSystemComponentTest.cpp + ../../Tests/Editor/Attribution/AWSAttributionServiceApiTest.cpp + ../../Tests/Editor/UI/AWSCoreEditorMenuTest.cpp + ../../Tests/Editor/UI/AWSCoreEditorUIFixture.h + ../../Tests/Editor/UI/AWSCoreResourceMappingToolActionTest.cpp + ../../Tests/Editor/AWSCoreEditorManagerTest.cpp +) diff --git a/Gems/AWSCore/Code/Platform/Windows/platform_windows_files.cmake b/Gems/AWSCore/Code/Platform/Windows/platform_windows_files.cmake new file mode 100644 index 0000000000..aa0119d042 --- /dev/null +++ b/Gems/AWSCore/Code/Platform/Windows/platform_windows_files.cmake @@ -0,0 +1,13 @@ +# +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# + +set(FILES + AWSCore_Traits_Platform.h + AWSCore_Traits_Windows.h + ../Common/GetCertsPath_Null.cpp +) diff --git a/Gems/AWSCore/Code/Include/Private/Editor/Platform/iOS/AWSCoreEditor_Traits_Platform.h b/Gems/AWSCore/Code/Platform/iOS/AWSCoreEditor_Traits_Platform.h similarity index 100% rename from Gems/AWSCore/Code/Include/Private/Editor/Platform/iOS/AWSCoreEditor_Traits_Platform.h rename to Gems/AWSCore/Code/Platform/iOS/AWSCoreEditor_Traits_Platform.h diff --git a/Gems/AWSCore/Code/Include/Private/Editor/Platform/iOS/AWSCoreEditor_Traits_iOS.h b/Gems/AWSCore/Code/Platform/iOS/AWSCoreEditor_Traits_iOS.h similarity index 100% rename from Gems/AWSCore/Code/Include/Private/Editor/Platform/iOS/AWSCoreEditor_Traits_iOS.h rename to Gems/AWSCore/Code/Platform/iOS/AWSCoreEditor_Traits_iOS.h diff --git a/Gems/AWSCore/Code/Platform/iOS/AWSCore_Traits_Platform.h b/Gems/AWSCore/Code/Platform/iOS/AWSCore_Traits_Platform.h new file mode 100644 index 0000000000..7384900938 --- /dev/null +++ b/Gems/AWSCore/Code/Platform/iOS/AWSCore_Traits_Platform.h @@ -0,0 +1,10 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#pragma once + +#include diff --git a/Gems/AWSCore/Code/Platform/iOS/AWSCore_Traits_iOS.h b/Gems/AWSCore/Code/Platform/iOS/AWSCore_Traits_iOS.h new file mode 100644 index 0000000000..d7b1f32461 --- /dev/null +++ b/Gems/AWSCore/Code/Platform/iOS/AWSCore_Traits_iOS.h @@ -0,0 +1,10 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#pragma once + +#define AWSCORE_BACKWARD_INCOMPATIBLE_CHANGE 0 diff --git a/Gems/AWSCore/Code/Include/Private/Editor/Platform/iOS/platform_ios_files.cmake b/Gems/AWSCore/Code/Platform/iOS/platform_ios_editor_files.cmake similarity index 100% rename from Gems/AWSCore/Code/Include/Private/Editor/Platform/iOS/platform_ios_files.cmake rename to Gems/AWSCore/Code/Platform/iOS/platform_ios_editor_files.cmake diff --git a/Gems/AWSCore/Code/Source/Framework/Platform/Linux/platform_linux_files.cmake b/Gems/AWSCore/Code/Platform/iOS/platform_ios_editor_tests_files.cmake similarity index 82% rename from Gems/AWSCore/Code/Source/Framework/Platform/Linux/platform_linux_files.cmake rename to Gems/AWSCore/Code/Platform/iOS/platform_ios_editor_tests_files.cmake index 0abbd1adb8..07f96644ab 100644 --- a/Gems/AWSCore/Code/Source/Framework/Platform/Linux/platform_linux_files.cmake +++ b/Gems/AWSCore/Code/Platform/iOS/platform_ios_editor_tests_files.cmake @@ -6,6 +6,5 @@ # # -set(FILES - ../Common/GetCertsPath_Null.cpp +set(FILES ) diff --git a/Gems/AWSCore/Code/Source/Framework/Platform/iOS/platform_ios_files.cmake b/Gems/AWSCore/Code/Platform/iOS/platform_ios_files.cmake similarity index 82% rename from Gems/AWSCore/Code/Source/Framework/Platform/iOS/platform_ios_files.cmake rename to Gems/AWSCore/Code/Platform/iOS/platform_ios_files.cmake index 0abbd1adb8..c73796afe3 100644 --- a/Gems/AWSCore/Code/Source/Framework/Platform/iOS/platform_ios_files.cmake +++ b/Gems/AWSCore/Code/Platform/iOS/platform_ios_files.cmake @@ -7,5 +7,7 @@ # set(FILES + AWSCore_Traits_Platform.h + AWSCore_Traits_iOS.h ../Common/GetCertsPath_Null.cpp ) diff --git a/Gems/AWSCore/Code/Source/AWSCoreEditorSystemComponent.cpp b/Gems/AWSCore/Code/Source/AWSCoreEditorSystemComponent.cpp index f4420cb40a..a522f71084 100644 --- a/Gems/AWSCore/Code/Source/AWSCoreEditorSystemComponent.cpp +++ b/Gems/AWSCore/Code/Source/AWSCoreEditorSystemComponent.cpp @@ -79,7 +79,7 @@ namespace AWSCore QMenuBar* menuBar = mainWindow->menuBar(); QList actionList = menuBar->actions(); QAction* insertPivot = nullptr; - for (QList::iterator itr = actionList.begin(); itr != actionList.end(); itr++) + for (QList::iterator itr = actionList.begin(); itr != actionList.end(); ++itr) { if (QString::compare((*itr)->text(), EDITOR_HELP_MENU_TEXT) == 0) { @@ -88,7 +88,7 @@ namespace AWSCore } } - auto menu = m_awsCoreEditorManager->GetAWSCoreEditorMenu(); + const auto menu = m_awsCoreEditorManager->GetAWSCoreEditorMenu(); if (insertPivot) { menuBar->insertMenu(insertPivot, menu); diff --git a/Gems/AWSCore/Code/Source/Credential/AWSCVarCredentialHandler.cpp b/Gems/AWSCore/Code/Source/Credential/AWSCVarCredentialHandler.cpp index 83487f665d..8102efa2f8 100644 --- a/Gems/AWSCore/Code/Source/Credential/AWSCVarCredentialHandler.cpp +++ b/Gems/AWSCore/Code/Source/Credential/AWSCVarCredentialHandler.cpp @@ -5,15 +5,14 @@ * SPDX-License-Identifier: Apache-2.0 OR MIT * */ - #include #include namespace AWSCore { - AZ_CVAR(AZ::CVarFixedString, cl_awsAccessKey, "", nullptr, AZ::ConsoleFunctorFlags::Null, "Override AWS access key"); - AZ_CVAR(AZ::CVarFixedString, cl_awsSecretKey, "", nullptr, AZ::ConsoleFunctorFlags::Null, "Override AWS secret key"); + AZ_CVAR(AZ::CVarFixedString, cl_awsAccessKey, "", nullptr, AZ::ConsoleFunctorFlags::IsInvisible, "Override AWS access key"); + AZ_CVAR(AZ::CVarFixedString, cl_awsSecretKey, "", nullptr, AZ::ConsoleFunctorFlags::IsInvisible, "Override AWS secret key"); static constexpr char AWSCVARCREDENTIALHANDLER_ALLOC_TAG[] = "AWSCVarCredentialHandler"; @@ -36,12 +35,12 @@ namespace AWSCore std::shared_ptr AWSCVarCredentialHandler::GetCredentialsProvider() { - auto accessKey = static_cast(cl_awsAccessKey); - auto secretKey = static_cast(cl_awsSecretKey); + const auto accessKey = static_cast(cl_awsAccessKey); + const auto secretKey = static_cast(cl_awsSecretKey); if (!accessKey.empty() && !secretKey.empty()) { - AZStd::lock_guard credentialsLock{m_credentialMutex}; + AZStd::lock_guard credentialsLock{ m_credentialMutex }; m_cvarCredentialsProvider = Aws::MakeShared( AWSCVARCREDENTIALHANDLER_ALLOC_TAG, accessKey.c_str(), secretKey.c_str()); return m_cvarCredentialsProvider; @@ -52,7 +51,7 @@ namespace AWSCore void AWSCVarCredentialHandler::ResetCredentialsProvider() { // Must reset credential provider after AWSNativeSDKs init or before AWSNativeSDKs shutdown - AZStd::lock_guard credentialsLock{m_credentialMutex}; + AZStd::lock_guard credentialsLock{ m_credentialMutex }; m_cvarCredentialsProvider.reset(); } } // namespace AWSCore diff --git a/Gems/AWSCore/Code/Source/Editor/Attribution/AWSCoreAttributionConsentDialog.cpp b/Gems/AWSCore/Code/Source/Editor/Attribution/AWSCoreAttributionConsentDialog.cpp index 85912616c2..7ec9f9ea4f 100644 --- a/Gems/AWSCore/Code/Source/Editor/Attribution/AWSCoreAttributionConsentDialog.cpp +++ b/Gems/AWSCore/Code/Source/Editor/Attribution/AWSCoreAttributionConsentDialog.cpp @@ -35,8 +35,7 @@ namespace AWSCore this->setDefaultButton(QMessageBox::Save); this->button(QMessageBox::Cancel)->hide(); this->setIcon(QMessageBox::Information); - QGridLayout* layout = (QGridLayout*)this->layout(); - if (layout) + if (QGridLayout* layout = static_cast(this->layout())) { layout->setVerticalSpacing(20); layout->setHorizontalSpacing(10); diff --git a/Gems/AWSCore/Code/Source/Editor/Attribution/AWSCoreAttributionManager.cpp b/Gems/AWSCore/Code/Source/Editor/Attribution/AWSCoreAttributionManager.cpp index 712c083e7a..3b9352d250 100644 --- a/Gems/AWSCore/Code/Source/Editor/Attribution/AWSCoreAttributionManager.cpp +++ b/Gems/AWSCore/Code/Source/Editor/Attribution/AWSCoreAttributionManager.cpp @@ -68,19 +68,19 @@ namespace AWSCore AZ_Assert(fileIO, "File IO is not initialized."); // Resolve path to editor_aws_preferences.setreg - AZStd::string editorAWSPreferencesFilePath = + const AZStd::string editorAWSPreferencesFilePath = AZStd::string::format("@user@/%s/%s", AZ::SettingsRegistryInterface::RegistryFolder, EditorAWSPreferencesFileName); - AZStd::array resolvedPathAWSPreference{}; - if (!fileIO->ResolvePath(editorAWSPreferencesFilePath.c_str(), resolvedPathAWSPreference.data(), resolvedPathAWSPreference.size())) + AZ::IO::FixedMaxPath resolvedPathAWSPreference; + if (!fileIO->ResolvePath(resolvedPathAWSPreference, AZ::IO::PathView(editorAWSPreferencesFilePath))) { - AZ_Warning("AWSAttributionManager", false, "Error resolving path %s", resolvedPathAWSPreference.data()); + AZ_Warning("AWSAttributionManager", false, "Error resolving path %s", resolvedPathAWSPreference.c_str()); return; } - if (fileIO->Exists(resolvedPathAWSPreference.data())) + if (fileIO->Exists(resolvedPathAWSPreference.c_str())) { m_settingsRegistry->MergeSettingsFile( - resolvedPathAWSPreference.data(), AZ::SettingsRegistryInterface::Format::JsonMergePatch, ""); + resolvedPathAWSPreference.String(), AZ::SettingsRegistryInterface::Format::JsonMergePatch, ""); } } @@ -136,8 +136,8 @@ namespace AWSCore return true; } - AZStd::chrono::seconds lastSendTimeStamp = AZStd::chrono::seconds(lastSendTimeStampSeconds); - AZStd::chrono::seconds secondsSinceLastSend = + const AZStd::chrono::seconds lastSendTimeStamp = AZStd::chrono::seconds(lastSendTimeStampSeconds); + const AZStd::chrono::seconds secondsSinceLastSend = AZStd::chrono::duration_cast(AZStd::chrono::system_clock::now().time_since_epoch()) - lastSendTimeStamp; if (static_cast(secondsSinceLastSend.count()) >= delayInSeconds) { @@ -154,7 +154,7 @@ namespace AWSCore if (credentialResult.result) { std::shared_ptr provider = credentialResult.result; - auto creds = provider->GetAWSCredentials(); + const auto creds = provider->GetAWSCredentials(); if (!creds.IsEmpty()) { return true; @@ -200,9 +200,13 @@ namespace AWSCore AZ_Assert(fileIO, "File IO is not initialized."); // Resolve path to editor_aws_preferences.setreg - AZStd::string editorPreferencesFilePath = AZStd::string::format("@user@/%s/%s", AZ::SettingsRegistryInterface::RegistryFolder, EditorAWSPreferencesFileName); - AZStd::array resolvedPath {}; - fileIO->ResolvePath(editorPreferencesFilePath.c_str(), resolvedPath.data(), resolvedPath.size()); + const AZStd::string editorPreferencesFilePath = AZStd::string::format("@user@/%s/%s", AZ::SettingsRegistryInterface::RegistryFolder, EditorAWSPreferencesFileName); + AZ::IO::FixedMaxPath resolvedPathAWSPreference; + if (!fileIO->ResolvePath(resolvedPathAWSPreference, AZ::IO::PathView(editorPreferencesFilePath))) + { + AZ_Warning("AWSAttributionManager", false, "Error resolving path %s", editorPreferencesFilePath.c_str()); + return; + } AZ::SettingsRegistryMergeUtils::DumperSettings dumperSettings; dumperSettings.m_prettifyOutput = true; @@ -215,14 +219,14 @@ namespace AWSCore { AZ_Warning( "AWSAttributionManager", false, R"(Unable to save changes to the Editor AWS Preferences registry file at "%s"\n)", - resolvedPath.data()); + resolvedPathAWSPreference.c_str()); return; } bool saved {}; constexpr auto configurationMode = AZ::IO::SystemFile::SF_OPEN_CREATE | AZ::IO::SystemFile::SF_OPEN_CREATE_PATH | AZ::IO::SystemFile::SF_OPEN_WRITE_ONLY; - if (AZ::IO::SystemFile outputFile; outputFile.Open(resolvedPath.data(), configurationMode)) + if (AZ::IO::SystemFile outputFile; outputFile.Open(resolvedPathAWSPreference.c_str(), configurationMode)) { saved = outputFile.Write(stringBuffer.data(), stringBuffer.size()) == stringBuffer.size(); } diff --git a/Gems/AWSCore/Code/Source/Editor/UI/AWSCoreEditorMenu.cpp b/Gems/AWSCore/Code/Source/Editor/UI/AWSCoreEditorMenu.cpp index 0b485609ad..6391dd94ee 100644 --- a/Gems/AWSCore/Code/Source/Editor/UI/AWSCoreEditorMenu.cpp +++ b/Gems/AWSCore/Code/Source/Editor/UI/AWSCoreEditorMenu.cpp @@ -54,7 +54,7 @@ namespace AWSCore { if (m_resourceMappingToolWatcher->IsProcessRunning()) { - m_resourceMappingToolWatcher->TerminateProcess(AZ::u32(-1)); + m_resourceMappingToolWatcher->TerminateProcess(static_cast(-1)); } m_resourceMappingToolWatcher.reset(); } @@ -78,7 +78,7 @@ namespace AWSCore void AWSCoreEditorMenu::InitializeResourceMappingToolAction() { -#ifdef AWSCORE_EDITOR_RESOURCE_MAPPING_TOOL_ENABLED +#if AWSCORE_EDITOR_RESOURCE_MAPPING_TOOL_ENABLED AWSCoreResourceMappingToolAction* resourceMappingTool = new AWSCoreResourceMappingToolAction(QObject::tr(AWSResourceMappingToolActionText), this); QObject::connect(resourceMappingTool, &QAction::triggered, this, @@ -214,7 +214,7 @@ namespace AWSCore QMenu* AWSCoreEditorMenu::SetAWSFeatureSubMenu(const AZStd::string& menuText) { auto actionList = this->actions(); - for (QList::iterator itr = actionList.begin(); itr != actionList.end(); itr++) + for (QList::iterator itr = actionList.begin(); itr != actionList.end(); ++itr) { if (QString::compare((*itr)->text(), menuText.c_str()) == 0) { diff --git a/Gems/AWSCore/Code/Source/Framework/AWSApiJob.cpp b/Gems/AWSCore/Code/Source/Framework/AWSApiJob.cpp index f203506edd..34581e00ff 100644 --- a/Gems/AWSCore/Code/Source/Framework/AWSApiJob.cpp +++ b/Gems/AWSCore/Code/Source/Framework/AWSApiJob.cpp @@ -22,10 +22,6 @@ namespace AWSCore { } - AwsApiJob::~AwsApiJob() - { - } - AwsApiJob::Config* AwsApiJob::GetDefaultConfig() { static AwsApiJobConfigHolder s_configHolder{}; diff --git a/Gems/AWSCore/Code/Source/Framework/AWSApiJobConfig.cpp b/Gems/AWSCore/Code/Source/Framework/AWSApiJobConfig.cpp index cc51213e09..0f15c862d5 100644 --- a/Gems/AWSCore/Code/Source/Framework/AWSApiJobConfig.cpp +++ b/Gems/AWSCore/Code/Source/Framework/AWSApiJobConfig.cpp @@ -12,16 +12,6 @@ #include #include -// The AWS Native SDK AWSAllocator triggers a warning due to accessing members of std::allocator directly. -// AWSAllocator.h(70): warning C4996: 'std::allocator::pointer': warning STL4010: Various members of std::allocator are deprecated in C++17. -// Use std::allocator_traits instead of accessing these members directly. -// You can define _SILENCE_CXX17_OLD_ALLOCATOR_MEMBERS_DEPRECATION_WARNING or _SILENCE_ALL_CXX17_DEPRECATION_WARNINGS to acknowledge that you have received this warning. - -AZ_PUSH_DISABLE_WARNING(4251 4996, "-Wunknown-warning-option") -#include -AZ_POP_DISABLE_WARNING - - namespace AWSCore { void AwsApiJobConfig::ApplySettings() diff --git a/Gems/AWSCore/Code/Source/Framework/MultipartFormData.cpp b/Gems/AWSCore/Code/Source/Framework/MultipartFormData.cpp index ff4668a62b..47059ac156 100644 --- a/Gems/AWSCore/Code/Source/Framework/MultipartFormData.cpp +++ b/Gems/AWSCore/Code/Source/Framework/MultipartFormData.cpp @@ -49,7 +49,7 @@ namespace AWSCore { m_fileFields.emplace_back(FileField{ std::move(fieldName), std::move(fileName) , AZStd::vector{} }); m_fileFields.back().m_fileData.reserve(length); - m_fileFields.back().m_fileData.assign((const char*)bytes, (const char*)bytes + length); + m_fileFields.back().m_fileData.assign(static_cast(bytes), static_cast(bytes) + length); } void MultipartFormData::SetCustomBoundary(AZStd::string boundary) diff --git a/Gems/AWSCore/Code/Source/Framework/RequestBuilder.cpp b/Gems/AWSCore/Code/Source/Framework/RequestBuilder.cpp index 16a5163fc7..d32ec9d039 100644 --- a/Gems/AWSCore/Code/Source/Framework/RequestBuilder.cpp +++ b/Gems/AWSCore/Code/Source/Framework/RequestBuilder.cpp @@ -10,6 +10,10 @@ namespace AWSCore { + RequestBuilder::RequestBuilder() + : m_httpMethod(Aws::Http::HttpMethod::HTTP_GET) + { + } bool RequestBuilder::SetPathParameterUnescaped(const char* key, const char* value) { diff --git a/Gems/AWSCore/Code/Source/ResourceMapping/AWSResourceMappingManager.cpp b/Gems/AWSCore/Code/Source/ResourceMapping/AWSResourceMappingManager.cpp index c3d87bff86..faec07b19e 100644 --- a/Gems/AWSCore/Code/Source/ResourceMapping/AWSResourceMappingManager.cpp +++ b/Gems/AWSCore/Code/Source/ResourceMapping/AWSResourceMappingManager.cpp @@ -26,7 +26,6 @@ namespace AWSCore : m_status(Status::NotLoaded) , m_defaultAccountId("") , m_defaultRegion("") - , m_resourceMappings() { } @@ -164,7 +163,7 @@ namespace AWSCore m_defaultRegion = jsonDocument.FindMember(ResourceMappingRegionKeyName)->value.GetString(); auto resourceMappings = jsonDocument.FindMember(ResourceMappingResourcesKeyName)->value.GetObject(); - for (auto mappingIter = resourceMappings.MemberBegin(); mappingIter != resourceMappings.MemberEnd(); mappingIter++) + for (auto mappingIter = resourceMappings.MemberBegin(); mappingIter != resourceMappings.MemberEnd(); ++mappingIter) { auto mappingValue = mappingIter->value.GetObject(); if (mappingValue.MemberCount() != 0) diff --git a/Gems/AWSCore/Code/Source/ScriptCanvas/AWSScriptBehaviorDynamoDB.cpp b/Gems/AWSCore/Code/Source/ScriptCanvas/AWSScriptBehaviorDynamoDB.cpp index 4467cc5db7..4183997d4a 100644 --- a/Gems/AWSCore/Code/Source/ScriptCanvas/AWSScriptBehaviorDynamoDB.cpp +++ b/Gems/AWSCore/Code/Source/ScriptCanvas/AWSScriptBehaviorDynamoDB.cpp @@ -71,7 +71,7 @@ namespace AWSCore [](DynamoDBGetItemRequestJob* job) // OnSuccess handler { auto item = job->result.GetItem(); - if (item.size() > 0) + if (!item.empty()) { DynamoDBAttributeValueMap result; for (const auto& itermPair : item) diff --git a/Gems/AWSCore/Code/Tests/AWSCoreSystemComponentTest.cpp b/Gems/AWSCore/Code/Tests/AWSCoreSystemComponentTest.cpp index f9fff631f4..648648e945 100644 --- a/Gems/AWSCore/Code/Tests/AWSCoreSystemComponentTest.cpp +++ b/Gems/AWSCore/Code/Tests/AWSCoreSystemComponentTest.cpp @@ -40,7 +40,7 @@ public: AWSCoreNotificationsBus::Handler::BusConnect(); } - ~AWSCoreNotificationsBusMock() + ~AWSCoreNotificationsBusMock() override { AWSCoreNotificationsBus::Handler::BusDisconnect(); } diff --git a/Gems/AWSCore/Code/Tests/Credential/AWSCVarCredentialHandlerTest.cpp b/Gems/AWSCore/Code/Tests/Credential/AWSCVarCredentialHandlerTest.cpp index 73120d4adc..244c945af9 100644 --- a/Gems/AWSCore/Code/Tests/Credential/AWSCVarCredentialHandlerTest.cpp +++ b/Gems/AWSCore/Code/Tests/Credential/AWSCVarCredentialHandlerTest.cpp @@ -18,7 +18,7 @@ class AWSCVarCredentialHandlerTest { public: AWSCVarCredentialHandlerTest() = default; - virtual ~AWSCVarCredentialHandlerTest() = default; + ~AWSCVarCredentialHandlerTest() override = default; void SetUp() override { diff --git a/Gems/AWSCore/Code/Tests/Credential/AWSCredentialBusTest.cpp b/Gems/AWSCore/Code/Tests/Credential/AWSCredentialBusTest.cpp index 238b5c819c..82c8c84b81 100644 --- a/Gems/AWSCore/Code/Tests/Credential/AWSCredentialBusTest.cpp +++ b/Gems/AWSCore/Code/Tests/Credential/AWSCredentialBusTest.cpp @@ -36,14 +36,14 @@ public: m_credentialsProvider.reset(); } - int GetCredentialHandlerOrder() const + int GetCredentialHandlerOrder() const override { return 1; } - std::shared_ptr GetCredentialsProvider() + std::shared_ptr GetCredentialsProvider() override { - m_handlerCounter++; + ++m_handlerCounter; return m_credentialsProvider; } @@ -72,14 +72,14 @@ public: m_credentialsProvider.reset(); } - int GetCredentialHandlerOrder() const + int GetCredentialHandlerOrder() const override { return 2; } - std::shared_ptr GetCredentialsProvider() + std::shared_ptr GetCredentialsProvider() override { - m_handlerCounter++; + ++m_handlerCounter; return m_credentialsProvider; } @@ -115,10 +115,10 @@ public: TEST_F(AWSCredentialBusTest, GetCredentialsProvider_CallFromMultithread_GetExpectedCredentialsProviderAndNumberOfCalls) { - int testThreadNumber = 10; + constexpr int testThreadNumber = 10; AZStd::atomic actualEbusCalls = 0; AZStd::vector testThreadPool; - for (int index = 0; index < testThreadNumber; index++) + for (int index = 0; index < testThreadNumber; ++index) { testThreadPool.emplace_back(AZStd::thread([&]() { AWSCredentialResult result; diff --git a/Gems/AWSCore/Code/Tests/Credential/AWSDefaultCredentialHandlerTest.cpp b/Gems/AWSCore/Code/Tests/Credential/AWSDefaultCredentialHandlerTest.cpp index b3e2ec5738..af03afb337 100644 --- a/Gems/AWSCore/Code/Tests/Credential/AWSDefaultCredentialHandlerTest.cpp +++ b/Gems/AWSCore/Code/Tests/Credential/AWSDefaultCredentialHandlerTest.cpp @@ -49,7 +49,7 @@ class AWSDefaultCredentialHandlerTest { public: AWSDefaultCredentialHandlerTest() = default; - virtual ~AWSDefaultCredentialHandlerTest() = default; + ~AWSDefaultCredentialHandlerTest() override = default; void SetUp() override { diff --git a/Gems/AWSCore/Code/Tests/Editor/Platform/Windows/awscore_editor_tests_windows_files.cmake b/Gems/AWSCore/Code/Tests/Editor/Platform/Windows/awscore_editor_tests_windows_files.cmake deleted file mode 100644 index 0d47bbe795..0000000000 --- a/Gems/AWSCore/Code/Tests/Editor/Platform/Windows/awscore_editor_tests_windows_files.cmake +++ /dev/null @@ -1,19 +0,0 @@ -# -# Copyright (c) Contributors to the Open 3D Engine Project. -# For complete copyright and license terms please see the LICENSE at the root of this distribution. -# -# SPDX-License-Identifier: Apache-2.0 OR MIT -# -# - -set(FILES - ../../AWSCoreEditorSystemComponentTest.cpp - ../../Attribution/AWSCoreAttributionManagerTest.cpp - ../../Attribution/AWSCoreAttributionMetricTest.cpp - ../../Attribution/AWSCoreAttributionSystemComponentTest.cpp - ../../Attribution/AWSAttributionServiceApiTest.cpp - ../../UI/AWSCoreEditorMenuTest.cpp - ../../UI/AWSCoreEditorUIFixture.h - ../../UI/AWSCoreResourceMappingToolActionTest.cpp - ../../AWSCoreEditorManagerTest.cpp -) diff --git a/Gems/AWSCore/Code/Tests/Framework/AWSApiClientJobConfigTest.cpp b/Gems/AWSCore/Code/Tests/Framework/AWSApiClientJobConfigTest.cpp index b49cdc6a71..db433062ad 100644 --- a/Gems/AWSCore/Code/Tests/Framework/AWSApiClientJobConfigTest.cpp +++ b/Gems/AWSCore/Code/Tests/Framework/AWSApiClientJobConfigTest.cpp @@ -23,6 +23,11 @@ class AWSApiClientJobConfigTest , public AWSCredentialRequestBus::Handler { public: + AWSApiClientJobConfigTest() + : m_credentialHandlerCounter(0) + { + } + void SetUp() override { AWSNativeSDKInit::InitializationManager::InitAwsApi(); diff --git a/Gems/AWSCore/Code/Tests/Framework/ServiceClientJobConfigTest.cpp b/Gems/AWSCore/Code/Tests/Framework/ServiceClientJobConfigTest.cpp index 7a45ffb739..9bc43482cd 100644 --- a/Gems/AWSCore/Code/Tests/Framework/ServiceClientJobConfigTest.cpp +++ b/Gems/AWSCore/Code/Tests/Framework/ServiceClientJobConfigTest.cpp @@ -84,7 +84,7 @@ class ServiceClientJobConfigTest void ReloadConfigFile(bool reloadConfigFileName = false) override { AZ_UNUSED(reloadConfigFileName); - }; + } }; TEST_F(ServiceClientJobConfigTest, GetServiceUrl_CreateServiceWithServiceNameOnly_GetExpectedFeatureServiceUrl) diff --git a/Gems/AWSCore/Code/Tests/ResourceMapping/AWSResourceMappingManagerTest.cpp b/Gems/AWSCore/Code/Tests/ResourceMapping/AWSResourceMappingManagerTest.cpp index d438a99f83..fb03dea4c0 100644 --- a/Gems/AWSCore/Code/Tests/ResourceMapping/AWSResourceMappingManagerTest.cpp +++ b/Gems/AWSCore/Code/Tests/ResourceMapping/AWSResourceMappingManagerTest.cpp @@ -217,7 +217,7 @@ TEST_F(AWSResourceMappingManagerTest, ActivateManager_ParseValidConfigFile_Confi CreateTestConfigFile(TEST_VALID_RESOURCE_MAPPING_CONFIG_FILE); m_resourceMappingManager->ActivateManager(); - int testThreadNumber = 10; + constexpr int testThreadNumber = 10; AZStd::atomic actualEbusCalls = 0; AZStd::vector testThreadPool; for (int index = 0; index < testThreadNumber; index++) @@ -226,7 +226,7 @@ TEST_F(AWSResourceMappingManagerTest, ActivateManager_ParseValidConfigFile_Confi AZStd::string actualAccountId; AWSResourceMappingRequestBus::BroadcastResult(actualAccountId, &AWSResourceMappingRequests::GetDefaultAccountId); EXPECT_FALSE(actualAccountId.empty()); - actualEbusCalls++; + ++actualEbusCalls; })); } diff --git a/Gems/AWSCore/Code/Tests/ResourceMapping/AWSResourceMappingUtilsTest.cpp b/Gems/AWSCore/Code/Tests/ResourceMapping/AWSResourceMappingUtilsTest.cpp index 9c23615917..f1a90aa2ae 100644 --- a/Gems/AWSCore/Code/Tests/ResourceMapping/AWSResourceMappingUtilsTest.cpp +++ b/Gems/AWSCore/Code/Tests/ResourceMapping/AWSResourceMappingUtilsTest.cpp @@ -44,19 +44,19 @@ TEST_F(AWSResourceMappingUtilsTest, FormatRESTApiUrl_PassingInvalidRESTApiId_Ret { auto actualUrl = AWSResourceMappingUtils::FormatRESTApiUrl("", TEST_VALID_RESTAPI_REGION, TEST_VALID_RESTAPI_STAGE); - EXPECT_TRUE(actualUrl == ""); + EXPECT_TRUE(actualUrl.empty()); } TEST_F(AWSResourceMappingUtilsTest, FormatRESTApiUrl_PassingInvalidRESTApiRegion_ReturnEmptyResult) { auto actualUrl = AWSResourceMappingUtils::FormatRESTApiUrl(TEST_VALID_RESTAPI_ID, "", TEST_VALID_RESTAPI_STAGE); - EXPECT_TRUE(actualUrl == ""); + EXPECT_TRUE(actualUrl.empty()); } TEST_F(AWSResourceMappingUtilsTest, FormatRESTApiUrl_PassingInvalidRESTApiStage_ReturnEmptyResult) { auto actualUrl = AWSResourceMappingUtils::FormatRESTApiUrl(TEST_VALID_RESTAPI_ID, TEST_VALID_RESTAPI_REGION, ""); - EXPECT_TRUE(actualUrl == ""); + EXPECT_TRUE(actualUrl.empty()); } diff --git a/Gems/AWSCore/Code/Tests/ScriptCanvas/AWSScriptBehaviorDynamoDBTest.cpp b/Gems/AWSCore/Code/Tests/ScriptCanvas/AWSScriptBehaviorDynamoDBTest.cpp index 52b91c306d..e42e270bc2 100644 --- a/Gems/AWSCore/Code/Tests/ScriptCanvas/AWSScriptBehaviorDynamoDBTest.cpp +++ b/Gems/AWSCore/Code/Tests/ScriptCanvas/AWSScriptBehaviorDynamoDBTest.cpp @@ -23,7 +23,7 @@ public: AWSScriptBehaviorDynamoDBNotificationBus::Handler::BusConnect(); } - ~AWSScriptBehaviorDynamoDBNotificationBusHandlerMock() + ~AWSScriptBehaviorDynamoDBNotificationBusHandlerMock() override { AWSScriptBehaviorDynamoDBNotificationBus::Handler::BusDisconnect(); } diff --git a/Gems/AWSCore/Code/Tests/ScriptCanvas/AWSScriptBehaviorLambdaTest.cpp b/Gems/AWSCore/Code/Tests/ScriptCanvas/AWSScriptBehaviorLambdaTest.cpp index 299f4a95d6..42ccd6eddc 100644 --- a/Gems/AWSCore/Code/Tests/ScriptCanvas/AWSScriptBehaviorLambdaTest.cpp +++ b/Gems/AWSCore/Code/Tests/ScriptCanvas/AWSScriptBehaviorLambdaTest.cpp @@ -22,7 +22,7 @@ public: AWSScriptBehaviorLambdaNotificationBus::Handler::BusConnect(); } - ~AWSScriptBehaviorLambdaNotificationBusHandlerMock() + ~AWSScriptBehaviorLambdaNotificationBusHandlerMock() override { AWSScriptBehaviorLambdaNotificationBus::Handler::BusDisconnect(); } diff --git a/Gems/AWSCore/Code/Tests/ScriptCanvas/AWSScriptBehaviorS3Test.cpp b/Gems/AWSCore/Code/Tests/ScriptCanvas/AWSScriptBehaviorS3Test.cpp index 118174576c..34a0166e32 100644 --- a/Gems/AWSCore/Code/Tests/ScriptCanvas/AWSScriptBehaviorS3Test.cpp +++ b/Gems/AWSCore/Code/Tests/ScriptCanvas/AWSScriptBehaviorS3Test.cpp @@ -24,7 +24,7 @@ public: AWSScriptBehaviorS3NotificationBus::Handler::BusConnect(); } - ~AWSScriptBehaviorS3NotificationBusHandlerMock() + ~AWSScriptBehaviorS3NotificationBusHandlerMock() override { AWSScriptBehaviorS3NotificationBus::Handler::BusDisconnect(); } diff --git a/Gems/AWSCore/Code/Tests/TestFramework/AWSCoreFixture.h b/Gems/AWSCore/Code/Tests/TestFramework/AWSCoreFixture.h index a5c2bf6475..1d14484387 100644 --- a/Gems/AWSCore/Code/Tests/TestFramework/AWSCoreFixture.h +++ b/Gems/AWSCore/Code/Tests/TestFramework/AWSCoreFixture.h @@ -107,8 +107,8 @@ class AWSCoreFixture : public UnitTest::ScopedAllocatorSetupFixture { public: - AWSCoreFixture() {} - virtual ~AWSCoreFixture() = default; + AWSCoreFixture() = default; + ~AWSCoreFixture() override = default; void SetUp() override { diff --git a/Gems/AWSCore/cdk/README.md b/Gems/AWSCore/cdk/README.md index e4c0335c9c..d50ca40279 100644 --- a/Gems/AWSCore/cdk/README.md +++ b/Gems/AWSCore/cdk/README.md @@ -64,6 +64,16 @@ To add additional dependencies, for example other CDK libraries, just add them to your `setup.py` file and rerun the `pip install -r requirements.txt` command. +## Optional Features +Server access logging is enabled by default. To disable the feature, use the following commands to synthesize and deploy this CDK application. + +``` +$ cdk synth -c disable_access_log=true --all +$ cdk deploy -c disable_access_log=true --all +``` + +See https://docs.aws.amazon.com/AmazonS3/latest/userguide/ServerLogs.html for more information about server access logging. + ## Useful commands * `cdk ls` list all stacks in the app diff --git a/Gems/AWSCore/cdk/app.py b/Gems/AWSCore/cdk/app.py index 16773b5f69..9a401033ef 100755 --- a/Gems/AWSCore/cdk/app.py +++ b/Gems/AWSCore/cdk/app.py @@ -57,8 +57,9 @@ example_stack = ExampleResources( tags={Constants.O3DE_PROJECT_TAG_NAME: PROJECT_NAME, Constants.O3DE_FEATURE_TAG_NAME: FEATURE_NAME}, env=env ) -# -# Add the common stack as a dependency of the feature stack + +# Add the core stack as a dependency of the feature stack since the feature stack +# requires the core stack outputs for deployment. example_stack.add_dependency(core_construct.common_stack) app.synth() diff --git a/Gems/AWSCore/cdk/core/core_stack.py b/Gems/AWSCore/cdk/core/core_stack.py index fc1b4cf8d8..c124cb72ab 100755 --- a/Gems/AWSCore/cdk/core/core_stack.py +++ b/Gems/AWSCore/cdk/core/core_stack.py @@ -60,17 +60,6 @@ class CoreStack(core.Stack): type='TAG_FILTERS_1_0') ) - # Create an S3 bucket for Amazon S3 server access logging - # See https://docs.aws.amazon.com/AmazonS3/latest/dev/security-best-practices.html - self._server_access_logs_bucket = s3.Bucket( - self, - f'{self._project_name}-{self._feature_name}-Access-Log-Bucket', - block_public_access=s3.BlockPublicAccess.BLOCK_ALL, - encryption=s3.BucketEncryption.S3_MANAGED, - access_control=s3.BucketAccessControl.LOG_DELIVERY_WRITE - ) - self._server_access_logs_bucket.grant_read(self._admin_group) - # Define exports # Export resource group self._resource_group_output = core.CfnOutput( @@ -94,10 +83,22 @@ class CoreStack(core.Stack): export_name=f"{self._project_name}:AdminGroup", value=self._admin_group.group_arn) - # Export access log bucket name - self._server_access_logs_bucket_output = core.CfnOutput( - self, - id=f'ServerAccessLogsBucketOutput', - description='Name of the S3 bucket for storing server access logs generated by the sample CDK application(s)', - export_name=f"{self._project_name}:ServerAccessLogsBucket", - value=self._server_access_logs_bucket.bucket_name) + # Create an S3 bucket for Amazon S3 server access logging + # See https://docs.aws.amazon.com/AmazonS3/latest/dev/security-best-practices.html + if self.node.try_get_context('disable_access_log') != 'true': + self._server_access_logs_bucket = s3.Bucket( + self, + f'{self._project_name}-{self._feature_name}-Access-Log-Bucket', + block_public_access=s3.BlockPublicAccess.BLOCK_ALL, + encryption=s3.BucketEncryption.S3_MANAGED, + access_control=s3.BucketAccessControl.LOG_DELIVERY_WRITE + ) + self._server_access_logs_bucket.grant_read(self._admin_group) + + # Export access log bucket name + self._server_access_logs_bucket_output = core.CfnOutput( + self, + id=f'ServerAccessLogsBucketOutput', + description='Name of the S3 bucket for storing server access logs generated by the sample CDK application(s)', + export_name=f"{self._project_name}:ServerAccessLogsBucket", + value=self._server_access_logs_bucket.bucket_name) diff --git a/Gems/AWSCore/cdk/example/example_resources_stack.py b/Gems/AWSCore/cdk/example/example_resources_stack.py index 23bc78d8fc..ac229cb313 100755 --- a/Gems/AWSCore/cdk/example/example_resources_stack.py +++ b/Gems/AWSCore/cdk/example/example_resources_stack.py @@ -118,19 +118,23 @@ class ExampleResources(core.Stack): # https://docs.aws.amazon.com/AmazonS3/latest/userguide/serv-side-encryption.html # 3. Enable Amazon S3 server access logging # https://docs.aws.amazon.com/AmazonS3/latest/userguide/ServerLogs.html - server_access_logs_bucket = s3.Bucket.from_bucket_name( - self, - f'{self._project_name}-{self._feature_name}-ImportedAccessLogsBucket', - core.Fn.import_value(f"{self._project_name}:ServerAccessLogsBucket") - ) + server_access_logs_bucket = None + if self.node.try_get_context('disable_access_log') != 'true': + server_access_logs_bucket = s3.Bucket.from_bucket_name( + self, + f'{self._project_name}-{self._feature_name}-ImportedAccessLogsBucket', + core.Fn.import_value(f"{self._project_name}:ServerAccessLogsBucket") + ) example_bucket = s3.Bucket( self, f'{self._project_name}-{self._feature_name}-Example-S3bucket', block_public_access=s3.BlockPublicAccess.BLOCK_ALL, encryption=s3.BucketEncryption.S3_MANAGED, - server_access_logs_bucket=server_access_logs_bucket, - server_access_logs_prefix=f'{self._project_name}-{self._feature_name}-{self.region}-AccessLogs' + server_access_logs_bucket= + server_access_logs_bucket if server_access_logs_bucket else None, + server_access_logs_prefix= + f'{self._project_name}-{self._feature_name}-{self.region}-AccessLogs' if server_access_logs_bucket else None ) s3_deployment.BucketDeployment( diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftAcceptMatchRequest.h b/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftAcceptMatchRequest.h new file mode 100644 index 0000000000..415550b6bc --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftAcceptMatchRequest.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include + +namespace AWSGameLift +{ + //! AWSGameLiftAcceptMatchRequest + //! GameLift accept match request which corresponds to Amazon GameLift + //! Registers a player's acceptance or rejection of a proposed FlexMatch match. + //! AcceptMatchRequest + struct AWSGameLiftAcceptMatchRequest + : public AzFramework::AcceptMatchRequest + { + public: + AZ_RTTI(AWSGameLiftAcceptMatchRequest, "{8372B297-88E8-4C13-B31D-BE87236CA416}", AzFramework::AcceptMatchRequest); + static void Reflect(AZ::ReflectContext* context); + + AWSGameLiftAcceptMatchRequest() = default; + virtual ~AWSGameLiftAcceptMatchRequest() = default; + }; +} // namespace AWSGameLift diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftCreateSessionOnQueueRequest.h b/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftCreateSessionOnQueueRequest.h index be2c766d7e..a9b06c5bc9 100644 --- a/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftCreateSessionOnQueueRequest.h +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftCreateSessionOnQueueRequest.h @@ -8,7 +8,7 @@ #pragma once -#include +#include namespace AWSGameLift { diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftCreateSessionRequest.h b/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftCreateSessionRequest.h index 75440868c9..4fab16ca70 100644 --- a/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftCreateSessionRequest.h +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftCreateSessionRequest.h @@ -8,7 +8,7 @@ #pragma once -#include +#include namespace AWSGameLift { diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftJoinSessionRequest.h b/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftJoinSessionRequest.h index 32f549c7d5..07d6da3870 100644 --- a/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftJoinSessionRequest.h +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftJoinSessionRequest.h @@ -8,7 +8,7 @@ #pragma once -#include +#include namespace AWSGameLift { diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftSearchSessionsRequest.h b/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftSearchSessionsRequest.h index 655c83252e..bfc2dc630e 100644 --- a/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftSearchSessionsRequest.h +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftSearchSessionsRequest.h @@ -8,7 +8,7 @@ #pragma once -#include +#include namespace AWSGameLift { diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftStartMatchmakingRequest.h b/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftStartMatchmakingRequest.h new file mode 100644 index 0000000000..cd30bc45ab --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftStartMatchmakingRequest.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include + +namespace AWSGameLift +{ + //! AWSGameLiftPlayerInformation + //! Information on each player to be matched + //! This information must include a player ID, and may contain player attributes and latency data to be used in the matchmaking process + //! After a successful match, Player objects contain the name of the team the player is assigned to + struct AWSGameLiftPlayerInformation + { + AZ_RTTI(AWSGameLiftPlayerInformation, "{B62C118E-C55D-4903-8ECB-E58E8CA613C4}"); + static void Reflect(AZ::ReflectContext* context); + + AWSGameLiftPlayerInformation() = default; + virtual ~AWSGameLiftPlayerInformation() = default; + + // A map of region names to latencies in millseconds, that indicates + // the amount of latency that a player experiences when connected to AWS Regions + AZStd::unordered_map m_latencyInMs; + // A collection of key:value pairs containing player information for use in matchmaking + // Player attribute keys must match the playerAttributes used in a matchmaking rule set + // Example: {"skill": "{\"N\": \"23\"}", "gameMode": "{\"S\": \"deathmatch\"}"} + AZStd::unordered_map m_playerAttributes; + // A unique identifier for a player + AZStd::string m_playerId; + // Name of the team that the player is assigned to in a match + AZStd::string m_team; + }; + + //! AWSGameLiftStartMatchmakingRequest + //! GameLift start matchmaking request which corresponds to Amazon GameLift + //! Uses FlexMatch to create a game match for a group of players based on custom matchmaking rules + //! StartMatchmakingRequest + struct AWSGameLiftStartMatchmakingRequest + : public AzFramework::StartMatchmakingRequest + { + public: + AZ_RTTI(AWSGameLiftStartMatchmakingRequest, "{D273DF71-9C55-48C1-95F9-8D7B66B9CF3E}", AzFramework::StartMatchmakingRequest); + static void Reflect(AZ::ReflectContext* context); + + AWSGameLiftStartMatchmakingRequest() = default; + virtual ~AWSGameLiftStartMatchmakingRequest() = default; + + // Name of the matchmaking configuration to use for this request + AZStd::string m_configurationName; + // Information on each player to be matched + AZStd::vector m_players; + }; +} // namespace AWSGameLift diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftStopMatchmakingRequest.h b/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftStopMatchmakingRequest.h new file mode 100644 index 0000000000..81d811d32d --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftStopMatchmakingRequest.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include + +namespace AWSGameLift +{ + //! AWSGameLiftStopMatchmakingRequest + //! GameLift stop matchmaking request which corresponds to Amazon GameLift + //! Cancels a matchmaking ticket or match backfill ticket that is currently being processed. + //! StopMatchmakingRequest + struct AWSGameLiftStopMatchmakingRequest + : public AzFramework::StopMatchmakingRequest + { + public: + AZ_RTTI(AWSGameLiftStopMatchmakingRequest, "{2766BC03-9F84-4346-A52B-49129BBAF38B}", AzFramework::StopMatchmakingRequest); + static void Reflect(AZ::ReflectContext* context); + + AWSGameLiftStopMatchmakingRequest() = default; + virtual ~AWSGameLiftStopMatchmakingRequest() = default; + }; +} // namespace AWSGameLift diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/IAWSGameLiftRequests.h b/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/IAWSGameLiftRequests.h index 25445553f4..10269dc8c3 100644 --- a/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/IAWSGameLiftRequests.h +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/IAWSGameLiftRequests.h @@ -11,6 +11,7 @@ #include #include #include +#include #include namespace AWSGameLift @@ -71,4 +72,26 @@ namespace AWSGameLift static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; }; using AWSGameLiftSessionRequestBus = AZ::EBus; + + // IMatchmakingAsyncRequests EBus wrapper for scripting + class AWSGameLiftMatchmakingAsyncRequests + : public AZ::EBusTraits + { + public: + using MutexType = AZStd::recursive_mutex; + static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; + static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; + }; + using AWSGameLiftMatchmakingAsyncRequestBus = AZ::EBus; + + // IMatchmakingRequests EBus wrapper for scripting + class AWSGameLiftMatchmakingRequests + : public AZ::EBusTraits + { + public: + using MutexType = AZStd::recursive_mutex; + static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; + static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; + }; + using AWSGameLiftMatchmakingRequestBus = AZ::EBus; } // namespace AWSGameLift diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientLocalTicketTracker.cpp b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientLocalTicketTracker.cpp new file mode 100644 index 0000000000..9ae168aea9 --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientLocalTicketTracker.cpp @@ -0,0 +1,169 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +namespace AWSGameLift +{ + AWSGameLiftClientLocalTicketTracker::AWSGameLiftClientLocalTicketTracker() + : m_status(TicketTrackerStatus::Idle) + , m_pollingPeriodInMS(AWSGameLiftClientDefaultPollingPeriodInMS) + { + } + + void AWSGameLiftClientLocalTicketTracker::ActivateTracker() + { + AZ::Interface::Register(this); + } + + void AWSGameLiftClientLocalTicketTracker::DeactivateTracker() + { + AZ::Interface::Unregister(this); + StopPolling(); + } + + void AWSGameLiftClientLocalTicketTracker::StartPolling( + const AZStd::string& ticketId, const AZStd::string& playerId) + { + AZStd::lock_guard lock(m_trackerMutex); + if (m_status == TicketTrackerStatus::Running) + { + AZ_TracePrintf(AWSGameLiftClientLocalTicketTrackerName, "Matchmaking ticket tracker is running."); + return; + } + m_status = TicketTrackerStatus::Running; + m_trackerThread = AZStd::thread(AZStd::bind( + &AWSGameLiftClientLocalTicketTracker::ProcessPolling, this, ticketId, playerId)); + } + + void AWSGameLiftClientLocalTicketTracker::StopPolling() + { + AZStd::lock_guard lock(m_trackerMutex); + m_status = TicketTrackerStatus::Idle; + if (m_trackerThread.joinable()) + { + m_trackerThread.join(); + } + } + + void AWSGameLiftClientLocalTicketTracker::ProcessPolling( + const AZStd::string& ticketId, const AZStd::string& playerId) + { + while (m_status == TicketTrackerStatus::Running) + { + auto gameliftClient = AZ::Interface::Get()->GetGameLiftClient(); + if (gameliftClient) + { + Aws::GameLift::Model::DescribeMatchmakingRequest request; + request.AddTicketIds(ticketId.c_str()); + + auto describeMatchmakingOutcome = gameliftClient->DescribeMatchmaking(request); + if (describeMatchmakingOutcome.IsSuccess()) + { + if (describeMatchmakingOutcome.GetResult().GetTicketList().size() == 1) + { + auto ticket = describeMatchmakingOutcome.GetResult().GetTicketList().front(); + if (ticket.GetStatus() == Aws::GameLift::Model::MatchmakingConfigurationStatus::COMPLETED) + { + m_status = TicketTrackerStatus::Idle; + AZ_TracePrintf(AWSGameLiftClientLocalTicketTrackerName, + "Matchmaking ticket %s is complete.", ticket.GetTicketId().c_str()); + RequestPlayerJoinMatch(ticket, playerId); + return; + } + else if (ticket.GetStatus() == Aws::GameLift::Model::MatchmakingConfigurationStatus::TIMED_OUT || + ticket.GetStatus() == Aws::GameLift::Model::MatchmakingConfigurationStatus::FAILED || + ticket.GetStatus() == Aws::GameLift::Model::MatchmakingConfigurationStatus::CANCELLED) + { + m_status = TicketTrackerStatus::Idle; + AZ_Error(AWSGameLiftClientLocalTicketTrackerName, false, "Matchmaking ticket %s is not complete, %s", + ticket.GetTicketId().c_str(), ticket.GetStatusReason().c_str()); + return; + } + else if (ticket.GetStatus() == Aws::GameLift::Model::MatchmakingConfigurationStatus::REQUIRES_ACCEPTANCE) + { + // broadcast acceptance requires to player + } + else + { + AZ_TracePrintf(AWSGameLiftClientLocalTicketTrackerName, "Matchmaking ticket %s is processing, %s.", + ticket.GetTicketId().c_str(), ticket.GetStatusReason().c_str()); + } + } + else + { + AZ_Error(AWSGameLiftClientLocalTicketTrackerName, false, "Unable to find expected ticket with id %s", ticketId.c_str()); + } + } + else + { + AZ_Error(AWSGameLiftClientLocalTicketTrackerName, false, AWSGameLiftErrorMessageTemplate, + describeMatchmakingOutcome.GetError().GetExceptionName().c_str(), + describeMatchmakingOutcome.GetError().GetMessage().c_str()); + } + } + else + { + AZ_Error(AWSGameLiftClientLocalTicketTrackerName, false, AWSGameLiftClientMissingErrorMessage); + } + AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(m_pollingPeriodInMS)); + } + } + + void AWSGameLiftClientLocalTicketTracker::RequestPlayerJoinMatch( + const Aws::GameLift::Model::MatchmakingTicket& ticket, const AZStd::string& playerId) + { + auto connectionInfo = ticket.GetGameSessionConnectionInfo(); + AzFramework::SessionConnectionConfig sessionConnectionConfig; + sessionConnectionConfig.m_ipAddress = connectionInfo.GetIpAddress().c_str(); + for (auto matchedPlayer : connectionInfo.GetMatchedPlayerSessions()) + { + if (playerId.compare(matchedPlayer.GetPlayerId().c_str()) == 0) + { + sessionConnectionConfig.m_playerSessionId = matchedPlayer.GetPlayerSessionId().c_str(); + break; + } + } + sessionConnectionConfig.m_port = static_cast(connectionInfo.GetPort()); + + if (!sessionConnectionConfig.m_playerSessionId.empty()) + { + AZ_TracePrintf(AWSGameLiftClientLocalTicketTrackerName, + "Requesting and validating player session %s to connect to the match ...", + sessionConnectionConfig.m_playerSessionId.c_str()); + bool result = + AZ::Interface::Get()->RequestPlayerJoinSession(sessionConnectionConfig); + if (result) + { + AZ_TracePrintf(AWSGameLiftClientLocalTicketTrackerName, + "Started connection process, and connection validation is in process."); + } + else + { + AZ_Error(AWSGameLiftClientLocalTicketTrackerName, false, + "Failed to start connection process."); + } + } + else + { + AZ_Error(AWSGameLiftClientLocalTicketTrackerName, false, + "Player session id is missing for player % to join the match.", playerId.c_str()); + } + } +} diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientLocalTicketTracker.h b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientLocalTicketTracker.h new file mode 100644 index 0000000000..7b317a0485 --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientLocalTicketTracker.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include + +#include + +#include + +namespace AWSGameLift +{ + enum TicketTrackerStatus + { + Idle, + Running + }; + + //! AWSGameLiftClientLocalTicketTracker + //! GameLift client ticket tracker to describe submitted matchmaking ticket periodically, + //! and join player to the match once matchmaking ticket is complete. + //! For use in production, please see GameLifts guidance about matchmaking at volume. + //! The continuous polling approach here is only suitable for low volume matchmaking and is meant to aid with development only + class AWSGameLiftClientLocalTicketTracker + : public IAWSGameLiftMatchmakingInternalRequests + { + public: + static constexpr const char AWSGameLiftClientLocalTicketTrackerName[] = "AWSGameLiftClientLocalTicketTracker"; + // Set ticket polling period to 10 seconds + // https://docs.aws.amazon.com/gamelift/latest/flexmatchguide/match-client.html#match-client-track + static constexpr const uint64_t AWSGameLiftClientDefaultPollingPeriodInMS = 10000; + + AWSGameLiftClientLocalTicketTracker(); + virtual ~AWSGameLiftClientLocalTicketTracker() = default; + + virtual void ActivateTracker(); + virtual void DeactivateTracker(); + + // IAWSGameLiftMatchmakingInternalRequests interface implementation + void StartPolling(const AZStd::string& ticketId, const AZStd::string& playerId) override; + void StopPolling() override; + + protected: + // For testing friendly access + uint64_t m_pollingPeriodInMS; + TicketTrackerStatus m_status; + + private: + void ProcessPolling(const AZStd::string& ticketId, const AZStd::string& playerId); + void RequestPlayerJoinMatch(const Aws::GameLift::Model::MatchmakingTicket& ticket, const AZStd::string& playerId); + + AZStd::mutex m_trackerMutex; + AZStd::thread m_trackerThread; + }; +} // namespace AWSGameLift diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientManager.cpp b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientManager.cpp index f9249d1ad2..ee731993aa 100644 --- a/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientManager.cpp +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientManager.cpp @@ -17,11 +17,13 @@ #include #include +#include #include #include #include #include #include +#include #include @@ -31,11 +33,6 @@ namespace AWSGameLift AZ_CVAR(AZ::CVarFixedString, cl_gameliftLocalEndpoint, "", nullptr, AZ::ConsoleFunctorFlags::Null, "The local endpoint to test with GameLiftLocal SDK."); #endif - AWSGameLiftClientManager::AWSGameLiftClientManager() - { - m_gameliftClient.reset(); - } - void AWSGameLiftClientManager::ActivateManager() { AZ::Interface::Register(this); @@ -62,8 +59,7 @@ namespace AWSGameLift bool AWSGameLiftClientManager::ConfigureGameLiftClient(const AZStd::string& region) { - m_gameliftClient.reset(); - + AZ::Interface::Get()->SetGameLiftClient(nullptr); Aws::Client::ClientConfiguration clientConfig; // Set up client endpoint or region AZStd::string localEndpoint = ""; @@ -103,7 +99,8 @@ namespace AWSGameLift AZ_Error(AWSGameLiftClientManagerName, false, AWSGameLiftClientCredentialMissingErrorMessage); return false; } - m_gameliftClient = AZStd::make_shared(credentialResult.result, clientConfig); + AZ::Interface::Get()->SetGameLiftClient( + AZStd::make_shared(credentialResult.result, clientConfig)); return true; } @@ -112,6 +109,16 @@ namespace AWSGameLift return AZ::Uuid::CreateRandom().ToString(includeBrackets, includeDashes); } + void AWSGameLiftClientManager::AcceptMatch(const AzFramework::AcceptMatchRequest& acceptMatchRequest) + { + AZ_UNUSED(acceptMatchRequest); + } + + void AWSGameLiftClientManager::AcceptMatchAsync(const AzFramework::AcceptMatchRequest& acceptMatchRequest) + { + AZ_UNUSED(acceptMatchRequest); + } + AZStd::string AWSGameLiftClientManager::CreateSession(const AzFramework::CreateSessionRequest& createSessionRequest) { AZStd::string result = ""; @@ -184,15 +191,15 @@ namespace AWSGameLift AZStd::string AWSGameLiftClientManager::CreateSessionHelper( const AWSGameLiftCreateSessionRequest& createSessionRequest) { - AZStd::shared_ptr gameLiftClient = m_gameliftClient; + auto gameliftClient = AZ::Interface::Get()->GetGameLiftClient(); AZStd::string result = ""; - if (!gameLiftClient) + if (!gameliftClient) { AZ_Error(AWSGameLiftClientManagerName, false, AWSGameLiftClientMissingErrorMessage); } else { - result = CreateSessionActivity::CreateSession(*gameLiftClient, createSessionRequest); + result = CreateSessionActivity::CreateSession(*gameliftClient, createSessionRequest); } return result; } @@ -200,7 +207,7 @@ namespace AWSGameLift AZStd::string AWSGameLiftClientManager::CreateSessionOnQueueHelper( const AWSGameLiftCreateSessionOnQueueRequest& createSessionOnQueueRequest) { - AZStd::shared_ptr gameliftClient = m_gameliftClient; + auto gameliftClient = AZ::Interface::Get()->GetGameLiftClient(); AZStd::string result; if (!gameliftClient) { @@ -255,7 +262,7 @@ namespace AWSGameLift bool AWSGameLiftClientManager::JoinSessionHelper(const AWSGameLiftJoinSessionRequest& joinSessionRequest) { - AZStd::shared_ptr gameliftClient = m_gameliftClient; + auto gameliftClient = AZ::Interface::Get()->GetGameLiftClient(); bool result = false; if (!gameliftClient) { @@ -335,8 +342,7 @@ namespace AWSGameLift AzFramework::SearchSessionsResponse AWSGameLiftClientManager::SearchSessionsHelper( const AWSGameLiftSearchSessionsRequest& searchSessionsRequest) const { - AZStd::shared_ptr gameliftClient = m_gameliftClient; - + auto gameliftClient = AZ::Interface::Get()->GetGameLiftClient(); AzFramework::SearchSessionsResponse response; if (!gameliftClient) { @@ -349,8 +355,25 @@ namespace AWSGameLift return response; } - void AWSGameLiftClientManager::SetGameLiftClient(AZStd::shared_ptr gameliftClient) + AZStd::string AWSGameLiftClientManager::StartMatchmaking(const AzFramework::StartMatchmakingRequest& startMatchmakingRequest) + { + AZ_UNUSED(startMatchmakingRequest); + + return AZStd::string{}; + } + + void AWSGameLiftClientManager::StartMatchmakingAsync(const AzFramework::StartMatchmakingRequest& startMatchmakingRequest) + { + AZ_UNUSED(startMatchmakingRequest); + } + + void AWSGameLiftClientManager::StopMatchmaking(const AzFramework::StopMatchmakingRequest& stopMatchmakingRequest) + { + AZ_UNUSED(stopMatchmakingRequest); + } + + void AWSGameLiftClientManager::StopMatchmakingAsync(const AzFramework::StopMatchmakingRequest& stopMatchmakingRequest) { - m_gameliftClient.swap(gameliftClient); + AZ_UNUSED(stopMatchmakingRequest); } } // namespace AWSGameLift diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientManager.h b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientManager.h index 1b9296d20a..fbdc1d6104 100644 --- a/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientManager.h +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientManager.h @@ -9,15 +9,9 @@ #pragma once #include -#include +#include -namespace Aws -{ - namespace GameLift - { - class GameLiftClient; - } -} +#include namespace AWSGameLift { @@ -26,6 +20,54 @@ namespace AWSGameLift struct AWSGameLiftJoinSessionRequest; struct AWSGameLiftSearchSessionsRequest; + // MatchAcceptanceNotificationBus EBus handler for scripting + class AWSGameLiftMatchAcceptanceNotificationBusHandler + : public AzFramework::MatchAcceptanceNotificationBus::Handler + , public AZ::BehaviorEBusHandler + { + public: + AZ_EBUS_BEHAVIOR_BINDER( + AWSGameLiftMatchAcceptanceNotificationBusHandler, + "{CBE057D3-F5CE-46D3-B02D-8A6A1446B169}", + AZ::SystemAllocator, + OnMatchAcceptance); + + void OnMatchAcceptance() override + { + Call(FN_OnMatchAcceptance); + } + }; + + // MatchmakingAsyncRequestNotificationBus EBus handler for scripting + class AWSGameLiftMatchmakingAsyncRequestNotificationBusHandler + : public AzFramework::MatchmakingAsyncRequestNotificationBus::Handler + , public AZ::BehaviorEBusHandler + { + public: + AZ_EBUS_BEHAVIOR_BINDER( + AWSGameLiftMatchmakingAsyncRequestNotificationBusHandler, + "{2045EE8F-2AB7-4ED0-9614-3496A1A43677}", + AZ::SystemAllocator, + OnAcceptMatchAsyncComplete, + OnStartMatchmakingAsyncComplete, + OnStopMatchmakingAsyncComplete); + + void OnAcceptMatchAsyncComplete() override + { + Call(FN_OnAcceptMatchAsyncComplete); + } + + void OnStartMatchmakingAsyncComplete(const AZStd::string& matchmakingTicketId) override + { + Call(FN_OnStartMatchmakingAsyncComplete, matchmakingTicketId); + } + + void OnStopMatchmakingAsyncComplete() override + { + Call(FN_OnStopMatchmakingAsyncComplete); + } + }; + // SessionAsyncRequestNotificationBus EBus handler for scripting class AWSGameLiftSessionAsyncRequestNotificationBusHandler : public AzFramework::SessionAsyncRequestNotificationBus::Handler @@ -66,6 +108,8 @@ namespace AWSGameLift //! GameLift client manager to support game and player session related client requests class AWSGameLiftClientManager : public AWSGameLiftRequestBus::Handler + , public AWSGameLiftMatchmakingAsyncRequestBus::Handler + , public AWSGameLiftMatchmakingRequestBus::Handler , public AWSGameLiftSessionAsyncRequestBus::Handler , public AWSGameLiftSessionRequestBus::Handler { @@ -75,13 +119,11 @@ namespace AWSGameLift "Missing AWS region for GameLift client."; static constexpr const char AWSGameLiftClientCredentialMissingErrorMessage[] = "Missing AWS credential for GameLift client."; - static constexpr const char AWSGameLiftClientMissingErrorMessage[] = - "GameLift client is not configured yet."; static constexpr const char AWSGameLiftCreateSessionRequestInvalidErrorMessage[] = "Invalid GameLift CreateSession or CreateSessionOnQueue request."; - AWSGameLiftClientManager(); + AWSGameLiftClientManager() = default; virtual ~AWSGameLiftClientManager() = default; virtual void ActivateManager(); @@ -91,28 +133,32 @@ namespace AWSGameLift bool ConfigureGameLiftClient(const AZStd::string& region) override; AZStd::string CreatePlayerId(bool includeBrackets, bool includeDashes) override; + // AWSGameLiftMatchmakingAsyncRequestBus interface implementation + void AcceptMatchAsync(const AzFramework::AcceptMatchRequest& acceptMatchRequest) override; + void StartMatchmakingAsync(const AzFramework::StartMatchmakingRequest& startMatchmakingRequest) override; + void StopMatchmakingAsync(const AzFramework::StopMatchmakingRequest& stopMatchmakingRequest) override; + // AWSGameLiftSessionAsyncRequestBus interface implementation void CreateSessionAsync(const AzFramework::CreateSessionRequest& createSessionRequest) override; void JoinSessionAsync(const AzFramework::JoinSessionRequest& joinSessionRequest) override; void SearchSessionsAsync(const AzFramework::SearchSessionsRequest& searchSessionsRequest) const override; void LeaveSessionAsync() override; + // AWSGameLiftMatchmakingRequestBus interface implementation + void AcceptMatch(const AzFramework::AcceptMatchRequest& acceptMatchRequest) override; + AZStd::string StartMatchmaking(const AzFramework::StartMatchmakingRequest& startMatchmakingRequest) override; + void StopMatchmaking(const AzFramework::StopMatchmakingRequest& stopMatchmakingRequest) override; + // AWSGameLiftSessionRequestBus interface implementation AZStd::string CreateSession(const AzFramework::CreateSessionRequest& createSessionRequest) override; bool JoinSession(const AzFramework::JoinSessionRequest& joinSessionRequest) override; AzFramework::SearchSessionsResponse SearchSessions(const AzFramework::SearchSessionsRequest& searchSessionsRequest) const override; void LeaveSession() override; - protected: - // Use for automation tests only to inject mock objects. - void SetGameLiftClient(AZStd::shared_ptr gameliftClient); - private: AZStd::string CreateSessionHelper(const AWSGameLiftCreateSessionRequest& createSessionRequest); AZStd::string CreateSessionOnQueueHelper(const AWSGameLiftCreateSessionOnQueueRequest& createSessionOnQueueRequest); bool JoinSessionHelper(const AWSGameLiftJoinSessionRequest& joinSessionRequest); AzFramework::SearchSessionsResponse SearchSessionsHelper(const AWSGameLiftSearchSessionsRequest& searchSessionsRequest) const; - - AZStd::shared_ptr m_gameliftClient; }; } // namespace AWSGameLift diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientSystemComponent.cpp b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientSystemComponent.cpp index ceb7376b85..ea5fff99a3 100644 --- a/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientSystemComponent.cpp +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientSystemComponent.cpp @@ -6,6 +6,7 @@ * */ +#include #include #include #include @@ -13,10 +14,13 @@ #include #include +#include #include #include #include #include +#include +#include #include @@ -24,23 +28,19 @@ namespace AWSGameLift { AWSGameLiftClientSystemComponent::AWSGameLiftClientSystemComponent() { - m_gameliftClientManager = AZStd::make_unique(); + m_gameliftManager = AZStd::make_unique(); + m_gameliftTicketTracker = AZStd::make_unique(); } void AWSGameLiftClientSystemComponent::Reflect(AZ::ReflectContext* context) { - ReflectCreateSessionRequest(context); - AWSGameLiftCreateSessionOnQueueRequest::Reflect(context); - AWSGameLiftCreateSessionRequest::Reflect(context); - AWSGameLiftJoinSessionRequest::Reflect(context); - AWSGameLiftSearchSessionsRequest::Reflect(context); - ReflectSearchSessionsResponse(context); + ReflectGameLiftMatchmaking(context); + ReflectGameLiftSession(context); if (AZ::SerializeContext* serialize = azrtti_cast(context)) { serialize->Class() - ->Version(0) - ; + ->Version(1); if (AZ::EditContext* editContext = serialize->GetEditContext()) { @@ -50,8 +50,7 @@ namespace AWSGameLift "Create the GameLift client manager that handles communication between game clients and the GameLift service.") ->ClassElement(AZ::Edit::ClassElements::EditorData, "") ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("System")) - ->Attribute(AZ::Edit::Attributes::AutoExpand, true) - ; + ->Attribute(AZ::Edit::Attributes::AutoExpand, true); } } @@ -63,33 +62,7 @@ namespace AWSGameLift {{{"Region", ""}}}) ->Event("CreatePlayerId", &AWSGameLiftRequestBus::Events::CreatePlayerId, {{{"IncludeBrackets", ""}, - {"IncludeDashes", ""}}}) - ; - behaviorContext->EBus("AWSGameLiftSessionAsyncRequestBus") - ->Attribute(AZ::Script::Attributes::Category, "AWSGameLift") - ->Event("CreateSessionAsync", &AWSGameLiftSessionAsyncRequestBus::Events::CreateSessionAsync, - {{{"CreateSessionRequest", ""}}}) - ->Event("JoinSessionAsync", &AWSGameLiftSessionAsyncRequestBus::Events::JoinSessionAsync, - {{{"JoinSessionRequest", ""}}}) - ->Event("SearchSessionsAsync", &AWSGameLiftSessionAsyncRequestBus::Events::SearchSessionsAsync, - {{{"SearchSessionsRequest", ""}}}) - ->Event("LeaveSessionAsync", &AWSGameLiftSessionAsyncRequestBus::Events::LeaveSessionAsync) - ; - behaviorContext - ->EBus("AWSGameLiftSessionAsyncRequestNotificationBus") - ->Attribute(AZ::Script::Attributes::Category, "AWSGameLift") - ->Handler() - ; - behaviorContext->EBus("AWSGameLiftSessionRequestBus") - ->Attribute(AZ::Script::Attributes::Category, "AWSGameLift") - ->Event("CreateSession", &AWSGameLiftSessionRequestBus::Events::CreateSession, - {{{"CreateSessionRequest", ""}}}) - ->Event("JoinSession", &AWSGameLiftSessionRequestBus::Events::JoinSession, - {{{"JoinSessionRequest", ""}}}) - ->Event("SearchSessions", &AWSGameLiftSessionRequestBus::Events::SearchSessions, - {{{"SearchSessionsRequest", ""}}}) - ->Event("LeaveSession", &AWSGameLiftSessionRequestBus::Events::LeaveSession) - ; + {"IncludeDashes", ""}}}); } } @@ -119,12 +92,88 @@ namespace AWSGameLift void AWSGameLiftClientSystemComponent::Activate() { - m_gameliftClientManager->ActivateManager(); + AZ::Interface::Register(this); + + m_gameliftClient.reset(); + m_gameliftManager->ActivateManager(); + m_gameliftTicketTracker->ActivateTracker(); } void AWSGameLiftClientSystemComponent::Deactivate() { - m_gameliftClientManager->DeactivateManager(); + m_gameliftTicketTracker->DeactivateTracker(); + m_gameliftManager->DeactivateManager(); + m_gameliftClient.reset(); + + AZ::Interface::Unregister(this); + } + + void AWSGameLiftClientSystemComponent::ReflectGameLiftMatchmaking(AZ::ReflectContext* context) + { + AWSGameLiftAcceptMatchRequest::Reflect(context); + AWSGameLiftStartMatchmakingRequest::Reflect(context); + AWSGameLiftStopMatchmakingRequest::Reflect(context); + + if (AZ::BehaviorContext* behaviorContext = azrtti_cast(context)) + { + behaviorContext->EBus("AWSGameLiftMatchmakingAsyncRequestBus") + ->Attribute(AZ::Script::Attributes::Category, "AWSGameLift") + ->Event("AcceptMatchAsync", &AWSGameLiftMatchmakingAsyncRequestBus::Events::AcceptMatchAsync, + { { { "AcceptMatchRequest", "" } } }) + ->Event("StartMatchmakingAsync", &AWSGameLiftMatchmakingAsyncRequestBus::Events::StartMatchmakingAsync, + { { { "StartMatchmakingRequest", "" } } }) + ->Event("StopMatchmakingAsync", &AWSGameLiftMatchmakingAsyncRequestBus::Events::StopMatchmakingAsync, + { { { "StopMatchmakingRequest", "" } } }); + + behaviorContext->EBus("AWSGameLiftMatchmakingAsyncRequestNotificationBus") + ->Attribute(AZ::Script::Attributes::Category, "AWSGameLift") + ->Handler(); + + behaviorContext->EBus("AWSGameLiftMatchmakingRequestBus") + ->Attribute(AZ::Script::Attributes::Category, "AWSGameLift") + ->Event("AcceptMatch", &AWSGameLiftMatchmakingRequestBus::Events::AcceptMatch, { { { "AcceptMatchRequest", "" } } }) + ->Event("StartMatchmaking", &AWSGameLiftMatchmakingRequestBus::Events::StartMatchmaking, + { { { "StartMatchmakingRequest", "" } } }) + ->Event("StopMatchmaking", &AWSGameLiftMatchmakingRequestBus::Events::StopMatchmaking, + { { { "StopMatchmakingRequest", "" } } }); + + behaviorContext->EBus("AWSGameLiftMatchAcceptanceNotificationBus") + ->Attribute(AZ::Script::Attributes::Category, "AWSGameLift") + ->Handler(); + } + } + + void AWSGameLiftClientSystemComponent::ReflectGameLiftSession(AZ::ReflectContext* context) + { + ReflectCreateSessionRequest(context); + AWSGameLiftCreateSessionOnQueueRequest::Reflect(context); + AWSGameLiftCreateSessionRequest::Reflect(context); + AWSGameLiftJoinSessionRequest::Reflect(context); + AWSGameLiftSearchSessionsRequest::Reflect(context); + ReflectSearchSessionsResponse(context); + + if (AZ::BehaviorContext* behaviorContext = azrtti_cast(context)) + { + behaviorContext->EBus("AWSGameLiftSessionAsyncRequestBus") + ->Attribute(AZ::Script::Attributes::Category, "AWSGameLift") + ->Event("CreateSessionAsync", &AWSGameLiftSessionAsyncRequestBus::Events::CreateSessionAsync, + { { { "CreateSessionRequest", "" } } }) + ->Event("JoinSessionAsync", &AWSGameLiftSessionAsyncRequestBus::Events::JoinSessionAsync, { { { "JoinSessionRequest", "" } } }) + ->Event("SearchSessionsAsync", &AWSGameLiftSessionAsyncRequestBus::Events::SearchSessionsAsync, + { { { "SearchSessionsRequest", "" } } }) + ->Event("LeaveSessionAsync", &AWSGameLiftSessionAsyncRequestBus::Events::LeaveSessionAsync); + + behaviorContext->EBus("AWSGameLiftSessionAsyncRequestNotificationBus") + ->Attribute(AZ::Script::Attributes::Category, "AWSGameLift") + ->Handler(); + + behaviorContext->EBus("AWSGameLiftSessionRequestBus") + ->Attribute(AZ::Script::Attributes::Category, "AWSGameLift") + ->Event("CreateSession", &AWSGameLiftSessionRequestBus::Events::CreateSession, { { { "CreateSessionRequest", "" } } }) + ->Event("JoinSession", &AWSGameLiftSessionRequestBus::Events::JoinSession, { { { "JoinSessionRequest", "" } } }) + ->Event("SearchSessions", &AWSGameLiftSessionRequestBus::Events::SearchSessions, { { { "SearchSessionsRequest", "" } } }) + ->Event("LeaveSession", &AWSGameLiftSessionRequestBus::Events::LeaveSession); + } } void AWSGameLiftClientSystemComponent::ReflectCreateSessionRequest(AZ::ReflectContext* context) @@ -174,9 +223,25 @@ namespace AWSGameLift } } - void AWSGameLiftClientSystemComponent::SetGameLiftClientManager(AZStd::unique_ptr gameliftClientManager) + AZStd::shared_ptr AWSGameLiftClientSystemComponent::GetGameLiftClient() const + { + return m_gameliftClient; + } + + void AWSGameLiftClientSystemComponent::SetGameLiftClient(AZStd::shared_ptr gameliftClient) + { + m_gameliftClient.swap(gameliftClient); + } + + void AWSGameLiftClientSystemComponent::SetGameLiftClientManager(AZStd::unique_ptr gameliftManager) + { + m_gameliftManager.reset(); + m_gameliftManager = AZStd::move(gameliftManager); + } + + void AWSGameLiftClientSystemComponent::SetGameLiftClientTicketTracker(AZStd::unique_ptr gameliftTicketTracker) { - m_gameliftClientManager.reset(); - m_gameliftClientManager = AZStd::move(gameliftClientManager); + m_gameliftTicketTracker.reset(); + m_gameliftTicketTracker = AZStd::move(gameliftTicketTracker); } } // namespace AWSGameLift diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientSystemComponent.h b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientSystemComponent.h index bac24d44fa..30d4ee0106 100644 --- a/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientSystemComponent.h +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientSystemComponent.h @@ -11,6 +11,9 @@ #include #include +#include +#include + namespace AWSGameLift { class AWSGameLiftClientManager; @@ -18,6 +21,7 @@ namespace AWSGameLift //! Gem client system component. Responsible for creating the gamelift client manager. class AWSGameLiftClientSystemComponent : public AZ::Component + , public IAWSGameLiftInternalRequests { public: AZ_COMPONENT(AWSGameLiftClientSystemComponent, "{d481c15c-732a-4eea-9853-4965ed1bc2be}"); @@ -32,6 +36,10 @@ namespace AWSGameLift static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required); static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent); + // IAWSGameLiftInternalRequests interface implementation + AZStd::shared_ptr GetGameLiftClient() const override; + void SetGameLiftClient(AZStd::shared_ptr gameliftClient) override; + protected: //////////////////////////////////////////////////////////////////////// // AZ::Component interface implementation @@ -40,13 +48,19 @@ namespace AWSGameLift void Deactivate() override; //////////////////////////////////////////////////////////////////////// - void SetGameLiftClientManager(AZStd::unique_ptr gameliftClientManager); + void SetGameLiftClientManager(AZStd::unique_ptr gameliftManager); + void SetGameLiftClientTicketTracker(AZStd::unique_ptr gameliftTicketTracker); private: + static void ReflectGameLiftMatchmaking(AZ::ReflectContext* context); + static void ReflectGameLiftSession(AZ::ReflectContext* context); + static void ReflectCreateSessionRequest(AZ::ReflectContext* context); static void ReflectSearchSessionsResponse(AZ::ReflectContext* context); - AZStd::unique_ptr m_gameliftClientManager; + AZStd::shared_ptr m_gameliftClient; + AZStd::unique_ptr m_gameliftManager; + AZStd::unique_ptr m_gameliftTicketTracker; }; } // namespace AWSGameLift diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Request/AWSGameLiftAcceptMatchRequest.cpp b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Request/AWSGameLiftAcceptMatchRequest.cpp new file mode 100644 index 0000000000..8a8327b92b --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Request/AWSGameLiftAcceptMatchRequest.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include + +#include + +namespace AWSGameLift +{ + void AWSGameLiftAcceptMatchRequest::Reflect(AZ::ReflectContext* context) + { + AzFramework::AcceptMatchRequest::Reflect(context); + + if (auto serializeContext = azrtti_cast(context)) + { + serializeContext->Class() + ->Version(0); + + if (AZ::EditContext* editContext = serializeContext->GetEditContext()) + { + editContext->Class("AWSGameLiftAcceptMatchRequest", "") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly); + } + } + + if (AZ::BehaviorContext* behaviorContext = azrtti_cast(context)) + { + behaviorContext->Class("AWSGameLiftAcceptMatchRequest") + ->Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value) + ->Property("AcceptMatch", BehaviorValueProperty(&AWSGameLiftAcceptMatchRequest::m_acceptMatch)) + ->Property("PlayerIds", BehaviorValueProperty(&AWSGameLiftAcceptMatchRequest::m_playerIds)) + ->Property("TicketId", BehaviorValueProperty(&AWSGameLiftAcceptMatchRequest::m_ticketId)); + } + } +} // namespace AWSGameLift diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Request/AWSGameLiftStartMatchmakingRequest.cpp b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Request/AWSGameLiftStartMatchmakingRequest.cpp new file mode 100644 index 0000000000..03d802fd36 --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Request/AWSGameLiftStartMatchmakingRequest.cpp @@ -0,0 +1,93 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include + +#include + +namespace AWSGameLift +{ + void AWSGameLiftPlayerInformation::Reflect(AZ::ReflectContext* context) + { + if (auto serializeContext = azrtti_cast(context)) + { + serializeContext->Class() + ->Version(0) + ->Field("latencyInMs", &AWSGameLiftPlayerInformation::m_latencyInMs) + ->Field("playerAttributes", &AWSGameLiftPlayerInformation::m_playerAttributes) + ->Field("playerId", &AWSGameLiftPlayerInformation::m_playerId) + ->Field("team", &AWSGameLiftPlayerInformation::m_team); + + if (AZ::EditContext* editContext = serializeContext->GetEditContext()) + { + editContext->Class("AWSGameLiftPlayerInformation", "") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) + ->DataElement( + AZ::Edit::UIHandlers::Default, &AWSGameLiftPlayerInformation::m_latencyInMs, "LatencyInMs", + "A set of values, expressed in milliseconds, that indicates the amount of latency that" + "a player experiences when connected to AWS Regions") + ->DataElement( + AZ::Edit::UIHandlers::Default, &AWSGameLiftPlayerInformation::m_playerAttributes, "PlayerAttributes", + "A collection of key:value pairs containing player information for use in matchmaking") + ->DataElement( + AZ::Edit::UIHandlers::Default, &AWSGameLiftPlayerInformation::m_playerId, "PlayerId", + "A unique identifier for a player") + ->DataElement( + AZ::Edit::UIHandlers::Default, &AWSGameLiftPlayerInformation::m_team, "Team", + "Name of the team that the player is assigned to in a match"); + } + } + + if (AZ::BehaviorContext* behaviorContext = azrtti_cast(context)) + { + behaviorContext->Class("AWSGameLiftPlayerInformation") + ->Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value) + ->Property("LatencyInMs", BehaviorValueProperty(&AWSGameLiftPlayerInformation::m_latencyInMs)) + ->Property("PlayerAttributes", BehaviorValueProperty(&AWSGameLiftPlayerInformation::m_playerAttributes)) + ->Property("PlayerId", BehaviorValueProperty(&AWSGameLiftPlayerInformation::m_playerId)) + ->Property("Team", BehaviorValueProperty(&AWSGameLiftPlayerInformation::m_team)); + } + } + + void AWSGameLiftStartMatchmakingRequest::Reflect(AZ::ReflectContext* context) + { + AzFramework::StartMatchmakingRequest::Reflect(context); + AWSGameLiftPlayerInformation::Reflect(context); + + if (auto serializeContext = azrtti_cast(context)) + { + serializeContext->Class() + ->Version(0) + ->Field("configurationName", &AWSGameLiftStartMatchmakingRequest::m_configurationName) + ->Field("players", &AWSGameLiftStartMatchmakingRequest::m_players); + + if (AZ::EditContext* editContext = serializeContext->GetEditContext()) + { + editContext->Class("AWSGameLiftStartMatchmakingRequest", "") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) + ->DataElement(AZ::Edit::UIHandlers::Default, &AWSGameLiftStartMatchmakingRequest::m_configurationName, "ConfigurationName", + "Name of the matchmaking configuration to use for this request") + ->DataElement(AZ::Edit::UIHandlers::Default, &AWSGameLiftStartMatchmakingRequest::m_players, "Players", + "Information on each player to be matched."); + } + } + + if (AZ::BehaviorContext* behaviorContext = azrtti_cast(context)) + { + behaviorContext->Class("AWSGameLiftStartMatchmakingRequest") + ->Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value) + ->Property("TicketId", BehaviorValueProperty(&AWSGameLiftStartMatchmakingRequest::m_ticketId)) + ->Property("ConfigurationName", BehaviorValueProperty(&AWSGameLiftStartMatchmakingRequest::m_configurationName)) + ->Property("Players", BehaviorValueProperty(&AWSGameLiftStartMatchmakingRequest::m_players)); + } + } +} // namespace AWSGameLift diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Request/AWSGameLiftStopMatchmakingRequest.cpp b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Request/AWSGameLiftStopMatchmakingRequest.cpp new file mode 100644 index 0000000000..bcea1fa8fe --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Request/AWSGameLiftStopMatchmakingRequest.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include + +#include + +namespace AWSGameLift +{ + void AWSGameLiftStopMatchmakingRequest::Reflect(AZ::ReflectContext* context) + { + AzFramework::StopMatchmakingRequest::Reflect(context); + + if (auto serializeContext = azrtti_cast(context)) + { + serializeContext->Class() + ->Version(0); + + if (AZ::EditContext* editContext = serializeContext->GetEditContext()) + { + editContext->Class("AWSGameLiftStopMatchmakingRequest", "") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly); + } + } + + if (AZ::BehaviorContext* behaviorContext = azrtti_cast(context)) + { + behaviorContext->Class("AWSGameLiftStopMatchmakingRequest") + ->Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value) + ->Property("TicketId", BehaviorValueProperty(&AWSGameLiftStopMatchmakingRequest::m_ticketId)); + } + } +} // namespace AWSGameLift diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Request/IAWSGameLiftInternalRequests.h b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Request/IAWSGameLiftInternalRequests.h new file mode 100644 index 0000000000..dbac9a798a --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Request/IAWSGameLiftInternalRequests.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include + +namespace Aws +{ + namespace GameLift + { + class GameLiftClient; + } +} // namespace Aws + +namespace AWSGameLift +{ + //! IAWSGameLiftRequests + //! GameLift Gem internal interface which is used to fetch gem global GameLift client + class IAWSGameLiftInternalRequests + { + public: + AZ_RTTI(IAWSGameLiftInternalRequests, "{DC0CC1C4-21EE-4A41-B428-D12D697F88A2}"); + + IAWSGameLiftInternalRequests() = default; + virtual ~IAWSGameLiftInternalRequests() = default; + + //! GetGameLiftClient + //! Get GameLift client to interact with Amazon GameLift service + //! @return Shared pointer to the GameLift client + virtual AZStd::shared_ptr GetGameLiftClient() const = 0; + + //! SetGameLiftClient + //! Set GameLift client to provided GameLift client + //! @param gameliftClient Input new GameLift client + virtual void SetGameLiftClient(AZStd::shared_ptr gameliftClient) = 0; + }; +} // namespace AWSGameLift diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Request/IAWSGameLiftMatchmakingInternalRequests.h b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Request/IAWSGameLiftMatchmakingInternalRequests.h new file mode 100644 index 0000000000..81d04314af --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Request/IAWSGameLiftMatchmakingInternalRequests.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include +#include + +namespace AWSGameLift +{ + //! IAWSGameLiftMatchmakingInternalRequests + //! GameLift Gem matchmaking internal interfaces which is used to communicate + //! with client side ticket tracker to sync matchmaking ticket data and join + //! player to the match + class IAWSGameLiftMatchmakingInternalRequests + { + public: + AZ_RTTI(IAWSGameLiftMatchmakingInternalRequests, "{C2DA440E-74E0-411E-813D-5880B50B0C9E}"); + + IAWSGameLiftMatchmakingInternalRequests() = default; + virtual ~IAWSGameLiftMatchmakingInternalRequests() = default; + + //! StartPolling + //! Request to start process for polling matchmaking ticket based on given ticket id and player id + //! @param ticketId The requested matchmaking ticket id + //! @param playerId The requested matchmaking player id + virtual void StartPolling(const AZStd::string& ticketId, const AZStd::string& playerId) = 0; + + //! StopPolling + //! Request to stop process for polling matchmaking ticket + virtual void StopPolling() = 0; + }; +} // namespace AWSGameLift diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/AWSGameLiftClientLocalTicketTrackerTest.cpp b/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/AWSGameLiftClientLocalTicketTrackerTest.cpp new file mode 100644 index 0000000000..6e688b28e3 --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/AWSGameLiftClientLocalTicketTrackerTest.cpp @@ -0,0 +1,353 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include + +#include +#include +#include + +#include + +using namespace AWSGameLift; + +static constexpr const uint64_t TEST_RACKER_POLLING_PERIOD_MS = 100; +static constexpr const uint64_t TEST_WAIT_BUFFER_TIME_MS = 10; +static constexpr const uint64_t TEST_WAIT_MAXIMUM_TIME_MS = 10000; + +class TestAWSGameLiftClientLocalTicketTracker + : public AWSGameLiftClientLocalTicketTracker +{ +public: + TestAWSGameLiftClientLocalTicketTracker() = default; + virtual ~TestAWSGameLiftClientLocalTicketTracker() = default; + + void SetUp() + { + ActivateTracker(); + m_pollingPeriodInMS = TEST_RACKER_POLLING_PERIOD_MS; + } + + void TearDown() + { + DeactivateTracker(); + } + + bool IsTrackerIdle() + { + return m_status == TicketTrackerStatus::Idle; + } +}; + +class AWSGameLiftClientLocalTicketTrackerTest + : public AWSGameLiftClientFixture + , public IAWSGameLiftInternalRequests +{ +protected: + void SetUp() override + { + AWSGameLiftClientFixture::SetUp(); + + AZ::Interface::Register(this); + + m_gameliftClientMockPtr = AZStd::make_shared(); + m_gameliftClientTicketTracker = AZStd::make_unique(); + m_gameliftClientTicketTracker->SetUp(); + } + + void TearDown() override + { + m_gameliftClientTicketTracker->TearDown(); + m_gameliftClientTicketTracker.reset(); + m_gameliftClientMockPtr.reset(); + + AZ::Interface::Unregister(this); + + AWSGameLiftClientFixture::TearDown(); + } + + AZStd::shared_ptr GetGameLiftClient() const + { + return m_gameliftClientMockPtr; + } + + void SetGameLiftClient(AZStd::shared_ptr gameliftClient) + { + AZ_UNUSED(gameliftClient); + m_gameliftClientMockPtr.reset(); + } + + void WaitForProcessFinish(uint64_t expectedNum) + { + int processingTime = 0; + while (processingTime < TEST_WAIT_MAXIMUM_TIME_MS) + { + if (::UnitTest::TestRunner::Instance().m_numAssertsFailed == expectedNum) + { + AZ_TEST_STOP_TRACE_SUPPRESSION(expectedNum); + return; + } + AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(TEST_WAIT_BUFFER_TIME_MS)); + processingTime += TEST_WAIT_BUFFER_TIME_MS; + } + } + + void WaitForProcessFinish() + { + int processingTime = 0; + while (processingTime < TEST_WAIT_MAXIMUM_TIME_MS) + { + if (m_gameliftClientTicketTracker->IsTrackerIdle()) + { + return; + } + AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(TEST_WAIT_BUFFER_TIME_MS)); + processingTime += TEST_WAIT_BUFFER_TIME_MS; + } + } + +public: + AZStd::unique_ptr m_gameliftClientTicketTracker; + AZStd::shared_ptr m_gameliftClientMockPtr; +}; + +TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallWithoutClientSetup_GetExpectedErrors) +{ + AZ::Interface::Get()->SetGameLiftClient(nullptr); + AZ_TEST_START_TRACE_SUPPRESSION; + m_gameliftClientTicketTracker->StartPolling("ticket1", "player1"); + WaitForProcessFinish(1); + ASSERT_FALSE(m_gameliftClientTicketTracker->IsTrackerIdle()); +} + +TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_MultipleCallsWithoutClientSetup_GetExpectedErrors) +{ + AZ::Interface::Get()->SetGameLiftClient(nullptr); + AZ_TEST_START_TRACE_SUPPRESSION; + m_gameliftClientTicketTracker->StartPolling("ticket1", "player1"); + m_gameliftClientTicketTracker->StartPolling("ticket1", "player1"); + WaitForProcessFinish(1); + ASSERT_FALSE(m_gameliftClientTicketTracker->IsTrackerIdle()); +} + +TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallButWithFailedOutcome_GetExpectedErrors) +{ + Aws::Client::AWSError error; + Aws::GameLift::Model::DescribeMatchmakingOutcome outcome(error); + + EXPECT_CALL(*m_gameliftClientMockPtr, DescribeMatchmaking(::testing::_)) + .Times(1) + .WillOnce(::testing::Return(outcome)); + + AZ_TEST_START_TRACE_SUPPRESSION; + m_gameliftClientTicketTracker->StartPolling("ticket1", "player1"); + WaitForProcessFinish(1); + ASSERT_FALSE(m_gameliftClientTicketTracker->IsTrackerIdle()); +} + +TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallWithMoreThanOneTicket_GetExpectedErrors) +{ + Aws::GameLift::Model::DescribeMatchmakingResult result; + result.AddTicketList(Aws::GameLift::Model::MatchmakingTicket()); + result.AddTicketList(Aws::GameLift::Model::MatchmakingTicket()); + Aws::GameLift::Model::DescribeMatchmakingOutcome outcome(result); + + EXPECT_CALL(*m_gameliftClientMockPtr, DescribeMatchmaking(::testing::_)) + .Times(1) + .WillOnce(::testing::Return(outcome)); + + AZ_TEST_START_TRACE_SUPPRESSION; + m_gameliftClientTicketTracker->StartPolling("ticket1", "player1"); + WaitForProcessFinish(1); + ASSERT_FALSE(m_gameliftClientTicketTracker->IsTrackerIdle()); +} + +TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallWithCompleteStatus_ProcessStopsAndGetExpectedResult) +{ + Aws::GameLift::Model::GameSessionConnectionInfo connectionInfo; + connectionInfo.SetIpAddress("DummyIpAddress"); + connectionInfo.SetPort(123); + connectionInfo.AddMatchedPlayerSessions( + Aws::GameLift::Model::MatchedPlayerSession() + .WithPlayerId("player1") + .WithPlayerSessionId("playersession1")); + + Aws::GameLift::Model::MatchmakingTicket ticket; + ticket.SetStatus(Aws::GameLift::Model::MatchmakingConfigurationStatus::COMPLETED); + ticket.SetGameSessionConnectionInfo(connectionInfo); + + Aws::GameLift::Model::DescribeMatchmakingResult result; + result.AddTicketList(ticket); + + Aws::GameLift::Model::DescribeMatchmakingOutcome outcome(result); + EXPECT_CALL(*m_gameliftClientMockPtr, DescribeMatchmaking(::testing::_)) + .Times(1) + .WillOnce(::testing::Return(outcome)); + + SessionHandlingClientRequestsMock handlerMock; + EXPECT_CALL(handlerMock, RequestPlayerJoinSession(::testing::_)) + .Times(1) + .WillOnce(::testing::Return(true)); + + m_gameliftClientTicketTracker->StartPolling("ticket1", "player1"); + WaitForProcessFinish(); + ASSERT_TRUE(m_gameliftClientTicketTracker->IsTrackerIdle()); +} + +TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallButNoPlayerSession_ProcessStopsAndGetExpectedError) +{ + Aws::GameLift::Model::GameSessionConnectionInfo connectionInfo; + connectionInfo.SetIpAddress("DummyIpAddress"); + connectionInfo.SetPort(123); + + Aws::GameLift::Model::MatchmakingTicket ticket; + ticket.SetStatus(Aws::GameLift::Model::MatchmakingConfigurationStatus::COMPLETED); + ticket.SetGameSessionConnectionInfo(connectionInfo); + + Aws::GameLift::Model::DescribeMatchmakingResult result; + result.AddTicketList(ticket); + + Aws::GameLift::Model::DescribeMatchmakingOutcome outcome(result); + EXPECT_CALL(*m_gameliftClientMockPtr, DescribeMatchmaking(::testing::_)) + .Times(1) + .WillOnce(::testing::Return(outcome)); + + AZ_TEST_START_TRACE_SUPPRESSION; + m_gameliftClientTicketTracker->StartPolling("ticket1", "player1"); + WaitForProcessFinish(1); + ASSERT_TRUE(m_gameliftClientTicketTracker->IsTrackerIdle()); +} + +TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallButFailedToJoinMatch_ProcessStopsAndGetExpectedError) +{ + Aws::GameLift::Model::GameSessionConnectionInfo connectionInfo; + connectionInfo.SetIpAddress("DummyIpAddress"); + connectionInfo.SetPort(123); + connectionInfo.AddMatchedPlayerSessions( + Aws::GameLift::Model::MatchedPlayerSession() + .WithPlayerId("player1") + .WithPlayerSessionId("playersession1")); + + Aws::GameLift::Model::MatchmakingTicket ticket; + ticket.SetStatus(Aws::GameLift::Model::MatchmakingConfigurationStatus::COMPLETED); + ticket.SetGameSessionConnectionInfo(connectionInfo); + + Aws::GameLift::Model::DescribeMatchmakingResult result; + result.AddTicketList(ticket); + + Aws::GameLift::Model::DescribeMatchmakingOutcome outcome(result); + EXPECT_CALL(*m_gameliftClientMockPtr, DescribeMatchmaking(::testing::_)) + .Times(1) + .WillOnce(::testing::Return(outcome)); + + SessionHandlingClientRequestsMock handlerMock; + EXPECT_CALL(handlerMock, RequestPlayerJoinSession(::testing::_)) + .Times(1) + .WillOnce(::testing::Return(false)); + + AZ_TEST_START_TRACE_SUPPRESSION; + m_gameliftClientTicketTracker->StartPolling("ticket1", "player1"); + WaitForProcessFinish(1); + ASSERT_TRUE(m_gameliftClientTicketTracker->IsTrackerIdle()); +} + +TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallButTicketTimeOut_ProcessStopsAndGetExpectedError) +{ + Aws::GameLift::Model::MatchmakingTicket ticket; + ticket.SetStatus(Aws::GameLift::Model::MatchmakingConfigurationStatus::TIMED_OUT); + + Aws::GameLift::Model::DescribeMatchmakingResult result; + result.AddTicketList(ticket); + + Aws::GameLift::Model::DescribeMatchmakingOutcome outcome(result); + EXPECT_CALL(*m_gameliftClientMockPtr, DescribeMatchmaking(::testing::_)) + .Times(1) + .WillOnce(::testing::Return(outcome)); + + AZ_TEST_START_TRACE_SUPPRESSION; + m_gameliftClientTicketTracker->StartPolling("ticket1", "player1"); + WaitForProcessFinish(1); + ASSERT_TRUE(m_gameliftClientTicketTracker->IsTrackerIdle()); +} + +TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallButTicketFailed_ProcessStopsAndGetExpectedError) +{ + Aws::GameLift::Model::MatchmakingTicket ticket; + ticket.SetStatus(Aws::GameLift::Model::MatchmakingConfigurationStatus::FAILED); + + Aws::GameLift::Model::DescribeMatchmakingResult result; + result.AddTicketList(ticket); + + Aws::GameLift::Model::DescribeMatchmakingOutcome outcome(result); + EXPECT_CALL(*m_gameliftClientMockPtr, DescribeMatchmaking(::testing::_)) + .Times(1) + .WillOnce(::testing::Return(outcome)); + + AZ_TEST_START_TRACE_SUPPRESSION; + m_gameliftClientTicketTracker->StartPolling("ticket1", "player1"); + WaitForProcessFinish(1); + ASSERT_TRUE(m_gameliftClientTicketTracker->IsTrackerIdle()); +} + +TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallButTicketCancelled_ProcessStopsAndGetExpectedError) +{ + Aws::GameLift::Model::MatchmakingTicket ticket; + ticket.SetStatus(Aws::GameLift::Model::MatchmakingConfigurationStatus::CANCELLED); + + Aws::GameLift::Model::DescribeMatchmakingResult result; + result.AddTicketList(ticket); + + Aws::GameLift::Model::DescribeMatchmakingOutcome outcome(result); + EXPECT_CALL(*m_gameliftClientMockPtr, DescribeMatchmaking(::testing::_)) + .Times(1) + .WillOnce(::testing::Return(outcome)); + + AZ_TEST_START_TRACE_SUPPRESSION; + m_gameliftClientTicketTracker->StartPolling("ticket1", "player1"); + WaitForProcessFinish(1); + ASSERT_TRUE(m_gameliftClientTicketTracker->IsTrackerIdle()); +} + +TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallAndTicketCompleteAtLast_ProcessContinuesAndStop) +{ + Aws::GameLift::Model::MatchmakingTicket ticket1; + ticket1.SetStatus(Aws::GameLift::Model::MatchmakingConfigurationStatus::QUEUED); + + Aws::GameLift::Model::DescribeMatchmakingResult result1; + result1.AddTicketList(ticket1); + Aws::GameLift::Model::DescribeMatchmakingOutcome outcome1(result1); + + Aws::GameLift::Model::GameSessionConnectionInfo connectionInfo; + connectionInfo.SetIpAddress("DummyIpAddress"); + connectionInfo.SetPort(123); + connectionInfo.AddMatchedPlayerSessions( + Aws::GameLift::Model::MatchedPlayerSession() + .WithPlayerId("player1") + .WithPlayerSessionId("playersession1")); + + Aws::GameLift::Model::MatchmakingTicket ticket2; + ticket2.SetStatus(Aws::GameLift::Model::MatchmakingConfigurationStatus::COMPLETED); + ticket2.SetGameSessionConnectionInfo(connectionInfo); + + Aws::GameLift::Model::DescribeMatchmakingResult result2; + result2.AddTicketList(ticket2); + Aws::GameLift::Model::DescribeMatchmakingOutcome outcome2(result2); + + EXPECT_CALL(*m_gameliftClientMockPtr, DescribeMatchmaking(::testing::_)) + .WillOnce(::testing::Return(outcome1)) + .WillOnce(::testing::Return(outcome2)); + + SessionHandlingClientRequestsMock handlerMock; + EXPECT_CALL(handlerMock, RequestPlayerJoinSession(::testing::_)) + .Times(1) + .WillOnce(::testing::Return(true)); + + m_gameliftClientTicketTracker->StartPolling("ticket1", "player1"); + WaitForProcessFinish(); + ASSERT_TRUE(m_gameliftClientTicketTracker->IsTrackerIdle()); +} diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/AWSGameLiftClientManagerTest.cpp b/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/AWSGameLiftClientManagerTest.cpp index caa5113a25..e7612618ee 100644 --- a/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/AWSGameLiftClientManagerTest.cpp +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/AWSGameLiftClientManagerTest.cpp @@ -23,8 +23,7 @@ #include #include #include - -#include +#include using namespace AWSGameLift; @@ -117,38 +116,19 @@ public: MOCK_METHOD0(GetDefaultConfig, AWSCore::AwsApiJobConfig*()); }; -class TestAWSGameLiftClientManager - : public AWSGameLiftClientManager -{ -public: - TestAWSGameLiftClientManager() - { - m_gameliftClientMockPtr = nullptr; - } - ~TestAWSGameLiftClientManager() - { - m_gameliftClientMockPtr = nullptr; - } - - void SetUpMockClient() - { - m_gameliftClientMockPtr = AZStd::make_shared(); - SetGameLiftClient(m_gameliftClientMockPtr); - } - - AZStd::shared_ptr m_gameliftClientMockPtr; -}; - class AWSGameLiftClientManagerTest : public AWSGameLiftClientFixture + , public IAWSGameLiftInternalRequests { protected: void SetUp() override { AWSGameLiftClientFixture::SetUp(); - m_gameliftClientManager = AZStd::make_unique(); - m_gameliftClientManager->SetUpMockClient(); + AZ::Interface::Register(this); + + m_gameliftClientMockPtr = AZStd::make_shared(); + m_gameliftClientManager = AZStd::make_unique(); m_gameliftClientManager->ActivateManager(); } @@ -156,10 +136,24 @@ protected: { m_gameliftClientManager->DeactivateManager(); m_gameliftClientManager.reset(); + m_gameliftClientMockPtr.reset(); + + AZ::Interface::Unregister(this); AWSGameLiftClientFixture::TearDown(); } + AZStd::shared_ptr GetGameLiftClient() const + { + return m_gameliftClientMockPtr; + } + + void SetGameLiftClient(AZStd::shared_ptr gameliftClient) + { + AZ_UNUSED(gameliftClient); + m_gameliftClientMockPtr.reset(); + } + AWSGameLiftSearchSessionsRequest GetValidSearchSessionsRequest() { AWSGameLiftSearchSessionsRequest request; @@ -230,7 +224,8 @@ protected: } public: - AZStd::unique_ptr m_gameliftClientManager; + AZStd::unique_ptr m_gameliftClientManager; + AZStd::shared_ptr m_gameliftClientMockPtr; }; TEST_F(AWSGameLiftClientManagerTest, ConfigureGameLiftClient_CallWithoutRegion_GetFalseAsResult) @@ -319,7 +314,7 @@ TEST_F(AWSGameLiftClientManagerTest, CreateSession_CallWithValidRequest_GetSucce Aws::GameLift::Model::CreateGameSessionResult result; result.SetGameSession(Aws::GameLift::Model::GameSession()); Aws::GameLift::Model::CreateGameSessionOutcome outcome(result); - EXPECT_CALL(*(m_gameliftClientManager->m_gameliftClientMockPtr), CreateGameSession(::testing::_)) + EXPECT_CALL(*m_gameliftClientMockPtr, CreateGameSession(::testing::_)) .Times(1) .WillOnce(::testing::Return(outcome)); m_gameliftClientManager->CreateSession(request); @@ -331,7 +326,7 @@ TEST_F(AWSGameLiftClientManagerTest, CreateSession_CallWithValidRequest_GetError request.m_aliasId = "dummyAlias"; Aws::Client::AWSError error; Aws::GameLift::Model::CreateGameSessionOutcome outcome(error); - EXPECT_CALL(*(m_gameliftClientManager->m_gameliftClientMockPtr), CreateGameSession(::testing::_)) + EXPECT_CALL(*m_gameliftClientMockPtr, CreateGameSession(::testing::_)) .Times(1) .WillOnce(::testing::Return(outcome)); AZ_TEST_START_TRACE_SUPPRESSION; @@ -357,7 +352,7 @@ TEST_F(AWSGameLiftClientManagerTest, CreateSessionAsync_CallWithValidRequest_Get Aws::GameLift::Model::CreateGameSessionResult result; result.SetGameSession(Aws::GameLift::Model::GameSession()); Aws::GameLift::Model::CreateGameSessionOutcome outcome(result); - EXPECT_CALL(*(m_gameliftClientManager->m_gameliftClientMockPtr), CreateGameSession(::testing::_)) + EXPECT_CALL(*m_gameliftClientMockPtr, CreateGameSession(::testing::_)) .Times(1) .WillOnce(::testing::Return(outcome)); SessionAsyncRequestNotificationsHandlerMock sessionHandlerMock; @@ -373,7 +368,7 @@ TEST_F(AWSGameLiftClientManagerTest, CreateSessionAsync_CallWithValidRequest_Get request.m_aliasId = "dummyAlias"; Aws::Client::AWSError error; Aws::GameLift::Model::CreateGameSessionOutcome outcome(error); - EXPECT_CALL(*(m_gameliftClientManager->m_gameliftClientMockPtr), CreateGameSession(::testing::_)) + EXPECT_CALL(*m_gameliftClientMockPtr, CreateGameSession(::testing::_)) .Times(1) .WillOnce(::testing::Return(outcome)); SessionAsyncRequestNotificationsHandlerMock sessionHandlerMock; @@ -403,7 +398,7 @@ TEST_F(AWSGameLiftClientManagerTest, CreateSessionOnQueue_CallWithValidRequest_G Aws::GameLift::Model::StartGameSessionPlacementResult result; result.SetGameSessionPlacement(Aws::GameLift::Model::GameSessionPlacement()); Aws::GameLift::Model::StartGameSessionPlacementOutcome outcome(result); - EXPECT_CALL(*(m_gameliftClientManager->m_gameliftClientMockPtr), StartGameSessionPlacement(::testing::_)) + EXPECT_CALL(*m_gameliftClientMockPtr, StartGameSessionPlacement(::testing::_)) .Times(1) .WillOnce(::testing::Return(outcome)); m_gameliftClientManager->CreateSession(request); @@ -416,7 +411,7 @@ TEST_F(AWSGameLiftClientManagerTest, CreateSessionOnQueue_CallWithValidRequest_G request.m_placementId = "dummyPlacementId"; Aws::Client::AWSError error; Aws::GameLift::Model::StartGameSessionPlacementOutcome outcome(error); - EXPECT_CALL(*(m_gameliftClientManager->m_gameliftClientMockPtr), StartGameSessionPlacement(::testing::_)) + EXPECT_CALL(*m_gameliftClientMockPtr, StartGameSessionPlacement(::testing::_)) .Times(1) .WillOnce(::testing::Return(outcome)); AZ_TEST_START_TRACE_SUPPRESSION; @@ -434,7 +429,7 @@ TEST_F(AWSGameLiftClientManagerTest, CreateSessionOnQueueAsync_CallWithValidRequ Aws::GameLift::Model::StartGameSessionPlacementResult result; result.SetGameSessionPlacement(Aws::GameLift::Model::GameSessionPlacement()); Aws::GameLift::Model::StartGameSessionPlacementOutcome outcome(result); - EXPECT_CALL(*(m_gameliftClientManager->m_gameliftClientMockPtr), StartGameSessionPlacement(::testing::_)) + EXPECT_CALL(*m_gameliftClientMockPtr, StartGameSessionPlacement(::testing::_)) .Times(1) .WillOnce(::testing::Return(outcome)); SessionAsyncRequestNotificationsHandlerMock sessionHandlerMock; @@ -451,7 +446,7 @@ TEST_F(AWSGameLiftClientManagerTest, CreateSessionOnQueueAsync_CallWithValidRequ request.m_placementId = "dummyPlacementId"; Aws::Client::AWSError error; Aws::GameLift::Model::StartGameSessionPlacementOutcome outcome(error); - EXPECT_CALL(*(m_gameliftClientManager->m_gameliftClientMockPtr), StartGameSessionPlacement(::testing::_)) + EXPECT_CALL(*m_gameliftClientMockPtr, StartGameSessionPlacement(::testing::_)) .Times(1) .WillOnce(::testing::Return(outcome)); SessionAsyncRequestNotificationsHandlerMock sessionHandlerMock; @@ -489,7 +484,7 @@ TEST_F(AWSGameLiftClientManagerTest, JoinSession_CallWithValidRequestButNoReques Aws::GameLift::Model::CreatePlayerSessionResult result; result.SetPlayerSession(Aws::GameLift::Model::PlayerSession()); Aws::GameLift::Model::CreatePlayerSessionOutcome outcome(result); - EXPECT_CALL(*(m_gameliftClientManager->m_gameliftClientMockPtr), CreatePlayerSession(::testing::_)) + EXPECT_CALL(*m_gameliftClientMockPtr, CreatePlayerSession(::testing::_)) .Times(1) .WillOnce(::testing::Return(outcome)); AZ_TEST_START_TRACE_SUPPRESSION; @@ -505,7 +500,7 @@ TEST_F(AWSGameLiftClientManagerTest, JoinSession_CallWithValidRequest_GetErrorOu request.m_playerId = "dummyPlayerId"; Aws::Client::AWSError error; Aws::GameLift::Model::CreatePlayerSessionOutcome outcome(error); - EXPECT_CALL(*(m_gameliftClientManager->m_gameliftClientMockPtr), CreatePlayerSession(::testing::_)) + EXPECT_CALL(*m_gameliftClientMockPtr, CreatePlayerSession(::testing::_)) .Times(1) .WillOnce(::testing::Return(outcome)); AZ_TEST_START_TRACE_SUPPRESSION; @@ -524,7 +519,7 @@ TEST_F(AWSGameLiftClientManagerTest, JoinSession_CallWithValidRequestAndRequestH Aws::GameLift::Model::CreatePlayerSessionResult result; result.SetPlayerSession(Aws::GameLift::Model::PlayerSession()); Aws::GameLift::Model::CreatePlayerSessionOutcome outcome(result); - EXPECT_CALL(*(m_gameliftClientManager->m_gameliftClientMockPtr), CreatePlayerSession(::testing::_)) + EXPECT_CALL(*m_gameliftClientMockPtr, CreatePlayerSession(::testing::_)) .Times(1) .WillOnce(::testing::Return(outcome)); auto response = m_gameliftClientManager->JoinSession(request); @@ -541,7 +536,7 @@ TEST_F(AWSGameLiftClientManagerTest, JoinSession_CallWithValidRequestAndRequestH Aws::GameLift::Model::CreatePlayerSessionResult result; result.SetPlayerSession(Aws::GameLift::Model::PlayerSession()); Aws::GameLift::Model::CreatePlayerSessionOutcome outcome(result); - EXPECT_CALL(*(m_gameliftClientManager->m_gameliftClientMockPtr), CreatePlayerSession(::testing::_)) + EXPECT_CALL(*m_gameliftClientMockPtr, CreatePlayerSession(::testing::_)) .Times(1) .WillOnce(::testing::Return(outcome)); auto response = m_gameliftClientManager->JoinSession(request); @@ -567,7 +562,7 @@ TEST_F(AWSGameLiftClientManagerTest, JoinSessionAsync_CallWithValidRequestButNoR Aws::GameLift::Model::CreatePlayerSessionResult result; result.SetPlayerSession(Aws::GameLift::Model::PlayerSession()); Aws::GameLift::Model::CreatePlayerSessionOutcome outcome(result); - EXPECT_CALL(*(m_gameliftClientManager->m_gameliftClientMockPtr), CreatePlayerSession(::testing::_)) + EXPECT_CALL(*m_gameliftClientMockPtr, CreatePlayerSession(::testing::_)) .Times(1) .WillOnce(::testing::Return(outcome)); SessionAsyncRequestNotificationsHandlerMock sessionHandlerMock; @@ -586,7 +581,7 @@ TEST_F(AWSGameLiftClientManagerTest, JoinSessionAsync_CallWithValidRequest_GetEr request.m_playerId = "dummyPlayerId"; Aws::Client::AWSError error; Aws::GameLift::Model::CreatePlayerSessionOutcome outcome(error); - EXPECT_CALL(*(m_gameliftClientManager->m_gameliftClientMockPtr), CreatePlayerSession(::testing::_)) + EXPECT_CALL(*m_gameliftClientMockPtr, CreatePlayerSession(::testing::_)) .Times(1) .WillOnce(::testing::Return(outcome)); SessionAsyncRequestNotificationsHandlerMock sessionHandlerMock; @@ -608,7 +603,7 @@ TEST_F(AWSGameLiftClientManagerTest, JoinSessionAsync_CallWithValidRequestAndReq Aws::GameLift::Model::CreatePlayerSessionResult result; result.SetPlayerSession(Aws::GameLift::Model::PlayerSession()); Aws::GameLift::Model::CreatePlayerSessionOutcome outcome(result); - EXPECT_CALL(*(m_gameliftClientManager->m_gameliftClientMockPtr), CreatePlayerSession(::testing::_)) + EXPECT_CALL(*m_gameliftClientMockPtr, CreatePlayerSession(::testing::_)) .Times(1) .WillOnce(::testing::Return(outcome)); SessionAsyncRequestNotificationsHandlerMock sessionHandlerMock; @@ -628,7 +623,7 @@ TEST_F(AWSGameLiftClientManagerTest, JoinSessionAsync_CallWithValidRequestAndReq Aws::GameLift::Model::CreatePlayerSessionResult result; result.SetPlayerSession(Aws::GameLift::Model::PlayerSession()); Aws::GameLift::Model::CreatePlayerSessionOutcome outcome(result); - EXPECT_CALL(*(m_gameliftClientManager->m_gameliftClientMockPtr), CreatePlayerSession(::testing::_)) + EXPECT_CALL(*m_gameliftClientMockPtr, CreatePlayerSession(::testing::_)) .Times(1) .WillOnce(::testing::Return(outcome)); SessionAsyncRequestNotificationsHandlerMock sessionHandlerMock; @@ -642,7 +637,7 @@ TEST_F(AWSGameLiftClientManagerTest, SearchSessions_CallWithValidRequestAndError Aws::Client::AWSError error; Aws::GameLift::Model::SearchGameSessionsOutcome outcome(error); - EXPECT_CALL(*(m_gameliftClientManager->m_gameliftClientMockPtr), SearchGameSessions(::testing::_)) + EXPECT_CALL(*m_gameliftClientMockPtr, SearchGameSessions(::testing::_)) .Times(1) .WillOnce(::testing::Return(outcome)); @@ -657,7 +652,7 @@ TEST_F(AWSGameLiftClientManagerTest, SearchSessions_CallWithValidRequestAndSucce AWSGameLiftSearchSessionsRequest request = GetValidSearchSessionsRequest(); Aws::GameLift::Model::SearchGameSessionsOutcome outcome = GetValidSearchGameSessionsOutcome(); - EXPECT_CALL(*(m_gameliftClientManager->m_gameliftClientMockPtr), SearchGameSessions(::testing::_)) + EXPECT_CALL(*m_gameliftClientMockPtr, SearchGameSessions(::testing::_)) .Times(1) .WillOnce(::testing::Return(outcome)); @@ -703,7 +698,7 @@ TEST_F(AWSGameLiftClientManagerTest, SearchSessionsAsync_CallWithValidRequestAnd Aws::Client::AWSError error; Aws::GameLift::Model::SearchGameSessionsOutcome outcome(error); - EXPECT_CALL(*(m_gameliftClientManager->m_gameliftClientMockPtr), SearchGameSessions(::testing::_)) + EXPECT_CALL(*m_gameliftClientMockPtr, SearchGameSessions(::testing::_)) .Times(1) .WillOnce(::testing::Return(outcome)); @@ -724,7 +719,7 @@ TEST_F(AWSGameLiftClientManagerTest, SearchSessionsAsync_CallWithValidRequestAnd EXPECT_CALL(coreHandlerMock, GetDefaultJobContext()).Times(1).WillOnce(::testing::Return(m_jobContext.get())); Aws::GameLift::Model::SearchGameSessionsOutcome outcome = GetValidSearchGameSessionsOutcome(); - EXPECT_CALL(*(m_gameliftClientManager->m_gameliftClientMockPtr), SearchGameSessions(::testing::_)) + EXPECT_CALL(*m_gameliftClientMockPtr, SearchGameSessions(::testing::_)) .Times(1) .WillOnce(::testing::Return(outcome)); diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/AWSGameLiftClientMocks.h b/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/AWSGameLiftClientMocks.h index a161168159..5c09f43e08 100644 --- a/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/AWSGameLiftClientMocks.h +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/AWSGameLiftClientMocks.h @@ -21,6 +21,8 @@ #include #include #include +#include +#include #include #include #include @@ -39,6 +41,7 @@ public: MOCK_CONST_METHOD1(CreateGameSession, Model::CreateGameSessionOutcome(const Model::CreateGameSessionRequest&)); MOCK_CONST_METHOD1(CreatePlayerSession, Model::CreatePlayerSessionOutcome(const Model::CreatePlayerSessionRequest&)); + MOCK_CONST_METHOD1(DescribeMatchmaking, Model::DescribeMatchmakingOutcome(const Model::DescribeMatchmakingRequest&)); MOCK_CONST_METHOD1(SearchGameSessions, Model::SearchGameSessionsOutcome(const Model::SearchGameSessionsRequest&)); MOCK_CONST_METHOD1(StartGameSessionPlacement, Model::StartGameSessionPlacementOutcome(const Model::StartGameSessionPlacementRequest&)); }; diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/AWSGameLiftClientSystemComponentTest.cpp b/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/AWSGameLiftClientSystemComponentTest.cpp index df6fb0d666..096a182819 100644 --- a/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/AWSGameLiftClientSystemComponentTest.cpp +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/AWSGameLiftClientSystemComponentTest.cpp @@ -13,10 +13,9 @@ #include #include +#include #include -#include - using namespace AWSGameLift; class AWSGameLiftClientManagerMock @@ -30,6 +29,17 @@ public: MOCK_METHOD0(DeactivateManager, void()); }; +class AWSGameLiftClientLocalTicketTrackerMock + : public AWSGameLiftClientLocalTicketTracker +{ +public: + AWSGameLiftClientLocalTicketTrackerMock() = default; + ~AWSGameLiftClientLocalTicketTrackerMock() = default; + + MOCK_METHOD0(ActivateTracker, void()); + MOCK_METHOD0(DeactivateTracker, void()); +}; + class TestAWSGameLiftClientSystemComponent : public AWSGameLiftClientSystemComponent { @@ -37,20 +47,29 @@ public: TestAWSGameLiftClientSystemComponent() { m_gameliftClientManagerMockPtr = nullptr; + m_gameliftClientTicketTrackerMockPtr = nullptr; } ~TestAWSGameLiftClientSystemComponent() { m_gameliftClientManagerMockPtr = nullptr; + m_gameliftClientTicketTrackerMockPtr = nullptr; } void SetUpMockManager() { - AZStd::unique_ptr gameliftClientManagerMock = AZStd::make_unique(); + AZStd::unique_ptr gameliftClientManagerMock = + AZStd::make_unique(); m_gameliftClientManagerMockPtr = gameliftClientManagerMock.get(); SetGameLiftClientManager(AZStd::move(gameliftClientManagerMock)); + + AZStd::unique_ptr gameliftClientTicketTrackerMock = + AZStd::make_unique(); + m_gameliftClientTicketTrackerMockPtr = gameliftClientTicketTrackerMock.get(); + SetGameLiftClientTicketTracker(AZStd::move(gameliftClientTicketTrackerMock)); } AWSGameLiftClientManagerMock* m_gameliftClientManagerMockPtr; + AWSGameLiftClientLocalTicketTrackerMock* m_gameliftClientTicketTrackerMockPtr; }; class AWSCoreSystemComponentMock @@ -145,8 +164,10 @@ TEST_F(AWSGameLiftClientSystemComponentTest, ActivateDeactivate_Call_GameLiftCli { m_entity->Init(); EXPECT_CALL(*(m_gameliftClientSystemComponent->m_gameliftClientManagerMockPtr), ActivateManager()).Times(1); + EXPECT_CALL(*(m_gameliftClientSystemComponent->m_gameliftClientTicketTrackerMockPtr), ActivateTracker()).Times(1); m_entity->Activate(); EXPECT_CALL(*(m_gameliftClientSystemComponent->m_gameliftClientManagerMockPtr), DeactivateManager()).Times(1); + EXPECT_CALL(*(m_gameliftClientSystemComponent->m_gameliftClientTicketTrackerMockPtr), DeactivateTracker()).Times(1); m_entity->Deactivate(); } diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/awsgamelift_client_files.cmake b/Gems/AWSGameLift/Code/AWSGameLiftClient/awsgamelift_client_files.cmake index 771bdf0c08..d3b9d8d1b9 100644 --- a/Gems/AWSGameLift/Code/AWSGameLiftClient/awsgamelift_client_files.cmake +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/awsgamelift_client_files.cmake @@ -7,10 +7,14 @@ # set(FILES + ../AWSGameLiftCommon/Source/AWSGameLiftSessionConstants.h + Include/Request/AWSGameLiftAcceptMatchRequest.h Include/Request/AWSGameLiftCreateSessionOnQueueRequest.h Include/Request/AWSGameLiftCreateSessionRequest.h Include/Request/AWSGameLiftJoinSessionRequest.h Include/Request/AWSGameLiftSearchSessionsRequest.h + Include/Request/AWSGameLiftStartMatchmakingRequest.h + Include/Request/AWSGameLiftStopMatchmakingRequest.h Include/Request/IAWSGameLiftRequests.h Source/Activity/AWSGameLiftActivityUtils.cpp Source/Activity/AWSGameLiftActivityUtils.h @@ -24,12 +28,19 @@ set(FILES Source/Activity/AWSGameLiftLeaveSessionActivity.h Source/Activity/AWSGameLiftSearchSessionsActivity.cpp Source/Activity/AWSGameLiftSearchSessionsActivity.h + Source/AWSGameLiftClientLocalTicketTracker.cpp + Source/AWSGameLiftClientLocalTicketTracker.h Source/AWSGameLiftClientManager.cpp Source/AWSGameLiftClientManager.h Source/AWSGameLiftClientSystemComponent.cpp Source/AWSGameLiftClientSystemComponent.h + Source/Request/AWSGameLiftAcceptMatchRequest.cpp Source/Request/AWSGameLiftCreateSessionOnQueueRequest.cpp Source/Request/AWSGameLiftCreateSessionRequest.cpp Source/Request/AWSGameLiftJoinSessionRequest.cpp Source/Request/AWSGameLiftSearchSessionsRequest.cpp + Source/Request/AWSGameLiftStartMatchmakingRequest.cpp + Source/Request/AWSGameLiftStopMatchmakingRequest.cpp + Source/Request/IAWSGameLiftInternalRequests.h + Source/Request/IAWSGameLiftMatchmakingInternalRequests.h ) diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/awsgamelift_client_tests_files.cmake b/Gems/AWSGameLift/Code/AWSGameLiftClient/awsgamelift_client_tests_files.cmake index 6614eabb46..130ed28e4e 100644 --- a/Gems/AWSGameLift/Code/AWSGameLiftClient/awsgamelift_client_tests_files.cmake +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/awsgamelift_client_tests_files.cmake @@ -12,6 +12,7 @@ set(FILES Tests/Activity/AWSGameLiftJoinSessionActivityTest.cpp Tests/Activity/AWSGameLiftSearchSessionsActivityTest.cpp Tests/AWSGameLiftClientFixture.h + Tests/AWSGameLiftClientLocalTicketTrackerTest.cpp Tests/AWSGameLiftClientManagerTest.cpp Tests/AWSGameLiftClientMocks.h Tests/AWSGameLiftClientSystemComponentTest.cpp diff --git a/Gems/AWSGameLift/Code/AWSGameLiftCommon/Source/AWSGameLiftSessionConstants.h b/Gems/AWSGameLift/Code/AWSGameLiftCommon/Source/AWSGameLiftSessionConstants.h index 588bd51380..e29df324d3 100644 --- a/Gems/AWSGameLift/Code/AWSGameLiftCommon/Source/AWSGameLiftSessionConstants.h +++ b/Gems/AWSGameLift/Code/AWSGameLiftCommon/Source/AWSGameLiftSessionConstants.h @@ -17,4 +17,5 @@ namespace AWSGameLift static const char* AWSGameLiftSessionStatusReasons[2] = { "NotSet", "Interrupted" }; static constexpr const char AWSGameLiftErrorMessageTemplate[] = "Exception: %s, Message: %s"; + static constexpr const char AWSGameLiftClientMissingErrorMessage[] = "GameLift client is not configured yet."; } // namespace AWSGameLift diff --git a/Gems/AWSMetrics/Code/Source/AWSMetricsSystemComponent.cpp b/Gems/AWSMetrics/Code/Source/AWSMetricsSystemComponent.cpp index 6850fe694b..7f320f398a 100644 --- a/Gems/AWSMetrics/Code/Source/AWSMetricsSystemComponent.cpp +++ b/Gems/AWSMetrics/Code/Source/AWSMetricsSystemComponent.cpp @@ -74,7 +74,12 @@ namespace AWSMetrics { behaviorContext->EBus("AWSMetricsRequestBus", "Generate and submit metrics to the metrics analytics pipeline") ->Attribute(AZ::Script::Attributes::Category, "AWSMetrics") - ->Event("SubmitMetrics", &AWSMetricsRequestBus::Events::SubmitMetrics) + ->Event( + "SubmitMetrics", &AWSMetricsRequestBus::Events::SubmitMetrics, + { { { "Metrics Attributes list", "The list of metrics attributes to submit." }, + { "Event priority", "Priority of the event. Defaults to 0, which is highest priority." }, + { "Event source override", "Event source used to override the default, 'AWSMetricGem'." }, + { "Buffer metrics", "Whether to buffer metrics and send them in a batch." } } }) ->Event("FlushMetrics", &AWSMetricsRequestBus::Events::FlushMetrics) ; diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/CMakeLists.txt b/Gems/Atom/Asset/ImageProcessingAtom/Code/CMakeLists.txt index 1463124e27..836f173632 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/CMakeLists.txt +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/CMakeLists.txt @@ -63,13 +63,10 @@ ly_add_target( 3rdParty::Qt::Widgets 3rdParty::Qt::Gui 3rdParty::astc-encoder - 3rdParty::etc2comp - 3rdParty::PVRTexTool 3rdParty::squish-ccr 3rdParty::tiff 3rdParty::ISPCTexComp 3rdParty::ilmbase - Legacy::CryCommon AZ::AzFramework AZ::AzToolsFramework AZ::AzQtComponents diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Include/Atom/ImageProcessing/PixelFormats.h b/Gems/Atom/Asset/ImageProcessingAtom/Code/Include/Atom/ImageProcessing/PixelFormats.h index fbde69abb2..4da996dbde 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Include/Atom/ImageProcessing/PixelFormats.h +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Include/Atom/ImageProcessing/PixelFormats.h @@ -39,16 +39,7 @@ namespace ImageProcessingAtom ePixelFormat_ASTC_10x8, ePixelFormat_ASTC_10x10, ePixelFormat_ASTC_12x10, - ePixelFormat_ASTC_12x12, - //Formats supported by PowerVR GPU. Mainly for ios devices. - ePixelFormat_PVRTC2, //2bpp - ePixelFormat_PVRTC4, //4bpp - //formats for opengl and opengles 3.0 (android devices) - ePixelFormat_EAC_R11, //one channel unsigned data - ePixelFormat_EAC_RG11, //two channel unsigned data - ePixelFormat_ETC2, //Compresses RGB888 data, it taks 4x4 groups of pixel data and compresses each into a 64-bit - ePixelFormat_ETC2a1, //Compresses RGB888A1 data, it taks 4x4 groups of pixel data and compresses each into a 64-bit - ePixelFormat_ETC2a, //Compresses RGBA8888 data with full alpha support + ePixelFormat_ASTC_12x12, // Standardized Compressed DXGI Formats (DX10+) // Data in these compressed formats is hardware decodable on all DX10 chips, and manageable with the DX10-API. @@ -88,7 +79,6 @@ namespace ImageProcessingAtom }; bool IsASTCFormat(EPixelFormat fmt); - bool IsETCFormat(EPixelFormat fmt); } // namespace ImageProcessingAtom namespace AZ diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/BuilderSettings/PresetSettings.cpp b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/BuilderSettings/PresetSettings.cpp index 7ffabc45a9..3812b86026 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/BuilderSettings/PresetSettings.cpp +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/BuilderSettings/PresetSettings.cpp @@ -109,13 +109,6 @@ namespace ImageProcessingAtom ->Value("ASTC_10x10", EPixelFormat::ePixelFormat_ASTC_10x10) ->Value("ASTC_12x10", EPixelFormat::ePixelFormat_ASTC_12x10) ->Value("ASTC_12x12", EPixelFormat::ePixelFormat_ASTC_12x12) - ->Value("PVRTC2", EPixelFormat::ePixelFormat_PVRTC2) - ->Value("PVRTC4", EPixelFormat::ePixelFormat_PVRTC4) - ->Value("EAC_R11", EPixelFormat::ePixelFormat_EAC_R11) - ->Value("EAC_RG11", EPixelFormat::ePixelFormat_EAC_RG11) - ->Value("ETC2", EPixelFormat::ePixelFormat_ETC2) - ->Value("ETC2a1", EPixelFormat::ePixelFormat_ETC2a1) - ->Value("ETC2a", EPixelFormat::ePixelFormat_ETC2a) ->Value("BC1", EPixelFormat::ePixelFormat_BC1) ->Value("BC1a", EPixelFormat::ePixelFormat_BC1a) ->Value("BC3", EPixelFormat::ePixelFormat_BC3) diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Compressors/Compressor.cpp b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Compressors/Compressor.cpp index 1dd18faaf3..ac5340eaab 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Compressors/Compressor.cpp +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Compressors/Compressor.cpp @@ -10,8 +10,6 @@ #include #include #include -#include -#include #include namespace ImageProcessingAtom @@ -42,26 +40,6 @@ namespace ImageProcessingAtom } } - // Both ETC2Compressor and PVRTCCompressor can process ETC formats - // According to Mobile team, Etc2Com is faster than PVRTexLib, so we check with ETC2Compressor before PVRTCCompressor - // Note: with the test I have done, I found out it cost similar time for both Etc2Com and PVRTexLib to compress - // a 2048x2048 test texture to EAC_R11 and EAC_RG11. It was around 7 minutes for EAC_R11 and 14 minutes for EAC_RG11 - if (ETC2Compressor::IsCompressedPixelFormatSupported(fmt)) - { - if (isCompressing || (!isCompressing && ETC2Compressor::DoesSupportDecompress(fmt))) - { - return ICompressorPtr(new ETC2Compressor()); - } - } - - if (PVRTCCompressor::IsCompressedPixelFormatSupported(fmt)) - { - if (isCompressing || (!isCompressing && PVRTCCompressor::DoesSupportDecompress(fmt))) - { - return ICompressorPtr(new PVRTCCompressor()); - } - } - return nullptr; } diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Compressors/ETC2.cpp b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Compressors/ETC2.cpp deleted file mode 100644 index 73108d5502..0000000000 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Compressors/ETC2.cpp +++ /dev/null @@ -1,233 +0,0 @@ -/* - * Copyright (c) Contributors to the Open 3D Engine Project. - * For complete copyright and license terms please see the LICENSE at the root of this distribution. - * - * SPDX-License-Identifier: Apache-2.0 OR MIT - * - */ - - -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -namespace ImageProcessingAtom -{ - //limited to 1 thread because AP requires so. We may change to n when AP allocate n thread to a job in the furture - static const int MAX_COMP_JOBS = 1; - static const int MIN_COMP_JOBS = 1; - static const float ETC_LOW_EFFORT_LEVEL = 25.0f; - static const float ETC_MED_EFFORT_LEVEL = 40.0f; - static const float ETC_HIGH_EFFORT_LEVEL = 80.0f; - - //Grab the Etc2Comp specific pixel format enum - static Etc::Image::Format FindEtc2PixelFormat(EPixelFormat fmt) - { - switch (fmt) - { - case ePixelFormat_EAC_RG11: - return Etc::Image::Format::RG11; - case ePixelFormat_EAC_R11: - return Etc::Image::Format::R11; - case ePixelFormat_ETC2: - return Etc::Image::Format::RGB8; - case ePixelFormat_ETC2a1: - return Etc::Image::Format::RGB8A1; - case ePixelFormat_ETC2a: - return Etc::Image::Format::RGBA8; - default: - return Etc::Image::Format::FORMATS; - } - } - - //Get the errmetric required for the compression - static Etc::ErrorMetric FindErrMetric(Etc::Image::Format fmt) - { - switch (fmt) - { - case Etc::Image::Format::RG11: - return Etc::ErrorMetric::NORMALXYZ; - case Etc::Image::Format::R11: - return Etc::ErrorMetric::NUMERIC; - case Etc::Image::Format::RGB8: - return Etc::ErrorMetric::RGBX; - case Etc::Image::Format::RGBA8: - case Etc::Image::Format::RGB8A1: - return Etc::ErrorMetric::RGBA; - default: - return Etc::ErrorMetric::ERROR_METRICS; - } - } - - //Convert to sRGB format - static Etc::Image::Format FindGammaEtc2PixelFormat(Etc::Image::Format fmt) - { - switch (fmt) - { - case Etc::Image::Format::RGB8: - return Etc::Image::Format::SRGB8; - case Etc::Image::Format::RGBA8: - return Etc::Image::Format::SRGBA8; - case Etc::Image::Format::RGB8A1: - return Etc::Image::Format::SRGB8A1; - default: - return Etc::Image::Format::FORMATS; - } - } - - bool ETC2Compressor::IsCompressedPixelFormatSupported(EPixelFormat fmt) - { - return (FindEtc2PixelFormat(fmt) != Etc::Image::Format::FORMATS); - } - - bool ETC2Compressor::IsUncompressedPixelFormatSupported(EPixelFormat fmt) - { - //for uncompress format - if (fmt == ePixelFormat_R8G8B8A8) - { - return true; - } - return false; - } - - EPixelFormat ETC2Compressor::GetSuggestedUncompressedFormat([[maybe_unused]] EPixelFormat compressedfmt, [[maybe_unused]] EPixelFormat uncompressedfmt) const - { - return ePixelFormat_R8G8B8A8; - } - - bool ETC2Compressor::DoesSupportDecompress([[maybe_unused]] EPixelFormat fmtDst) - { - return false; - } - - ColorSpace ETC2Compressor::GetSupportedColorSpace([[maybe_unused]] EPixelFormat compressFormat) const - { - return ColorSpace::autoSelect; - } - - const char* ETC2Compressor::GetName() const - { - return "ETC2Compressor"; - } - - IImageObjectPtr ETC2Compressor::CompressImage(IImageObjectPtr srcImage, EPixelFormat fmtDst, - const CompressOption* compressOption) const - { - //validate input - EPixelFormat fmtSrc = srcImage->GetPixelFormat(); - - //src format need to be uncompressed and dst format need to compressed. - if (!IsUncompressedPixelFormatSupported(fmtSrc) || !IsCompressedPixelFormatSupported(fmtDst)) - { - return nullptr; - } - - IImageObjectPtr dstImage(srcImage->AllocateImage(fmtDst)); - - //determinate compression quality - ICompressor::EQuality quality = ICompressor::eQuality_Normal; - //get setting from compression option - if (compressOption) - { - quality = compressOption->compressQuality; - } - - float qualityEffort = 0.0f; - switch (quality) - { - case eQuality_Preview: - case eQuality_Fast: - { - qualityEffort = ETC_LOW_EFFORT_LEVEL; - break; - } - case eQuality_Normal: - { - qualityEffort = ETC_MED_EFFORT_LEVEL; - break; - } - default: - { - qualityEffort = ETC_HIGH_EFFORT_LEVEL; - } - } - - Etc::Image::Format dstEtc2Format = FindEtc2PixelFormat(fmtDst); - if (srcImage->GetImageFlags() & EIF_SRGBRead) - { - dstEtc2Format = FindGammaEtc2PixelFormat(dstEtc2Format); - } - - //use to read pixel data from src image - IPixelOperationPtr pixelOp = CreatePixelOperation(fmtSrc); - //get count of bytes per pixel for images - AZ::u32 pixelBytes = CPixelFormats::GetInstance().GetPixelFormatInfo(fmtSrc)->bitsPerBlock / 8; - - const AZ::u32 mipCount = dstImage->GetMipCount(); - for (AZ::u32 mip = 0; mip < mipCount; ++mip) - { - const AZ::u32 width = srcImage->GetWidth(mip); - const AZ::u32 height = srcImage->GetHeight(mip); - - // Prepare source data - AZ::u8* srcMem; - AZ::u32 srcPitch; - srcImage->GetImagePointer(mip, srcMem, srcPitch); - const AZ::u32 pixelCount = srcImage->GetPixelCount(mip); - - Etc::ColorFloatRGBA* rgbaPixels = new Etc::ColorFloatRGBA[pixelCount]; - Etc::ColorFloatRGBA* rgbaPixelPtr = rgbaPixels; - float r, g, b, a; - for (AZ::u32 pixelIdx = 0; pixelIdx < pixelCount; pixelIdx++, srcMem += pixelBytes, rgbaPixelPtr++) - { - pixelOp->GetRGBA(srcMem, r, g, b, a); - rgbaPixelPtr->fA = a; - rgbaPixelPtr->fR = r; - rgbaPixelPtr->fG = g; - rgbaPixelPtr->fB = b; - } - - //Call into etc2Comp lib to compress. https://medium.com/@duhroach/building-a-blazing-fast-etc2-compressor-307f3e9aad99 - Etc::ErrorMetric errMetric = FindErrMetric(dstEtc2Format); - unsigned char* paucEncodingBits; - unsigned int uiEncodingBitsBytes; - unsigned int uiExtendedWidth; - unsigned int uiExtendedHeight; - int iEncodingTime_ms; - - Etc::Encode(reinterpret_cast(rgbaPixels), - width, height, - dstEtc2Format, - errMetric, - qualityEffort, - MIN_COMP_JOBS, - MAX_COMP_JOBS, - &paucEncodingBits, &uiEncodingBitsBytes, - &uiExtendedWidth, &uiExtendedHeight, - &iEncodingTime_ms); - - AZ::u8* dstMem; - AZ::u32 dstPitch; - dstImage->GetImagePointer(mip, dstMem, dstPitch); - - memcpy(dstMem, paucEncodingBits, uiEncodingBitsBytes); - delete[] rgbaPixels; - } - - return dstImage; - } - - IImageObjectPtr ETC2Compressor::DecompressImage(IImageObjectPtr srcImage, [[maybe_unused]] EPixelFormat fmtDst) const - { - //etc2Comp doesn't support decompression - //Since PVRTexLib support ETC formats too. It may take over the decompression. - return nullptr; - } -} // namespace ImageProcessingAtom diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Compressors/ETC2.h b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Compressors/ETC2.h deleted file mode 100644 index 9ab5a164a5..0000000000 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Compressors/ETC2.h +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) Contributors to the Open 3D Engine Project. - * For complete copyright and license terms please see the LICENSE at the root of this distribution. - * - * SPDX-License-Identifier: Apache-2.0 OR MIT - * - */ - -#pragma once - -#include - -namespace ImageProcessingAtom -{ - class ETC2Compressor - : public ICompressor - { - public: - static bool IsCompressedPixelFormatSupported(EPixelFormat fmt); - static bool IsUncompressedPixelFormatSupported(EPixelFormat fmt); - static bool DoesSupportDecompress(EPixelFormat fmtDst); - - IImageObjectPtr CompressImage(IImageObjectPtr srcImage, EPixelFormat fmtDst, const CompressOption* compressOption) const override; - IImageObjectPtr DecompressImage(IImageObjectPtr srcImage, EPixelFormat fmtDst) const override; - - EPixelFormat GetSuggestedUncompressedFormat(EPixelFormat compressedfmt, EPixelFormat uncompressedfmt) const override; - ColorSpace GetSupportedColorSpace(EPixelFormat compressFormat) const final; - const char* GetName() const final; - - }; -} // namespace ImageProcessingAtom diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Compressors/PVRTC.cpp b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Compressors/PVRTC.cpp deleted file mode 100644 index f3a630e8c1..0000000000 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Compressors/PVRTC.cpp +++ /dev/null @@ -1,338 +0,0 @@ -/* - * Copyright (c) Contributors to the Open 3D Engine Project. - * For complete copyright and license terms please see the LICENSE at the root of this distribution. - * - * SPDX-License-Identifier: Apache-2.0 OR MIT - * - */ - -#include -#include -#include -#include -#include -#include - -#include -#include - - -namespace ImageProcessingAtom -{ - // Note: PVRTexLib supports ETC formats, PVRTC formats and BC formats - // We haven't tested the performace to compress BC formats compare to CTSquisher - // For PVRTC formats, we only added PVRTC 1 support for now - // The compression for ePVRTPF_EAC_R11 and ePVRTPF_EAC_RG11 are very slow. It takes 7 and 14 minutes for a 2048x2048 texture. - EPVRTPixelFormat FindPvrPixelFormat(EPixelFormat fmt) - { - switch (fmt) - { - case ePixelFormat_PVRTC2: - return ePVRTPF_PVRTCI_2bpp_RGBA; - case ePixelFormat_PVRTC4: - return ePVRTPF_PVRTCI_4bpp_RGBA; - case ePixelFormat_EAC_R11: - return ePVRTPF_EAC_R11; - case ePixelFormat_EAC_RG11: - return ePVRTPF_EAC_RG11; - case ePixelFormat_ETC2: - return ePVRTPF_ETC2_RGB; - case ePixelFormat_ETC2a1: - return ePVRTPF_ETC2_RGB_A1; - case ePixelFormat_ETC2a: - return ePVRTPF_ETC2_RGBA; - default: - return ePVRTPF_NumCompressedPFs; - } - } - - bool PVRTCCompressor::IsCompressedPixelFormatSupported(EPixelFormat fmt) - { - return (FindPvrPixelFormat(fmt) != ePVRTPF_NumCompressedPFs); - } - - bool PVRTCCompressor::IsUncompressedPixelFormatSupported(EPixelFormat fmt) - { - //for uncompress format - if (fmt == ePixelFormat_R8G8B8A8) - { - return true; - } - return false; - } - - EPixelFormat PVRTCCompressor::GetSuggestedUncompressedFormat([[maybe_unused]] EPixelFormat compressedfmt, [[maybe_unused]] EPixelFormat uncompressedfmt) const - { - return ePixelFormat_R8G8B8A8; - } - - ColorSpace PVRTCCompressor::GetSupportedColorSpace([[maybe_unused]] EPixelFormat compressFormat) const - { - return ColorSpace::autoSelect; - } - - const char* PVRTCCompressor::GetName() const - { - return "PVRTCCompressor"; - } - - bool PVRTCCompressor::DoesSupportDecompress([[maybe_unused]] EPixelFormat fmtDst) - { - return true; - } - - IImageObjectPtr PVRTCCompressor::CompressImage(IImageObjectPtr srcImage, EPixelFormat fmtDst, - const CompressOption* compressOption) const - { - //validate input - EPixelFormat fmtSrc = srcImage->GetPixelFormat(); - - //src format need to be uncompressed and dst format need to compressed. - if (!IsUncompressedPixelFormatSupported(fmtSrc) || !IsCompressedPixelFormatSupported(fmtDst)) - { - return nullptr; - } - - IImageObjectPtr dstImage(srcImage->AllocateImage(fmtDst)); - - //determinate compression quality - pvrtexture::ECompressorQuality internalQuality = pvrtexture::eETCFast; - ICompressor::EQuality quality = ICompressor::eQuality_Normal; - AZ::Vector3 uniformWeights = AZ::Vector3(0.3333f, 0.3334f, 0.3333f); - AZ::Vector3 weights = uniformWeights; - bool isUniform = true; - //get setting from compression option - if (compressOption) - { - quality = compressOption->compressQuality; - weights = compressOption->rgbWeight; - isUniform = (weights == uniformWeights); - } - - if (IsETCFormat(fmtDst)) - { - if ((quality <= eQuality_Normal) && isUniform) - { - internalQuality = pvrtexture::eETCFast; - } - else if (quality <= eQuality_Normal) - { - internalQuality = pvrtexture::eETCNormal; - } - else if (isUniform) - { - internalQuality = pvrtexture::eETCSlow; - } - else - { - internalQuality = pvrtexture::eETCSlow; - } - } - else - { - if (quality == eQuality_Preview) - { - internalQuality = pvrtexture::ePVRTCFastest; - } - else if (quality == eQuality_Fast) - { - internalQuality = pvrtexture::ePVRTCFast; - } - else if (quality == eQuality_Normal) - { - internalQuality = pvrtexture::ePVRTCNormal; - } - else - { - internalQuality = pvrtexture::ePVRTCHigh; - } - } - - // setup color space - EPVRTColourSpace cspace = ePVRTCSpacelRGB; - if (srcImage->GetImageFlags() & EIF_SRGBRead) - { - cspace = ePVRTCSpacesRGB; - } - - //setup src texture for compression - const pvrtexture::PixelType srcPixelType('r', 'g', 'b', 'a', 8, 8, 8, 8); - const AZ::u32 dstMips = dstImage->GetMipCount(); - for (AZ::u32 mip = 0; mip < dstMips; ++mip) - { - const AZ::u32 width = srcImage->GetWidth(mip); - const AZ::u32 height = srcImage->GetHeight(mip); - - // Prepare source data - AZ::u8* srcMem; - uint32 srcPitch; - srcImage->GetImagePointer(mip, srcMem, srcPitch); - - const pvrtexture::CPVRTextureHeader srcHeader( - srcPixelType.PixelTypeID, // AZ::u64 u64PixelFormat, - width, // uint32 u32Height=1, - height, // uint32 u32Width=1, - 1, // uint32 u32Depth=1, - 1, // uint32 u32NumMipMaps=1, - 1, // uint32 u32NumArrayMembers=1, - 1, // uint32 u32NumFaces=1, - cspace, // EPVRTColourSpace eColourSpace=ePVRTCSpacelRGB, - ePVRTVarTypeUnsignedByteNorm, // EPVRTVariableType eChannelType=ePVRTVarTypeUnsignedByteNorm, - false); // bool bPreMultiplied=false); - - pvrtexture::CPVRTexture compressTexture(srcHeader, srcMem); - - //compressing - bool isSuccess = false; -#if AZ_TRAIT_IMAGEPROCESSING_SUPPORT_TRY_CATCH - try -#endif // AZ_TRAIT_IMAGEPROCESSING_SUPPORT_TRY_CATCH - { - isSuccess = pvrtexture::Transcode( - compressTexture, - pvrtexture::PixelType(FindPvrPixelFormat(fmtDst)), - ePVRTVarTypeUnsignedByteNorm, - cspace, - internalQuality); - } -#if AZ_TRAIT_IMAGEPROCESSING_SUPPORT_TRY_CATCH - catch (...) - { - AZ_Error("Image Processing", false, "Unknown exception in PVRTexLib"); - return nullptr; - } -#endif // AZ_TRAIT_IMAGEPROCESSING_SUPPORT_TRY_CATCH - - if (!isSuccess) - { - AZ_Error("Image Processing", false, "Failed to compress image with PVRTexLib."); - return nullptr; - } - - // Getting compressed data - const void* const compressedData = compressTexture.getDataPtr(); - if (!compressedData) - { - AZ_Error("Image Processing", false, "Failed to obtain compressed image data by using PVRTexLib"); - return nullptr; - } - - const AZ::u32 compressedDataSize = compressTexture.getDataSize(); - if (dstImage->GetMipBufSize(mip) != compressedDataSize) - { - AZ_Error("Image Processing", false, "Compressed image data size mismatch while using PVRTexLib"); - return nullptr; - } - - //save compressed data to dst image - AZ::u8* dstMem; - AZ::u32 dstPitch; - dstImage->GetImagePointer(mip, dstMem, dstPitch); - memcpy(dstMem, compressedData, compressedDataSize); - } - - return dstImage; - } - - IImageObjectPtr PVRTCCompressor::DecompressImage(IImageObjectPtr srcImage, EPixelFormat fmtDst) const - { - //validate input - EPixelFormat fmtSrc = srcImage->GetPixelFormat(); //compressed - - if (!IsCompressedPixelFormatSupported(fmtSrc) || !IsUncompressedPixelFormatSupported(fmtDst)) - { - return nullptr; - } - - EPVRTColourSpace colorSpace = ePVRTCSpacelRGB; - if (srcImage->GetImageFlags() & EIF_SRGBRead) - { - colorSpace = ePVRTCSpacesRGB; - } - - IImageObjectPtr dstImage(srcImage->AllocateImage(fmtDst)); - - const AZ::u32 mipCount = dstImage->GetMipCount(); - for (AZ::u32 mip = 0; mip < mipCount; ++mip) - { - const AZ::u32 width = srcImage->GetWidth(mip); - const AZ::u32 height = srcImage->GetHeight(mip); - - // Preparing source compressed data - const pvrtexture::CPVRTextureHeader compressedHeader( - FindPvrPixelFormat(fmtSrc), // AZ::u64 u64PixelFormat, - width, // uint32 u32Height=1, - height, // uint32 u32Width=1, - 1, // uint32 u32Depth=1, - 1, // uint32 u32NumMipMaps=1, - 1, // uint32 u32NumArrayMembers=1, - 1, // uint32 u32NumFaces=1, - colorSpace, // EPVRTColourSpace eColourSpace=ePVRTCSpacelRGB, - ePVRTVarTypeUnsignedByteNorm, // EPVRTVariableType eChannelType=ePVRTVarTypeUnsignedByteNorm, - false); // bool bPreMultiplied=false); - - const AZ::u32 compressedDataSize = compressedHeader.getDataSize(); - if (srcImage->GetMipBufSize(mip) != compressedDataSize) - { - AZ_Error("Image Processing", false, "Decompressed image data size mismatch while using PVRTexLib"); - return nullptr; - } - - AZ::u8* srcMem; - AZ::u32 srcPitch; - srcImage->GetImagePointer(mip, srcMem, srcPitch); - pvrtexture::CPVRTexture cTexture(compressedHeader, srcMem); - - // Decompress - bool bOk = false; -#if AZ_TRAIT_IMAGEPROCESSING_SUPPORT_TRY_CATCH - try - { -#endif // AZ_TRAIT_IMAGEPROCESSING_SUPPORT_TRY_CATCH - bOk = pvrtexture::Transcode( - cTexture, - pvrtexture::PVRStandard8PixelType, - ePVRTVarTypeUnsignedByteNorm, - colorSpace, - pvrtexture::ePVRTCHigh); - -#if AZ_TRAIT_IMAGEPROCESSING_SUPPORT_TRY_CATCH - } - catch (...) - { - AZ_Error("Image Processing", false, "Unknown exception in PVRTexLib when decompressing"); - return nullptr; - } -#endif // AZ_TRAIT_IMAGEPROCESSING_SUPPORT_TRY_CATCH - - if (!bOk) - { - AZ_Error("Image Processing", false, "Failed to decompress an image by using PVRTexLib"); - return nullptr; - } - - // Getting decompressed data - const void* const pDecompressedData = cTexture.getDataPtr(); - if (!pDecompressedData) - { - AZ_Error("Image Processing", false, "Failed to obtain decompressed image data by using PVRTexLib"); - return nullptr; - } - - const AZ::u32 decompressedDataSize = cTexture.getDataSize(); - if (dstImage->GetMipBufSize(mip) != decompressedDataSize) - { - AZ_Error("Image Processing", false, "Decompressed image data size mismatch while using PVRTexLib"); - return nullptr; - } - - //save decompressed image to dst image - AZ::u8* dstMem; - AZ::u32 dstPitch; - dstImage->GetImagePointer(mip, dstMem, dstPitch); - memcpy(dstMem, pDecompressedData, decompressedDataSize); - } - - return dstImage; - } -} //namespace ImageProcessingAtom diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Compressors/PVRTC.h b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Compressors/PVRTC.h deleted file mode 100644 index cedd6d6643..0000000000 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Compressors/PVRTC.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) Contributors to the Open 3D Engine Project. - * For complete copyright and license terms please see the LICENSE at the root of this distribution. - * - * SPDX-License-Identifier: Apache-2.0 OR MIT - * - */ - -#pragma once - -#include - -namespace ImageProcessingAtom -{ - class PVRTCCompressor - : public ICompressor - { - public: - static bool IsCompressedPixelFormatSupported(EPixelFormat fmt); - static bool IsUncompressedPixelFormatSupported(EPixelFormat fmt); - static bool DoesSupportDecompress(EPixelFormat fmtDst); - - IImageObjectPtr CompressImage(IImageObjectPtr srcImage, EPixelFormat fmtDst, const CompressOption* compressOption) const override; - IImageObjectPtr DecompressImage(IImageObjectPtr srcImage, EPixelFormat fmtDst) const override; - - EPixelFormat GetSuggestedUncompressedFormat(EPixelFormat compressedfmt, EPixelFormat uncompressedfmt) const override; - ColorSpace GetSupportedColorSpace(EPixelFormat compressFormat) const final; - const char* GetName() const final; - }; -} // namespace ImageProcessingAtom diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Editor/PresetInfoPopup.cpp b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Editor/PresetInfoPopup.cpp index daaac5176b..2d5c89e8f5 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Editor/PresetInfoPopup.cpp +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Editor/PresetInfoPopup.cpp @@ -82,7 +82,6 @@ namespace ImageProcessingAtomEditor presetInfoText += "\n"; presetInfoText += QString("Suppress Engine Reduce: %1\n").arg(presetSettings->m_suppressEngineReduce ? "True" : "False"); presetInfoText += QString("Discard Alpha: %1\n").arg(presetSettings->m_discardAlpha ? "True" : "False"); - presetInfoText += QString("Is Power Of 2: %1\n").arg(presetSettings->m_isPowerOf2 ? "True" : "False"); presetInfoText += QString("Is Color Chart: %1\n").arg(presetSettings->m_isColorChart ? "True" : "False"); presetInfoText += QString("High Pass Mip: %1\n").arg(presetSettings->m_highPassMip); presetInfoText += QString("Gloss From Normal: %1\n").arg(presetSettings->m_glossFromNormals); diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/ImageLoader/DdsLoader.cpp b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/ImageLoader/DdsLoader.cpp index 20367227bd..d7f36398e3 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/ImageLoader/DdsLoader.cpp +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/ImageLoader/DdsLoader.cpp @@ -24,201 +24,6 @@ namespace ImageProcessingAtom { namespace DdsLoader { - IImageObject* CreateImageFromHeaderLegacy(DDS_HEADER_LEGACY& header, DDS_HEADER_DXT10& exthead) - { - EPixelFormat eFormat = ePixelFormat_Unknown; - AZ::u32 dwWidth, dwMips, dwHeight; - AZ::u32 imageFlags = header.dwReserved1; - AZ::Color colMinARGB, colMaxARGB; - - dwWidth = header.dwWidth; - dwHeight = header.dwHeight; - dwMips = 1; - if (header.dwHeaderFlags & DDS_HEADER_FLAGS_MIPMAP) - { - dwMips = header.dwMipMapCount; - } - if ((header.dwSurfaceFlags & DDS_SURFACE_FLAGS_CUBEMAP) && (header.dwCubemapFlags & DDS_CUBEMAP_ALLFACES)) - { - AZ_Assert(header.dwReserved1 & EIF_Cubemap, "Image flag should have cubemap flag"); - dwHeight *= 6; - } - - colMinARGB = AZ::Color(header.cMinColor[0], header.cMinColor[1], header.cMinColor[2], header.cMinColor[3]); - colMaxARGB = AZ::Color(header.cMaxColor[0], header.cMaxColor[1], header.cMaxColor[2], header.cMaxColor[3]); - - //get pixel format - { - // DX10 formats - if (header.ddspf.dwFourCC == FOURCC_DX10) - { - AZ::u32 dxgiFormat = exthead.dxgiFormat; - - //remove the SRGB from dxgi format and add sRGB to image flag - if (dxgiFormat == DXGI_FORMAT_R8G8B8A8_UNORM_SRGB) - { - dxgiFormat = DXGI_FORMAT_R8G8B8A8_UNORM; - } - else if (dxgiFormat == DXGI_FORMAT_BC1_UNORM_SRGB) - { - dxgiFormat = DXGI_FORMAT_BC1_UNORM; - } - else if (dxgiFormat == DXGI_FORMAT_BC2_UNORM_SRGB) - { - dxgiFormat = DXGI_FORMAT_BC2_UNORM; - } - else if (dxgiFormat == DXGI_FORMAT_BC3_UNORM_SRGB) - { - dxgiFormat = DXGI_FORMAT_BC3_UNORM; - } - else if (dxgiFormat == DXGI_FORMAT_BC7_UNORM_SRGB) - { - dxgiFormat = DXGI_FORMAT_BC7_UNORM; - } - - //add rgb flag if the dxgiformat was changed (which means it was sRGB format) above - if (dxgiFormat != exthead.dxgiFormat) - { - AZ_Assert(imageFlags & EIF_SRGBRead, "Image flags should have SRGBRead flag"); - imageFlags |= EIF_SRGBRead; - } - - //check all the pixel formats and find matching one - if (dxgiFormat != DXGI_FORMAT_UNKNOWN) - { - int i = 0; - for (; i < ePixelFormat_Count; i++) - { - const PixelFormatInfo* info = CPixelFormats::GetInstance().GetPixelFormatInfo((EPixelFormat)i); - if (static_cast(info->d3d10Format) == dxgiFormat) - { - eFormat = (EPixelFormat)i; - break; - } - } - if (i == ePixelFormat_Count) - { - AZ_Error("Image Processing", false, "Unhandled d3d10 format: %d", dxgiFormat); - return nullptr; - } - } - } - else - { - //for non-dx10 formats, use fourCC to find out its pixel formats - //go through all pixel formats and find a match with the fourcc - for (AZ::u32 formatIdx = 0; formatIdx < ePixelFormat_Count; formatIdx++) - { - const PixelFormatInfo* info = CPixelFormats::GetInstance().GetPixelFormatInfo((EPixelFormat)formatIdx); - if (header.ddspf.dwFourCC == info->fourCC) - { - eFormat = (EPixelFormat)formatIdx; - break; - } - } - - //legacy formats. This section is only used for load dds files converted by RC.exe - //our save to dds file function won't use any of these fourcc - if (eFormat == ePixelFormat_Unknown) - { - if (header.ddspf.dwFourCC == FOURCC_DXT1) - { - eFormat = ePixelFormat_BC1; - } - else if (header.ddspf.dwFourCC == FOURCC_DXT5) - { - eFormat = ePixelFormat_BC3; - } - else if (header.ddspf.dwFourCC == FOURCC_3DCP) - { - eFormat = ePixelFormat_BC4; - } - else if (header.ddspf.dwFourCC == FOURCC_3DC) - { - eFormat = ePixelFormat_BC5; - } - else if (header.ddspf.dwFourCC == DDS_FOURCC_R32F) - { - eFormat = ePixelFormat_R32F; - } - else if (header.ddspf.dwFourCC == DDS_FOURCC_G32R32F) - { - eFormat = ePixelFormat_R32G32F; - } - else if (header.ddspf.dwFourCC == DDS_FOURCC_A32B32G32R32F) - { - eFormat = ePixelFormat_R32G32B32A32F; - } - else if (header.ddspf.dwFourCC == DDS_FOURCC_R16F) - { - eFormat = ePixelFormat_R16F; - } - else if (header.ddspf.dwFourCC == DDS_FOURCC_G16R16F) - { - eFormat = ePixelFormat_R16G16F; - } - else if (header.ddspf.dwFourCC == DDS_FOURCC_A16B16G16R16F) - { - eFormat = ePixelFormat_R16G16B16A16F; - } - else if (header.ddspf.dwFourCC == DDS_FOURCC_A16B16G16R16) - { - eFormat = ePixelFormat_R16G16B16A16; - } - else if ((header.ddspf.dwFlags == DDS_RGBA || header.ddspf.dwFlags == DDS_RGB) - && header.ddspf.dwRGBBitCount == 32) - { - if (header.ddspf.dwRBitMask == 0x00ff0000) - { - eFormat = ePixelFormat_B8G8R8A8; - } - else - { - eFormat = ePixelFormat_R8G8B8A8; - } - } - else if (header.ddspf.dwFlags == DDS_LUMINANCEA && header.ddspf.dwRGBBitCount == 8) - { - eFormat = ePixelFormat_R8G8; - } - else if (header.ddspf.dwFlags == DDS_LUMINANCE && header.ddspf.dwRGBBitCount == 8) - { - eFormat = ePixelFormat_A8; - } - else if ((header.ddspf.dwFlags == DDS_A || header.ddspf.dwFlags == DDS_A_ONLY || header.ddspf.dwFlags == (DDS_A | DDS_A_ONLY)) && header.ddspf.dwRGBBitCount == 8) - { - eFormat = ePixelFormat_A8; - } - } - } - } - - if (eFormat == ePixelFormat_Unknown) - { - AZ_Error("Image Processing", false, "Unhandled dds pixel format fourCC: %d, flags: %d", - header.ddspf.dwFourCC, header.ddspf.dwFlags); - return nullptr; - } - - IImageObject* newImage = IImageObject::CreateImage(dwWidth, dwHeight, dwMips, eFormat); - - if (dwMips != newImage->GetMipCount()) - { - AZ_Error("Image Processing", false, "Mipcount from image data doesn't match image size and pixelformat"); - delete newImage; - return nullptr; - } - - //set properties - newImage->SetImageFlags(imageFlags); - newImage->SetAverageBrightness(header.fAvgBrightness); - newImage->SetColorRange(colMinARGB, colMaxARGB); - newImage->SetNumPersistentMips(header.bNumPersistentMips); - - return newImage; - } - - bool IsExtensionSupported(const char* extension) { QString ext = QString(extension).toLower(); @@ -226,215 +31,6 @@ namespace ImageProcessingAtom return ext == "dds"; } - IImageObject* LoadImageFromFileLegacy(const AZStd::string& filename) - { - AZ::IO::SystemFile file; - file.Open(filename.c_str(), AZ::IO::SystemFile::SF_OPEN_READ_ONLY); - - AZ::IO::SystemFileStream fileLoadStream(&file, true); - if (!fileLoadStream.IsOpen()) - { - AZ_Warning("Image Processing", false, "%s: failed to open file %s", __FUNCTION__, filename.c_str()); - return nullptr; - } - - AZStd::string ext = ""; - AzFramework::StringFunc::Path::GetExtension(filename.c_str(), ext, false); - bool isAlphaImage = (ext == "a"); - - IImageObject* imageObj = LoadImageFromFileStreamLegacy(fileLoadStream); - - //load mips from seperated files if it's splitted - if (imageObj && imageObj->HasImageFlags(EIF_Splitted)) - { - AZStd::string baseName; - if (isAlphaImage) - { - baseName = filename.substr(0, filename.size() - 2); - } - else - { - baseName = filename; - } - - AZ::u32 externalMipCount = 0; - if (imageObj->GetNumPersistentMips() < imageObj->GetMipCount()) - { - externalMipCount = imageObj->GetMipCount() - imageObj->GetNumPersistentMips(); - } - //load other mips from files with number extensions - for (AZ::u32 mipIdx = 1; mipIdx <= externalMipCount; mipIdx++) - { - AZ::u32 mip = externalMipCount - mipIdx; - AZStd::string mipFileName = AZStd::string::format("%s.%d%s", baseName.c_str(), mipIdx, isAlphaImage ? "a" : ""); - - AZ::IO::SystemFile mipFile; - mipFile.Open(mipFileName.c_str(), AZ::IO::SystemFile::SF_OPEN_READ_ONLY); - - AZ::IO::SystemFileStream mipFileLoadStream(&mipFile, true); - - if (!mipFileLoadStream.IsOpen()) - { - AZ_Warning("Image Processing", false, "%s: failed to open mip file %s", __FUNCTION__, mipFileName.c_str()); - break; - } - - AZ::u32 pitch; - AZ::u8* mem; - imageObj->GetImagePointer(mip, mem, pitch); - AZ::u32 bufSize = imageObj->GetMipBufSize(mip); - mipFileLoadStream.Read(bufSize, mem); - } - } - - return imageObj; - } - - IImageObject* LoadImageFromFileStreamLegacy(AZ::IO::SystemFileStream& fileLoadStream) - { - if (fileLoadStream.GetLength() - fileLoadStream.GetCurPos() < sizeof(DDS_FILE_DESC_LEGACY)) - { - AZ_Error("Image Processing", false, "%s: Trying to load a none-DDS file", __FUNCTION__); - return nullptr; - } - - DDS_FILE_DESC_LEGACY desc; - DDS_HEADER_DXT10 exthead; - - AZ::IO::SizeType startPos = fileLoadStream.GetCurPos(); - fileLoadStream.Read(sizeof(desc.dwMagic), &desc.dwMagic); - - if (desc.dwMagic != FOURCC_DDS) - { - desc.dwMagic = FOURCC_DDS; - //the old cry .a file doesn't have "DDS " in the beginning of the file. - //so reset to previous position - fileLoadStream.Seek(startPos, AZ::IO::GenericStream::ST_SEEK_BEGIN); - } - - fileLoadStream.Read(sizeof(desc.header), &desc.header); - - if (!desc.IsValid()) - { - AZ_Error("Image Processing", false, "%s: Trying to load a none-DDS file", __FUNCTION__); - return nullptr; - } - - if (desc.header.IsDX10Ext()) - { - fileLoadStream.Read(sizeof(exthead), &exthead); - } - - IImageObject* outImage = CreateImageFromHeaderLegacy(desc.header, exthead); - - if (outImage == nullptr) - { - return nullptr; - } - - //load mip data - AZ::u32 mipStart = 0; - //There are at least three lowest mips are in the file if it was splitted. This is to load splitted dds file exported by legacy rc.exe - int numPersistentMips = outImage->GetNumPersistentMips(); - if (numPersistentMips == 0 && outImage->HasImageFlags(EIF_Splitted)) - { - outImage->SetNumPersistentMips(3); - } - - if (outImage->HasImageFlags(EIF_Splitted) - && outImage->GetMipCount() > outImage->GetNumPersistentMips()) - { - mipStart = outImage->GetMipCount() - outImage->GetNumPersistentMips(); - } - - AZ::u32 faces = 1; - if (outImage->HasImageFlags(EIF_Cubemap)) - { - faces = 6; - } - - for (AZ::u32 face = 0; face < faces; face++) - { - for (AZ::u32 mip = mipStart; mip < outImage->GetMipCount(); ++mip) - { - AZ::u32 pitch; - AZ::u8* mem; - outImage->GetImagePointer(mip, mem, pitch); - AZ::u32 faceBufSize = outImage->GetMipBufSize(mip) / faces; - fileLoadStream.Read(faceBufSize, mem + faceBufSize * face); - } - } - - return outImage; - } - - IImageObject* LoadAttachedImageFromDdsFileLegacy(const AZStd::string& filename, IImageObjectPtr originImage) - { - if (originImage == nullptr) - { - return nullptr; - } - - AZ_Assert(originImage->HasImageFlags(EIF_AttachedAlpha), - "this function should only be called for origin image loaded from same file with attached alpha flag"); - - AZ::IO::SystemFile file; - file.Open(filename.c_str(), AZ::IO::SystemFile::SF_OPEN_READ_ONLY); - - AZ::IO::SystemFileStream fileLoadStream(&file, true); - if (!fileLoadStream.IsOpen()) - { - AZ_Warning("Image Processing", false, "%s: failed to open file %s", __FUNCTION__, filename.c_str()); - return nullptr; - } - - DDS_FILE_DESC_LEGACY desc; - DDS_HEADER_DXT10 exthead; - - fileLoadStream.Read(sizeof(desc), &desc); - if (desc.dwMagic != FOURCC_DDS) - { - AZ_Error("Image Processing", false, "%s:Trying to load a none-DDS file", __FUNCTION__); - return nullptr; - } - - if (desc.header.IsDX10Ext()) - { - fileLoadStream.Read(sizeof(exthead), &exthead); - } - - //skip size for originImage's mip data - for (AZ::u32 mip = 0; mip < originImage->GetMipCount(); ++mip) - { - AZ::u32 bufSize = originImage->GetMipBufSize(mip); - fileLoadStream.Seek(bufSize, AZ::IO::GenericStream::ST_SEEK_CUR); - } - - IImageObject* alphaImage = nullptr; - - AZ::u32 marker = 0; - fileLoadStream.Read(4, &marker); - if (marker == FOURCC_CExt) // marker for the start of O3DE Extended data - { - fileLoadStream.Read(4, &marker); - if (FOURCC_AttC == marker) // Attached Channel chunk - { - AZ::u32 size = 0; - fileLoadStream.Read(4, &size); - - alphaImage = LoadImageFromFileStreamLegacy(fileLoadStream); - fileLoadStream.Read(4, &marker); - } - - if (FOURCC_CEnd == marker) // marker for the end of O3DE Extended data - { - fileLoadStream.Read(4, &marker); - } - } - - return alphaImage; - } - // Create an image object from standard dds header IImageObject* CreateImageFromHeader(DDS_HEADER& header, DDS_HEADER_DXT10& exthead) { @@ -526,45 +122,14 @@ namespace ImageProcessingAtom { format = ePixelFormat_BC1; } - else if (header.ddspf.dwFourCC == FOURCC_DXT5) + else if (header.ddspf.dwFourCC == FOURCC_DXT5 || header.ddspf.dwFourCC == FOURCC_DXT4) { format = ePixelFormat_BC3; } - else if (header.ddspf.dwFourCC == FOURCC_3DCP) - { - format = ePixelFormat_BC4; - } - else if (header.ddspf.dwFourCC == FOURCC_3DC) - { - format = ePixelFormat_BC5; - } - else if (header.ddspf.dwFourCC == DDS_FOURCC_R32F) - { - format = ePixelFormat_R32F; - } - else if (header.ddspf.dwFourCC == DDS_FOURCC_G32R32F) - { - format = ePixelFormat_R32G32F; - } - else if (header.ddspf.dwFourCC == DDS_FOURCC_A32B32G32R32F) - { - format = ePixelFormat_R32G32B32A32F; - } - else if (header.ddspf.dwFourCC == DDS_FOURCC_R16F) - { - format = ePixelFormat_R16F; - } - else if (header.ddspf.dwFourCC == DDS_FOURCC_G16R16F) - { - format = ePixelFormat_R16G16F; - } - else if (header.ddspf.dwFourCC == DDS_FOURCC_A16B16G16R16F) - { - format = ePixelFormat_R16G16B16A16F; - } - else if (header.ddspf.dwFourCC == DDS_FOURCC_A16B16G16R16) + else { - format = ePixelFormat_R16G16B16A16; + AZ_Error("Image Processing", false, "unsupported fourCC format: 0x%x", header.ddspf.dwFourCC); + return nullptr; } } else diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/ImageLoader/ImageLoaders.h b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/ImageLoader/ImageLoaders.h index 587c9b551b..b860e82631 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/ImageLoader/ImageLoaders.h +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/ImageLoader/ImageLoaders.h @@ -44,11 +44,6 @@ namespace ImageProcessingAtom { bool IsExtensionSupported(const char* extension); IImageObject* LoadImageFromFile(const AZStd::string& filename); - - // These functions are for loading legacy O3DE dds files - IImageObject* LoadImageFromFileLegacy(const AZStd::string& filename); - IImageObject* LoadImageFromFileStreamLegacy(AZ::IO::SystemFileStream& fileLoadStream); - IImageObject* LoadAttachedImageFromDdsFileLegacy(const AZStd::string& filename, IImageObjectPtr originImage); };// namespace DdsLoader // Load .exr files to an image object diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Platform/Mac/platform_mac.cmake b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Platform/Mac/platform_mac.cmake index 7008b352a8..7a325ca97e 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Platform/Mac/platform_mac.cmake +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Platform/Mac/platform_mac.cmake @@ -5,8 +5,3 @@ # SPDX-License-Identifier: Apache-2.0 OR MIT # # - -set(LY_COMPILE_OPTIONS - PRIVATE - -fexceptions #ImageLoader/ExrLoader.cpp and PVRTC.cpp uses exceptions -) diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/DDSHeader.h b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/DDSHeader.h index 785424a9b1..0f953897c9 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/DDSHeader.h +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/DDSHeader.h @@ -237,14 +237,8 @@ namespace ImageProcessingAtom const static AZ::u32 FOURCC_CEnd = IMAGE_BUIDER_MAKEFOURCC('C', 'E', 'n', 'd'); // O3DE extension end const static AZ::u32 FOURCC_AttC = IMAGE_BUIDER_MAKEFOURCC('A', 't', 't', 'C'); // Chunk Attached Channel - //Fourcc for pixel formats which aren't supported by dx10, such as astc formats, etc formats, pvrtc formats + //Fourcc for pixel formats which aren't supported by dx10, such as astc formats //They are used for dwFourCC of dds header's DDS_PIXELFORMAT to identify non-dx10 pixel formats - const static AZ::u32 FOURCC_EAC_R11 = IMAGE_BUIDER_MAKEFOURCC('E', 'A', 'R', ' '); - const static AZ::u32 FOURCC_EAC_RG11 = IMAGE_BUIDER_MAKEFOURCC('E', 'A', 'R', 'G'); - const static AZ::u32 FOURCC_ETC2 = IMAGE_BUIDER_MAKEFOURCC('E', 'T', '2', ' '); - const static AZ::u32 FOURCC_ETC2A = IMAGE_BUIDER_MAKEFOURCC('E', 'T', '2', 'A'); - const static AZ::u32 FOURCC_PVRTC2 = IMAGE_BUIDER_MAKEFOURCC('P', 'V', 'R', '2'); - const static AZ::u32 FOURCC_PVRTC4 = IMAGE_BUIDER_MAKEFOURCC('P', 'V', 'R', '4'); const static AZ::u32 FOURCC_ASTC_4x4 = IMAGE_BUIDER_MAKEFOURCC('A', 'S', '4', '4'); const static AZ::u32 FOURCC_ASTC_5x4 = IMAGE_BUIDER_MAKEFOURCC('A', 'S', '5', '4'); const static AZ::u32 FOURCC_ASTC_5x5 = IMAGE_BUIDER_MAKEFOURCC('A', 'S', '5', '5'); @@ -260,10 +254,10 @@ namespace ImageProcessingAtom const static AZ::u32 FOURCC_ASTC_12x10 = IMAGE_BUIDER_MAKEFOURCC('A', 'S', 'C', 'A'); const static AZ::u32 FOURCC_ASTC_12x12 = IMAGE_BUIDER_MAKEFOURCC('A', 'S', 'C', 'C'); - //legacy formats names. they are only used for load rc.exe's dds formats + //legacy formats names. they are only used for load old dds formats. const static AZ::u32 FOURCC_DXT1 = IMAGE_BUIDER_MAKEFOURCC('D', 'X', 'T', '1'); + const static AZ::u32 FOURCC_DXT2 = IMAGE_BUIDER_MAKEFOURCC('D', 'X', 'T', '2'); const static AZ::u32 FOURCC_DXT3 = IMAGE_BUIDER_MAKEFOURCC('D', 'X', 'T', '3'); + const static AZ::u32 FOURCC_DXT4 = IMAGE_BUIDER_MAKEFOURCC('D', 'X', 'T', '4'); const static AZ::u32 FOURCC_DXT5 = IMAGE_BUIDER_MAKEFOURCC('D', 'X', 'T', '5'); - const static AZ::u32 FOURCC_3DCP = IMAGE_BUIDER_MAKEFOURCC('A', 'T', 'I', '1'); - const static AZ::u32 FOURCC_3DC = IMAGE_BUIDER_MAKEFOURCC('A', 'T', 'I', '2'); } diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/PixelFormatInfo.cpp b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/PixelFormatInfo.cpp index 94fce9bc1f..8a741d6637 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/PixelFormatInfo.cpp +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/PixelFormatInfo.cpp @@ -52,19 +52,6 @@ namespace ImageProcessingAtom return false; } - bool IsETCFormat(EPixelFormat fmt) - { - if (fmt == ePixelFormat_ETC2 - || fmt == ePixelFormat_ETC2a - || fmt == ePixelFormat_ETC2a1 - || fmt == ePixelFormat_EAC_R11 - || fmt == ePixelFormat_EAC_RG11) - { - return true; - } - return false; - } - PixelFormatInfo::PixelFormatInfo( uint32_t a_bitsPerPixel, uint32_t a_Channels, @@ -96,7 +83,6 @@ namespace ImageProcessingAtom , fourCC(a_fourCC) , eSampleType(a_eSampleType) , szName(a_szName) - , szLegacyName(a_szName) , szDescription(a_szDescription) , bCompressed(a_bCompressed) , bSelectable(a_bSelectable) @@ -122,15 +108,6 @@ namespace ImageProcessingAtom CPixelFormats::CPixelFormats() { InitPixelFormats(); - - m_removedLegacyFormats["DXT1"] = ePixelFormat_BC1; - m_removedLegacyFormats["DXT1a"] = ePixelFormat_BC1a; - m_removedLegacyFormats["DXT3"] = ePixelFormat_BC3; - m_removedLegacyFormats["DXT3t"] = ePixelFormat_BC3t; - m_removedLegacyFormats["DXT5"] = ePixelFormat_BC3; - m_removedLegacyFormats["DXT5t"] = ePixelFormat_BC3t; - m_removedLegacyFormats["3DCp"] = ePixelFormat_BC4; - m_removedLegacyFormats["3DC"] = ePixelFormat_BC5; } void CPixelFormats::InitPixelFormat(EPixelFormat format, const PixelFormatInfo& formatInfo) @@ -176,13 +153,6 @@ namespace ImageProcessingAtom InitPixelFormat(ePixelFormat_ASTC_10x10, PixelFormatInfo(0, 4, true, "?", 16, 16, 10, 10, 128, false, DXGI_FORMAT_UNKNOWN, FOURCC_ASTC_10x10, ESampleType::eSampleType_Compressed, "ASTC_10x10", "ASTC 10x10 compressed texture format", true, false)); InitPixelFormat(ePixelFormat_ASTC_12x10, PixelFormatInfo(0, 4, true, "?", 16, 16, 12, 10, 128, false, DXGI_FORMAT_UNKNOWN, FOURCC_ASTC_12x10, ESampleType::eSampleType_Compressed, "ASTC_12x10", "ASTC 12x10 compressed texture format", true, false)); InitPixelFormat(ePixelFormat_ASTC_12x12, PixelFormatInfo(0, 4, true, "?", 16, 16, 12, 12, 128, false, DXGI_FORMAT_UNKNOWN, FOURCC_ASTC_12x12, ESampleType::eSampleType_Compressed, "ASTC_12x12", "ASTC 12x12 compressed texture format", true, false)); - InitPixelFormat(ePixelFormat_PVRTC2, PixelFormatInfo(2, 4, true, "2", 16, 16, 8, 4, 64, true, DXGI_FORMAT_UNKNOWN, FOURCC_PVRTC2, ESampleType::eSampleType_Compressed, "PVRTC2", "POWERVR 2 bpp compressed texture format", true, false)); - InitPixelFormat(ePixelFormat_PVRTC4, PixelFormatInfo(4, 4, true, "2", 8, 8, 4, 4, 64, true, DXGI_FORMAT_UNKNOWN, FOURCC_PVRTC4, ESampleType::eSampleType_Compressed, "PVRTC4", "POWERVR 4 bpp compressed texture format", true, false)); - InitPixelFormat(ePixelFormat_EAC_R11, PixelFormatInfo(4, 1, true, "4", 4, 4, 4, 4, 64, false, DXGI_FORMAT_UNKNOWN, FOURCC_EAC_R11, ESampleType::eSampleType_Compressed, "EAC_R11", "EAC 4 bpp single channel texture format", true, false)); - InitPixelFormat(ePixelFormat_EAC_RG11, PixelFormatInfo(8, 2, false, "0", 4, 4, 4, 4, 128, false, DXGI_FORMAT_UNKNOWN, FOURCC_EAC_RG11, ESampleType::eSampleType_Compressed, "EAC_RG11", "EAC 8 bpp dual channel texture format", true, false)); - InitPixelFormat(ePixelFormat_ETC2, PixelFormatInfo(4, 3, false, "0", 4, 4, 4, 4, 64, false, DXGI_FORMAT_UNKNOWN, FOURCC_ETC2, ESampleType::eSampleType_Compressed, "ETC2", "ETC2 RGB 4 bpp compressed texture format", true, false)); - InitPixelFormat(ePixelFormat_ETC2a, PixelFormatInfo(8, 4, true, "4", 4, 4, 4, 4, 128, false, DXGI_FORMAT_UNKNOWN, FOURCC_ETC2A, ESampleType::eSampleType_Compressed, "ETC2a", "ETC2 RGBA 8 bpp compressed texture format", true, false)); - InitPixelFormat(ePixelFormat_ETC2a1, PixelFormatInfo(4, 4, true, "1", 4, 4, 4, 4, 64, false, DXGI_FORMAT_UNKNOWN, FOURCC_ETC2A, ESampleType::eSampleType_Compressed, "ETC2a1", "ETC2 RGBA1 8 bpp compressed texture format", true, false)); // Standardized Compressed DXGI Formats (DX10+) // Data in these compressed formats is hardware decodable on all DX10 chips, and manageable with the DX10-API. @@ -216,17 +186,6 @@ namespace ImageProcessingAtom InitPixelFormat(ePixelFormat_R32, PixelFormatInfo(32, 1, false, "0", 1, 1, 1, 1, 32, false, DXGI_FORMAT_FORCE_UINT, FOURCC_DX10, ESampleType::eSampleType_Uint32, "R32", "32-bit red only", false, false)); - //Set legacy name it can be used for convertion - m_pixelFormatInfo[ePixelFormat_R8G8B8A8].szLegacyName = "A8R8G8B8"; - m_pixelFormatInfo[ePixelFormat_R8G8B8X8].szLegacyName = "X8R8G8B8"; - m_pixelFormatInfo[ePixelFormat_R8G8].szLegacyName = "G8R8"; - m_pixelFormatInfo[ePixelFormat_R16G16B16A16].szLegacyName = "A16B16G16R16"; - m_pixelFormatInfo[ePixelFormat_R16G16].szLegacyName = "G16R16"; - m_pixelFormatInfo[ePixelFormat_R32G32B32A32F].szLegacyName = "A32B32G32R32F"; - m_pixelFormatInfo[ePixelFormat_R32G32F].szLegacyName = "G32R32F"; - m_pixelFormatInfo[ePixelFormat_R16G16B16A16F].szLegacyName = "A16B16G16R16F"; - m_pixelFormatInfo[ePixelFormat_R16G16F].szLegacyName = "G16R16F"; - //validate all pixel formats are proper initialized for (int i = 0; i < ePixelFormat_Count; ++i) { @@ -249,23 +208,6 @@ namespace ImageProcessingAtom return ePixelFormat_Unknown; } - EPixelFormat CPixelFormats::FindPixelFormatByLegacyName(const char* name) - { - if (m_removedLegacyFormats.find(name) != m_removedLegacyFormats.end()) - { - return m_removedLegacyFormats[name]; - } - - for (int i = 0; i < ePixelFormat_Count; ++i) - { - if (azstricmp(m_pixelFormatInfo[i].szLegacyName, name) == 0) - { - return (EPixelFormat)i; - } - } - return ePixelFormat_Unknown; - } - const PixelFormatInfo* CPixelFormats::GetPixelFormatInfo(EPixelFormat format) { AZ_Assert((format >= 0) && (format < ePixelFormat_Count), "Unsupport pixel format: %d", format); diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/PixelFormatInfo.h b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/PixelFormatInfo.h index ec1ec1bfaf..92c4bdd99e 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/PixelFormatInfo.h +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/PixelFormatInfo.h @@ -119,7 +119,6 @@ namespace ImageProcessingAtom bool bSquarePow2; // whether the pixel format requires image size be square and power of 2. DXGI_FORMAT d3d10Format; // the mapping d3d10 pixel format ESampleType eSampleType; // the data type used to present pixel - const char* szLegacyName; // name used for cryEngine const char* szName; // name for showing in editors const char* szDescription; // description for showing in editors bool bCompressed; // if it's a compressed format @@ -173,10 +172,6 @@ namespace ImageProcessingAtom bool IsFormatSigned(EPixelFormat fmt); bool IsFormatFloatingPoint(EPixelFormat fmt, bool bFullPrecision); - //find the pixel format for name used by Cry's RC.ini - //returns ePixelFormat_Unknown if the name was not found in registed format list - EPixelFormat FindPixelFormatByLegacyName(const char* name); - //find pixel format by its name EPixelFormat FindPixelFormatByName(const char* name); @@ -208,9 +203,6 @@ namespace ImageProcessingAtom //pixel format name to pixel format enum AZStd::map m_pixelFormatNameMap; - - // some formats from cryEngine were removed. using this name-pixelFormat mapping to look for new format - AZStd::map m_removedLegacyFormats; }; template diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/Utils.cpp b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/Utils.cpp index d282e9b02b..29ba8c3351 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/Utils.cpp +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Processing/Utils.cpp @@ -102,32 +102,6 @@ namespace ImageProcessingAtom case AZ::RHI::Format::ASTC_12x12_UNORM: return ePixelFormat_ASTC_12x12; - case AZ::RHI::Format::PVRTC2_UNORM_SRGB: - isSRGB = true; - case AZ::RHI::Format::PVRTC2_UNORM: - return ePixelFormat_PVRTC2; - case AZ::RHI::Format::PVRTC4_UNORM_SRGB: - isSRGB = true; - case AZ::RHI::Format::PVRTC4_UNORM: - return ePixelFormat_PVRTC4; - - case AZ::RHI::Format::EAC_R11_UNORM: - return ePixelFormat_EAC_R11; - case AZ::RHI::Format::EAC_RG11_UNORM: - return ePixelFormat_EAC_RG11; - case AZ::RHI::Format::ETC2_UNORM_SRGB: - isSRGB = true; - case AZ::RHI::Format::ETC2_UNORM: - return ePixelFormat_ETC2; - case AZ::RHI::Format::ETC2A_UNORM_SRGB: - isSRGB = true; - case AZ::RHI::Format::ETC2A_UNORM: - return ePixelFormat_ETC2a; - case AZ::RHI::Format::ETC2A1_UNORM_SRGB: - isSRGB = true; - case AZ::RHI::Format::ETC2A1_UNORM: - return ePixelFormat_ETC2a1; - case AZ::RHI::Format::BC1_UNORM_SRGB: isSRGB = true; case AZ::RHI::Format::BC1_UNORM: @@ -225,22 +199,6 @@ namespace ImageProcessingAtom case ePixelFormat_ASTC_12x12: return isSrgb ? RHI::Format::ASTC_12x12_UNORM_SRGB : RHI::Format::ASTC_12x12_UNORM; - case ePixelFormat_PVRTC2: - return isSrgb ? RHI::Format::PVRTC2_UNORM_SRGB : RHI::Format::PVRTC2_UNORM; - case ePixelFormat_PVRTC4: - return isSrgb ? RHI::Format::PVRTC4_UNORM_SRGB : RHI::Format::PVRTC4_UNORM; - - case ePixelFormat_EAC_R11: - return RHI::Format::EAC_R11_UNORM; - case ePixelFormat_EAC_RG11: - return RHI::Format::EAC_RG11_UNORM; - case ePixelFormat_ETC2: - return isSrgb ? RHI::Format::ETC2_UNORM_SRGB : RHI::Format::ETC2_UNORM; - case ePixelFormat_ETC2a: - return isSrgb ? RHI::Format::ETC2A_UNORM_SRGB : RHI::Format::ETC2A_UNORM; - case ePixelFormat_ETC2a1: - return isSrgb ? RHI::Format::ETC2A1_UNORM_SRGB : RHI::Format::ETC2A1_UNORM; - case ePixelFormat_BC1: case ePixelFormat_BC1a: return isSrgb ? RHI::Format::BC1_UNORM_SRGB : RHI::Format::BC1_UNORM; diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Tests/ImageProcessing_Test.cpp b/Gems/Atom/Asset/ImageProcessingAtom/Code/Tests/ImageProcessing_Test.cpp index c72e1dc309..9e4aa83908 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Tests/ImageProcessing_Test.cpp +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Tests/ImageProcessing_Test.cpp @@ -426,60 +426,6 @@ namespace UnitTest return isDifferent; } - - bool CompareDDSImage(const QString& imagePath1, const QString& imagePath2, QString& output) - { - IImageObjectPtr image1, alphaImage1, image2, alphaImage2; - - - image1 = IImageObjectPtr(DdsLoader::LoadImageFromFileLegacy(imagePath1.toUtf8().constData())); - if (image1 && image1->HasImageFlags(EIF_AttachedAlpha)) - { - if (image1->HasImageFlags(EIF_Splitted)) - { - alphaImage1 = IImageObjectPtr(DdsLoader::LoadImageFromFileLegacy(QString(imagePath1 + ".a").toUtf8().constData())); - } - else - { - alphaImage1 = IImageObjectPtr(DdsLoader::LoadAttachedImageFromDdsFileLegacy(imagePath1.toUtf8().constData(), image1)); - } - } - - image2 = IImageObjectPtr(DdsLoader::LoadImageFromFileLegacy(imagePath2.toUtf8().constData())); - if (image2 && image2->HasImageFlags(EIF_AttachedAlpha)) - { - if (image2->HasImageFlags(EIF_Splitted)) - { - alphaImage2 = IImageObjectPtr(DdsLoader::LoadImageFromFileLegacy(QString(imagePath2 + ".a").toUtf8().constData())); - } - else - { - alphaImage2 = IImageObjectPtr(DdsLoader::LoadAttachedImageFromDdsFileLegacy(imagePath2.toUtf8().constData(), image2)); - } - } - - if (!image1 && !image2) - { - output += "Cannot load both image file! "; - return false; - } - bool isDifferent = false; - - isDifferent = GetComparisonResult(image1, image2, output); - - - QFileInfo fi(imagePath1); - AZStd::string imageName = fi.baseName().toUtf8().constData(); - SaveImageToFile(image1, imageName + "_new"); - SaveImageToFile(image2, imageName + "_old"); - - if (alphaImage1 || alphaImage2) - { - isDifferent |= GetComparisonResult(alphaImage1, alphaImage2, output); - } - - return isDifferent; - } }; // test CPixelFormats related functions @@ -487,34 +433,6 @@ namespace UnitTest { CPixelFormats& pixelFormats = CPixelFormats::GetInstance(); - //verify names which was used for legacy rc.ini - ASSERT_TRUE(pixelFormats.FindPixelFormatByLegacyName("BC7t") == ePixelFormat_BC7t); - ASSERT_TRUE(pixelFormats.FindPixelFormatByLegacyName("ETC2A") == ePixelFormat_ETC2a); - ASSERT_TRUE(pixelFormats.FindPixelFormatByLegacyName("PVRTC4") == ePixelFormat_PVRTC4); - ASSERT_TRUE(pixelFormats.FindPixelFormatByLegacyName("BC1") == ePixelFormat_BC1); - ASSERT_TRUE(pixelFormats.FindPixelFormatByLegacyName("ETC2") == ePixelFormat_ETC2); - ASSERT_TRUE(pixelFormats.FindPixelFormatByLegacyName("BC1a") == ePixelFormat_BC1a); - ASSERT_TRUE(pixelFormats.FindPixelFormatByLegacyName("BC3") == ePixelFormat_BC3); - ASSERT_TRUE(pixelFormats.FindPixelFormatByLegacyName("BC7") == ePixelFormat_BC7); - ASSERT_TRUE(pixelFormats.FindPixelFormatByLegacyName("BC5s") == ePixelFormat_BC5s); - ASSERT_TRUE(pixelFormats.FindPixelFormatByLegacyName("EAC_RG11") == ePixelFormat_EAC_RG11); - ASSERT_TRUE(pixelFormats.FindPixelFormatByLegacyName("BC4") == ePixelFormat_BC4); - ASSERT_TRUE(pixelFormats.FindPixelFormatByLegacyName("EAC_R11") == ePixelFormat_EAC_R11); - ASSERT_TRUE(pixelFormats.FindPixelFormatByLegacyName("A8R8G8B8") == ePixelFormat_R8G8B8A8); - ASSERT_TRUE(pixelFormats.FindPixelFormatByLegacyName("BC6UH") == ePixelFormat_BC6UH); - ASSERT_TRUE(pixelFormats.FindPixelFormatByLegacyName("R9G9B9E5") == ePixelFormat_R9G9B9E5); - ASSERT_TRUE(pixelFormats.FindPixelFormatByLegacyName("X8R8G8B8") == ePixelFormat_R8G8B8X8); - ASSERT_TRUE(pixelFormats.FindPixelFormatByLegacyName("A16B16G16R16F") == ePixelFormat_R16G16B16A16F); - ASSERT_TRUE(pixelFormats.FindPixelFormatByLegacyName("G8R8") == ePixelFormat_R8G8); - ASSERT_TRUE(pixelFormats.FindPixelFormatByLegacyName("G16R16") == ePixelFormat_R16G16); - ASSERT_TRUE(pixelFormats.FindPixelFormatByLegacyName("G16R16F") == ePixelFormat_R16G16F); - - //some legacy format need to be mapping to new format. - ASSERT_TRUE(pixelFormats.FindPixelFormatByLegacyName("DXT1") == ePixelFormat_BC1); - ASSERT_TRUE(pixelFormats.FindPixelFormatByLegacyName("DXT5") == ePixelFormat_BC3); - - //calculate mipmap count. no cubemap support at this moment - //for all the non-compressed textures, if there minimum required texture size is 1x1 for (uint32 i = 0; i < ePixelFormat_Count; i++) { @@ -543,13 +461,6 @@ namespace UnitTest } //check function IsImageSizeValid && EvaluateImageDataSize function - ASSERT_TRUE(pixelFormats.IsImageSizeValid(ePixelFormat_PVRTC4, 2, 1, false) == false); - ASSERT_TRUE(pixelFormats.IsImageSizeValid(ePixelFormat_PVRTC4, 4, 4, false) == false); - ASSERT_TRUE(pixelFormats.IsImageSizeValid(ePixelFormat_PVRTC4, 16, 16, false) == true); - ASSERT_TRUE(pixelFormats.IsImageSizeValid(ePixelFormat_PVRTC4, 16, 32, false) == false); - ASSERT_TRUE(pixelFormats.IsImageSizeValid(ePixelFormat_PVRTC4, 34, 34, false) == false); - ASSERT_TRUE(pixelFormats.IsImageSizeValid(ePixelFormat_PVRTC4, 256, 256, false) == true); - ASSERT_TRUE(pixelFormats.IsImageSizeValid(ePixelFormat_BC1, 2, 1, false) == false); ASSERT_TRUE(pixelFormats.IsImageSizeValid(ePixelFormat_BC1, 16, 16, false) == true); ASSERT_TRUE(pixelFormats.IsImageSizeValid(ePixelFormat_BC1, 16, 32, false) == true); @@ -834,9 +745,7 @@ namespace UnitTest if (formatInfo->bCompressed) { // skip ASTC formats which are tested in TestConvertASTCCompressor - if (!IsASTCFormat(pixelFormat) - && pixelFormat != ePixelFormat_PVRTC2 && pixelFormat != ePixelFormat_PVRTC4 - && !IsETCFormat(pixelFormat)) // skip ETC since it's very slow + if (!IsASTCFormat(pixelFormat)) { compressedFormats.push_back(pixelFormat); } @@ -1112,55 +1021,6 @@ namespace UnitTest } } - TEST_F(ImageProcessingTest, DISABLED_CompareOutputImage) - { - AZStd::string curretTextureFolder = "../TestAssets/TextureAssets/assets_new/textures"; - AZStd::string oldTextureFolder = "../TestAssets/TextureAssets/assets_old/textures"; - bool outputOnlyDifferent = false; - QDirIterator it(curretTextureFolder.c_str(), QStringList() << "*.dds", QDir::Files, QDirIterator::Subdirectories); - QFile f("../texture_comparison_output.csv"); - f.open(QIODevice::ReadWrite | QIODevice::Truncate); - // Write a header for csv file - f.write("Texture Name, Path, Mip new/old, MipDiff, Format new/old, Flag new/old, MemSize new/old, MemDiff, Error, AlphaMip new/old, AlphaMipDiff, AlphaFormat new/old, AlphaFlag new/old, AlphaMemSize new/old, AlphaMemDiff, AlphaError\r\n"); - int i = 0; - while (it.hasNext()) - { - i++; - it.next(); - - QString fileName = it.fileName(); - QString newFilePath = it.filePath(); - QString sharedPath = QString(newFilePath).remove(curretTextureFolder.c_str()); - QString oldFilePath = QString(oldTextureFolder.c_str()) + sharedPath; - QString output; - if (QFile::exists(oldFilePath)) - { - bool isDifferent = CompareDDSImage(newFilePath, oldFilePath, output); - if (outputOnlyDifferent && !isDifferent) - { - continue; - } - else - { - f.write(fileName.toUtf8().constData()); - f.write(","); - f.write(sharedPath.toUtf8().constData()); - f.write(output.toUtf8().constData()); - } - } - else - { - f.write(fileName.toUtf8().constData()); - f.write(","); - f.write(sharedPath.toUtf8().constData()); - output += ",No old file for comparison!"; - f.write(output.toUtf8().constData()); - } - f.write("\r\n"); - } - f.close(); - } - TEST_F(ImageProcessingTest, TextureSettingReflect_SerializingModernDataInAndOut_WritesAndParsesFileAccurately) { AZStd::string filepath = "test.xml"; diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/imageprocessing_files.cmake b/Gems/Atom/Asset/ImageProcessingAtom/Code/imageprocessing_files.cmake index ba9e9f29b7..aad400518d 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/imageprocessing_files.cmake +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/imageprocessing_files.cmake @@ -120,10 +120,6 @@ set(FILES Source/Compressors/Compressor.cpp Source/Compressors/CTSquisher.h Source/Compressors/CTSquisher.cpp - Source/Compressors/PVRTC.cpp - Source/Compressors/PVRTC.h - Source/Compressors/ETC2.cpp - Source/Compressors/ETC2.h Source/Compressors/CryTextureSquisher/ColorBlockRGBA4x4f.cpp Source/Compressors/CryTextureSquisher/ColorBlockRGBA4x4s.cpp Source/Compressors/CryTextureSquisher/ColorBlockRGBA4x4c.cpp diff --git a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/SkinnedMesh/SkinnedMeshOutputStreamManagerInterface.h b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/SkinnedMesh/SkinnedMeshOutputStreamManagerInterface.h index 3c3ddc7ddc..fea6d090f7 100644 --- a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/SkinnedMesh/SkinnedMeshOutputStreamManagerInterface.h +++ b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/SkinnedMesh/SkinnedMeshOutputStreamManagerInterface.h @@ -51,7 +51,7 @@ namespace AZ } //! Returns the buffer asset that is used for all skinned mesh outputs - virtual Data::Asset GetBufferAsset() const = 0; + virtual Data::Asset GetBufferAsset() = 0; //! Returns the buffer that is used for all skinned mesh outputs virtual Data::Instance GetBuffer() = 0; diff --git a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshOutputStreamManager.cpp b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshOutputStreamManager.cpp index c4eac30431..8bf518e277 100644 --- a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshOutputStreamManager.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshOutputStreamManager.cpp @@ -8,6 +8,8 @@ #include +#include + #include #include @@ -72,11 +74,32 @@ namespace AZ creator.End(m_bufferAsset); } + + // default value of 256mb supports roughly 42 character instances at 100,000 vertices per character x 64 bytes per vertex (12 byte position + 12 byte previous frame position + 12 byte normal + 16 byte tangent + 12 byte bitangent) + // This includes only the output of the skinning compute shader, not the input buffers or bone transforms + AZ_CVAR( + int, + r_skinnedMeshInstanceMemoryPoolSize, + 256, + nullptr, + AZ::ConsoleFunctorFlags::NeedsReload, + "The amount of memory in Mb available for all actor skinning data. Note that this must only be set once at application startup" + ); + void SkinnedMeshOutputStreamManager::Init() { - // 256mb supports roughly 42 character instances at 100,000 vertices per character x 64 bytes per vertex (12 byte position + 12 byte previous frame position + 12 byte normal + 16 byte tangent + 12 byte bitangent) - // This includes only the output of the skinning compute shader, not the input buffers or bone transforms - m_sizeInBytes = 256u * (1024u * 1024u); + } + + void SkinnedMeshOutputStreamManager::EnsureInit() + { + if (!m_needsInit) + { + return; + } + m_needsInit = false; + + const AZ::u64 sizeInMb = r_skinnedMeshInstanceMemoryPoolSize; + m_sizeInBytes = sizeInMb * (1024u * 1024u); CalculateAlignment(); @@ -90,6 +113,8 @@ namespace AZ RHI::VirtualAddress result; { AZStd::lock_guard lock(m_allocatorMutex); + + EnsureInit(); result = m_freeListAllocator.Allocate(byteCount, m_alignment); } @@ -127,13 +152,15 @@ namespace AZ } } - Data::Asset SkinnedMeshOutputStreamManager::GetBufferAsset() const + Data::Asset SkinnedMeshOutputStreamManager::GetBufferAsset() { + EnsureInit(); return m_bufferAsset; } Data::Instance SkinnedMeshOutputStreamManager::GetBuffer() { + EnsureInit(); if (!m_buffer) { m_buffer = RPI::Buffer::FindOrCreate(m_bufferAsset); diff --git a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshOutputStreamManager.h b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshOutputStreamManager.h index 3cec34a2e2..d0d00ead18 100644 --- a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshOutputStreamManager.h +++ b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshOutputStreamManager.h @@ -39,13 +39,14 @@ namespace AZ AZStd::intrusive_ptr Allocate(size_t byteCount) override; void DeAllocate(RHI::VirtualAddress allocation) override; void DeAllocateNoSignal(RHI::VirtualAddress allocation) override; - Data::Asset GetBufferAsset() const override; + Data::Asset GetBufferAsset() override; Data::Instance GetBuffer() override; private: // SystemTickBus void OnSystemTick() override; + void EnsureInit(); void GarbageCollect(); void CalculateAlignment(); void CreateBufferAsset(); @@ -58,6 +59,7 @@ namespace AZ size_t m_sizeInBytes = 0; bool m_memoryWasFreed = false; bool m_broadcastMemoryAvailableEvent = false; + bool m_needsInit = true; }; } // namespace Render } // namespace AZ diff --git a/Gems/Atom/RHI/Code/Source/RHI.Reflect/PhysicalDeviceDescriptor.cpp b/Gems/Atom/RHI/Code/Source/RHI.Reflect/PhysicalDeviceDescriptor.cpp index 17819ab1f5..9da4f583ac 100644 --- a/Gems/Atom/RHI/Code/Source/RHI.Reflect/PhysicalDeviceDescriptor.cpp +++ b/Gems/Atom/RHI/Code/Source/RHI.Reflect/PhysicalDeviceDescriptor.cpp @@ -86,6 +86,13 @@ namespace AZ PhysicalDeviceDriverValidator::ValidationResult PhysicalDeviceDriverValidator::ValidateDriverVersion(const PhysicalDeviceDescriptor& descriptor) const { + // [GFX TODO] Add driver info for other platforms besides Windows. Currently, avoid spamming warnings. + // ATOM-14967 [RHI][Metal] - Address driver version validator for Mac + if (m_driverInfo.size() == 0) + { + return ValidationResult::MissingInfo; + } + auto iter = m_driverInfo.find(descriptor.m_vendorId); if (iter == m_driverInfo.end()) diff --git a/Gems/Atom/RHI/Registry/Platform/Android/PhysicalDeviceDriverInfo.setreg b/Gems/Atom/RHI/Registry/Platform/Android/PhysicalDeviceDriverInfo.setreg new file mode 100644 index 0000000000..a0df86c923 --- /dev/null +++ b/Gems/Atom/RHI/Registry/Platform/Android/PhysicalDeviceDriverInfo.setreg @@ -0,0 +1,23 @@ +// +// Copyright (c) Contributors to the Open 3D Engine Project. +// For complete copyright and license terms please see the LICENSE at the root of this distribution. +// +// SPDX-License-Identifier: Apache-2.0 OR MIT +// +// +// + +{ + "O3DE": + { + "Atom": + { + "RHI": + { + "PhysicalDeviceDriverInfo": + { + } + } + } + } +} diff --git a/Gems/Atom/RHI/Registry/Platform/Linux/PhysicalDeviceDriverInfo.setreg b/Gems/Atom/RHI/Registry/Platform/Linux/PhysicalDeviceDriverInfo.setreg new file mode 100644 index 0000000000..a0df86c923 --- /dev/null +++ b/Gems/Atom/RHI/Registry/Platform/Linux/PhysicalDeviceDriverInfo.setreg @@ -0,0 +1,23 @@ +// +// Copyright (c) Contributors to the Open 3D Engine Project. +// For complete copyright and license terms please see the LICENSE at the root of this distribution. +// +// SPDX-License-Identifier: Apache-2.0 OR MIT +// +// +// + +{ + "O3DE": + { + "Atom": + { + "RHI": + { + "PhysicalDeviceDriverInfo": + { + } + } + } + } +} diff --git a/Gems/Atom/RHI/Registry/Platform/Mac/PhysicalDeviceDriverInfo.setreg b/Gems/Atom/RHI/Registry/Platform/Mac/PhysicalDeviceDriverInfo.setreg new file mode 100644 index 0000000000..a0df86c923 --- /dev/null +++ b/Gems/Atom/RHI/Registry/Platform/Mac/PhysicalDeviceDriverInfo.setreg @@ -0,0 +1,23 @@ +// +// Copyright (c) Contributors to the Open 3D Engine Project. +// For complete copyright and license terms please see the LICENSE at the root of this distribution. +// +// SPDX-License-Identifier: Apache-2.0 OR MIT +// +// +// + +{ + "O3DE": + { + "Atom": + { + "RHI": + { + "PhysicalDeviceDriverInfo": + { + } + } + } + } +} diff --git a/Gems/Atom/RHI/Registry/PhysicalDeviceDriverInfo.setreg b/Gems/Atom/RHI/Registry/Platform/Windows/PhysicalDeviceDriverInfo.setreg similarity index 100% rename from Gems/Atom/RHI/Registry/PhysicalDeviceDriverInfo.setreg rename to Gems/Atom/RHI/Registry/Platform/Windows/PhysicalDeviceDriverInfo.setreg diff --git a/Gems/Atom/RHI/Registry/Platform/iOS/PhysicalDeviceDriverInfo.setreg b/Gems/Atom/RHI/Registry/Platform/iOS/PhysicalDeviceDriverInfo.setreg new file mode 100644 index 0000000000..a0df86c923 --- /dev/null +++ b/Gems/Atom/RHI/Registry/Platform/iOS/PhysicalDeviceDriverInfo.setreg @@ -0,0 +1,23 @@ +// +// Copyright (c) Contributors to the Open 3D Engine Project. +// For complete copyright and license terms please see the LICENSE at the root of this distribution. +// +// SPDX-License-Identifier: Apache-2.0 OR MIT +// +// +// + +{ + "O3DE": + { + "Atom": + { + "RHI": + { + "PhysicalDeviceDriverInfo": + { + } + } + } + } +} diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Animation/EditorAttachmentComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Animation/EditorAttachmentComponent.cpp index b10a3e6695..fb21e74d8e 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Animation/EditorAttachmentComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Animation/EditorAttachmentComponent.cpp @@ -71,7 +71,7 @@ namespace AZ ->Attribute(AZ::Edit::Attributes::AutoExpand, true) ->Attribute( AZ::Edit::Attributes::HelpPageURL, - "https://o3de.org/docs/user-guide/components/reference/attachment/") + "https://o3de.org/docs/user-guide/components/reference/animation/attachment/") ->DataElement(0, &EditorAttachmentComponent::m_targetId, "Target entity", "Attach to this entity.") ->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorAttachmentComponent::OnTargetIdChanged) ->DataElement( diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/EditorAreaLightComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/EditorAreaLightComponent.cpp index 659c394cba..954ec4cad8 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/EditorAreaLightComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/EditorAreaLightComponent.cpp @@ -49,7 +49,7 @@ namespace AZ ->Attribute(Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/AreaLight.svg") ->Attribute(Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c)) ->Attribute(Edit::Attributes::AutoExpand, true) - ->Attribute(Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/atom/area-light/") + ->Attribute(Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/atom/light/") ; editContext->Class( diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/EditorDirectionalLightComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/EditorDirectionalLightComponent.cpp index ef9c73c0b4..2854244f6b 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/EditorDirectionalLightComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/CoreLights/EditorDirectionalLightComponent.cpp @@ -44,7 +44,7 @@ namespace AZ ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/Component_Placeholder.svg") // [GFX TODO][ATOM-1998] create icons. ->Attribute(Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c)) ->Attribute(Edit::Attributes::AutoExpand, true) - ->Attribute(Edit::Attributes::HelpPageURL, "https://") // [GFX TODO][ATOM-1998] create page + ->Attribute(Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/atom/directional-light/") // [GFX TODO][ATOM-1998] create page ; editContext->Class( diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/DiffuseGlobalIllumination/EditorDiffuseProbeGridComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/DiffuseGlobalIllumination/EditorDiffuseProbeGridComponent.cpp index 4363ac1a62..c14510f195 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/DiffuseGlobalIllumination/EditorDiffuseProbeGridComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/DiffuseGlobalIllumination/EditorDiffuseProbeGridComponent.cpp @@ -54,6 +54,7 @@ namespace AZ ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/Component_Placeholder.svg") ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c)) ->Attribute(AZ::Edit::Attributes::AutoExpand, true) + ->Attribute(Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/atom/diffuse-probe-grid/") ->Attribute(AZ::Edit::Attributes::PrimaryAssetType, AZ::AzTypeInfo::Uuid()) ->ClassElement(AZ::Edit::ClassElements::Group, "Probe Spacing") ->Attribute(AZ::Edit::Attributes::AutoExpand, true) diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Grid/EditorGridComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Grid/EditorGridComponent.cpp index 76f198852e..253f3ddd90 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Grid/EditorGridComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Grid/EditorGridComponent.cpp @@ -34,7 +34,7 @@ namespace AZ ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/Component_Placeholder.svg") ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c)) ->Attribute(AZ::Edit::Attributes::AutoExpand, true) - ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/atom/") + ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/atom/grid/") ; editContext->Class( diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ImageBasedLights/EditorImageBasedLightComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ImageBasedLights/EditorImageBasedLightComponent.cpp index 0547da6240..31eb0d51b1 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ImageBasedLights/EditorImageBasedLightComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ImageBasedLights/EditorImageBasedLightComponent.cpp @@ -34,7 +34,7 @@ namespace AZ ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/Component_Placeholder.svg") ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c)) ->Attribute(AZ::Edit::Attributes::AutoExpand, true) - ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/atom/") + ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/atom/global-skylight-ibl/") ; editContext->Class( diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.cpp index b7bf191bc9..8e5e7e2345 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponent.cpp @@ -86,7 +86,7 @@ namespace AZ ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/Component_Placeholder.svg") ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c)) ->Attribute(AZ::Edit::Attributes::AutoExpand, true) - ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/atom/") + ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/atom/material/") ->Attribute(AZ::Edit::Attributes::PrimaryAssetType, AZ::AzTypeInfo::Uuid()) ->DataElement(AZ::Edit::UIHandlers::MultiLineEdit, &EditorMaterialComponent::m_message, "Message", "") ->Attribute(AZ_CRC("PlaceholderText", 0xa23ec278), "Component cannot be edited with multiple entities selected") diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/EditorMeshComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/EditorMeshComponent.cpp index 3324bdfa8d..c89546264a 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/EditorMeshComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/EditorMeshComponent.cpp @@ -48,7 +48,7 @@ namespace AZ ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/Mesh.svg") ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c)) ->Attribute(AZ::Edit::Attributes::AutoExpand, true) - ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/atom/") + ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/atom/mesh/") ->Attribute(AZ::Edit::Attributes::PrimaryAssetType, AZ::AzTypeInfo::Uuid()) ->DataElement(AZ::Edit::UIHandlers::Button, &EditorMeshComponent::m_addMaterialComponentFlag, "Add Material Component", "Add Material Component") ->Attribute(AZ::Edit::Attributes::NameLabelOverride, "") diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/OcclusionCullingPlane/EditorOcclusionCullingPlaneComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/OcclusionCullingPlane/EditorOcclusionCullingPlaneComponent.cpp index 3908aafe89..85eb4a209a 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/OcclusionCullingPlane/EditorOcclusionCullingPlaneComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/OcclusionCullingPlane/EditorOcclusionCullingPlaneComponent.cpp @@ -37,6 +37,7 @@ namespace AZ ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/Component_Placeholder.svg") ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c)) ->Attribute(AZ::Edit::Attributes::AutoExpand, true) + ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/atom/occlusion-culling-plane/") ; editContext->Class( diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/Bloom/EditorBloomComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/Bloom/EditorBloomComponent.cpp index 22c8e2dceb..9018b0860b 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/Bloom/EditorBloomComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/Bloom/EditorBloomComponent.cpp @@ -32,7 +32,7 @@ namespace AZ ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/Component_Placeholder.svg") // [GFX TODO ATOM-2672][PostFX] need to create icons for PostProcessing. ->Attribute(Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c)) ->Attribute(Edit::Attributes::AutoExpand, true) - ->Attribute(Edit::Attributes::HelpPageURL, "https://") // [TODO ATOM-2672][PostFX] need create page for PostProcessing. + ->Attribute(Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/atom/bloom/") // [TODO ATOM-2672][PostFX] need create page for PostProcessing. ; editContext->Class( diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/DepthOfField/EditorDepthOfFieldComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/DepthOfField/EditorDepthOfFieldComponent.cpp index b3f77b1b8f..4b9ed8f6af 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/DepthOfField/EditorDepthOfFieldComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/DepthOfField/EditorDepthOfFieldComponent.cpp @@ -32,7 +32,7 @@ namespace AZ ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/Component_Placeholder.svg") // [GFX TODO ATOM-2672][PostFX] need to create icons for PostProcessing.y ->Attribute(Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c)) ->Attribute(Edit::Attributes::AutoExpand, true) - ->Attribute(Edit::Attributes::HelpPageURL, "https://") // [GFX TODO][ATOM-2672][PostFX] need create page for PostProcessing. + ->Attribute(Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/atom/depth-of-field/") // [GFX TODO][ATOM-2672][PostFX] need create page for PostProcessing. ; editContext->Class( diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/DisplayMapper/EditorDisplayMapperComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/DisplayMapper/EditorDisplayMapperComponent.cpp index e10a8b14c9..5ae3d88e50 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/DisplayMapper/EditorDisplayMapperComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/DisplayMapper/EditorDisplayMapperComponent.cpp @@ -34,7 +34,7 @@ namespace AZ ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/Component_Placeholder.svg") // [GFX TODO][ATOM-2672][PostFX] need to create icons for PostProcessing. ->Attribute(Edit::Attributes::AppearsInAddComponentMenu, AZStd::vector({ AZ_CRC("Level", 0x9aeacc13), AZ_CRC("Game", 0x232b318c) })) ->Attribute(Edit::Attributes::AutoExpand, true) - ->Attribute(Edit::Attributes::HelpPageURL, "https://") // [GFX TODO][ATOM-2672][PostFX] need to create page for PostProcessing. + ->Attribute(Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/atom/display-mapper/") // [GFX TODO][ATOM-2672][PostFX] need to create page for PostProcessing. ; editContext->Class( diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/EditorPostFxLayerComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/EditorPostFxLayerComponent.cpp index 344c5f8edd..b61cce839e 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/EditorPostFxLayerComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/EditorPostFxLayerComponent.cpp @@ -32,7 +32,7 @@ namespace AZ ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/Component_Placeholder.svg") ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c)) ->Attribute(AZ::Edit::Attributes::AutoExpand, true) - ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/atom/") + ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/atom/postfx-layer/") ; editContext->Class( diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/ExposureControl/EditorExposureControlComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/ExposureControl/EditorExposureControlComponent.cpp index 5bf3e0ec9c..d8bf13dd81 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/ExposureControl/EditorExposureControlComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/ExposureControl/EditorExposureControlComponent.cpp @@ -32,7 +32,7 @@ namespace AZ ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/Component_Placeholder.svg") // [GFX TODO ATOM-2672][PostFX] need to create icons for PostProcessing. ->Attribute(Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c)) ->Attribute(Edit::Attributes::AutoExpand, true) - ->Attribute(Edit::Attributes::HelpPageURL, "https://") // [TODO ATOM-2672][PostFX] need create page for PostProcessing. + ->Attribute(Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/atom/exposure-control/") // [TODO ATOM-2672][PostFX] need create page for PostProcessing. ; editContext->Class( diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/GradientWeightModifier/EditorGradientWeightModifierComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/GradientWeightModifier/EditorGradientWeightModifierComponent.cpp index 86b5ad554a..2f143491f5 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/GradientWeightModifier/EditorGradientWeightModifierComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/GradientWeightModifier/EditorGradientWeightModifierComponent.cpp @@ -32,7 +32,7 @@ namespace AZ ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/Component_Placeholder.svg") ->Attribute(Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c)) ->Attribute(Edit::Attributes::AutoExpand, true) - ->Attribute(Edit::Attributes::HelpPageURL, "") + ->Attribute(Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/atom/postfx-gradient-weight-modifier/") ; editContext->Class("GradientWeightModifierComponentController", "") diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/LookModification/EditorLookModificationComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/LookModification/EditorLookModificationComponent.cpp index 310762863a..b23ae9805b 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/LookModification/EditorLookModificationComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/LookModification/EditorLookModificationComponent.cpp @@ -32,7 +32,7 @@ namespace AZ ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/Component_Placeholder.svg") // [GFX TODO ATOM-2672][PostFX] need to create icons for PostProcessing. ->Attribute(Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c)) ->Attribute(Edit::Attributes::AutoExpand, true) - ->Attribute(Edit::Attributes::HelpPageURL, "https://") // [TODO ATOM-2672][PostFX] need to create page for PostProcessing. + ->Attribute(Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/atom/look-modification/") // [TODO ATOM-2672][PostFX] need to create page for PostProcessing. ; editContext->Class( diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/RadiusWeightModifier/EditorRadiusWeightModifierComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/RadiusWeightModifier/EditorRadiusWeightModifierComponent.cpp index 219ab6acb9..d4d86d6496 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/RadiusWeightModifier/EditorRadiusWeightModifierComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/RadiusWeightModifier/EditorRadiusWeightModifierComponent.cpp @@ -32,7 +32,7 @@ namespace AZ ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/Component_Placeholder.svg") ->Attribute(Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c)) ->Attribute(Edit::Attributes::AutoExpand, true) - ->Attribute(Edit::Attributes::HelpPageURL, "") + ->Attribute(Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/atom/radius-weight-modifier/") ; editContext->Class("RadiusWeightModifierComponentController", "") diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/ShapeWeightModifier/EditorShapeWeightModifierComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/ShapeWeightModifier/EditorShapeWeightModifierComponent.cpp index 46de0f0c68..842e1a6995 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/ShapeWeightModifier/EditorShapeWeightModifierComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/ShapeWeightModifier/EditorShapeWeightModifierComponent.cpp @@ -32,7 +32,7 @@ namespace AZ ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/Component_Placeholder.svg") ->Attribute(Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c)) ->Attribute(Edit::Attributes::AutoExpand, true) - ->Attribute(Edit::Attributes::HelpPageURL, "") + ->Attribute(Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/atom/postfx-shape-weight-modifier/") ; editContext->Class("ShapeWeightModifierComponentController", "") diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/Ssao/EditorSsaoComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/Ssao/EditorSsaoComponent.cpp index b018ac7a54..43f311a77e 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/Ssao/EditorSsaoComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/Ssao/EditorSsaoComponent.cpp @@ -32,7 +32,7 @@ namespace AZ ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/Component_Placeholder.svg") // [GFX TODO ATOM-2672][PostFX] need to create icons for PostProcessing. ->Attribute(Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game")) ->Attribute(Edit::Attributes::AutoExpand, true) - ->Attribute(Edit::Attributes::HelpPageURL, "https://") // [GFX TODO][ATOM-2672][PostFX] need create page for PostProcessing. + ->Attribute(Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/atom/ssao/") // [GFX TODO][ATOM-2672][PostFX] need create page for PostProcessing. ; editContext->Class( diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ReflectionProbe/EditorReflectionProbeComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ReflectionProbe/EditorReflectionProbeComponent.cpp index f325a20ac1..99e99abf4a 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ReflectionProbe/EditorReflectionProbeComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ReflectionProbe/EditorReflectionProbeComponent.cpp @@ -53,6 +53,7 @@ namespace AZ ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c)) ->Attribute(AZ::Edit::Attributes::AutoExpand, true) ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) + ->Attribute(Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/atom/reflection-probe/") ->Attribute(AZ::Edit::Attributes::PrimaryAssetType, AZ::AzTypeInfo::Uuid()) ->ClassElement(AZ::Edit::ClassElements::Group, "Cubemap Bake") ->Attribute(AZ::Edit::Attributes::AutoExpand, true) diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ScreenSpace/EditorDeferredFogComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ScreenSpace/EditorDeferredFogComponent.cpp index 208747abaa..139c24a959 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ScreenSpace/EditorDeferredFogComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ScreenSpace/EditorDeferredFogComponent.cpp @@ -32,7 +32,7 @@ namespace AZ ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/Component_Placeholder.svg") // [GFX TODO ATOM-2672][PostFX] need to create icons for PostProcessing. ->Attribute(Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c)) ->Attribute(Edit::Attributes::AutoExpand, true) - ->Attribute(Edit::Attributes::HelpPageURL, "https://") // [TODO][ATOM-13427] Create Wiki for Deferred Fog + ->Attribute(Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/atom/deferred-fog/") // [TODO][ATOM-13427] Create Wiki for Deferred Fog ; editContext->Class( diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Scripting/EditorEntityReferenceComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Scripting/EditorEntityReferenceComponent.cpp index 65491a9833..e297b7cc12 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Scripting/EditorEntityReferenceComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Scripting/EditorEntityReferenceComponent.cpp @@ -31,7 +31,7 @@ namespace AZ ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/Component_Placeholder.svg") ->Attribute(Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c)) ->Attribute(Edit::Attributes::AutoExpand, true) - ->Attribute(Edit::Attributes::HelpPageURL, "") + ->Attribute(Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/atom/entity-reference/") ; editContext->Class("EntityReferenceComponentController", "") diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SkyBox/EditorHDRiSkyboxComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SkyBox/EditorHDRiSkyboxComponent.cpp index a0ffb4e509..1cf14cca71 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SkyBox/EditorHDRiSkyboxComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SkyBox/EditorHDRiSkyboxComponent.cpp @@ -32,7 +32,7 @@ namespace AZ ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/Component_Placeholder.svg") ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c)) ->Attribute(AZ::Edit::Attributes::AutoExpand, true) - ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/atom/") + ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/atom/hdri-skybox/") ; editContext->Class( diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SkyBox/EditorPhysicalSkyComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SkyBox/EditorPhysicalSkyComponent.cpp index 28cb4121cf..81b182ccaa 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SkyBox/EditorPhysicalSkyComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SkyBox/EditorPhysicalSkyComponent.cpp @@ -32,7 +32,7 @@ namespace AZ ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/Component_Placeholder.svg") ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c)) ->Attribute(AZ::Edit::Attributes::AutoExpand, true) - ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/atom/") + ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/atom/physical-sky/") ; editContext->Class( diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/CMakeLists.txt b/Gems/AtomLyIntegration/EMotionFXAtom/Code/CMakeLists.txt index 5a08bab4fa..cc71e4f237 100644 --- a/Gems/AtomLyIntegration/EMotionFXAtom/Code/CMakeLists.txt +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/CMakeLists.txt @@ -54,11 +54,21 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS) INCLUDE_DIRECTORIES PRIVATE Source + Tools BUILD_DEPENDENCIES PRIVATE AZ::AzCore Gem::EMotionFX_Atom.Static + Gem::EMotionFX.Editor.Static + Gem::AtomToolsFramework.Static + Gem::AtomToolsFramework.Editor + Gem::Atom_Component_DebugCamera.Static + Gem::Atom_Feature_Common.Static + Gem::AtomLyIntegration_CommonFeatures.Static RUNTIME_DEPENDENCIES Gem::EMotionFX.Editor + COMPILE_DEFINITIONS + PUBLIC + EMOTIONFXATOM_EDITOR ) endif() diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/ActorModule.cpp b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/ActorModule.cpp index 49c2e52c1a..93481c1da6 100644 --- a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/ActorModule.cpp +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/ActorModule.cpp @@ -11,6 +11,9 @@ #include #include #include +#ifdef EMOTIONFXATOM_EDITOR +#include +#endif namespace AZ { @@ -28,7 +31,10 @@ namespace AZ : Module() { m_descriptors.insert(m_descriptors.end(), { - ActorSystemComponent::CreateDescriptor() + ActorSystemComponent::CreateDescriptor(), +#ifdef EMOTIONFXATOM_EDITOR + EMotionFXAtom::EditorSystemComponent::CreateDescriptor(), +#endif }); } @@ -39,6 +45,9 @@ namespace AZ { return ComponentTypeList{ azrtti_typeid(), +#ifdef EMOTIONFXATOM_EDITOR + azrtti_typeid(), +#endif }; } }; diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/Editor/EditorSystemComponent.cpp b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/Editor/EditorSystemComponent.cpp new file mode 100644 index 0000000000..edd2f074fe --- /dev/null +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/Editor/EditorSystemComponent.cpp @@ -0,0 +1,39 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include + +#include + +namespace AZ::EMotionFXAtom +{ + void EditorSystemComponent::Reflect(ReflectContext* context) + { + if (SerializeContext* serialize = azrtti_cast(context)) + { + serialize->Class()->Version(0); + } + } + + void EditorSystemComponent::Activate() + { + EMotionFX::Integration::SystemNotificationBus::Handler::BusConnect(); + } + + void EditorSystemComponent::Deactivate() + { + EMotionFX::Integration::SystemNotificationBus::Handler::BusDisconnect(); + } + + void EditorSystemComponent::OnRegisterPlugin() + { + EMStudio::PluginManager* pluginManager = EMStudio::EMStudioManager::GetInstance()->GetPluginManager(); + pluginManager->RegisterPlugin(aznew EMStudio::AtomRenderPlugin()); + } +} // namespace AZ::EMotionFXAtom diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/Editor/EditorSystemComponent.h b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/Editor/EditorSystemComponent.h new file mode 100644 index 0000000000..80ff7b0b8d --- /dev/null +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/Editor/EditorSystemComponent.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#pragma once + +#include +#include + +namespace AZ::EMotionFXAtom +{ + class EditorSystemComponent + : public Component + , private EMotionFX::Integration::SystemNotificationBus::Handler + { + public: + AZ_COMPONENT(EditorSystemComponent, "{1FAEC046-255D-4664-8F12-D16503C34431}"); + + static void Reflect(ReflectContext* context); + + protected: + // AZ::Component overrides + void Activate() override; + void Deactivate() override; + + // SystemNotificationBus::OnRegisterPlugin + void OnRegisterPlugin() override; + }; +} // namespace AZ::EMotionFXAtom diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportRenderer.cpp b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportRenderer.cpp new file mode 100644 index 0000000000..ab54b1fa38 --- /dev/null +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportRenderer.cpp @@ -0,0 +1,293 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + + +namespace EMStudio +{ + static constexpr float DepthNear = 0.01f; + + AnimViewportRenderer::AnimViewportRenderer(AZ::RPI::ViewportContextPtr viewportContext) + : m_windowContext(viewportContext->GetWindowContext()) + { + // Create a new entity context + m_entityContext = AZStd::make_unique(); + m_entityContext->InitContext(); + + // Create the scene + auto sceneSystem = AzFramework::SceneSystemInterface::Get(); + AZ_Assert(sceneSystem, "Unable to retrieve scene system."); + AZ::Outcome, AZStd::string> createSceneOutcome = sceneSystem->CreateScene("AnimViewport"); + AZ_Assert(createSceneOutcome, "%s", createSceneOutcome.GetError().data()); + m_frameworkScene = createSceneOutcome.TakeValue(); + m_frameworkScene->SetSubsystem(m_entityContext.get()); + + // Create and register a scene with all available feature processors + AZ::RPI::SceneDescriptor sceneDesc; + m_scene = AZ::RPI::Scene::CreateScene(sceneDesc); + m_scene->EnableAllFeatureProcessors(); + + // Link our RPI::Scene to the AzFramework::Scene + m_frameworkScene->SetSubsystem(m_scene); + + // Create a render pipeline from the specified asset for the window context and add the pipeline to the scene + AZStd::string defaultPipelineAssetPath = "passes/MainRenderPipeline.azasset"; + AZ::Data::Asset pipelineAsset = AZ::RPI::AssetUtils::LoadAssetByProductPath( + defaultPipelineAssetPath.c_str(), AZ::RPI::AssetUtils::TraceLevel::Error); + m_renderPipeline = AZ::RPI::RenderPipeline::CreateRenderPipelineForWindow(pipelineAsset, *m_windowContext.get()); + pipelineAsset.Release(); + m_scene->AddRenderPipeline(m_renderPipeline); + m_renderPipeline->SetDefaultView(viewportContext->GetDefaultView()); + + // Currently the scene has to be activated after render pipeline was added so some feature processors (i.e. imgui) can be + // initialized properly with pipeline's pass information. + m_scene->Activate(); + AZ::RPI::RPISystemInterface::Get()->RegisterScene(m_scene); + AzFramework::EntityContextId entityContextId = m_entityContext->GetContextId(); + + // Get the FeatureProcessors + m_meshFeatureProcessor = m_scene->GetFeatureProcessor(); + + // Configure tone mapper + AzFramework::EntityContextRequestBus::EventResult( + m_postProcessEntity, entityContextId, &AzFramework::EntityContextRequestBus::Events::CreateEntity, "postProcessEntity"); + AZ_Assert(m_postProcessEntity != nullptr, "Failed to create post process entity."); + + m_postProcessEntity->CreateComponent(AZ::Render::PostFxLayerComponentTypeId); + m_postProcessEntity->CreateComponent(AZ::Render::ExposureControlComponentTypeId); + m_postProcessEntity->CreateComponent(azrtti_typeid()); + m_postProcessEntity->Activate(); + + // Init directional light processor + m_directionalLightFeatureProcessor = m_scene->GetFeatureProcessor(); + + // Init display mapper processor + m_displayMapperFeatureProcessor = m_scene->GetFeatureProcessor(); + + // Init Skybox + m_skyboxFeatureProcessor = m_scene->GetFeatureProcessor(); + m_skyboxFeatureProcessor->Enable(true); + m_skyboxFeatureProcessor->SetSkyboxMode(AZ::Render::SkyBoxMode::Cubemap); + + // Create IBL + AzFramework::EntityContextRequestBus::EventResult( + m_iblEntity, entityContextId, &AzFramework::EntityContextRequestBus::Events::CreateEntity, "IblEntity"); + AZ_Assert(m_iblEntity != nullptr, "Failed to create ibl entity."); + + m_iblEntity->CreateComponent(AZ::Render::ImageBasedLightComponentTypeId); + m_iblEntity->CreateComponent(azrtti_typeid()); + m_iblEntity->Activate(); + + // Load light preset + AZ::Data::Asset lightingPresetAsset = AZ::RPI::AssetUtils::LoadAssetByProductPath( + "lightingpresets/default.lightingpreset.azasset", AZ::RPI::AssetUtils::TraceLevel::Warning); + const AZ::Render::LightingPreset* preset = lightingPresetAsset->GetDataAs(); + SetLightingPreset(preset); + + // Create grid + AzFramework::EntityContextRequestBus::EventResult( + m_gridEntity, entityContextId, &AzFramework::EntityContextRequestBus::Events::CreateEntity, "ViewportGrid"); + AZ_Assert(m_gridEntity != nullptr, "Failed to create grid entity."); + + AZ::Render::GridComponentConfig gridConfig; + gridConfig.m_gridSize = 4.0f; + gridConfig.m_axisColor = AZ::Color(0.5f, 0.5f, 0.5f, 1.0f); + gridConfig.m_primaryColor = AZ::Color(0.3f, 0.3f, 0.3f, 1.0f); + gridConfig.m_secondaryColor = AZ::Color(0.5f, 0.1f, 0.1f, 1.0f); + auto gridComponent = m_gridEntity->CreateComponent(AZ::Render::GridComponentTypeId); + gridComponent->SetConfiguration(gridConfig); + + m_gridEntity->CreateComponent(azrtti_typeid()); + m_gridEntity->Activate(); + + Reinit(); + } + + AnimViewportRenderer::~AnimViewportRenderer() + { + // Destroy all the entity we created. + m_entityContext->DestroyEntity(m_iblEntity); + m_entityContext->DestroyEntity(m_postProcessEntity); + m_entityContext->DestroyEntity(m_gridEntity); + for (AZ::Entity* entity : m_actorEntities) + { + m_entityContext->DestroyEntity(entity); + } + m_actorEntities.clear(); + m_entityContext->DestroyContext(); + + for (AZ::Render::DirectionalLightFeatureProcessorInterface::LightHandle& handle : m_lightHandles) + { + m_directionalLightFeatureProcessor->ReleaseLight(handle); + } + m_lightHandles.clear(); + + m_frameworkScene->UnsetSubsystem(m_scene); + + auto sceneSystem = AzFramework::SceneSystemInterface::Get(); + AZ_Assert(sceneSystem, "AtomViewportRenderer was unable to get the scene system during destruction."); + bool removeSuccess = sceneSystem->RemoveScene("AnimViewport"); + if (!removeSuccess) + { + AZ_Assert(false, "AtomViewportRenderer should be removed."); + } + + AZ::RPI::RPISystemInterface::Get()->UnregisterScene(m_scene); + m_scene = nullptr; + } + + void AnimViewportRenderer::Reinit() + { + ReinitActorEntities(); + ResetEnvironment(); + } + + void AnimViewportRenderer::ResetEnvironment() + { + // Reset environment + AZ::Transform iblTransform = AZ::Transform::CreateIdentity(); + AZ::TransformBus::Event(m_iblEntity->GetId(), &AZ::TransformBus::Events::SetLocalTM, iblTransform); + + const AZ::Matrix4x4 rotationMatrix = AZ::Matrix4x4::CreateIdentity(); + AZ::RPI::ScenePtr scene = AZ::RPI::RPISystemInterface::Get()->GetDefaultScene(); + auto skyBoxFeatureProcessorInterface = scene->GetFeatureProcessor(); + skyBoxFeatureProcessorInterface->SetCubemapRotationMatrix(rotationMatrix); + } + + void AnimViewportRenderer::ReinitActorEntities() + { + // 1. Destroy all the entities that do not point to any actorAsset anymore. + AZStd::set assetLookup; + AzFramework::EntityContext* entityContext = m_entityContext.get(); + const size_t numActors = EMotionFX::GetActorManager().GetNumActors(); + for (size_t i = 0; i < numActors; ++i) + { + assetLookup.emplace(EMotionFX::GetActorManager().GetActorAsset(i).GetId()); + } + m_actorEntities.erase( + AZStd::remove_if( + m_actorEntities.begin(), m_actorEntities.end(), + [&assetLookup, entityContext](AZ::Entity* entity) + { + EMotionFX::Integration::ActorComponent* actorComponent = + entity->FindComponent(); + if (assetLookup.find(actorComponent->GetActorAsset().GetId()) == assetLookup.end()) + { + entityContext->DestroyEntity(entity); + return true; + } + return false; + }), + m_actorEntities.end()); + + // 2. Create an entity for every actorAsset stored in actor manager. + for (size_t i = 0; i < numActors; ++i) + { + AZ::Data::Asset actorAsset = EMotionFX::GetActorManager().GetActorAsset(i); + if (!actorAsset->IsReady()) + { + continue; + } + + AZ::Entity* entity = FindActorEntity(actorAsset); + if (!entity) + { + m_actorEntities.emplace_back(CreateActorEntity(actorAsset)); + } + } + } + + AZ::Entity* AnimViewportRenderer::FindActorEntity(AZ::Data::Asset actorAsset) const + { + const auto foundEntity = AZStd::find_if( + begin(m_actorEntities), end(m_actorEntities), + [match = actorAsset](const AZ::Entity* entity) + { + EMotionFX::Integration::ActorComponent* actorComponent = entity->FindComponent(); + return actorComponent->GetActorAsset() == match; + }); + return foundEntity != end(m_actorEntities) ? (*foundEntity) : nullptr; + } + + AZ::Entity* AnimViewportRenderer::CreateActorEntity(AZ::Data::Asset actorAsset) + { + AZ::Entity* actorEntity = m_entityContext->CreateEntity(actorAsset->GetActor()->GetName()); + actorEntity->CreateComponent(azrtti_typeid()); + actorEntity->CreateComponent(AZ::Render::MaterialComponentTypeId); + actorEntity->CreateComponent(azrtti_typeid()); + actorEntity->Activate(); + + EMotionFX::Integration::ActorComponent* actorComponent = actorEntity->FindComponent(); + actorComponent->SetActorAsset(actorAsset); + + // Since this entity belongs to the animation editor, we need to set the isOwnByRuntime flag to false. + actorComponent->GetActorInstance()->SetIsOwnedByRuntime(false); + // Selet the actor instance in the command manager after it has been created. + AZStd::string outResult; + EMStudioManager::GetInstance()->GetCommandManager()->ExecuteCommandInsideCommand( + AZStd::string::format("Select -actorInstanceID %i", actorComponent->GetActorInstance()->GetID()).c_str(), outResult); + + return actorEntity; + } + + void AnimViewportRenderer::SetLightingPreset(const AZ::Render::LightingPreset* preset) + { + if (!preset) + { + AZ_Warning("AnimViewportRenderer", false, "Attempting to set invalid lighting preset."); + return; + } + + AZ::Render::ImageBasedLightFeatureProcessorInterface* iblFeatureProcessor = + m_scene->GetFeatureProcessor(); + AZ::Render::PostProcessFeatureProcessorInterface* postProcessFeatureProcessor = + m_scene->GetFeatureProcessor(); + AZ::Render::ExposureControlSettingsInterface* exposureControlSettingInterface = + postProcessFeatureProcessor->GetOrCreateSettingsInterface(m_postProcessEntity->GetId()) + ->GetOrCreateExposureControlSettingsInterface(); + + Camera::Configuration cameraConfig; + cameraConfig.m_fovRadians = AZ::Constants::HalfPi; + cameraConfig.m_nearClipDistance = DepthNear; + + preset->ApplyLightingPreset( + iblFeatureProcessor, m_skyboxFeatureProcessor, exposureControlSettingInterface, m_directionalLightFeatureProcessor, + cameraConfig, m_lightHandles, nullptr, AZ::RPI::MaterialPropertyIndex::Null, false); + } +} // namespace EMStudio diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportRenderer.h b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportRenderer.h new file mode 100644 index 0000000000..dd420c175c --- /dev/null +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportRenderer.h @@ -0,0 +1,88 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace AZ +{ + class Entity; + class Component; + + namespace Render + { + class DisplayMapperFeatureProcessorInterface; + class DirectionalLightFeatureProcessorInterface; + class MeshFeatureProcessorInterface; + } + + namespace RPI + { + class WindowContext; + } +} + +namespace EMStudio +{ + class AnimViewportRenderer + { + public: + AZ_CLASS_ALLOCATOR(AnimViewportRenderer, AZ::SystemAllocator, 0); + + AnimViewportRenderer(AZ::RPI::ViewportContextPtr viewportContext); + ~AnimViewportRenderer(); + + void Reinit(); + + private: + + // This function resets the light, camera and other environment settings. + void ResetEnvironment(); + + // This function creates in-editor entities for all actor assets stored in the actor manager, + // and deletes all the actor entities that no longer has an actor asset in the actor manager. + // Those entities are used in atom render viewport to visualize actors in animation editor. + void ReinitActorEntities(); + + AZ::Entity* CreateActorEntity(AZ::Data::Asset actorAsset); + + AZ::Entity* FindActorEntity(AZ::Data::Asset actorAsset) const; + void SetLightingPreset(const AZ::Render::LightingPreset* preset); + + AZStd::shared_ptr m_windowContext; + AZStd::unique_ptr m_entityContext; + AZStd::shared_ptr m_frameworkScene; + AZ::RPI::ScenePtr m_scene; + AZ::RPI::RenderPipelinePtr m_renderPipeline; + AZ::Render::DirectionalLightFeatureProcessorInterface* m_directionalLightFeatureProcessor = nullptr; + AZ::Render::DisplayMapperFeatureProcessorInterface* m_displayMapperFeatureProcessor = nullptr; + AZ::Render::SkyBoxFeatureProcessorInterface* m_skyboxFeatureProcessor = nullptr; + AZ::Render::MeshFeatureProcessorInterface* m_meshFeatureProcessor = nullptr; + + AZ::Entity* m_postProcessEntity = nullptr; + AZ::Entity* m_iblEntity = nullptr; + AZ::Entity* m_cameraEntity = nullptr; + AZ::Component* m_cameraComponent = nullptr; + AZ::Entity* m_modelEntity = nullptr; + AZ::Data::AssetId m_modelAssetId; + AZ::Entity* m_gridEntity = nullptr; + AZStd::vector m_actorEntities; + + AZStd::vector m_lightHandles; + }; +} diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportSettings.cpp b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportSettings.cpp new file mode 100644 index 0000000000..347e077af3 --- /dev/null +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportSettings.cpp @@ -0,0 +1,71 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#include + +namespace EMStudio::ViewportUtil +{ + constexpr AZStd::string_view CameraRotateSmoothnessSetting = "/Amazon/Preferences/Editor/Camera/RotateSmoothness"; + constexpr AZStd::string_view CameraTranslateSmoothnessSetting = "/Amazon/Preferences/Editor/Camera/TranslateSmoothness"; + constexpr AZStd::string_view CameraTranslateSmoothingSetting = "/Amazon/Preferences/Editor/Camera/TranslateSmoothing"; + constexpr AZStd::string_view CameraRotateSmoothingSetting = "/Amazon/Preferences/Editor/Camera/RotateSmoothing"; + + constexpr AZStd::string_view CameraOrbitLookIdSetting = "/Amazon/Preferences/Editor/Camera/OrbitLookId"; + constexpr AZStd::string_view CameraTranslateForwardIdSetting = "/Amazon/Preferences/Editor/Camera/CameraTranslateForwardId"; + constexpr AZStd::string_view CameraTranslateBackwardIdSetting = "/Amazon/Preferences/Editor/Camera/CameraTranslateBackwardId"; + constexpr AZStd::string_view CameraTranslateLeftIdSetting = "/Amazon/Preferences/Editor/Camera/CameraTranslateLeftId"; + constexpr AZStd::string_view CameraTranslateRightIdSetting = "/Amazon/Preferences/Editor/Camera/CameraTranslateRightId"; + constexpr AZStd::string_view CameraTranslateUpIdSetting = "/Amazon/Preferences/Editor/Camera/CameraTranslateUpId"; + constexpr AZStd::string_view CameraTranslateDownIdSetting = "/Amazon/Preferences/Editor/Camera/CameraTranslateUpDownId"; + constexpr AZStd::string_view CameraTranslateBoostIdSetting = "/Amazon/Preferences/Editor/Camera/TranslateBoostId"; + + AzFramework::TranslateCameraInputChannelIds BuildTranslateCameraInputChannelIds() + { + AzFramework::TranslateCameraInputChannelIds translateCameraInputChannelIds; + translateCameraInputChannelIds.m_leftChannelId = + AzFramework::InputChannelId(GetRegistry(CameraTranslateLeftIdSetting, AZStd::string("keyboard_key_alphanumeric_A")).c_str()); + translateCameraInputChannelIds.m_rightChannelId = + AzFramework::InputChannelId(GetRegistry(CameraTranslateRightIdSetting, AZStd::string("keyboard_key_alphanumeric_D")).c_str()); + translateCameraInputChannelIds.m_forwardChannelId = + AzFramework::InputChannelId(GetRegistry(CameraTranslateForwardIdSetting, AZStd::string("keyboard_key_alphanumeric_W")).c_str()); + translateCameraInputChannelIds.m_backwardChannelId = AzFramework::InputChannelId( + GetRegistry(CameraTranslateBackwardIdSetting, AZStd::string("keyboard_key_alphanumeric_S")).c_str()); + translateCameraInputChannelIds.m_upChannelId = + AzFramework::InputChannelId(GetRegistry(CameraTranslateUpIdSetting, AZStd::string("keyboard_key_alphanumeric_E")).c_str()); + translateCameraInputChannelIds.m_downChannelId = + AzFramework::InputChannelId(GetRegistry(CameraTranslateDownIdSetting, AZStd::string("keyboard_key_alphanumeric_Q")).c_str()); + translateCameraInputChannelIds.m_boostChannelId = + AzFramework::InputChannelId(GetRegistry(CameraTranslateBoostIdSetting, AZStd::string("keyboard_key_modifier_shift_l")).c_str()); + + return translateCameraInputChannelIds; + } + + float CameraRotateSmoothness() + { + return aznumeric_cast(GetRegistry(CameraRotateSmoothnessSetting, 5.0)); + } + + float CameraTranslateSmoothness() + { + return aznumeric_cast(GetRegistry(CameraTranslateSmoothnessSetting, 5.0)); + } + + bool CameraRotateSmoothingEnabled() + { + return GetRegistry(CameraRotateSmoothingSetting, true); + } + + bool CameraTranslateSmoothingEnabled() + { + return GetRegistry(CameraTranslateSmoothingSetting, true); + } + + AzFramework::InputChannelId BuildRotateCameraInputId() + { + return AzFramework::InputChannelId(GetRegistry(CameraOrbitLookIdSetting, AZStd::string("mouse_button_left")).c_str()); + } +} diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportSettings.h b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportSettings.h new file mode 100644 index 0000000000..5fa14a8aae --- /dev/null +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportSettings.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#pragma once +#include +#include + +namespace EMStudio::ViewportUtil +{ + template + AZStd::remove_cvref_t GetRegistry(const AZStd::string_view setting, T&& defaultValue) + { + AZStd::remove_cvref_t value = AZStd::forward(defaultValue); + if (const auto* registry = AZ::SettingsRegistry::Get()) + { + T potentialValue; + if (registry->Get(potentialValue, setting)) + { + value = AZStd::move(potentialValue); + } + } + + return value; + } + + float CameraRotateSmoothness(); + float CameraTranslateSmoothness(); + bool CameraRotateSmoothingEnabled(); + bool CameraTranslateSmoothingEnabled(); + + AzFramework::TranslateCameraInputChannelIds BuildTranslateCameraInputChannelIds(); + AzFramework::InputChannelId BuildRotateCameraInputId(); +} diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportWidget.cpp b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportWidget.cpp new file mode 100644 index 0000000000..6a6c9df901 --- /dev/null +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportWidget.cpp @@ -0,0 +1,103 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace EMStudio +{ + AnimViewportWidget::AnimViewportWidget(QWidget* parent) + : AtomToolsFramework::RenderViewportWidget(parent) + { + setObjectName(QString::fromUtf8("AtomViewportWidget")); + QSizePolicy qSize(QSizePolicy::Preferred, QSizePolicy::Preferred); + qSize.setHorizontalStretch(0); + qSize.setVerticalStretch(0); + qSize.setHeightForWidth(sizePolicy().hasHeightForWidth()); + setSizePolicy(qSize); + setAutoFillBackground(false); + setStyleSheet(QString::fromUtf8("")); + + m_renderer = AZStd::make_unique(GetViewportContext()); + + SetupCameras(); + SetupCameraController(); + } + + void AnimViewportWidget::SetupCameras() + { + m_rotateCamera = AZStd::make_shared(EMStudio::ViewportUtil::BuildRotateCameraInputId()); + + const auto translateCameraInputChannelIds = EMStudio::ViewportUtil::BuildTranslateCameraInputChannelIds(); + m_translateCamera = AZStd::make_shared( + translateCameraInputChannelIds, AzFramework::LookTranslation, AzFramework::TranslatePivotLook); + m_translateCamera.get()->m_translateSpeedFn = [] + { + return 3.0f; + }; + + m_orbitDollyScrollCamera = AZStd::make_shared(); + } + + void AnimViewportWidget::SetupCameraController() + { + auto controller = AZStd::make_shared(); + controller->SetCameraViewportContextBuilderCallback( + [viewportId = + GetViewportContext()->GetId()](AZStd::unique_ptr& cameraViewportContext) + { + cameraViewportContext = AZStd::make_unique(viewportId); + }); + + controller->SetCameraPriorityBuilderCallback( + [](AtomToolsFramework::CameraControllerPriorityFn& cameraControllerPriorityFn) + { + cameraControllerPriorityFn = AtomToolsFramework::DefaultCameraControllerPriority; + }); + + controller->SetCameraPropsBuilderCallback( + [](AzFramework::CameraProps& cameraProps) + { + cameraProps.m_rotateSmoothnessFn = [] + { + return EMStudio::ViewportUtil::CameraRotateSmoothness(); + }; + + cameraProps.m_translateSmoothnessFn = [] + { + return EMStudio::ViewportUtil::CameraTranslateSmoothness(); + }; + + cameraProps.m_rotateSmoothingEnabledFn = [] + { + return EMStudio::ViewportUtil::CameraRotateSmoothingEnabled(); + }; + + cameraProps.m_translateSmoothingEnabledFn = [] + { + return EMStudio::ViewportUtil::CameraTranslateSmoothingEnabled(); + }; + }); + + controller->SetCameraListBuilderCallback( + [this](AzFramework::Cameras& cameras) + { + cameras.AddCamera(m_rotateCamera); + cameras.AddCamera(m_translateCamera); + cameras.AddCamera(m_orbitDollyScrollCamera); + }); + GetControllerList()->Add(controller); + } +} // namespace EMStudio diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportWidget.h b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportWidget.h new file mode 100644 index 0000000000..7c2e085f84 --- /dev/null +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AnimViewportWidget.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#pragma once + +#include +#include + +namespace EMStudio +{ + class AnimViewportRenderer; + + class AnimViewportWidget + : public AtomToolsFramework::RenderViewportWidget + { + public: + AnimViewportWidget(QWidget* parent = nullptr); + AnimViewportRenderer* GetAnimViewportRenderer() { return m_renderer.get(); } + + private: + void SetupCameras(); + void SetupCameraController(); + + AZStd::unique_ptr m_renderer; + AZStd::shared_ptr m_rotateCamera; + AZStd::shared_ptr m_translateCamera; + AZStd::shared_ptr m_orbitDollyScrollCamera; + }; +} diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AtomRenderPlugin.cpp b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AtomRenderPlugin.cpp new file mode 100644 index 0000000000..91a34e16cd --- /dev/null +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AtomRenderPlugin.cpp @@ -0,0 +1,140 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include + +#include +#include +#include +#include +#include + +namespace EMStudio +{ + AZ_CLASS_ALLOCATOR_IMPL(AtomRenderPlugin, EMotionFX::EditorAllocator, 0); + + AtomRenderPlugin::AtomRenderPlugin() + : DockWidgetPlugin() + { + } + + AtomRenderPlugin::~AtomRenderPlugin() + { + + } + + const char* AtomRenderPlugin::GetName() const + { + return "Atom Render Window (Preview)"; + } + + uint32 AtomRenderPlugin::GetClassID() const + { + return static_cast(AtomRenderPlugin::CLASS_ID); + } + + const char* AtomRenderPlugin::GetCreatorName() const + { + return "O3DE"; + } + + float AtomRenderPlugin::GetVersion() const + { + return 1.0f; + } + + bool AtomRenderPlugin::GetIsClosable() const + { + return true; + } + + bool AtomRenderPlugin::GetIsFloatable() const + { + return true; + } + + bool AtomRenderPlugin::GetIsVertical() const + { + return false; + } + + EMStudioPlugin* AtomRenderPlugin::Clone() + { + return new AtomRenderPlugin(); + } + + EMStudioPlugin::EPluginType AtomRenderPlugin::GetPluginType() const + { + return EMStudioPlugin::PLUGINTYPE_RENDERING; + } + + void AtomRenderPlugin::ReinitRenderer() + { + m_animViewportWidget->GetAnimViewportRenderer()->Reinit(); + } + + bool AtomRenderPlugin::Init() + { + m_innerWidget = new QWidget(); + m_dock->setWidget(m_innerWidget); + + QVBoxLayout* verticalLayout = new QVBoxLayout(m_innerWidget); + verticalLayout->setSizeConstraint(QLayout::SetNoConstraint); + verticalLayout->setSpacing(1); + verticalLayout->setMargin(0); + m_animViewportWidget = new AnimViewportWidget(m_innerWidget); + verticalLayout->addWidget(m_animViewportWidget); + + // Register command callbacks. + m_importActorCallback = new ImportActorCallback(false); + m_removeActorCallback = new RemoveActorCallback(false); + EMStudioManager::GetInstance()->GetCommandManager()->RegisterCommandCallback("ImportActor", m_importActorCallback); + EMStudioManager::GetInstance()->GetCommandManager()->RegisterCommandCallback("RemoveActor", m_removeActorCallback); + + return true; + } + + // Command callbacks + bool ReinitAtomRenderPlugin() + { + EMStudioPlugin* plugin = EMStudio::GetPluginManager()->FindActivePlugin(static_cast(AtomRenderPlugin::CLASS_ID)); + if (!plugin) + { + AZ_Error("AtomRenderPlugin", false, "Cannot execute command callback. Atom render plugin does not exist."); + return false; + } + + AtomRenderPlugin* atomRenderPlugin = static_cast(plugin); + atomRenderPlugin->ReinitRenderer(); + + return true; + } + + bool AtomRenderPlugin::ImportActorCallback::Execute( + [[maybe_unused]] MCore::Command* command, [[maybe_unused]] const MCore::CommandLine& commandLine) + { + return ReinitAtomRenderPlugin(); + } + bool AtomRenderPlugin::ImportActorCallback::Undo( + [[maybe_unused]] MCore::Command* command, [[maybe_unused]] const MCore::CommandLine& commandLine) + { + return ReinitAtomRenderPlugin(); + } + + bool AtomRenderPlugin::RemoveActorCallback::Execute( + [[maybe_unused]] MCore::Command* command, [[maybe_unused]] const MCore::CommandLine& commandLine) + { + return ReinitAtomRenderPlugin(); + } + bool AtomRenderPlugin::RemoveActorCallback::Undo( + [[maybe_unused]] MCore::Command* command, [[maybe_unused]] const MCore::CommandLine& commandLine) + { + return ReinitAtomRenderPlugin(); + } +}// namespace EMStudio diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AtomRenderPlugin.h b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AtomRenderPlugin.h new file mode 100644 index 0000000000..1516b45e77 --- /dev/null +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Tools/EMStudio/AtomRenderPlugin.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#if !defined(Q_MOC_RUN) +#include +#include + +#include +#include +#endif + +namespace AZ +{ + class Entity; +} + +namespace EMStudio +{ + class AtomRenderPlugin + : public DockWidgetPlugin + { + public: + AZ_CLASS_ALLOCATOR_DECL + + enum + { + CLASS_ID = 0x32b0c04d + }; + AtomRenderPlugin(); + ~AtomRenderPlugin(); + + // Plugin information + const char* GetName() const override; + uint32 GetClassID() const override; + const char* GetCreatorName() const override; + float GetVersion() const override; + bool GetIsClosable() const override; + bool GetIsFloatable() const override; + bool GetIsVertical() const override; + bool Init() override; + EMStudioPlugin* Clone(); + EMStudioPlugin::EPluginType GetPluginType() const override; + + void ReinitRenderer(); + + private: + MCORE_DEFINECOMMANDCALLBACK(ImportActorCallback); + MCORE_DEFINECOMMANDCALLBACK(RemoveActorCallback); + ImportActorCallback* m_importActorCallback = nullptr; + RemoveActorCallback* m_removeActorCallback = nullptr; + QWidget* m_innerWidget = nullptr; + AnimViewportWidget* m_animViewportWidget = nullptr; + }; +}// namespace EMStudio diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/emotionfx_atom_editor_files.cmake b/Gems/AtomLyIntegration/EMotionFXAtom/Code/emotionfx_atom_editor_files.cmake index 6401beb232..a88581099f 100644 --- a/Gems/AtomLyIntegration/EMotionFXAtom/Code/emotionfx_atom_editor_files.cmake +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/emotionfx_atom_editor_files.cmake @@ -8,4 +8,14 @@ set(FILES Source/ActorModule.cpp + Source/Editor/EditorSystemComponent.h + Source/Editor/EditorSystemComponent.cpp + Tools/EMStudio/AtomRenderPlugin.h + Tools/EMStudio/AtomRenderPlugin.cpp + Tools/EMStudio/AnimViewportWidget.h + Tools/EMStudio/AnimViewportWidget.cpp + Tools/EMStudio/AnimViewportRenderer.h + Tools/EMStudio/AnimViewportRenderer.cpp + Tools/EMStudio/AnimViewportSettings.h + Tools/EMStudio/AnimViewportSettings.cpp ) diff --git a/Gems/AtomTressFX/Assets/Shaders/HairSimulationCompute.azsl b/Gems/AtomTressFX/Assets/Shaders/HairSimulationCompute.azsl index f8d178206a..496aa5c7da 100644 --- a/Gems/AtomTressFX/Assets/Shaders/HairSimulationCompute.azsl +++ b/Gems/AtomTressFX/Assets/Shaders/HairSimulationCompute.azsl @@ -31,7 +31,7 @@ // THE SOFTWARE. // //-------------------------------------------------------------------------------------- -#include +#include #include //-------------------------------------------------------------------------------------- diff --git a/Gems/AtomTressFX/Code/Rendering/HairFeatureProcessor.cpp b/Gems/AtomTressFX/Code/Rendering/HairFeatureProcessor.cpp index e4f1b02a88..7c8ce74f8e 100644 --- a/Gems/AtomTressFX/Code/Rendering/HairFeatureProcessor.cpp +++ b/Gems/AtomTressFX/Code/Rendering/HairFeatureProcessor.cpp @@ -226,27 +226,34 @@ namespace AZ // Prepare materials array for the per pass srg std::vector hairObjectsRenderMaterials; - uint32_t obj = 0; - for (auto objIter = m_hairRenderObjects.begin(); objIter != m_hairRenderObjects.end(); ++objIter, ++obj) + uint32_t objectIndex = 0; + for (auto& renderObject : m_hairRenderObjects) { - HairRenderObject* renderObject = objIter->get(); if (!renderObject->IsEnabled()) { continue; } - renderObject->Update(); + + renderObject->SetRenderIndex(objectIndex); // [To Do] Hair - update the following parameters for dynamic LOD control // should change or when parameters are being changed on the editor side. // float Distance = sqrtf( m_activeScene.scene->GetCameraPos().x * m_activeScene.scene->GetCameraPos().x + // m_activeScene.scene->GetCameraPos().y * m_activeScene.scene->GetCameraPos().y + // m_activeScene.scene->GetCameraPos().z * m_activeScene.scene->GetCameraPos().z); -// objIter->get()->UpdateRenderingParameters( -// renderingSettings, m_nScreenWidth * m_nScreenHeight * AVE_FRAGS_PER_PIXEL, m_deltaTime, Distance); + const float distanceFromCamera = 1.0f; // fixed distance until LOD mechanism is worked on + const float updateShadows = false; // same here - currently cheap self shadow approx + renderObject->UpdateRenderingParameters( nullptr, RESERVED_PIXELS_FOR_OIT, distanceFromCamera, updateShadows); - // this will be used for the constant buffer + // this will be used in the constant buffer to set the material array used by the resolve pass hairObjectsRenderMaterials.push_back(renderObject->GetHairRenderParams()); + + // The data update for the GPU bind - this should be the very last thing done after the + // data has been read and / or altered on the CPU side. + renderObject->Update(); + ++objectIndex; } + FillHairMaterialsArray(hairObjectsRenderMaterials); } @@ -532,4 +539,3 @@ namespace AZ } // namespace Hair } // namespace Render } // namespace AZ - diff --git a/Gems/AtomTressFX/Code/Rendering/HairRenderObject.cpp b/Gems/AtomTressFX/Code/Rendering/HairRenderObject.cpp index a89a3eb1b8..baf986daf6 100644 --- a/Gems/AtomTressFX/Code/Rendering/HairRenderObject.cpp +++ b/Gems/AtomTressFX/Code/Rendering/HairRenderObject.cpp @@ -878,6 +878,11 @@ namespace AZ const AMD::TressFXRenderingSettings* parameters, const int nodePoolSize, float distance, bool shadowUpdate /*= false*/) { + if (!parameters) + { + parameters = m_renderSettings; + } + // Update Render Parameters // If you alter FiberRadius make sure to change it also in the material properties // passed by the Feature Processor for the shading. @@ -888,7 +893,7 @@ namespace AZ // original TressFX lighting parameters - two specular lobes approximating // the Marschner R and and TRT lobes + diffuse component. - m_renderCB->MatKValue = {{{0.f, parameters->m_HairKDiffuse, parameters->m_HairKSpec1, parameters->m_HairSpecExp1}}}; + m_renderCB->MatKValue = { {{0.f, parameters->m_HairKDiffuse, parameters->m_HairKSpec1, parameters->m_HairSpecExp1}} }; m_renderCB->HairKs2 = parameters->m_HairKSpec2; m_renderCB->HairEx2 = parameters->m_HairSpecExp2; @@ -903,11 +908,13 @@ namespace AZ m_strandCB->TipPercentage = parameters->m_TipPercentage; m_strandCB->StrandUVTilingFactor = parameters->m_StrandUVTilingFactor; m_strandCB->FiberRatio = parameters->m_FiberRatio; + m_strandCB->EnableThinTip = parameters->m_EnableThinTip; + m_strandCB->EnableStrandUV = parameters->m_EnableStrandUV; // Reset LOD hair density for the frame m_LODHairDensity = 1.f; + float fiberRadius = parameters->m_FiberRadius; - float FiberRadius = parameters->m_FiberRadius; if (parameters->m_EnableHairLOD) { float MinLODDist = shadowUpdate ? @@ -922,23 +929,18 @@ namespace AZ float DistanceRatio = AZStd::min((distance - MinLODDist) / AZStd::max(MaxLODDist - MinLODDist, 0.00001f), 1.f); // Lerp: x + s(y-x) - float MaxLODFiberRadius = FiberRadius * (shadowUpdate ? parameters->m_ShadowLODWidthMultiplier : parameters->m_LODWidthMultiplier); - FiberRadius = FiberRadius + (DistanceRatio * (MaxLODFiberRadius - FiberRadius)); + float MaxLODFiberRadius = fiberRadius * (shadowUpdate ? parameters->m_ShadowLODWidthMultiplier : parameters->m_LODWidthMultiplier); + fiberRadius = fiberRadius + (DistanceRatio * (MaxLODFiberRadius - fiberRadius)); // Lerp: x + s(y-x) m_LODHairDensity = 1.f + (DistanceRatio * ((shadowUpdate ? parameters->m_ShadowLODPercent : parameters->m_LODPercent) - 1.f)); } } - m_strandCB->FiberRadius = FiberRadius; - - m_strandCB->NumVerticesPerStrand = m_NumVerticesPerStrand; // Always constant - m_strandCB->EnableThinTip = parameters->m_EnableThinTip; + m_strandCB->FiberRadius = fiberRadius; + m_strandCB->NumVerticesPerStrand = m_NumVerticesPerStrand; // Constant through the run per object m_strandCB->NodePoolSize = nodePoolSize; - m_strandCB->RenderParamsIndex = m_RenderIndex; // Always constant - - m_strandCB->EnableStrandUV = parameters->m_EnableStrandUV; - m_strandCB->EnableStrandTangent = parameters->m_EnableStrandTangent; + m_strandCB->RenderParamsIndex = m_RenderIndex; // Per Objects specific according to its index in the FP } //!===================================================================================== @@ -977,8 +979,10 @@ namespace AZ UpdateSimulationParameters(simSettings, SIMULATION_TIME_STEP); // [To Do] Hair - change to be dynamically calculated - const float distanceFromCamera = 1.0; + const float distanceFromCamera = 1.0f; const float updateShadows = false; + m_renderSettings = renderSettings; + m_simSettings = simSettings; UpdateRenderingParameters(renderSettings, RESERVED_PIXELS_FOR_OIT, distanceFromCamera, updateShadows); if (!GetShaders()) diff --git a/Gems/AtomTressFX/Code/Rendering/HairRenderObject.h b/Gems/AtomTressFX/Code/Rendering/HairRenderObject.h index c81d2b9079..fa4095eed0 100644 --- a/Gems/AtomTressFX/Code/Rendering/HairRenderObject.h +++ b/Gems/AtomTressFX/Code/Rendering/HairRenderObject.h @@ -269,6 +269,7 @@ namespace AZ void UpdateSimulationParameters(const AMD::TressFXSimulationSettings* settings, float timeStep); void SetWind(const Vector3& windDir, float windMag, int frame); + void SetRenderIndex(uint32_t renderIndex) { m_RenderIndex = renderIndex; } void ResetPositions() { m_simCB->g_ResetPositions = 1.0f; } void IncreaseSimulationFrame() @@ -315,6 +316,10 @@ namespace AZ float m_frameDeltaTime = 0.02; + //! The following are the configuration settings that might be required during the update. + AMD::TressFXSimulationSettings* m_simSettings = nullptr; + AMD::TressFXRenderingSettings* m_renderSettings = nullptr; + //! Hair asset information uint32_t m_TotalIndices = 0; uint32_t m_NumTotalVertices = 0; @@ -331,7 +336,7 @@ namespace AZ //! Controls reset / copy base hair state uint32_t m_SimulationFrame = 0; - // [To Do] - verify if still required + //! The index used as a look up into the material array during the resolve pass uint32_t m_RenderIndex = 0; //!----------------------------------------------------------------- diff --git a/Gems/Camera/Code/Source/EditorCameraComponent.cpp b/Gems/Camera/Code/Source/EditorCameraComponent.cpp index 3ac60cbff8..cf32fdedff 100644 --- a/Gems/Camera/Code/Source/EditorCameraComponent.cpp +++ b/Gems/Camera/Code/Source/EditorCameraComponent.cpp @@ -102,7 +102,7 @@ namespace Camera ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Editor/Icons/Components/Viewport/Camera.svg") ->Attribute(AZ::Edit::Attributes::AutoExpand, true) ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c)) - ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/camera/") + ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/camera/camera/") ->UIElement(AZ::Edit::UIHandlers::Button,"", "Sets the view to this camera") ->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorCameraComponent::OnPossessCameraButtonClicked) ->Attribute(AZ::Edit::Attributes::ButtonText, &EditorCameraComponent::GetCameraViewButtonText) diff --git a/Gems/CameraFramework/Code/Source/CameraRigComponent.cpp b/Gems/CameraFramework/Code/Source/CameraRigComponent.cpp index e8df095ed0..12c1e5861b 100644 --- a/Gems/CameraFramework/Code/Source/CameraRigComponent.cpp +++ b/Gems/CameraFramework/Code/Source/CameraRigComponent.cpp @@ -126,7 +126,7 @@ namespace Camera ->Attribute(AZ::Edit::Attributes::Icon, "Editor/Icons/Components/CameraRig.svg") ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Editor/Icons/Components/Viewport/CameraRig.png") ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c)) - ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/camera-rig/") + ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/camera/camera-rig/") ->DataElement(0, &CameraRigComponent::m_targetAcquirers, "Target acquirers", "A list of behaviors that define how a camera will select a target. They are executed in order until one succeeds") ->Attribute(AZ::Edit::Attributes::AutoExpand, true) diff --git a/Gems/EMotionFX/Code/CMakeLists.txt b/Gems/EMotionFX/Code/CMakeLists.txt index dace02b2ae..67d0ba6d85 100644 --- a/Gems/EMotionFX/Code/CMakeLists.txt +++ b/Gems/EMotionFX/Code/CMakeLists.txt @@ -109,6 +109,8 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS) AZ::AzToolsFramework Legacy::Editor.Headers 3rdParty::OpenGLInterface + Gem::AtomToolsFramework.Static + Gem::AtomToolsFramework.Editor COMPILE_DEFINITIONS PUBLIC EMFX_EMSTUDIOLYEMBEDDED diff --git a/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/ActorCommands.cpp b/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/ActorCommands.cpp index 85bb726822..57e206560a 100644 --- a/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/ActorCommands.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/ActorCommands.cpp @@ -22,6 +22,7 @@ #include "CommandManager.h" #include #include +#include namespace CommandSystem @@ -729,7 +730,8 @@ namespace CommandSystem m_oldWorkspaceDirtyFlag = GetCommandManager()->GetWorkspaceDirtyFlag(); // get rid of the actor - EMotionFX::GetActorManager().UnregisterActor(EMotionFX::GetActorManager().FindSharedActorByID(actor->GetID())); + const AZ::Data::AssetId actorAssetId = EMotionFX::GetActorManager().FindAssetIdByActorId(actor->GetID()); + EMotionFX::GetActorManager().UnregisterActor(actorAssetId); // mark the workspace as dirty GetCommandManager()->SetWorkspaceDirtyFlag(true); @@ -818,7 +820,6 @@ namespace CommandSystem { continue; } - // ignore visualization actor instances if (actorInstance->GetIsUsedForVisualization()) { @@ -849,12 +850,6 @@ namespace CommandSystem // get the current actor EMotionFX::Actor* actor = EMotionFX::GetActorManager().GetActor(i); - // ignore runtime-owned actors - if (actor->GetIsOwnedByRuntime()) - { - continue; - } - // ignore visualization actors if (actor->GetIsUsedForVisualization()) { diff --git a/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/ImporterCommands.cpp b/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/ImporterCommands.cpp index 168de93e38..ea3f816e86 100644 --- a/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/ImporterCommands.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/ImporterCommands.cpp @@ -17,6 +17,7 @@ #include #include "CommandManager.h" #include +#include namespace CommandSystem @@ -65,35 +66,29 @@ namespace CommandSystem filename = EMotionFX::EMotionFXManager::ResolvePath(filename.c_str()); } - // check if we have already loaded the actor - EMotionFX::Actor* actorFromManager = EMotionFX::GetActorManager().FindActorByFileName(filename.c_str()); - if (actorFromManager) + AZ::Data::AssetId actorAssetId; + EBUS_EVENT_RESULT( + actorAssetId, AZ::Data::AssetCatalogRequestBus, GetAssetIdByPath, filename.c_str(), AZ::Data::s_invalidAssetType, false); + if (!actorAssetId.IsValid()) { - AZStd::to_string(outResult, actorFromManager->GetID()); - return true; + outResult = AZStd::string::format("Cannot import actor. Cannot find asset at path %s.", filename.c_str()); + return false; } - // init the settings - EMotionFX::Importer::ActorSettings settings; - - // extract default values from the command syntax automatically, if they aren't specified explicitly - settings.m_loadLimits = parameters.GetValueAsBool("loadLimits", this); - settings.m_loadMorphTargets = parameters.GetValueAsBool("loadMorphTargets", this); - settings.m_loadSkeletalLoDs = parameters.GetValueAsBool("loadSkeletalLODs", this); - settings.m_dualQuatSkinning = parameters.GetValueAsBool("dualQuatSkinning", this); - - // try to load the actor - AZStd::shared_ptr actor {EMotionFX::GetImporter().LoadActor(filename.c_str(), &settings)}; - if (!actor) + // check if we have already loaded the actor + const size_t actorIndex = EMotionFX::GetActorManager().FindActorIndex(actorAssetId); + if (actorIndex != InvalidIndex) { - outResult = AZStd::string::format("Failed to load actor from '%s'. File may not exist at this path or may have incorrect permissions", filename.c_str()); - return false; + return true; } - // Because the actor is directly loaded from disk (without going through an actor asset), we need to ask for a blocking - // load for the asset that actor is depend on. - actor->Finalize(EMotionFX::Actor::LoadRequirement::RequireBlockingLoad); + // Do a blocking load of the asset. + AZ::Data::Asset actorAsset = + AZ::Data::AssetManager::Instance().GetAsset( + actorAssetId, AZ::Data::AssetLoadBehavior::Default); + actorAsset.BlockUntilLoadComplete(); + EMotionFX::Actor* actor = actorAsset->GetActor(); // set the actor id in case we have specified it as parameter if (actorID != MCORE_INVALIDINDEX32) { @@ -113,7 +108,6 @@ namespace CommandSystem GetCommandManager()->ExecuteCommandInsideCommand(AZStd::string::format("Select -actorID %i", actor->GetID()).c_str(), outResult); } - // mark the workspace as dirty m_oldWorkspaceDirtyFlag = GetCommandManager()->GetWorkspaceDirtyFlag(); GetCommandManager()->SetWorkspaceDirtyFlag(true); @@ -121,7 +115,8 @@ namespace CommandSystem // return the id of the newly created actor AZStd::to_string(outResult, actor->GetID()); - EMotionFX::GetActorManager().RegisterActor(AZStd::move(actor)); + // Register actor asset. + EMotionFX::GetActorManager().RegisterActor(AZStd::move(actorAsset)); return true; } @@ -145,14 +140,14 @@ namespace CommandSystem } // find the actor based on the given id - AZStd::shared_ptr actor = EMotionFX::GetActorManager().FindSharedActorByID(actorID); - if (actor == nullptr) + AZ::Data::AssetId actorAssetId = EMotionFX::GetActorManager().FindAssetIdByActorId(actorID); + if (!actorAssetId.IsValid()) { outResult = AZStd::string::format("Cannot remove actor. Actor ID %i is not valid.", actorID); return false; } - EMotionFX::GetActorManager().UnregisterActor(actor); + EMotionFX::GetActorManager().UnregisterActor(actorAssetId); // update our render actors AZStd::string updateRenderActorsResult; diff --git a/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/SelectionCommands.cpp b/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/SelectionCommands.cpp index bcc9769c44..7e1a9fe0f5 100644 --- a/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/SelectionCommands.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/SelectionCommands.cpp @@ -183,11 +183,6 @@ namespace CommandSystem for (size_t i = 0; i < numActors; ++i) { EMotionFX::Actor* actor = EMotionFX::GetActorManager().GetActor(i); - - if (actor->GetIsOwnedByRuntime()) - { - continue; - } if (unselect == false) { @@ -211,11 +206,6 @@ namespace CommandSystem return false; } - if (actor->GetIsOwnedByRuntime()) - { - return false; - } - if (unselect == false) { selection.AddActor(actor); @@ -244,11 +234,6 @@ namespace CommandSystem { EMotionFX::Actor* actor = EMotionFX::GetActorManager().GetActor(i); - if (actor->GetIsOwnedByRuntime()) - { - continue; - } - if (AzFramework::StringFunc::Equal(valueString.c_str(), actor->GetName(), false /* no case */)) { if (unselect == false) diff --git a/Gems/EMotionFX/Code/EMotionFX/Pipeline/RCExt/Actor/ActorGroupExporter.cpp b/Gems/EMotionFX/Code/EMotionFX/Pipeline/RCExt/Actor/ActorGroupExporter.cpp index 4cba50138a..3d5e4fa413 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Pipeline/RCExt/Actor/ActorGroupExporter.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Pipeline/RCExt/Actor/ActorGroupExporter.cpp @@ -8,7 +8,6 @@ #include #include -#include #include #include #include diff --git a/Gems/EMotionFX/Code/EMotionFX/Pipeline/RCExt/Actor/ActorGroupExporter.h b/Gems/EMotionFX/Code/EMotionFX/Pipeline/RCExt/Actor/ActorGroupExporter.h index e94137b317..d2db7d91c5 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Pipeline/RCExt/Actor/ActorGroupExporter.h +++ b/Gems/EMotionFX/Code/EMotionFX/Pipeline/RCExt/Actor/ActorGroupExporter.h @@ -8,7 +8,6 @@ #pragma once #include -#include #include #include #include @@ -44,7 +43,7 @@ namespace EMotionFX static AZStd::optional GetFirstProductByType( const ActorGroupExportContext& context, AZ::Data::AssetType type); - AutoRegisteredActor m_actor; + AZStd::shared_ptr m_actor; AZStd::vector m_actorMaterialReferences; }; } // namespace Pipeline diff --git a/Gems/EMotionFX/Code/EMotionFX/Source/Actor.cpp b/Gems/EMotionFX/Code/EMotionFX/Source/Actor.cpp index 3e592113b1..861271fdb3 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Source/Actor.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Source/Actor.cpp @@ -91,9 +91,6 @@ namespace EMotionFX m_simulatedObjectSetup = AZStd::make_shared(this); m_optimizeSkeleton = false; -#if defined(EMFX_DEVELOPMENT_BUILD) - m_isOwnedByRuntime = false; -#endif // EMFX_DEVELOPMENT_BUILD // make sure we have at least allocated the first LOD of materials and facial setups m_materials.reserve(4); // reserve space for 4 lods @@ -2074,25 +2071,6 @@ namespace EMotionFX return m_usedForVisualization; } - void Actor::SetIsOwnedByRuntime(bool isOwnedByRuntime) - { -#if defined(EMFX_DEVELOPMENT_BUILD) - m_isOwnedByRuntime = isOwnedByRuntime; -#else - AZ_UNUSED(isOwnedByRuntime); -#endif - } - - - bool Actor::GetIsOwnedByRuntime() const - { -#if defined(EMFX_DEVELOPMENT_BUILD) - return m_isOwnedByRuntime; -#else - return true; -#endif - } - const AZ::Aabb& Actor::GetStaticAabb() const { return m_staticAabb; diff --git a/Gems/EMotionFX/Code/EMotionFX/Source/Actor.h b/Gems/EMotionFX/Code/EMotionFX/Source/Actor.h index aa5c5df45c..dc83972d40 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Source/Actor.h +++ b/Gems/EMotionFX/Code/EMotionFX/Source/Actor.h @@ -720,12 +720,6 @@ namespace EMotionFX void SetIsUsedForVisualization(bool flag); bool GetIsUsedForVisualization() const; - /** - * Marks the actor as used by the engine runtime, as opposed to the tool suite. - */ - void SetIsOwnedByRuntime(bool isOwnedByRuntime); - bool GetIsOwnedByRuntime() const; - /** * Recursively find the parent bone that is enabled in a given LOD, starting from a given node. * For example if you have a finger bone, while the finger bones are disabled in the skeletal LOD, this function will return the index to the hand bone. @@ -940,8 +934,5 @@ namespace EMotionFX bool m_usedForVisualization; /**< Indicates if the actor is used for visualization specific things and is not used as a normal in-game actor. */ bool m_optimizeSkeleton; /**< Indicates if we should perform/ */ bool m_isReady = false; /**< If actor as well as its dependent files are fully loaded and initialized.*/ -#if defined(EMFX_DEVELOPMENT_BUILD) - bool m_isOwnedByRuntime; /**< Set if the actor is used/owned by the engine runtime. */ -#endif // EMFX_DEVELOPMENT_BUILD }; } // namespace EMotionFX diff --git a/Gems/EMotionFX/Code/EMotionFX/Source/ActorManager.cpp b/Gems/EMotionFX/Code/EMotionFX/Source/ActorManager.cpp index 681efb6039..184aac6e2c 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Source/ActorManager.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Source/ActorManager.cpp @@ -31,7 +31,6 @@ namespace EMotionFX SetScheduler(MultiThreadScheduler::Create()); // reserve memory - m_actors.reserve(512); m_actorInstances.reserve(1024); m_rootActorInstances.reserve(1024); } @@ -111,20 +110,23 @@ namespace EMotionFX // register the actor - void ActorManager::RegisterActor(AZStd::shared_ptr actor) + void ActorManager::RegisterActor(ActorAssetData actorAsset) { LockActors(); // check if we already registered - if (FindActorIndex(actor.get()) != InvalidIndex) + if (FindActorIndex(actorAsset.GetId()) != InvalidIndex) { - MCore::LogWarning("EMotionFX::ActorManager::RegisterActor() - The actor at location 0x%x has already been registered as actor, most likely already by the LoadActor of the importer.", actor.get()); + MCore::LogWarning( + "EMotionFX::ActorManager::RegisterActor() - The actor %s has already been registered as actor, most likely " + "already by the LoadActor of the importer.", + actorAsset->GetActor()->GetName()); UnlockActors(); return; } // register it - m_actors.emplace_back(AZStd::move(actor)); + m_actorAssets.emplace_back(AZStd::move(actorAsset)); UnlockActors(); } @@ -146,60 +148,82 @@ namespace EMotionFX Actor* ActorManager::FindActorByName(const char* actorName) const { // get the number of actors and iterate through them - const auto found = AZStd::find_if(m_actors.begin(), m_actors.end(), [actorName](const AZStd::shared_ptr& a) + const auto found = AZStd::find_if( + m_actorAssets.begin(), m_actorAssets.end(), + [actorName](const ActorAssetData& a) { - return a->GetNameString() == actorName; + return a->GetActor()->GetNameString() == actorName; }); - return (found != m_actors.end()) ? found->get() : nullptr; + return (found != m_actorAssets.end()) ? (*found)->GetActor() : nullptr; } // find the actor for a given filename Actor* ActorManager::FindActorByFileName(const char* fileName) const { - const auto found = AZStd::find_if(m_actors.begin(), m_actors.end(), [fileName](const AZStd::shared_ptr& a) + const auto found = AZStd::find_if( + m_actorAssets.begin(), m_actorAssets.end(), + [fileName](const ActorAssetData& a) { - return AzFramework::StringFunc::Equal(a->GetFileNameString().c_str(), fileName, false /* no case */); + return AzFramework::StringFunc::Equal( + a->GetActor()->GetFileNameString().c_str(), fileName, false /* no case */); }); - return (found != m_actors.end()) ? found->get() : nullptr; + return (found != m_actorAssets.end()) ? (*found)->GetActor() : nullptr; } // find the leader actor record for a given actor - size_t ActorManager::FindActorIndex(Actor* actor) const + size_t ActorManager::FindActorIndex(AZ::Data::AssetId assetId) const { - const auto found = AZStd::find_if(m_actors.begin(), m_actors.end(), [actor](const AZStd::shared_ptr& a) + const auto found = AZStd::find_if( + m_actorAssets.begin(), m_actorAssets.end(), + [assetId](const ActorAssetData& a) { - return a.get() == actor; + return a.GetId() == assetId; }); - return (found != m_actors.end()) ? AZStd::distance(m_actors.begin(), found) : InvalidIndex; + return (found != m_actorAssets.end()) ? AZStd::distance(m_actorAssets.begin(), found) : InvalidIndex; } + size_t ActorManager::FindActorIndex(const Actor* actor) const + { + const auto found = AZStd::find_if( + m_actorAssets.begin(), m_actorAssets.end(), + [actor](const ActorAssetData& a) + { + return a->GetActor() == actor; + }); + + return (found != m_actorAssets.end()) ? AZStd::distance(m_actorAssets.begin(), found) : InvalidIndex; + } // find the actor for a given actor name size_t ActorManager::FindActorIndexByName(const char* actorName) const { - const auto found = AZStd::find_if(m_actors.begin(), m_actors.end(), [actorName](const AZStd::shared_ptr& a) + const auto found = AZStd::find_if( + m_actorAssets.begin(), m_actorAssets.end(), + [actorName](const ActorAssetData& a) { - return a->GetNameString() == actorName; + return a->GetActor()->GetNameString() == actorName; }); - return (found != m_actors.end()) ? AZStd::distance(m_actors.begin(), found) : InvalidIndex; + return (found != m_actorAssets.end()) ? AZStd::distance(m_actorAssets.begin(), found) : InvalidIndex; } // find the actor for a given actor filename size_t ActorManager::FindActorIndexByFileName(const char* filename) const { - const auto found = AZStd::find_if(m_actors.begin(), m_actors.end(), [filename](const AZStd::shared_ptr& a) + const auto found = AZStd::find_if( + m_actorAssets.begin(), m_actorAssets.end(), + [filename](const ActorAssetData& a) { - return a->GetFileNameString() == filename; + return a->GetActor()->GetFileNameString() == filename; }); - return (found != m_actors.end()) ? AZStd::distance(m_actors.begin(), found) : InvalidIndex; + return (found != m_actorAssets.end()) ? AZStd::distance(m_actorAssets.begin(), found) : InvalidIndex; } @@ -237,22 +261,26 @@ namespace EMotionFX // find the actor by the identification number Actor* ActorManager::FindActorByID(uint32 id) const { - const auto found = AZStd::find_if(m_actors.begin(), m_actors.end(), [id](const AZStd::shared_ptr& a) + const auto found = AZStd::find_if( + m_actorAssets.begin(), m_actorAssets.end(), + [id](const ActorAssetData& a) { - return a->GetID() == id; + return a->GetActor()->GetID() == id; }); - return (found != m_actors.end()) ? found->get() : nullptr; + return (found != m_actorAssets.end()) ? (*found)->GetActor() : nullptr; } - AZStd::shared_ptr ActorManager::FindSharedActorByID(uint32 id) const + AZ::Data::AssetId ActorManager::FindAssetIdByActorId(uint32 id) const { - const auto found = AZStd::find_if(m_actors.begin(), m_actors.end(), [id](const AZStd::shared_ptr& a) + const auto found = AZStd::find_if( + m_actorAssets.begin(), m_actorAssets.end(), + [id](const ActorAssetData& a) { - return a->GetID() == id; + return a->GetActor()->GetID() == id; }); - return (found != m_actors.end()) ? *found : nullptr; + return (found != m_actorAssets.end()) ? found->GetId() : AZ::Data::AssetId(); } @@ -264,14 +292,20 @@ namespace EMotionFX // unregister an actor - void ActorManager::UnregisterActor(const AZStd::shared_ptr& actor) + void ActorManager::UnregisterActor(AZ::Data::AssetId actorAssetID) { LockActors(); - auto result = AZStd::find(m_actors.begin(), m_actors.end(), actor); - if (result != m_actors.end()) + const auto found = AZStd::find_if( + m_actorAssets.begin(), m_actorAssets.end(), + [actorAssetID](const ActorAssetData& a) + { + return a.GetId() == actorAssetID; + }); + if (found != m_actorAssets.end()) { - m_actors.erase(result); + m_actorAssets.erase(found); } + UnlockActors(); } @@ -297,7 +331,7 @@ namespace EMotionFX LockActors(); // clear all actors - m_actors.clear(); + m_actorAssets.clear(); // TODO: what if there are still references to the actors inside the list of registered actor instances? UnlockActors(); @@ -390,7 +424,13 @@ namespace EMotionFX Actor* ActorManager::GetActor(size_t nr) const { - return m_actors[nr].get(); + return m_actorAssets[nr]->GetActor(); + } + + + ActorAssetData ActorManager::GetActorAsset(size_t nr) const + { + return m_actorAssets[nr]; } diff --git a/Gems/EMotionFX/Code/EMotionFX/Source/ActorManager.h b/Gems/EMotionFX/Code/EMotionFX/Source/ActorManager.h index ff78250141..003153b36c 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Source/ActorManager.h +++ b/Gems/EMotionFX/Code/EMotionFX/Source/ActorManager.h @@ -16,7 +16,8 @@ #include #include #include - +#include +#include namespace EMotionFX { @@ -47,7 +48,7 @@ namespace EMotionFX * Register an actor. * @param actor The actor to register. */ - void RegisterActor(AZStd::shared_ptr actor); + void RegisterActor(ActorAssetData actorAsset); /** * Unregister all actors. @@ -60,14 +61,14 @@ namespace EMotionFX * Unregister a specific actor. * @param actor The actor you passed to the RegisterActor function sometime before. */ - void UnregisterActor(const AZStd::shared_ptr& actor); + void UnregisterActor(AZ::Data::AssetId actorAssetID); /** * Get the number of registered actors. * This does not include the clones that have been optionally created. * @result The number of registered actors. */ - MCORE_INLINE size_t GetNumActors() const { return m_actors.size(); } + MCORE_INLINE size_t GetNumActors() const { return m_actorAssets.size(); } /** * Get a given actor. @@ -78,6 +79,7 @@ namespace EMotionFX * @result A reference to the actor object that contains the array of Actor objects. */ Actor* GetActor(size_t nr) const; + ActorAssetData GetActorAsset(size_t nr) const; /** * Find the given actor by name. @@ -99,7 +101,8 @@ namespace EMotionFX * @param actor The actor object you once passed to RegisterActor. * @result Returns the actor number, which is in range of [0..GetNumActors()-1], or returns MCORE_INVALIDINDEX32 when not found. */ - size_t FindActorIndex(Actor* actor) const; + size_t FindActorIndex(AZ::Data::AssetId assetId) const; + size_t FindActorIndex(const Actor* actor) const; /** * Find the actor number for a given actor name. @@ -160,7 +163,7 @@ namespace EMotionFX */ Actor* FindActorByID(uint32 id) const; - AZStd::shared_ptr FindSharedActorByID(uint32 id) const; + AZ::Data::AssetId FindAssetIdByActorId(uint32 id) const; /** * Check if the given actor instance is registered. @@ -255,9 +258,9 @@ namespace EMotionFX void UnlockActors(); private: - AZStd::vector m_actorInstances; /**< The registered actor instances. */ - AZStd::vector> m_actors; /**< The registered actors. */ - AZStd::vector m_rootActorInstances; /**< Root actor instances (roots of all attachment chains). */ + AZStd::vector m_actorInstances; /**< The registered actor instances. */ + AZStd::vector m_actorAssets; + AZStd::vector m_rootActorInstances; /**< Root actor instances (roots of all attachment chains). */ ActorUpdateScheduler* m_scheduler; /**< The update scheduler to use. */ MCore::MutexRecursive m_actorLock; /**< The multithread lock for touching the actors array. */ MCore::MutexRecursive m_actorInstanceLock; /**< The multithread lock for touching the actor instances array. */ diff --git a/Gems/EMotionFX/Code/EMotionFX/Source/AutoRegisteredActor.h b/Gems/EMotionFX/Code/EMotionFX/Source/AutoRegisteredActor.h deleted file mode 100644 index 8304976b15..0000000000 --- a/Gems/EMotionFX/Code/EMotionFX/Source/AutoRegisteredActor.h +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (c) Contributors to the Open 3D Engine Project. - * For complete copyright and license terms please see the LICENSE at the root of this distribution. - * - * SPDX-License-Identifier: Apache-2.0 OR MIT - * - */ - -#pragma once - -#include -#include -#include - -namespace EMotionFX -{ - class Actor; - - /** - * @brief An Actor pointer that unregisters itself when it goes out of - * scope - * - * This class allows for simple functionality of automatically registering - * and unregistering an actor from the manager. Its primary use case is the - * ActorAsset, that shares ownership with the manager. But it can also be - * used anywhere that needs to make an Actor that needs to be in the - * Manager for a given period of time. A good example of this is anything - * that needs Actor commands to work on an actor that is made in a given - * scope. One main place where this happens is in the Actor asset processor - * code. - */ - class AutoRegisteredActor - { - public: - AutoRegisteredActor() = default; - - template - AutoRegisteredActor(AZStd::shared_ptr actor) - : m_actor(AZStd::move(actor)) - { - Register(m_actor); - } - template - AutoRegisteredActor(AZStd::unique_ptr actor) - : m_actor(AZStd::move(actor)) - { - Register(m_actor); - } - - // This class is not copyable, because a given actor cannot be - // registered with the manager multiple times - AutoRegisteredActor(const AutoRegisteredActor&) = delete; - AutoRegisteredActor& operator=(const AutoRegisteredActor&) = delete; - - AutoRegisteredActor(AutoRegisteredActor&& other) noexcept - { - *this = AZStd::move(other); - } - - AutoRegisteredActor& operator=(AutoRegisteredActor&& other) noexcept - { - if (this != &other) - { - Unregister(m_actor); - m_actor = AZStd::move(other.m_actor); - } - return *this; - } - - ~AutoRegisteredActor() - { - Unregister(m_actor); - } - - Actor* operator->() const - { - return m_actor.operator->(); - } - - operator bool() const - { - return static_cast(m_actor); - } - - Actor* get() const - { - return m_actor.get(); - } - - private: - void Register(const AZStd::shared_ptr& actor) - { - if (actor) - { - GetActorManager().RegisterActor(actor); - } - } - - void Unregister(const AZStd::shared_ptr& actor) - { - if (actor) - { - GetActorManager().UnregisterActor(actor); - } - } - - AZStd::shared_ptr m_actor; - }; -} // namespace EMotionFX diff --git a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/EMStudioManager.cpp b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/EMStudioManager.cpp index 2321d7c71b..b385de62be 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/EMStudioManager.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/EMStudioManager.cpp @@ -46,15 +46,10 @@ AZ_POP_DISABLE_WARNING namespace EMStudio { - //-------------------------------------------------------------------------- - // globals - //-------------------------------------------------------------------------- - EMStudioManager* gEMStudioMgr = nullptr; - - //-------------------------------------------------------------------------- // class EMStudioManager //-------------------------------------------------------------------------- + AZ_CLASS_ALLOCATOR_IMPL(EMStudioManager, AZ::SystemAllocator, 0) // constructor EMStudioManager::EMStudioManager(QApplication* app, [[maybe_unused]] int& argc, [[maybe_unused]] char* argv[]) @@ -105,8 +100,9 @@ namespace EMStudio // log some information LogInfo(); - } + AZ::Interface::Register(this); + } // destructor EMStudioManager::~EMStudioManager() @@ -130,6 +126,8 @@ namespace EMStudio delete m_commandManager; AZ::AllocatorInstance::Destroy(); + + AZ::Interface::Unregister(this); } MainWindow* EMStudioManager::GetMainWindow() @@ -422,6 +420,12 @@ namespace EMStudio } + EMStudioManager* EMStudioManager::GetInstance() + { + return AZ::Interface().Get(); + } + + // function to add a gizmo to the manager MCommon::TransformationManipulator* EMStudioManager::AddTransformationManipulator(MCommon::TransformationManipulator* manipulator) { @@ -494,30 +498,48 @@ namespace EMStudio painter.drawPath(path); } - //-------------------------------------------------------------------------- - // class Initializer - //-------------------------------------------------------------------------- - // initialize EMotion Studio - bool Initializer::Init(QApplication* app, int& argc, char* argv[]) + // shortcuts + QApplication* GetApp() { - // do nothing if we already have initialized - if (gEMStudioMgr) - { - return true; - } + return EMStudioManager::GetInstance()->GetApp(); + } + EMStudioManager* GetManager() + { + return EMStudioManager::GetInstance(); + } + + bool HasMainWindow() + { + return EMStudioManager::GetInstance()->HasMainWindow(); + } + + MainWindow* GetMainWindow() + { + return EMStudioManager::GetInstance()->GetMainWindow(); + } - // create the new EMStudio object - gEMStudioMgr = new EMStudioManager(app, argc, argv); + PluginManager* GetPluginManager() + { + return EMStudioManager::GetInstance()->GetPluginManager(); + } + + LayoutManager* GetLayoutManager() + { + return EMStudioManager::GetInstance()->GetLayoutManager(); + } - // return success - return true; + NotificationWindowManager* GetNotificationWindowManager() + { + return EMStudioManager::GetInstance()->GetNotificationWindowManager(); } + MotionEventPresetManager* GetEventPresetManager() + { + return EMStudioManager::GetInstance()->GetEventPresetManger(); + } - // the shutdown function - void Initializer::Shutdown() + CommandSystem::CommandManager* GetCommandManager() { - delete gEMStudioMgr; - gEMStudioMgr = nullptr; + return EMStudioManager::GetInstance()->GetCommandManager(); } } // namespace EMStudio diff --git a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/EMStudioManager.h b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/EMStudioManager.h index 909f0231f3..4c3139b77a 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/EMStudioManager.h +++ b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/EMStudioManager.h @@ -53,9 +53,10 @@ namespace EMStudio class EMSTUDIO_API EMStudioManager : private EMotionFX::SkeletonOutlinerNotificationBus::Handler { - MCORE_MEMORYOBJECTCATEGORY(EMStudioManager, MCore::MCORE_DEFAULT_ALIGNMENT, MEMCATEGORY_EMSTUDIOSDK) - public: + AZ_RTTI(EMStudio::EMStudioManager, "{D45E95CF-0C7B-44F1-A9D4-99A1E12A5AB5}") + AZ_CLASS_ALLOCATOR_DECL + EMStudioManager(QApplication* app, int& argc, char* argv[]); ~EMStudioManager(); @@ -72,6 +73,9 @@ namespace EMStudio AZStd::string GetRecoverFolder() const; AZStd::string GetAutosavesFolder() const; + // Singleton pattern + static EMStudioManager* GetInstance(); + // text rendering helper function static void RenderText(QPainter& painter, const QString& text, const QColor& textColor, const QFont& font, const QFontMetrics& fontMetrics, Qt::Alignment textAlignment, const QRect& rect); @@ -150,34 +154,17 @@ namespace EMStudio void OnRemoveCommand(size_t historyIndex) override { MCORE_UNUSED(historyIndex); } void OnSetCurrentCommand(size_t index) override { MCORE_UNUSED(index); } }; - EventProcessingCallback* m_eventProcessingCallback; + EventProcessingCallback* m_eventProcessingCallback = nullptr; }; - - /** - * - * - * - */ - class EMSTUDIO_API Initializer - { - public: - static bool MCORE_CDECL Init(QApplication* app, int& argc, char* argv[]); - static void MCORE_CDECL Shutdown(); - }; - - - // the global manager - extern EMSTUDIO_API EMStudioManager* gEMStudioMgr; - - // shortcuts - MCORE_INLINE QApplication* GetApp() { return gEMStudioMgr->GetApp(); } - MCORE_INLINE EMStudioManager* GetManager() { return gEMStudioMgr; } - MCORE_INLINE bool HasMainWindow() { return gEMStudioMgr->HasMainWindow(); } - MCORE_INLINE MainWindow* GetMainWindow() { return gEMStudioMgr->GetMainWindow(); } - MCORE_INLINE PluginManager* GetPluginManager() { return gEMStudioMgr->GetPluginManager(); } - MCORE_INLINE LayoutManager* GetLayoutManager() { return gEMStudioMgr->GetLayoutManager(); } - MCORE_INLINE NotificationWindowManager* GetNotificationWindowManager() { return gEMStudioMgr->GetNotificationWindowManager(); } - MCORE_INLINE MotionEventPresetManager* GetEventPresetManager() { return gEMStudioMgr->GetEventPresetManger(); } - MCORE_INLINE CommandSystem::CommandManager* GetCommandManager() { return gEMStudioMgr->GetCommandManager(); } + // Shortcuts + QApplication* GetApp(); + EMStudioManager* GetManager(); + bool HasMainWindow(); + MainWindow* GetMainWindow(); + PluginManager* GetPluginManager(); + LayoutManager* GetLayoutManager(); + NotificationWindowManager* GetNotificationWindowManager(); + MotionEventPresetManager* GetEventPresetManager(); + CommandSystem::CommandManager* GetCommandManager(); } // namespace EMStudio diff --git a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/FileManager.cpp b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/FileManager.cpp index 2d65a228b7..17438dd59a 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/FileManager.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/FileManager.cpp @@ -112,10 +112,6 @@ namespace EMStudio for (size_t i = 0; i < actorCount; ++i) { EMotionFX::Actor* actor = EMotionFX::GetActorManager().GetActor(i); - if (actor->GetIsOwnedByRuntime()) - { - continue; - } if (AzFramework::StringFunc::Equal(filename, actor->GetFileName())) { diff --git a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/MainWindow.cpp b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/MainWindow.cpp index 002634ea90..b75fb71060 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/MainWindow.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/MainWindow.cpp @@ -1792,11 +1792,6 @@ namespace EMStudio { EMotionFX::Actor* actor = selectionList.GetActorInstance(i)->GetActor(); - if (actor->GetIsOwnedByRuntime()) - { - continue; - } - if (AZStd::find(savingActors.begin(), savingActors.end(), actor) == savingActors.end()) { savingActors.push_back(actor); diff --git a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/MainWindow.h b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/MainWindow.h index 585101f7d2..1aa8676352 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/MainWindow.h +++ b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/MainWindow.h @@ -10,9 +10,9 @@ #if !defined(Q_MOC_RUN) #include -#include -#include -#include +#include +#include +#include #include #include #include diff --git a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/RenderPlugin/RenderPlugin.cpp b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/RenderPlugin/RenderPlugin.cpp index db0c355002..c74cb3cb0f 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/RenderPlugin/RenderPlugin.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/RenderPlugin/RenderPlugin.cpp @@ -389,7 +389,7 @@ namespace EMStudio // get the current actor and the number of clones EMotionFX::Actor* actor = EMotionFX::GetActorManager().GetActor(i); - if (actor->GetIsOwnedByRuntime() || !actor->IsReady()) + if (!actor->IsReady()) { continue; } @@ -421,7 +421,7 @@ namespace EMStudio // At this point the render actor could point to an already deleted actor. // In case the actor got deleted we might get an unexpected flag as result. - if (!found || (found && actor->GetIsOwnedByRuntime()) || (!actor->IsReady())) + if (!found || (!actor->IsReady())) { DestroyEMStudioActor(actor); } diff --git a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/RenderPlugin/RenderViewWidget.cpp b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/RenderPlugin/RenderViewWidget.cpp index 93a6dd3019..fe8fb61f02 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/RenderPlugin/RenderViewWidget.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/RenderPlugin/RenderViewWidget.cpp @@ -155,12 +155,12 @@ namespace EMStudio cameraMenu->addAction("Reset Camera", [this]() { this->OnResetCamera(); }); QAction* showSelectedAction = cameraMenu->addAction("Show Selected", this, &RenderViewWidget::OnShowSelected); - showSelectedAction->setShortcut(Qt::Key_S); + showSelectedAction->setShortcut(QKeySequence(Qt::Key_S + Qt::SHIFT)); GetMainWindow()->GetShortcutManager()->RegisterKeyboardShortcut(showSelectedAction, RenderPlugin::s_renderWindowShortcutGroupName, true); addAction(showSelectedAction); QAction* showEntireSceneAction = cameraMenu->addAction("Show Entire Scene", this, &RenderViewWidget::OnShowEntireScene); - showEntireSceneAction->setShortcut(Qt::Key_A); + showEntireSceneAction->setShortcut(QKeySequence(Qt::Key_A + Qt::SHIFT)); GetMainWindow()->GetShortcutManager()->RegisterKeyboardShortcut(showEntireSceneAction, RenderPlugin::s_renderWindowShortcutGroupName, true); addAction(showEntireSceneAction); diff --git a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/ResetSettingsDialog.cpp b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/ResetSettingsDialog.cpp index 2f45a89ea2..5c15f4b6be 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/ResetSettingsDialog.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/ResetSettingsDialog.cpp @@ -65,8 +65,7 @@ namespace EMStudio m_actorCheckbox = new QCheckBox("Actors"); m_actorCheckbox->setObjectName("EMFX.ResetSettingsDialog.Actors"); - const bool hasActors = HasEntityInEditor( - EMotionFX::GetActorManager(), &EMotionFX::ActorManager::GetNumActors, &EMotionFX::ActorManager::GetActor); + const bool hasActors = EMotionFX::GetActorManager().GetNumActors() > 0; m_actorCheckbox->setChecked(hasActors); m_actorCheckbox->setDisabled(!hasActors); diff --git a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/NodeWindow/NodeWindowPlugin.cpp b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/NodeWindow/NodeWindowPlugin.cpp index 6b7beb20de..4629e8dc54 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/NodeWindow/NodeWindowPlugin.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/NodeWindow/NodeWindowPlugin.cpp @@ -327,7 +327,16 @@ namespace EMStudio void NodeWindowPlugin::OnActorReady([[maybe_unused]] EMotionFX::Actor* actor) { - ReInit(); + m_reinitRequested = true; + } + + void NodeWindowPlugin::ProcessFrame([[maybe_unused]] float timePassedInSeconds) + { + if (m_reinitRequested) + { + ReInit(); + m_reinitRequested = false; + } } //----------------------------------------------------------------------------------------- diff --git a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/NodeWindow/NodeWindowPlugin.h b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/NodeWindow/NodeWindowPlugin.h index 157486df10..59c9f78719 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/NodeWindow/NodeWindowPlugin.h +++ b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/NodeWindow/NodeWindowPlugin.h @@ -60,6 +60,8 @@ namespace EMStudio EMStudioPlugin* Clone() override; void ReInit(); + void ProcessFrame(float timePassedInSeconds) override; + public slots: void OnNodeChanged(); void VisibilityChanged(bool isVisible); @@ -87,5 +89,8 @@ namespace EMStudio AZStd::unique_ptr m_actorInfo; AZStd::unique_ptr m_nodeInfo; + + // Use this flag to defer the reinit function to main thread. + bool m_reinitRequested = false; }; } // namespace EMStudio diff --git a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/SceneManager/ActorsWindow.cpp b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/SceneManager/ActorsWindow.cpp index 5012ee2578..c1e35967ec 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/SceneManager/ActorsWindow.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/SceneManager/ActorsWindow.cpp @@ -123,12 +123,6 @@ namespace EMStudio continue; } - // ignore engine actors - if (actor->GetIsOwnedByRuntime()) - { - continue; - } - // create a tree item for the new attachment QTreeWidgetItem* newItem = new QTreeWidgetItem(m_treeWidget); diff --git a/Gems/EMotionFX/Code/EMotionFX/emotionfx_files.cmake b/Gems/EMotionFX/Code/EMotionFX/emotionfx_files.cmake index b12cbe102f..9947850471 100644 --- a/Gems/EMotionFX/Code/EMotionFX/emotionfx_files.cmake +++ b/Gems/EMotionFX/Code/EMotionFX/emotionfx_files.cmake @@ -25,7 +25,6 @@ set(FILES Source/AttachmentNode.h Source/AttachmentSkin.cpp Source/AttachmentSkin.h - Source/AutoRegisteredActor.h Source/BaseObject.cpp Source/BaseObject.h Source/CompressedKeyFrames.h diff --git a/Gems/EMotionFX/Code/Include/Integration/ActorComponentBus.h b/Gems/EMotionFX/Code/Include/Integration/ActorComponentBus.h index 6000873db1..4161b3c77d 100644 --- a/Gems/EMotionFX/Code/Include/Integration/ActorComponentBus.h +++ b/Gems/EMotionFX/Code/Include/Integration/ActorComponentBus.h @@ -16,7 +16,7 @@ #include #include #include - +#include namespace EMotionFX { @@ -96,6 +96,9 @@ namespace EMotionFX /// Returns skinning method used by the actor. virtual SkinningMethod GetSkinningMethod() const = 0; + // Use this to alter the actor asset. + virtual void SetActorAsset(AZ::Data::Asset actorAsset) = 0; + static const size_t s_invalidJointIndex = std::numeric_limits::max(); }; diff --git a/Gems/EMotionFX/Code/Include/Integration/AnimationBus.h b/Gems/EMotionFX/Code/Include/Integration/AnimationBus.h index 1b9eb55216..474ac1b6d5 100644 --- a/Gems/EMotionFX/Code/Include/Integration/AnimationBus.h +++ b/Gems/EMotionFX/Code/Include/Integration/AnimationBus.h @@ -46,6 +46,9 @@ namespace EMotionFX public: static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple; static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; + + // Use this bus to register custom EMotionFX plugin. + virtual void OnRegisterPlugin() = 0; }; using SystemNotificationBus = AZ::EBus; diff --git a/Gems/EMotionFX/Code/Source/Integration/Assets/ActorAsset.cpp b/Gems/EMotionFX/Code/Source/Integration/Assets/ActorAsset.cpp index 5082d4b739..64117fa814 100644 --- a/Gems/EMotionFX/Code/Source/Integration/Assets/ActorAsset.cpp +++ b/Gems/EMotionFX/Code/Source/Integration/Assets/ActorAsset.cpp @@ -6,7 +6,6 @@ * */ -#include #include #include #include @@ -64,8 +63,7 @@ namespace EMotionFX &actorSettings, ""); - // Set the is owned by runtime flag before finalizing the actor, as that uses the flag already. - assetData->m_emfxActor->SetIsOwnedByRuntime(true); + assetData->m_emfxActor->SetFileName(asset.GetHint().c_str()); assetData->m_emfxActor->Finalize(); // Clear out the EMFX raw asset data. diff --git a/Gems/EMotionFX/Code/Source/Integration/Assets/ActorAsset.h b/Gems/EMotionFX/Code/Source/Integration/Assets/ActorAsset.h index 8732c6a448..4b13603ed5 100644 --- a/Gems/EMotionFX/Code/Source/Integration/Assets/ActorAsset.h +++ b/Gems/EMotionFX/Code/Source/Integration/Assets/ActorAsset.h @@ -15,7 +15,6 @@ #include #include -#include namespace EMotionFX @@ -58,7 +57,7 @@ namespace EMotionFX void InitRenderActor(); private: - AutoRegisteredActor m_emfxActor; ///< Pointer to shared EMotionFX actor + AZStd::shared_ptr m_emfxActor; AZStd::unique_ptr m_renderActor; }; @@ -81,6 +80,8 @@ namespace EMotionFX const char* GetBrowserIcon() const override; }; } // namespace Integration + + using ActorAssetData = AZ::Data::Asset; } // namespace EMotionFX namespace AZ diff --git a/Gems/EMotionFX/Code/Source/Integration/Components/ActorComponent.h b/Gems/EMotionFX/Code/Source/Integration/Components/ActorComponent.h index b376fa891d..15d9736f34 100644 --- a/Gems/EMotionFX/Code/Source/Integration/Components/ActorComponent.h +++ b/Gems/EMotionFX/Code/Source/Integration/Components/ActorComponent.h @@ -121,6 +121,7 @@ namespace EMotionFX void SetRenderCharacter(bool enable) override; bool GetRenderActorVisible() const override; SkinningMethod GetSkinningMethod() const override; + void SetActorAsset(AZ::Data::Asset actorAsset) override; ////////////////////////////////////////////////////////////////////////// // ActorComponentNotificationBus::Handler @@ -178,8 +179,6 @@ namespace EMotionFX void OnAssetReloaded(AZ::Data::Asset asset) override; bool IsPhysicsSceneSimulationFinishEventConnected() const; - - void SetActorAsset(AZ::Data::Asset actorAsset); AZ::Data::Asset GetActorAsset() const { return m_configuration.m_actorAsset; } private: diff --git a/Gems/EMotionFX/Code/Source/Integration/Editor/Components/EditorActorComponent.cpp b/Gems/EMotionFX/Code/Source/Integration/Editor/Components/EditorActorComponent.cpp index cf0ceab25d..5da9716d15 100644 --- a/Gems/EMotionFX/Code/Source/Integration/Editor/Components/EditorActorComponent.cpp +++ b/Gems/EMotionFX/Code/Source/Integration/Editor/Components/EditorActorComponent.cpp @@ -107,7 +107,7 @@ namespace EMotionFX ->Attribute(AZ::Edit::Attributes::ViewportIcon, ":/EMotionFX/Viewport/ActorComponent.svg") ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c)) ->Attribute(AZ::Edit::Attributes::AutoExpand, true) - ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/actor/") + ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/animation/actor/") ->DataElement(0, &EditorActorComponent::m_actorAsset, "Actor asset", "Assigned actor asset") ->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorActorComponent::OnAssetSelected) diff --git a/Gems/EMotionFX/Code/Source/Integration/Editor/Components/EditorActorComponent.h b/Gems/EMotionFX/Code/Source/Integration/Editor/Components/EditorActorComponent.h index e8f76bdd49..1d682b47d4 100644 --- a/Gems/EMotionFX/Code/Source/Integration/Editor/Components/EditorActorComponent.h +++ b/Gems/EMotionFX/Code/Source/Integration/Editor/Components/EditorActorComponent.h @@ -60,6 +60,7 @@ namespace EMotionFX bool GetRenderActorVisible() const override; size_t GetNumJoints() const override; SkinningMethod GetSkinningMethod() const override; + void SetActorAsset(AZ::Data::Asset actorAsset) override; // EditorActorComponentRequestBus overrides ... const AZ::Data::AssetId& GetActorAssetId() override; @@ -79,8 +80,6 @@ namespace EMotionFX void OnAssetReady(AZ::Data::Asset asset) override; void OnAssetReloaded(AZ::Data::Asset asset) override; - void SetActorAsset(AZ::Data::Asset actorAsset); - // BoundsRequestBus overrides ... AZ::Aabb GetWorldBounds() override; AZ::Aabb GetLocalBounds() override; diff --git a/Gems/EMotionFX/Code/Source/Integration/Editor/Components/EditorAnimGraphComponent.cpp b/Gems/EMotionFX/Code/Source/Integration/Editor/Components/EditorAnimGraphComponent.cpp index 9833fcc87a..3fc4f4a93d 100644 --- a/Gems/EMotionFX/Code/Source/Integration/Editor/Components/EditorAnimGraphComponent.cpp +++ b/Gems/EMotionFX/Code/Source/Integration/Editor/Components/EditorAnimGraphComponent.cpp @@ -65,7 +65,7 @@ namespace EMotionFX ->Attribute(AZ::Edit::Attributes::ViewportIcon, ":/EMotionFX/Viewport/AnimGraphComponent.svg") ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c)) ->Attribute(AZ::Edit::Attributes::AutoExpand, true) - ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/animgraph/") + ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/animation/animgraph/") ->DataElement(AZ::Edit::UIHandlers::Default, &EditorAnimGraphComponent::m_motionSetAsset, "Motion set asset", "EMotion FX motion set asset to be loaded for this actor.") ->Attribute("EditButton", "") diff --git a/Gems/EMotionFX/Code/Source/Integration/Editor/Components/EditorSimpleMotionComponent.cpp b/Gems/EMotionFX/Code/Source/Integration/Editor/Components/EditorSimpleMotionComponent.cpp index 8446050254..783399c956 100644 --- a/Gems/EMotionFX/Code/Source/Integration/Editor/Components/EditorSimpleMotionComponent.cpp +++ b/Gems/EMotionFX/Code/Source/Integration/Editor/Components/EditorSimpleMotionComponent.cpp @@ -46,12 +46,12 @@ namespace EMotionFX ->Attribute(AZ::Edit::Attributes::PrimaryAssetType, azrtti_typeid()) ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/Mannequin.svg") ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c)) + ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/animation/simple-motion/") ->Attribute(AZ::Edit::Attributes::AutoExpand, true) ->DataElement(0, &EditorSimpleMotionComponent::m_previewInEditor, "Preview In Editor", "Plays motion in Editor") ->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorSimpleMotionComponent::OnEditorPropertyChanged) ->DataElement(0, &EditorSimpleMotionComponent::m_configuration, "Configuration", "Settings for this Simple Motion") ->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorSimpleMotionComponent::OnEditorPropertyChanged) - ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/simple-motion/") ; } } diff --git a/Gems/EMotionFX/Code/Source/Integration/System/SystemComponent.cpp b/Gems/EMotionFX/Code/Source/Integration/System/SystemComponent.cpp index 08e99f97d0..38af4232ba 100644 --- a/Gems/EMotionFX/Code/Source/Integration/System/SystemComponent.cpp +++ b/Gems/EMotionFX/Code/Source/Integration/System/SystemComponent.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include @@ -45,6 +46,7 @@ #include #include +#include #include #include #include @@ -69,7 +71,6 @@ # include # include # include -# include # include # include // EMStudio plugins @@ -528,7 +529,7 @@ namespace EMotionFX if (EMStudio::GetManager()) { - EMStudio::Initializer::Shutdown(); + m_emstudioManager.reset(); MysticQt::Initializer::Shutdown(); } @@ -798,6 +799,8 @@ namespace EMotionFX pluginManager->RegisterPlugin(new EMotionFX::RagdollNodeInspectorPlugin()); pluginManager->RegisterPlugin(new EMotionFX::ClothJointInspectorPlugin()); pluginManager->RegisterPlugin(new EMotionFX::SimulatedObjectWidget()); + + SystemNotificationBus::Broadcast(&SystemNotificationBus::Events::OnRegisterPlugin); } ////////////////////////////////////////////////////////////////////////// @@ -813,7 +816,7 @@ namespace EMotionFX char** argv = nullptr; MysticQt::Initializer::Init("", editorAssetsPath.c_str()); - EMStudio::Initializer::Init(qApp, argc, argv); + m_emstudioManager = AZStd::make_unique(qApp, argc, argv); InitializeEMStudioPlugins(); diff --git a/Gems/EMotionFX/Code/Source/Integration/System/SystemComponent.h b/Gems/EMotionFX/Code/Source/Integration/System/SystemComponent.h index 5e30820ca3..3ef626c947 100644 --- a/Gems/EMotionFX/Code/Source/Integration/System/SystemComponent.h +++ b/Gems/EMotionFX/Code/Source/Integration/System/SystemComponent.h @@ -24,6 +24,7 @@ # include # include # include +# include #endif // EMOTIONFXANIMATION_EDITOR namespace AZ @@ -126,6 +127,10 @@ namespace EMotionFX AZStd::vector > m_assetHandlers; AZStd::unique_ptr m_eventHandler; AZStd::unique_ptr m_renderBackendManager; + +#if defined(EMOTIONFXANIMATION_EDITOR) + AZStd::unique_ptr m_emstudioManager; +#endif // EMOTIONFXANIMATION_EDITOR }; } } diff --git a/Gems/EMotionFX/Code/Tests/ActorFixture.cpp b/Gems/EMotionFX/Code/Tests/ActorFixture.cpp index e77df103e0..33c26ca11e 100644 --- a/Gems/EMotionFX/Code/Tests/ActorFixture.cpp +++ b/Gems/EMotionFX/Code/Tests/ActorFixture.cpp @@ -10,12 +10,15 @@ #include #include #include +#include #include #include #include #include #include +#include + namespace EMotionFX { @@ -23,8 +26,9 @@ namespace EMotionFX { SystemComponentFixture::SetUp(); - m_actor = ActorFactory::CreateAndInit(); - m_actorInstance = ActorInstance::Create(m_actor.get()); + AZ::Data::AssetId actorAssetId("{5060227D-B6F4-422E-BF82-41AAC5F228A5}"); + m_actorAsset = TestActorAssets::CreateActorAssetAndRegister(actorAssetId); + m_actorInstance = ActorInstance::Create(GetActor()); } void ActorFixture::TearDown() @@ -35,6 +39,7 @@ namespace EMotionFX m_actorInstance = nullptr; } + GetEMotionFX().GetActorManager()->UnregisterAllActors(); SystemComponentFixture::TearDown(); } @@ -71,7 +76,7 @@ namespace EMotionFX AZ::ObjectStream::FilterDescriptor loadFilter(nullptr, AZ::ObjectStream::FILTERFLAG_IGNORE_UNKNOWN_CLASSES); SimulatedObjectSetup* setup = AZ::Utils::LoadObjectFromBuffer(data.data(), data.size(), serializeContext, loadFilter); - setup->InitAfterLoad(m_actor.get()); + setup->InitAfterLoad(GetActor()); return setup; } @@ -80,4 +85,10 @@ namespace EMotionFX { return { "Bip01__pelvis", "l_upLeg", "l_loLeg", "l_ankle" }; } + + + Actor* ActorFixture::GetActor() const + { + return m_actorAsset->GetActor(); + } } // namespace EMotionFX diff --git a/Gems/EMotionFX/Code/Tests/ActorFixture.h b/Gems/EMotionFX/Code/Tests/ActorFixture.h index 9fee056292..569d55cf47 100644 --- a/Gems/EMotionFX/Code/Tests/ActorFixture.h +++ b/Gems/EMotionFX/Code/Tests/ActorFixture.h @@ -9,8 +9,7 @@ #pragma once #include "SystemComponentFixture.h" -#include - +#include namespace EMotionFX { @@ -31,7 +30,9 @@ namespace EMotionFX AZStd::vector GetTestJointNames() const; protected: - AutoRegisteredActor m_actor{}; + Actor* GetActor() const; + + AZ::Data::Asset m_actorAsset; ActorInstance* m_actorInstance = nullptr; }; } // namespace EMotionFX diff --git a/Gems/EMotionFX/Code/Tests/AdditiveMotionSamplingTests.cpp b/Gems/EMotionFX/Code/Tests/AdditiveMotionSamplingTests.cpp index 4171598801..114d5c08fb 100644 --- a/Gems/EMotionFX/Code/Tests/AdditiveMotionSamplingTests.cpp +++ b/Gems/EMotionFX/Code/Tests/AdditiveMotionSamplingTests.cpp @@ -26,7 +26,7 @@ namespace EMotionFX public: void CreateSubMotionLikeBindPose(const std::string& name) { - const Skeleton* skeleton = m_actor->GetSkeleton(); + const Skeleton* skeleton = GetActor()->GetSkeleton(); size_t jointIndex = InvalidIndex; const Node* node = skeleton->FindNodeAndIndexByName(name.c_str(), jointIndex); ASSERT_NE(node, nullptr); @@ -40,7 +40,7 @@ namespace EMotionFX void CreateSubMotion(const std::string& name, const Transform& transform) { // Find and store the joint index. - const Skeleton* skeleton = m_actor->GetSkeleton(); + const Skeleton* skeleton = GetActor()->GetSkeleton(); size_t jointIndex = InvalidIndex; const Node* node = skeleton->FindNodeAndIndexByName(name.c_str(), jointIndex); ASSERT_NE(node, nullptr); @@ -55,7 +55,7 @@ namespace EMotionFX ActorFixture::SetUp(); // Get the joint that isn't in the motion data. - Node* footNode = m_actor->GetSkeleton()->FindNodeAndIndexByName("l_ball", m_footIndex); + Node* footNode = GetActor()->GetSkeleton()->FindNodeAndIndexByName("l_ball", m_footIndex); ASSERT_NE(footNode, nullptr); ASSERT_NE(m_footIndex, InvalidIndex32); @@ -98,7 +98,7 @@ namespace EMotionFX TEST_F(MotionSamplingFixture, SampleAdditiveJoint) { - const Skeleton* skeleton = m_actor->GetSkeleton(); + const Skeleton* skeleton = GetActor()->GetSkeleton(); // Sample the joints that exist in our actor skeleton as well as inside the motion data. const Pose* bindPose = m_actorInstance->GetTransformData()->GetBindPose(); @@ -106,7 +106,7 @@ namespace EMotionFX { // Sample the motion. Transform transform = Transform::CreateZero(); // Set all to Zero, not identity as this methods might return identity and we want to verify that. - m_motion->CalcNodeTransform(m_motionInstance, &transform, m_actor.get(), skeleton->GetNode(jointIndex), /*timeValue=*/0.0f, /*enableRetargeting=*/false); + m_motion->CalcNodeTransform(m_motionInstance, &transform, GetActor(), skeleton->GetNode(jointIndex), /*timeValue=*/0.0f, /*enableRetargeting=*/false); const Transform& bindTransform = bindPose->GetLocalSpaceTransform(jointIndex); EXPECT_THAT(transform, IsClose(bindTransform)); @@ -114,7 +114,7 @@ namespace EMotionFX // Sample the motion for the foot node. Transform footTransform = Transform::CreateZero(); // Set all to Zero, not identity as this methods might return identity and we want to verify that. - m_motion->CalcNodeTransform(m_motionInstance, &footTransform, m_actor.get(), skeleton->GetNode(m_footIndex), /*timeValue=*/0.0f, /*enableRetargeting=*/false); + m_motion->CalcNodeTransform(m_motionInstance, &footTransform, GetActor(), skeleton->GetNode(m_footIndex), /*timeValue=*/0.0f, /*enableRetargeting=*/false); // Make sure we get an identity transform back as we try to sample a node that doesn't have a submotion in an additive motion. EXPECT_THAT(footTransform, IsClose(Transform::CreateIdentity())); @@ -125,7 +125,7 @@ namespace EMotionFX // Make sure we do not get an identity transform back now that it is a non-additive motion. footTransform.Zero(); // Set all to Zero, not identity as this methods might return identity and we want to verify that. const Transform& expectedFootTransform = m_actorInstance->GetTransformData()->GetCurrentPose()->GetLocalSpaceTransform(m_footIndex); - m_motion->CalcNodeTransform(m_motionInstance, &footTransform, m_actor.get(), skeleton->GetNode(m_footIndex), /*timeValue=*/0.0f, /*enableRetargeting=*/false); + m_motion->CalcNodeTransform(m_motionInstance, &footTransform, GetActor(), skeleton->GetNode(m_footIndex), /*timeValue=*/0.0f, /*enableRetargeting=*/false); EXPECT_THAT(footTransform, IsClose(expectedFootTransform)); } @@ -134,7 +134,7 @@ namespace EMotionFX // Sample a pose from the motion. Pose pose; pose.LinkToActorInstance(m_actorInstance); - pose.InitFromBindPose(m_actor.get()); + pose.InitFromBindPose(GetActor()); pose.Zero(); m_motion->Update(&pose, &pose, m_motionInstance); diff --git a/Gems/EMotionFX/Code/Tests/AnimGraphDeferredInitTests.cpp b/Gems/EMotionFX/Code/Tests/AnimGraphDeferredInitTests.cpp index 0449005659..4448deff27 100644 --- a/Gems/EMotionFX/Code/Tests/AnimGraphDeferredInitTests.cpp +++ b/Gems/EMotionFX/Code/Tests/AnimGraphDeferredInitTests.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include diff --git a/Gems/EMotionFX/Code/Tests/BlendTreeRagdollNodeTests.cpp b/Gems/EMotionFX/Code/Tests/BlendTreeRagdollNodeTests.cpp index a01d333e95..4608917523 100644 --- a/Gems/EMotionFX/Code/Tests/BlendTreeRagdollNodeTests.cpp +++ b/Gems/EMotionFX/Code/Tests/BlendTreeRagdollNodeTests.cpp @@ -105,7 +105,7 @@ namespace EMotionFX TEST_P(RagdollRootNodeFixture, RagdollRootNodeIsSimulatedTests) { - Physics::RagdollConfiguration& ragdollConfig = m_actor->GetPhysicsSetup()->GetRagdollConfig(); + Physics::RagdollConfiguration& ragdollConfig = GetActor()->GetPhysicsSetup()->GetRagdollConfig(); AZStd::vector& ragdollNodes = ragdollConfig.m_nodes; const RagdollRootNodeParam& param = GetParam(); const AZStd::string ragdollRootNodeName = param.m_ragdollRootNode.c_str(); diff --git a/Gems/EMotionFX/Code/Tests/ColliderCommandTests.cpp b/Gems/EMotionFX/Code/Tests/ColliderCommandTests.cpp index 6314253eee..a6cadc5c2f 100644 --- a/Gems/EMotionFX/Code/Tests/ColliderCommandTests.cpp +++ b/Gems/EMotionFX/Code/Tests/ColliderCommandTests.cpp @@ -24,13 +24,13 @@ namespace EMotionFX CommandSystem::CommandManager commandManager; MCore::CommandGroup commandGroup; - const AZ::u32 actorId = m_actor->GetID(); + const AZ::u32 actorId = GetActor()->GetID(); const AZStd::vector jointNames = GetTestJointNames(); const size_t jointCount = jointNames.size(); // 1. Add colliders - const AZStd::string serializedBeforeAdd = SerializePhysicsSetup(m_actor.get()); + const AZStd::string serializedBeforeAdd = SerializePhysicsSetup(GetActor()); for (const AZStd::string& jointName : jointNames) { CommandColliderHelpers::AddCollider(actorId, jointName, PhysicsSetup::HitDetection, azrtti_typeid(), &commandGroup); @@ -39,23 +39,27 @@ namespace EMotionFX } EXPECT_TRUE(commandManager.ExecuteCommandGroup(commandGroup, result)); - const AZStd::string serializedAfterAdd = SerializePhysicsSetup(m_actor.get()); - EXPECT_EQ(jointCount * 3, PhysicsSetupUtils::CountColliders(m_actor.get(), PhysicsSetup::HitDetection)); - EXPECT_EQ(jointCount, PhysicsSetupUtils::CountColliders(m_actor.get(), PhysicsSetup::HitDetection, /*ignoreShapeType*/false, Physics::ShapeType::Box)); + const AZStd::string serializedAfterAdd = SerializePhysicsSetup(GetActor()); + EXPECT_EQ(jointCount * 3, PhysicsSetupUtils::CountColliders(GetActor(), PhysicsSetup::HitDetection)); + EXPECT_EQ( + jointCount, + PhysicsSetupUtils::CountColliders(GetActor(), PhysicsSetup::HitDetection, /*ignoreShapeType*/ false, Physics::ShapeType::Box)); EXPECT_TRUE(commandManager.Undo(result)); - EXPECT_EQ(0, PhysicsSetupUtils::CountColliders(m_actor.get(), PhysicsSetup::HitDetection)); - EXPECT_EQ(serializedBeforeAdd, SerializePhysicsSetup(m_actor.get())); + EXPECT_EQ(0, PhysicsSetupUtils::CountColliders(GetActor(), PhysicsSetup::HitDetection)); + EXPECT_EQ(serializedBeforeAdd, SerializePhysicsSetup(GetActor())); EXPECT_TRUE(commandManager.Redo(result)); - EXPECT_EQ(jointCount * 3, PhysicsSetupUtils::CountColliders(m_actor.get(), PhysicsSetup::HitDetection)); - EXPECT_EQ(jointCount, PhysicsSetupUtils::CountColliders(m_actor.get(), PhysicsSetup::HitDetection, /*ignoreShapeType*/false, Physics::ShapeType::Box)); - EXPECT_EQ(serializedAfterAdd, SerializePhysicsSetup(m_actor.get())); + EXPECT_EQ(jointCount * 3, PhysicsSetupUtils::CountColliders(GetActor(), PhysicsSetup::HitDetection)); + EXPECT_EQ( + jointCount, + PhysicsSetupUtils::CountColliders(GetActor(), PhysicsSetup::HitDetection, /*ignoreShapeType*/ false, Physics::ShapeType::Box)); + EXPECT_EQ(serializedAfterAdd, SerializePhysicsSetup(GetActor())); // 2. Remove colliders commandGroup.RemoveAllCommands(); - const AZStd::string serializedBeforeRemove = SerializePhysicsSetup(m_actor.get()); + const AZStd::string serializedBeforeRemove = SerializePhysicsSetup(GetActor()); size_t colliderIndexToRemove = 1; for (const AZStd::string& jointName : jointNames) @@ -64,18 +68,24 @@ namespace EMotionFX } EXPECT_TRUE(commandManager.ExecuteCommandGroup(commandGroup, result)); - const AZStd::string serializedAfterRemove = SerializePhysicsSetup(m_actor.get()); - EXPECT_EQ(jointCount * 2, PhysicsSetupUtils::CountColliders(m_actor.get(), PhysicsSetup::HitDetection)); - EXPECT_EQ(0, PhysicsSetupUtils::CountColliders(m_actor.get(), PhysicsSetup::HitDetection, /*ignoreShapeType*/false, Physics::ShapeType::Capsule)); + const AZStd::string serializedAfterRemove = SerializePhysicsSetup(GetActor()); + EXPECT_EQ(jointCount * 2, PhysicsSetupUtils::CountColliders(GetActor(), PhysicsSetup::HitDetection)); + EXPECT_EQ( + 0, + PhysicsSetupUtils::CountColliders( + GetActor(), PhysicsSetup::HitDetection, /*ignoreShapeType*/ false, Physics::ShapeType::Capsule)); EXPECT_TRUE(commandManager.Undo(result)); - EXPECT_EQ(jointCount * 3, PhysicsSetupUtils::CountColliders(m_actor.get(), PhysicsSetup::HitDetection)); - EXPECT_EQ(serializedBeforeRemove, SerializePhysicsSetup(m_actor.get())); + EXPECT_EQ(jointCount * 3, PhysicsSetupUtils::CountColliders(GetActor(), PhysicsSetup::HitDetection)); + EXPECT_EQ(serializedBeforeRemove, SerializePhysicsSetup(GetActor())); EXPECT_TRUE(commandManager.Redo(result)); - EXPECT_EQ(jointCount * 2, PhysicsSetupUtils::CountColliders(m_actor.get(), PhysicsSetup::HitDetection)); - EXPECT_EQ(0, PhysicsSetupUtils::CountColliders(m_actor.get(), PhysicsSetup::HitDetection, /*ignoreShapeType*/false, Physics::ShapeType::Capsule)); - EXPECT_EQ(serializedAfterRemove, SerializePhysicsSetup(m_actor.get())); + EXPECT_EQ(jointCount * 2, PhysicsSetupUtils::CountColliders(GetActor(), PhysicsSetup::HitDetection)); + EXPECT_EQ( + 0, + PhysicsSetupUtils::CountColliders( + GetActor(), PhysicsSetup::HitDetection, /*ignoreShapeType*/ false, Physics::ShapeType::Capsule)); + EXPECT_EQ(serializedAfterRemove, SerializePhysicsSetup(GetActor())); } TEST_F(ColliderCommandTests, AddRemove1000Colliders) @@ -84,11 +94,11 @@ namespace EMotionFX CommandSystem::CommandManager commandManager; MCore::CommandGroup commandGroup; - const AZ::u32 actorId = m_actor->GetID(); + const AZ::u32 actorId = GetActor()->GetID(); const AZStd::string jointName = "Bip01__pelvis"; // 1. Add colliders - const AZStd::string serializedBeforeAdd = SerializePhysicsSetup(m_actor.get()); + const AZStd::string serializedBeforeAdd = SerializePhysicsSetup(GetActor()); const size_t colliderCount = 1000; for (AZ::u32 i = 0; i < colliderCount; ++i) { @@ -96,51 +106,51 @@ namespace EMotionFX } EXPECT_TRUE(commandManager.ExecuteCommandGroup(commandGroup, result)); - const AZStd::string serializedAfterAdd = SerializePhysicsSetup(m_actor.get()); - EXPECT_EQ(colliderCount, PhysicsSetupUtils::CountColliders(m_actor.get(), PhysicsSetup::HitDetection)); - EXPECT_EQ(colliderCount, PhysicsSetupUtils::CountColliders(m_actor.get(), PhysicsSetup::HitDetection, /*ignoreShapeType*/false, Physics::ShapeType::Box)); + const AZStd::string serializedAfterAdd = SerializePhysicsSetup(GetActor()); + EXPECT_EQ(colliderCount, PhysicsSetupUtils::CountColliders(GetActor(), PhysicsSetup::HitDetection)); + EXPECT_EQ(colliderCount, PhysicsSetupUtils::CountColliders(GetActor(), PhysicsSetup::HitDetection, /*ignoreShapeType*/false, Physics::ShapeType::Box)); EXPECT_TRUE(commandManager.Undo(result)); - EXPECT_EQ(0, PhysicsSetupUtils::CountColliders(m_actor.get(), PhysicsSetup::HitDetection)); - EXPECT_EQ(serializedBeforeAdd, SerializePhysicsSetup(m_actor.get())); + EXPECT_EQ(0, PhysicsSetupUtils::CountColliders(GetActor(), PhysicsSetup::HitDetection)); + EXPECT_EQ(serializedBeforeAdd, SerializePhysicsSetup(GetActor())); EXPECT_TRUE(commandManager.Redo(result)); - EXPECT_EQ(colliderCount, PhysicsSetupUtils::CountColliders(m_actor.get(), PhysicsSetup::HitDetection)); - EXPECT_EQ(colliderCount, PhysicsSetupUtils::CountColliders(m_actor.get(), PhysicsSetup::HitDetection, /*ignoreShapeType*/false, Physics::ShapeType::Box)); - EXPECT_EQ(serializedAfterAdd, SerializePhysicsSetup(m_actor.get())); + EXPECT_EQ(colliderCount, PhysicsSetupUtils::CountColliders(GetActor(), PhysicsSetup::HitDetection)); + EXPECT_EQ(colliderCount, PhysicsSetupUtils::CountColliders(GetActor(), PhysicsSetup::HitDetection, /*ignoreShapeType*/false, Physics::ShapeType::Box)); + EXPECT_EQ(serializedAfterAdd, SerializePhysicsSetup(GetActor())); // 2. Clear colliders commandGroup.RemoveAllCommands(); - const AZStd::string serializedBeforeRemove = SerializePhysicsSetup(m_actor.get()); + const AZStd::string serializedBeforeRemove = SerializePhysicsSetup(GetActor()); CommandColliderHelpers::ClearColliders(actorId, jointName, PhysicsSetup::HitDetection, &commandGroup); EXPECT_TRUE(commandManager.ExecuteCommandGroup(commandGroup, result)); - const AZStd::string serializedAfterRemove = SerializePhysicsSetup(m_actor.get()); - EXPECT_EQ(0, PhysicsSetupUtils::CountColliders(m_actor.get(), PhysicsSetup::HitDetection)); - EXPECT_EQ(0, PhysicsSetupUtils::CountColliders(m_actor.get(), PhysicsSetup::HitDetection, /*ignoreShapeType*/false, Physics::ShapeType::Box)); + const AZStd::string serializedAfterRemove = SerializePhysicsSetup(GetActor()); + EXPECT_EQ(0, PhysicsSetupUtils::CountColliders(GetActor(), PhysicsSetup::HitDetection)); + EXPECT_EQ(0, PhysicsSetupUtils::CountColliders(GetActor(), PhysicsSetup::HitDetection, /*ignoreShapeType*/false, Physics::ShapeType::Box)); EXPECT_TRUE(commandManager.Undo(result)); - EXPECT_EQ(colliderCount, PhysicsSetupUtils::CountColliders(m_actor.get(), PhysicsSetup::HitDetection)); - EXPECT_EQ(serializedBeforeRemove, SerializePhysicsSetup(m_actor.get())); + EXPECT_EQ(colliderCount, PhysicsSetupUtils::CountColliders(GetActor(), PhysicsSetup::HitDetection)); + EXPECT_EQ(serializedBeforeRemove, SerializePhysicsSetup(GetActor())); EXPECT_TRUE(commandManager.Redo(result)); - EXPECT_EQ(0, PhysicsSetupUtils::CountColliders(m_actor.get(), PhysicsSetup::HitDetection)); - EXPECT_EQ(0, PhysicsSetupUtils::CountColliders(m_actor.get(), PhysicsSetup::HitDetection, /*ignoreShapeType*/false, Physics::ShapeType::Box)); - EXPECT_EQ(serializedAfterRemove, SerializePhysicsSetup(m_actor.get())); + EXPECT_EQ(0, PhysicsSetupUtils::CountColliders(GetActor(), PhysicsSetup::HitDetection)); + EXPECT_EQ(0, PhysicsSetupUtils::CountColliders(GetActor(), PhysicsSetup::HitDetection, /*ignoreShapeType*/false, Physics::ShapeType::Box)); + EXPECT_EQ(serializedAfterRemove, SerializePhysicsSetup(GetActor())); } TEST_F(ColliderCommandTests, AutoSizingColliders) { CommandSystem::CommandManager commandManager; - const AZ::u32 actorId = m_actor->GetID(); + const AZ::u32 actorId = GetActor()->GetID(); const AZStd::vector jointNames = GetTestJointNames(); ASSERT_TRUE(jointNames.size() > 0) << "The joint names test data needs at least one joint for this test."; const AZStd::string& jointName = jointNames[0]; CommandColliderHelpers::AddCollider(actorId, jointName, PhysicsSetup::HitDetection, azrtti_typeid()); - const AZStd::shared_ptr& physicsSetup = m_actor->GetPhysicsSetup(); + const AZStd::shared_ptr& physicsSetup = GetActor()->GetPhysicsSetup(); Physics::CharacterColliderConfiguration* colliderConfig = physicsSetup->GetColliderConfigByType(PhysicsSetup::HitDetection); EXPECT_NE(colliderConfig, nullptr) << "Collider config should be valid after we added a collider to it."; @@ -184,11 +194,11 @@ namespace EMotionFX const PhysicsSetup::ColliderConfigType m_configType = PhysicsSetup::ColliderConfigType::HitDetection; // Add collider to the given joint first. - const AZStd::shared_ptr& physicsSetup = m_actor->GetPhysicsSetup(); - EXPECT_TRUE(CommandColliderHelpers::AddCollider(m_actor->GetID(), m_jointName, m_configType, param.m_shapeType)); + const AZStd::shared_ptr& physicsSetup = GetActor()->GetPhysicsSetup(); + EXPECT_TRUE(CommandColliderHelpers::AddCollider(GetActor()->GetID(), m_jointName, m_configType, param.m_shapeType)); Physics::CharacterColliderConfiguration* characterColliderConfig = physicsSetup->GetColliderConfigByType(m_configType); ASSERT_TRUE(characterColliderConfig != nullptr); - Physics::CharacterColliderNodeConfiguration* nodeConfig = CommandColliderHelpers::GetCreateNodeConfig(m_actor.get(), m_jointName, *characterColliderConfig, result); + Physics::CharacterColliderNodeConfiguration* nodeConfig = CommandColliderHelpers::GetCreateNodeConfig(GetActor(), m_jointName, *characterColliderConfig, result); ASSERT_TRUE(nodeConfig != nullptr); EXPECT_EQ(nodeConfig->m_shapes.size(), 1); @@ -200,7 +210,8 @@ namespace EMotionFX // Create the adjust collider command and using the data from the test parameter. MCore::Command* orgCommand = CommandSystem::GetCommandManager()->FindCommand(CommandAdjustCollider::s_commandName); - CommandAdjustCollider* command = aznew CommandAdjustCollider(m_actor->GetID(), m_jointName, m_configType, /*colliderIndex=*/0, orgCommand); + CommandAdjustCollider* command = + aznew CommandAdjustCollider(GetActor()->GetID(), m_jointName, m_configType, /*colliderIndex=*/0, orgCommand); command->SetOldIsTrigger(colliderConfig->m_isTrigger); command->SetIsTrigger(param.m_isTrigger); command->SetOldPosition(colliderConfig->m_position); @@ -223,9 +234,9 @@ namespace EMotionFX } // Check execute. - const AZStd::string serializedBeforeExecute = SerializePhysicsSetup(m_actor.get()); + const AZStd::string serializedBeforeExecute = SerializePhysicsSetup(GetActor()); EXPECT_TRUE(CommandSystem::GetCommandManager()->ExecuteCommand(command, result)); - const AZStd::string serializedAfterExecute = SerializePhysicsSetup(m_actor.get()); + const AZStd::string serializedAfterExecute = SerializePhysicsSetup(GetActor()); EXPECT_EQ(colliderConfig->m_isTrigger, param.m_isTrigger); EXPECT_EQ(colliderConfig->m_position, param.m_position); @@ -243,12 +254,12 @@ namespace EMotionFX // Check undo. EXPECT_TRUE(CommandSystem::GetCommandManager()->Undo(result)); - const AZStd::string serializedAfterUndo = SerializePhysicsSetup(m_actor.get()); + const AZStd::string serializedAfterUndo = SerializePhysicsSetup(GetActor()); EXPECT_EQ(serializedAfterUndo, serializedBeforeExecute); // Check redo. EXPECT_TRUE(CommandSystem::GetCommandManager()->Redo(result)); - const AZStd::string serializedAfterRedo = SerializePhysicsSetup(m_actor.get()); + const AZStd::string serializedAfterRedo = SerializePhysicsSetup(GetActor()); EXPECT_EQ(serializedAfterRedo, serializedAfterExecute); } diff --git a/Gems/EMotionFX/Code/Tests/Integration/CanAddActor.cpp b/Gems/EMotionFX/Code/Tests/Integration/CanAddActor.cpp index 31e1f75d29..0c3228567e 100644 --- a/Gems/EMotionFX/Code/Tests/Integration/CanAddActor.cpp +++ b/Gems/EMotionFX/Code/Tests/Integration/CanAddActor.cpp @@ -13,9 +13,11 @@ #include #include +#include +#include #include #include -#include +#include namespace EMotionFX { @@ -33,14 +35,12 @@ namespace EMotionFX ASSERT_EQ(GetActorManager().GetNumActors(), 0); // Load an Actor - const char* actorCmd{ "ImportActor -filename @engroot@/Gems/EMotionFX/Code/Tests/TestAssets/Rin/rin.actor" }; - { - AZStd::string result; - EXPECT_TRUE(CommandSystem::GetCommandManager()->ExecuteCommand(actorCmd, result)) << result.c_str(); - } + AZ::Data::AssetId actorAssetId("{5060227D-B6F4-422E-BF82-41AAC5F228A5}"); + AZ::Data::Asset actorAsset = + TestActorAssets::CreateActorAssetAndRegister(actorAssetId, "Jack"); // Ensure the Actor is correct - ASSERT_TRUE(GetActorManager().FindActorByName("rinActor")); + ASSERT_TRUE(GetActorManager().FindActorByName("Jack")); EXPECT_EQ(GetActorManager().GetNumActors(), 1); } } // namespace EMotionFX diff --git a/Gems/EMotionFX/Code/Tests/MotionExtractionBusTests.cpp b/Gems/EMotionFX/Code/Tests/MotionExtractionBusTests.cpp index 170f4d2d75..347d8f93a8 100644 --- a/Gems/EMotionFX/Code/Tests/MotionExtractionBusTests.cpp +++ b/Gems/EMotionFX/Code/Tests/MotionExtractionBusTests.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include #include diff --git a/Gems/EMotionFX/Code/Tests/MultiThreadSchedulerTests.cpp b/Gems/EMotionFX/Code/Tests/MultiThreadSchedulerTests.cpp index 180da46746..02622ecd67 100644 --- a/Gems/EMotionFX/Code/Tests/MultiThreadSchedulerTests.cpp +++ b/Gems/EMotionFX/Code/Tests/MultiThreadSchedulerTests.cpp @@ -9,6 +9,8 @@ #include #include #include +#include +#include #include #include #include diff --git a/Gems/EMotionFX/Code/Tests/ProvidesUI/AnimGraph/AnimGraphActivateTests.cpp b/Gems/EMotionFX/Code/Tests/ProvidesUI/AnimGraph/AnimGraphActivateTests.cpp index 78cac409a6..dea332b40e 100644 --- a/Gems/EMotionFX/Code/Tests/ProvidesUI/AnimGraph/AnimGraphActivateTests.cpp +++ b/Gems/EMotionFX/Code/Tests/ProvidesUI/AnimGraph/AnimGraphActivateTests.cpp @@ -19,11 +19,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include namespace EMotionFX @@ -78,7 +80,8 @@ namespace EMotionFX EXPECT_TRUE(CommandSystem::GetCommandManager()->ExecuteCommandGroup(group, commandResult)) << commandResult.c_str(); // Create temp Actor - m_actor = ActorFactory::CreateAndInit(1, "tempActor"); + AZ::Data::AssetId actorAssetId("{5060227D-B6F4-422E-BF82-41AAC5F228A5}"); + m_actorAsset = TestActorAssets::CreateActorAssetAndRegister(actorAssetId, 1, "tempActor"); // Cache some local poitners. m_animGraphPlugin = static_cast(EMStudio::GetPluginManager()->FindActivePlugin(EMStudio::AnimGraphPlugin::CLASS_ID)); @@ -90,6 +93,7 @@ namespace EMotionFX void TearDown() override { + GetEMotionFX().GetActorManager()->UnregisterAllActors(); QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); delete m_animGraph; UIFixture::TearDown(); @@ -104,7 +108,7 @@ namespace EMotionFX AZStd::string m_entryNodeName = "testEntry"; AnimGraph* m_animGraph = nullptr; EMStudio::AnimGraphPlugin* m_animGraphPlugin = nullptr; - AutoRegisteredActor m_actor; + AZ::Data::Asset m_actorAsset; }; TEST_F(PopulatedAnimGraphFixture, CanActivateValidGraph) diff --git a/Gems/EMotionFX/Code/Tests/ProvidesUI/AnimGraph/AnimGraphModelTests.cpp b/Gems/EMotionFX/Code/Tests/ProvidesUI/AnimGraph/AnimGraphModelTests.cpp index 4ffc9e0097..b9b96bb59c 100644 --- a/Gems/EMotionFX/Code/Tests/ProvidesUI/AnimGraph/AnimGraphModelTests.cpp +++ b/Gems/EMotionFX/Code/Tests/ProvidesUI/AnimGraph/AnimGraphModelTests.cpp @@ -27,7 +27,6 @@ #include #include #include -#include #include #include #include @@ -43,6 +42,7 @@ #include #include #include +#include #include namespace EMotionFX @@ -423,7 +423,10 @@ namespace EMotionFX using testing::Eq; using testing::Not; - AutoRegisteredActor actor = EMotionFX::ActorFactory::CreateAndInit(1); + AZ::Data::AssetId actorAssetId("{5060227D-B6F4-422E-BF82-41AAC5F228A5}"); + AZ::Data::Asset actorAsset = + TestActorAssets::CreateActorAssetAndRegister(actorAssetId, 1); + auto motionSet = AZStd::make_unique(); { @@ -432,7 +435,7 @@ namespace EMotionFX } auto* animGraph = EMotionFX::GetAnimGraphManager().FindAnimGraphByID(0); - auto* actorInstance = EMotionFX::ActorInstance::Create(actor.get()); + auto* actorInstance = EMotionFX::ActorInstance::Create(actorAsset->GetActor()); auto* animGraphInstance = EMotionFX::AnimGraphInstance::Create(animGraph, actorInstance, motionSet.get()); actorInstance->SetAnimGraphInstance(animGraphInstance); diff --git a/Gems/EMotionFX/Code/Tests/ProvidesUI/Menus/FileMenu/CanReset.cpp b/Gems/EMotionFX/Code/Tests/ProvidesUI/Menus/FileMenu/CanReset.cpp index d53ddcc924..c374938590 100644 --- a/Gems/EMotionFX/Code/Tests/ProvidesUI/Menus/FileMenu/CanReset.cpp +++ b/Gems/EMotionFX/Code/Tests/ProvidesUI/Menus/FileMenu/CanReset.cpp @@ -24,12 +24,11 @@ #include #include -#include #include #include +#include #include - namespace EMotionFX { @@ -58,8 +57,10 @@ namespace EMotionFX ASSERT_EQ(GetMotionManager().GetNumMotions(), 0) << "Expected exactly zero motions"; // Create Actor, AnimGraph, Motionset and Motion - AutoRegisteredActor actor = ActorFactory::CreateAndInit(2, "SampleActor"); - ActorInstance::Create(actor.get()); + AZ::Data::AssetId actorAssetId("{5060227D-B6F4-422E-BF82-41AAC5F228A5}"); + AZ::Data::Asset actorAsset = + TestActorAssets::CreateActorAssetAndRegister(actorAssetId, 2, "SampleActor"); + ActorInstance::Create(actorAsset->GetActor()); { AZStd::string result; ASSERT_TRUE(CommandSystem::GetCommandManager()->ExecuteCommand(createAnimGraphCmd, result)) << result.c_str(); diff --git a/Gems/EMotionFX/Code/Tests/ProvidesUI/Ragdoll/CanCopyPasteColliders.cpp b/Gems/EMotionFX/Code/Tests/ProvidesUI/Ragdoll/CanCopyPasteColliders.cpp index 4f43e7dae5..17f07b2180 100644 --- a/Gems/EMotionFX/Code/Tests/ProvidesUI/Ragdoll/CanCopyPasteColliders.cpp +++ b/Gems/EMotionFX/Code/Tests/ProvidesUI/Ragdoll/CanCopyPasteColliders.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include namespace EMotionFX @@ -79,7 +80,11 @@ namespace EMotionFX TEST_F(CopyPasteRagdollCollidersFixture, CanCopyCollider) #endif // AZ_TRAIT_DISABLE_FAILED_EMOTION_FX_EDITOR_TESTS { - AutoRegisteredActor actor{ActorFactory::CreateAndInit(4)}; + AZ::Data::AssetId actorAssetId("{5060227D-B6F4-422E-BF82-41AAC5F228A5}"); + AZ::Data::Asset actorAsset = + TestActorAssets::CreateActorAssetAndRegister(actorAssetId, 4); + const Actor* actor = actorAsset->GetActor(); + const Physics::RagdollConfiguration& ragdollConfig = actor->GetPhysicsSetup()->GetRagdollConfig(); const Physics::CharacterColliderConfiguration& simulatedObjectConfig = actor->GetPhysicsSetup()->GetSimulatedObjectColliderConfig(); @@ -104,8 +109,7 @@ namespace EMotionFX { AZStd::string result; - EXPECT_TRUE(CommandSystem::GetCommandManager()->ExecuteCommand( - "Select -actorId " + AZStd::to_string(actor->GetID()), + EXPECT_TRUE(CommandSystem::GetCommandManager()->ExecuteCommand("Select -actorId " + AZStd::to_string(actor->GetID()), result)) << result.c_str(); } diff --git a/Gems/EMotionFX/Code/Tests/ProvidesUI/Ragdoll/CanCopyPasteJointLimits.cpp b/Gems/EMotionFX/Code/Tests/ProvidesUI/Ragdoll/CanCopyPasteJointLimits.cpp index 8e4fba0dd6..3f475c7916 100644 --- a/Gems/EMotionFX/Code/Tests/ProvidesUI/Ragdoll/CanCopyPasteJointLimits.cpp +++ b/Gems/EMotionFX/Code/Tests/ProvidesUI/Ragdoll/CanCopyPasteJointLimits.cpp @@ -14,7 +14,6 @@ #include #include -#include #include #include #include @@ -25,6 +24,7 @@ #include #include #include +#include #include namespace EMotionFX @@ -76,7 +76,9 @@ namespace EMotionFX return AZStd::make_unique(); }); - AutoRegisteredActor actor {ActorFactory::CreateAndInit(4)}; + AZ::Data::AssetId actorAssetId("{5060227D-B6F4-422E-BF82-41AAC5F228A5}"); + AZ::Data::Asset actorAsset = TestActorAssets::CreateActorAssetAndRegister(actorAssetId, 4); + const Actor* actor = actorAsset->GetActor(); { AZStd::string result; diff --git a/Gems/EMotionFX/Code/Tests/RagdollCommandTests.cpp b/Gems/EMotionFX/Code/Tests/RagdollCommandTests.cpp index e9a4cc0c26..1004b0a1f6 100644 --- a/Gems/EMotionFX/Code/Tests/RagdollCommandTests.cpp +++ b/Gems/EMotionFX/Code/Tests/RagdollCommandTests.cpp @@ -99,28 +99,28 @@ namespace EMotionFX "l_hand", }; - CommandRagdollHelpers::AddJointsToRagdoll(m_actor->GetID(), {"l_shldr"}, &commandGroup); + CommandRagdollHelpers::AddJointsToRagdoll(GetActor()->GetID(), {"l_shldr"}, &commandGroup); EXPECT_TRUE(commandManager.ExecuteCommandGroup(commandGroup, result)) << result.c_str(); EXPECT_THAT( - GetRagdollJointNames(m_actor.get()), + GetRagdollJointNames(GetActor()), testing::UnorderedPointwise( StrEq(), jointsToLeftShoulder ) ); - const AZStd::string serializedBeforeHandAdded = SerializePhysicsSetup(m_actor.get()); + const AZStd::string serializedBeforeHandAdded = SerializePhysicsSetup(GetActor()); // Adding l_hand should add l_upArm and l_loArm as well commandGroup.RemoveAllCommands(); - CommandRagdollHelpers::AddJointsToRagdoll(m_actor->GetID(), {"l_hand"}, &commandGroup); + CommandRagdollHelpers::AddJointsToRagdoll(GetActor()->GetID(), {"l_hand"}, &commandGroup); EXPECT_TRUE(commandManager.ExecuteCommandGroup(commandGroup, result)) << result.c_str(); - const AZStd::string serializedAfterHandAdded = SerializePhysicsSetup(m_actor.get()); + const AZStd::string serializedAfterHandAdded = SerializePhysicsSetup(GetActor()); EXPECT_THAT( - GetRagdollJointNames(m_actor.get()), + GetRagdollJointNames(GetActor()), testing::UnorderedPointwise( StrEq(), jointsToLeftHand @@ -129,23 +129,23 @@ namespace EMotionFX EXPECT_TRUE(commandManager.Undo(result)) << result.c_str(); EXPECT_THAT( - GetRagdollJointNames(m_actor.get()), + GetRagdollJointNames(GetActor()), testing::UnorderedPointwise( StrEq(), jointsToLeftShoulder ) ); - EXPECT_THAT(SerializePhysicsSetup(m_actor.get()), StrEq(serializedBeforeHandAdded)); + EXPECT_THAT(SerializePhysicsSetup(GetActor()), StrEq(serializedBeforeHandAdded)); EXPECT_TRUE(commandManager.Redo(result)) << result.c_str(); EXPECT_THAT( - GetRagdollJointNames(m_actor.get()), + GetRagdollJointNames(GetActor()), testing::UnorderedPointwise( StrEq(), jointsToLeftHand ) ); - EXPECT_THAT(SerializePhysicsSetup(m_actor.get()), StrEq(serializedAfterHandAdded)); + EXPECT_THAT(SerializePhysicsSetup(GetActor()), StrEq(serializedAfterHandAdded)); } TEST_F(RagdollCommandTests, AddJointHigherInHierarchy) @@ -166,10 +166,10 @@ namespace EMotionFX "l_hand", }; - CommandRagdollHelpers::AddJointsToRagdoll(m_actor->GetID(), {"l_hand"}, &commandGroup); + CommandRagdollHelpers::AddJointsToRagdoll(GetActor()->GetID(), {"l_hand"}, &commandGroup); EXPECT_TRUE(commandManager.ExecuteCommandGroup(commandGroup, result)) << result.c_str(); EXPECT_THAT( - GetRagdollJointNames(m_actor.get()), + GetRagdollJointNames(GetActor()), testing::UnorderedPointwise( StrEq(), jointsToLeftHand @@ -178,10 +178,10 @@ namespace EMotionFX // l_shldr should already be in the ragdoll, so adding it should do nothing commandGroup.RemoveAllCommands(); - CommandRagdollHelpers::AddJointsToRagdoll(m_actor->GetID(), {"l_shldr"}, &commandGroup); + CommandRagdollHelpers::AddJointsToRagdoll(GetActor()->GetID(), {"l_shldr"}, &commandGroup); EXPECT_TRUE(commandManager.ExecuteCommandGroup(commandGroup, result)) << result.c_str(); EXPECT_THAT( - GetRagdollJointNames(m_actor.get()), + GetRagdollJointNames(GetActor()), testing::UnorderedPointwise( StrEq(), jointsToLeftHand @@ -190,11 +190,11 @@ namespace EMotionFX // Undo here undoes the addition of l_hand EXPECT_TRUE(commandManager.Undo(result)) << result.c_str(); - EXPECT_TRUE(GetRagdollJointNames(m_actor.get()).empty()); + EXPECT_TRUE(GetRagdollJointNames(GetActor()).empty()); EXPECT_TRUE(commandManager.Redo(result)) << result.c_str(); EXPECT_THAT( - GetRagdollJointNames(m_actor.get()), + GetRagdollJointNames(GetActor()), testing::UnorderedPointwise( StrEq(), jointsToLeftHand @@ -221,16 +221,16 @@ namespace EMotionFX }; // Add a joint to the ragdoll that does not make a chain all the way to the root - EXPECT_TRUE(commandManager.ExecuteCommand(aznew CommandAddRagdollJoint(m_actor->GetID(), "l_shldr"), result)) << result.c_str(); + EXPECT_TRUE(commandManager.ExecuteCommand(aznew CommandAddRagdollJoint(GetActor()->GetID(), "l_shldr"), result)) << result.c_str(); EXPECT_THAT( - GetRagdollJointNames(m_actor.get()), + GetRagdollJointNames(GetActor()), testing::UnorderedPointwise(StrEq(), AZStd::vector{"l_shldr"}) ); - CommandRagdollHelpers::AddJointsToRagdoll(m_actor->GetID(), {"l_hand"}, &commandGroup); + CommandRagdollHelpers::AddJointsToRagdoll(GetActor()->GetID(), {"l_hand"}, &commandGroup); EXPECT_TRUE(commandManager.ExecuteCommandGroup(commandGroup, result)) << result.c_str(); EXPECT_THAT( - GetRagdollJointNames(m_actor.get()), + GetRagdollJointNames(GetActor()), testing::UnorderedPointwise(StrEq(), jointsToLeftHand) ); } @@ -250,17 +250,17 @@ namespace EMotionFX }; // Add joints from the root to the left hand - CommandRagdollHelpers::AddJointsToRagdoll(m_actor->GetID(), {"l_hand"}, &commandGroup); + CommandRagdollHelpers::AddJointsToRagdoll(GetActor()->GetID(), {"l_hand"}, &commandGroup); EXPECT_TRUE(commandManager.ExecuteCommandGroup(commandGroup, result)) << result.c_str(); // Removing the left shoulder should remove the elbow, wrist, and hand // as well commandGroup.RemoveAllCommands(); - CommandRagdollHelpers::RemoveJointsFromRagdoll(m_actor->GetID(), {"l_shldr"}, &commandGroup); + CommandRagdollHelpers::RemoveJointsFromRagdoll(GetActor()->GetID(), {"l_shldr"}, &commandGroup); EXPECT_TRUE(commandManager.ExecuteCommandGroup(commandGroup, result)) << result.c_str(); EXPECT_THAT( - GetRagdollJointNames(m_actor.get()), + GetRagdollJointNames(GetActor()), testing::UnorderedPointwise(StrEq(), jointsToSpine3) ); } @@ -271,24 +271,24 @@ namespace EMotionFX CommandSystem::CommandManager commandManager; MCore::CommandGroup commandGroup; - CommandRagdollHelpers::AddJointsToRagdoll(m_actor->GetID(), {"l_hand"}, &commandGroup); + CommandRagdollHelpers::AddJointsToRagdoll(GetActor()->GetID(), {"l_hand"}, &commandGroup); EXPECT_TRUE(commandManager.ExecuteCommandGroup(commandGroup, result)) << result.c_str(); - const AZStd::string serializedBeforeSphereAdded = SerializePhysicsSetup(m_actor.get()); + const AZStd::string serializedBeforeSphereAdded = SerializePhysicsSetup(GetActor()); - CommandColliderHelpers::AddCollider(m_actor->GetID(), "l_hand", + CommandColliderHelpers::AddCollider(GetActor()->GetID(), "l_hand", PhysicsSetup::Ragdoll, azrtti_typeid()); - const AZStd::string serializedAfterSphereAdded = SerializePhysicsSetup(m_actor.get()); + const AZStd::string serializedAfterSphereAdded = SerializePhysicsSetup(GetActor()); EXPECT_THAT(serializedAfterSphereAdded, ::testing::Not(StrEq(serializedBeforeSphereAdded))); EXPECT_TRUE(commandManager.Undo(result)) << result.c_str(); - EXPECT_THAT(SerializePhysicsSetup(m_actor.get()), StrEq(serializedBeforeSphereAdded)); + EXPECT_THAT(SerializePhysicsSetup(GetActor()), StrEq(serializedBeforeSphereAdded)); EXPECT_TRUE(commandManager.Redo(result)) << result.c_str(); - EXPECT_THAT(SerializePhysicsSetup(m_actor.get()), StrEq(serializedAfterSphereAdded)); + EXPECT_THAT(SerializePhysicsSetup(GetActor()), StrEq(serializedAfterSphereAdded)); } } // namespace EMotionFX diff --git a/Gems/EMotionFX/Code/Tests/SimulatedObjectCommandTests.cpp b/Gems/EMotionFX/Code/Tests/SimulatedObjectCommandTests.cpp index 4bac70c13d..dab761f649 100644 --- a/Gems/EMotionFX/Code/Tests/SimulatedObjectCommandTests.cpp +++ b/Gems/EMotionFX/Code/Tests/SimulatedObjectCommandTests.cpp @@ -55,25 +55,25 @@ namespace EMotionFX CommandSystem::CommandManager commandManager; MCore::CommandGroup commandGroup; - const uint32 actorId = m_actor->GetID(); + const uint32 actorId = GetActor()->GetID(); const AZStd::vector jointNames = GetTestJointNames(); // 1. Add simulated object. - const AZStd::string serializedBeforeAdd = SerializeSimulatedObjectSetup(m_actor.get()); + const AZStd::string serializedBeforeAdd = SerializeSimulatedObjectSetup(GetActor()); CommandSimulatedObjectHelpers::AddSimulatedObject(actorId, AZStd::nullopt, &commandGroup); CommandSimulatedObjectHelpers::AddSimulatedObject(actorId, AZStd::nullopt, &commandGroup); CommandSimulatedObjectHelpers::AddSimulatedObject(actorId, AZStd::nullopt, &commandGroup); EXPECT_TRUE(commandManager.ExecuteCommandGroup(commandGroup, result)); - const AZStd::string serializedAfterAdd = SerializeSimulatedObjectSetup(m_actor.get()); - EXPECT_EQ(3, CountSimulatedObjects(m_actor.get())); + const AZStd::string serializedAfterAdd = SerializeSimulatedObjectSetup(GetActor()); + EXPECT_EQ(3, CountSimulatedObjects(GetActor())); EXPECT_TRUE(commandManager.Undo(result)); - EXPECT_EQ(0, CountSimulatedObjects(m_actor.get())); - EXPECT_EQ(serializedBeforeAdd, SerializeSimulatedObjectSetup(m_actor.get())); + EXPECT_EQ(0, CountSimulatedObjects(GetActor())); + EXPECT_EQ(serializedBeforeAdd, SerializeSimulatedObjectSetup(GetActor())); EXPECT_TRUE(commandManager.Redo(result)); - EXPECT_EQ(3, CountSimulatedObjects(m_actor.get())); - EXPECT_EQ(serializedAfterAdd, SerializeSimulatedObjectSetup(m_actor.get())); + EXPECT_EQ(3, CountSimulatedObjects(GetActor())); + EXPECT_EQ(serializedAfterAdd, SerializeSimulatedObjectSetup(GetActor())); // 2. Remove simulated object. commandGroup.RemoveAllCommands(); @@ -81,22 +81,22 @@ namespace EMotionFX CommandSimulatedObjectHelpers::RemoveSimulatedObject(actorId, 0, &commandGroup); CommandSimulatedObjectHelpers::RemoveSimulatedObject(actorId, 0, &commandGroup); EXPECT_TRUE(commandManager.ExecuteCommandGroup(commandGroup, result)); - EXPECT_EQ(0, CountSimulatedObjects(m_actor.get())); - EXPECT_EQ(serializedBeforeAdd, SerializeSimulatedObjectSetup(m_actor.get())); + EXPECT_EQ(0, CountSimulatedObjects(GetActor())); + EXPECT_EQ(serializedBeforeAdd, SerializeSimulatedObjectSetup(GetActor())); EXPECT_TRUE(commandManager.Undo(result)); - EXPECT_EQ(3, CountSimulatedObjects(m_actor.get())); - EXPECT_EQ(serializedAfterAdd, SerializeSimulatedObjectSetup(m_actor.get())); + EXPECT_EQ(3, CountSimulatedObjects(GetActor())); + EXPECT_EQ(serializedAfterAdd, SerializeSimulatedObjectSetup(GetActor())); EXPECT_TRUE(commandManager.Redo(result)); - EXPECT_EQ(0, CountSimulatedObjects(m_actor.get())); - EXPECT_EQ(serializedBeforeAdd, SerializeSimulatedObjectSetup(m_actor.get())); + EXPECT_EQ(0, CountSimulatedObjects(GetActor())); + EXPECT_EQ(serializedBeforeAdd, SerializeSimulatedObjectSetup(GetActor())); // 3. Add simulated joints. // 3.1 Add a simulated object first to put in the simulated joints. commandGroup.RemoveAllCommands(); CommandSimulatedObjectHelpers::AddSimulatedObject(actorId); - const AZStd::string serialized3_1 = SerializeSimulatedObjectSetup(m_actor.get()); + const AZStd::string serialized3_1 = SerializeSimulatedObjectSetup(GetActor()); // 3.2 Add simulated joints. // Joint hierarchy as follow: @@ -105,7 +105,7 @@ namespace EMotionFX // --l_loLeg // --l_ankle // --l_ball - const Skeleton* skeleton = m_actor->GetSkeleton(); + const Skeleton* skeleton = GetActor()->GetSkeleton(); const size_t l_upLegIdx = skeleton->FindNodeByName("l_upLeg")->GetNodeIndex(); const size_t l_upLegRollIdx = skeleton->FindNodeByName("l_upLegRoll")->GetNodeIndex(); const size_t l_loLegIdx = skeleton->FindNodeByName("l_loLeg")->GetNodeIndex(); @@ -113,33 +113,33 @@ namespace EMotionFX const size_t l_ballIdx = skeleton->FindNodeByName("l_ball")->GetNodeIndex(); CommandSimulatedObjectHelpers::AddSimulatedJoints(actorId, {l_upLegIdx, l_upLegRollIdx, l_loLegIdx, l_ankleIdx, l_ballIdx}, 0, false, &commandGroup); EXPECT_TRUE(commandManager.ExecuteCommandGroup(commandGroup, result)); - const AZStd::string serialized3_2 = SerializeSimulatedObjectSetup(m_actor.get()); - EXPECT_EQ(5, CountSimulatedJoints(m_actor.get(), 0)); - EXPECT_EQ(2, CountChildJoints(m_actor.get(), 0, l_upLegIdx)); - EXPECT_EQ(0, CountChildJoints(m_actor.get(), 0, l_upLegRollIdx)); - EXPECT_EQ(1, CountChildJoints(m_actor.get(), 0, l_loLegIdx)); + const AZStd::string serialized3_2 = SerializeSimulatedObjectSetup(GetActor()); + EXPECT_EQ(5, CountSimulatedJoints(GetActor(), 0)); + EXPECT_EQ(2, CountChildJoints(GetActor(), 0, l_upLegIdx)); + EXPECT_EQ(0, CountChildJoints(GetActor(), 0, l_upLegRollIdx)); + EXPECT_EQ(1, CountChildJoints(GetActor(), 0, l_loLegIdx)); EXPECT_TRUE(commandManager.Undo(result)); - EXPECT_EQ(0, CountSimulatedJoints(m_actor.get(), 0)); - EXPECT_EQ(serialized3_1, SerializeSimulatedObjectSetup(m_actor.get())); + EXPECT_EQ(0, CountSimulatedJoints(GetActor(), 0)); + EXPECT_EQ(serialized3_1, SerializeSimulatedObjectSetup(GetActor())); EXPECT_TRUE(commandManager.Redo(result)); - EXPECT_EQ(5, CountSimulatedJoints(m_actor.get(), 0)); - EXPECT_EQ(serialized3_2, SerializeSimulatedObjectSetup(m_actor.get())); + EXPECT_EQ(5, CountSimulatedJoints(GetActor(), 0)); + EXPECT_EQ(serialized3_2, SerializeSimulatedObjectSetup(GetActor())); // 4 Remove simulated joints. // 4.1 Test sparse chain. - EXPECT_EQ(1, CountRootJoints(m_actor.get(), 0)); + EXPECT_EQ(1, CountRootJoints(GetActor(), 0)); commandGroup.RemoveAllCommands(); CommandSimulatedObjectHelpers::RemoveSimulatedJoints(actorId, {l_loLegIdx}, 0, false, &commandGroup); EXPECT_TRUE(commandManager.ExecuteCommandGroup(commandGroup, result)); - EXPECT_EQ(2, CountRootJoints(m_actor.get(), 0)); + EXPECT_EQ(2, CountRootJoints(GetActor(), 0)); EXPECT_TRUE(commandManager.Undo(result)); - EXPECT_EQ(1, CountRootJoints(m_actor.get(), 0)); + EXPECT_EQ(1, CountRootJoints(GetActor(), 0)); EXPECT_TRUE(commandManager.Redo(result)); - EXPECT_EQ(2, CountRootJoints(m_actor.get(), 0)); + EXPECT_EQ(2, CountRootJoints(GetActor(), 0)); EXPECT_TRUE(commandManager.Undo(result)); @@ -147,23 +147,23 @@ namespace EMotionFX commandGroup.RemoveAllCommands(); CommandSimulatedObjectHelpers::RemoveSimulatedJoints(actorId, { l_upLegIdx, l_upLegRollIdx, l_loLegIdx, l_ankleIdx, l_ballIdx }, 0, false, &commandGroup); EXPECT_TRUE(commandManager.ExecuteCommandGroup(commandGroup, result)); - EXPECT_EQ(0, CountSimulatedJoints(m_actor.get(), 0)); - EXPECT_EQ(serialized3_1, SerializeSimulatedObjectSetup(m_actor.get())); + EXPECT_EQ(0, CountSimulatedJoints(GetActor(), 0)); + EXPECT_EQ(serialized3_1, SerializeSimulatedObjectSetup(GetActor())); EXPECT_TRUE(commandManager.Undo(result)); - EXPECT_EQ(5, CountSimulatedJoints(m_actor.get(), 0)); - EXPECT_EQ(serialized3_2, SerializeSimulatedObjectSetup(m_actor.get())); + EXPECT_EQ(5, CountSimulatedJoints(GetActor(), 0)); + EXPECT_EQ(serialized3_2, SerializeSimulatedObjectSetup(GetActor())); // 4.3 Test removing the root joint and children. commandGroup.RemoveAllCommands(); CommandSimulatedObjectHelpers::RemoveSimulatedJoints(actorId, { l_upLegIdx }, 0, true, &commandGroup); EXPECT_TRUE(commandManager.ExecuteCommandGroup(commandGroup, result)); - EXPECT_EQ(0, CountSimulatedJoints(m_actor.get(), 0)); - EXPECT_EQ(serialized3_1, SerializeSimulatedObjectSetup(m_actor.get())); + EXPECT_EQ(0, CountSimulatedJoints(GetActor(), 0)); + EXPECT_EQ(serialized3_1, SerializeSimulatedObjectSetup(GetActor())); EXPECT_TRUE(commandManager.Undo(result)); - EXPECT_EQ(5, CountSimulatedJoints(m_actor.get(), 0)); - EXPECT_EQ(serialized3_2, SerializeSimulatedObjectSetup(m_actor.get())); + EXPECT_EQ(5, CountSimulatedJoints(GetActor(), 0)); + EXPECT_EQ(serialized3_2, SerializeSimulatedObjectSetup(GetActor())); } TEST_F(SimulatedObjectCommandTests, SimulatedObjectCommands_UndoRemoveJointTest) @@ -172,35 +172,35 @@ namespace EMotionFX CommandSystem::CommandManager commandManager; MCore::CommandGroup commandGroup; - const uint32 actorId = m_actor->GetID(); + const uint32 actorId = GetActor()->GetID(); const AZStd::vector jointNames = GetTestJointNames(); // 1. Add simulated object CommandSimulatedObjectHelpers::AddSimulatedObject(actorId, AZStd::nullopt, &commandGroup); EXPECT_TRUE(commandManager.ExecuteCommandGroup(commandGroup, result)); - const AZStd::string serializedBase = SerializeSimulatedObjectSetup(m_actor.get()); + const AZStd::string serializedBase = SerializeSimulatedObjectSetup(GetActor()); const size_t simulatedObjectIndex = 0; // 2. Add r_upLeg simulated joints - const Skeleton* skeleton = m_actor->GetSkeleton(); + const Skeleton* skeleton = GetActor()->GetSkeleton(); const size_t r_upLegIdx = skeleton->FindNodeByName("r_upLeg")->GetNodeIndex(); const size_t r_loLegIdx = skeleton->FindNodeByName("r_loLeg")->GetNodeIndex(); CommandSimulatedObjectHelpers::AddSimulatedJoints(actorId, { r_upLegIdx, r_loLegIdx }, 0, false); - EXPECT_EQ(2, CountSimulatedJoints(m_actor.get(), 0)); - const AZStd::string serializedUpLeg = SerializeSimulatedObjectSetup(m_actor.get()); + EXPECT_EQ(2, CountSimulatedJoints(GetActor(), 0)); + const AZStd::string serializedUpLeg = SerializeSimulatedObjectSetup(GetActor()); // 3. Remove the r_loLeg simulated joint AZStd::vector jointsToBeRemoved; commandGroup.RemoveAllCommands(); CommandSimulatedObjectHelpers::RemoveSimulatedJoints(actorId, { r_upLegIdx }, simulatedObjectIndex, true, &commandGroup); EXPECT_TRUE(commandManager.ExecuteCommandGroup(commandGroup, result)); - EXPECT_EQ(0, CountSimulatedJoints(m_actor.get(), 0)); - EXPECT_EQ(serializedBase, SerializeSimulatedObjectSetup(m_actor.get())); + EXPECT_EQ(0, CountSimulatedJoints(GetActor(), 0)); + EXPECT_EQ(serializedBase, SerializeSimulatedObjectSetup(GetActor())); // 4. Undo // This recreates r_loLeg and r_loLeg but won't add all other children recursively as only these two joints got aded in step 3. EXPECT_TRUE(commandManager.Undo(result)); - EXPECT_EQ(2, CountSimulatedJoints(m_actor.get(), 0)); - EXPECT_EQ(serializedUpLeg, SerializeSimulatedObjectSetup(m_actor.get())); + EXPECT_EQ(2, CountSimulatedJoints(GetActor(), 0)); + EXPECT_EQ(serializedUpLeg, SerializeSimulatedObjectSetup(GetActor())); } } // namespace EMotionFX diff --git a/Gems/EMotionFX/Code/Tests/SimulatedObjectModelTests.cpp b/Gems/EMotionFX/Code/Tests/SimulatedObjectModelTests.cpp index 41269cec77..85da96412b 100644 --- a/Gems/EMotionFX/Code/Tests/SimulatedObjectModelTests.cpp +++ b/Gems/EMotionFX/Code/Tests/SimulatedObjectModelTests.cpp @@ -22,17 +22,21 @@ #include #include #include +#include namespace EMotionFX { using SimulatedObjectModelTestsFixture = UIFixture; TEST_F(SimulatedObjectModelTestsFixture, CanUndoAddSimulatedObjectAndSimulatedJointWithChildren) { - AutoRegisteredActor actor = ActorFactory::CreateAndInit(3, "simulatedObjectModelTestActor"); + AZ::Data::AssetId actorAssetId("{5060227D-B6F4-422E-BF82-41AAC5F228A5}"); + AZ::Data::Asset actorAsset = + TestActorAssets::CreateActorAssetAndRegister(actorAssetId, 3, "simulatedObjectModelTestActor"); + const Actor* actor = actorAsset->GetActor(); EMotionFX::SimulatedObjectWidget* simulatedObjectWidget = static_cast(EMStudio::GetPluginManager()->FindActivePlugin(EMotionFX::SimulatedObjectWidget::CLASS_ID)); ASSERT_TRUE(simulatedObjectWidget) << "Simulated Object plugin not loaded"; - simulatedObjectWidget->ActorSelectionChanged(actor.get()); + simulatedObjectWidget->ActorSelectionChanged(actorAsset->GetActor()); SimulatedObjectModel* model = simulatedObjectWidget->GetSimulatedObjectModel(); diff --git a/Gems/EMotionFX/Code/Tests/SimulatedObjectSerializeTests.cpp b/Gems/EMotionFX/Code/Tests/SimulatedObjectSerializeTests.cpp index c261f63bc1..e5c2c5ce4f 100644 --- a/Gems/EMotionFX/Code/Tests/SimulatedObjectSerializeTests.cpp +++ b/Gems/EMotionFX/Code/Tests/SimulatedObjectSerializeTests.cpp @@ -20,7 +20,7 @@ namespace EMotionFX TEST_F(SimulatedObjectSerializeTests, SerializeTest) { - SimulatedObjectSetup* setup = m_actor->GetSimulatedObjectSetup().get(); + SimulatedObjectSetup* setup = GetActor()->GetSimulatedObjectSetup().get(); // Build some setup. SimulatedObject* object = setup->AddSimulatedObject(); @@ -29,7 +29,7 @@ namespace EMotionFX object->SetGravityFactor(3.0f); object->SetStiffnessFactor(4.0f); const AZStd::vector jointNames = { "l_upArm", "l_loArm", "l_hand" }; - Skeleton* skeleton = m_actor->GetSkeleton(); + Skeleton* skeleton = GetActor()->GetSkeleton(); for (const AZStd::string& name : jointNames) { size_t skeletonJointIndex; @@ -50,7 +50,7 @@ namespace EMotionFX object->GetSimulatedJoint(0)->SetPinned(true); // Serialize it and deserialize it. - const AZStd::string serialized = SerializeSimulatedObjectSetup(m_actor.get()); + const AZStd::string serialized = SerializeSimulatedObjectSetup(GetActor()); AZStd::unique_ptr loadedSetup(DeserializeSimulatedObjectSetup(serialized)); // Verify some of the contents of the deserialized version. diff --git a/Gems/EMotionFX/Code/Tests/SkeletalLODTests.cpp b/Gems/EMotionFX/Code/Tests/SkeletalLODTests.cpp index 1252eb36ae..54a81cb559 100644 --- a/Gems/EMotionFX/Code/Tests/SkeletalLODTests.cpp +++ b/Gems/EMotionFX/Code/Tests/SkeletalLODTests.cpp @@ -22,13 +22,13 @@ namespace EMotionFX void SetUp() { ActorFixture::SetUp(); - m_actor->AddLODLevel(); + GetActor()->AddLODLevel(); DisableJointsForLOD(m_disabledJointNames, 1); } void DisableJointsForLOD(const std::vector& jointNames, size_t lodLevel) { - const Skeleton* skeleton = m_actor->GetSkeleton(); + const Skeleton* skeleton = GetActor()->GetSkeleton(); for (const std::string& jointName : jointNames) { Node* joint = skeleton->FindNodeByName(jointName.c_str()); diff --git a/Gems/EMotionFX/Code/Tests/TestAssetCode/TestActorAssets.cpp b/Gems/EMotionFX/Code/Tests/TestAssetCode/TestActorAssets.cpp index 6375d48c6b..bb3c4f76e2 100644 --- a/Gems/EMotionFX/Code/Tests/TestAssetCode/TestActorAssets.cpp +++ b/Gems/EMotionFX/Code/Tests/TestAssetCode/TestActorAssets.cpp @@ -10,9 +10,10 @@ #include #include #include -#include #include #include +#include +#include namespace EMotionFX { diff --git a/Gems/EMotionFX/Code/Tests/TestAssetCode/TestActorAssets.h b/Gems/EMotionFX/Code/Tests/TestAssetCode/TestActorAssets.h index 3a041fcf34..5b75ddf590 100644 --- a/Gems/EMotionFX/Code/Tests/TestAssetCode/TestActorAssets.h +++ b/Gems/EMotionFX/Code/Tests/TestAssetCode/TestActorAssets.h @@ -10,6 +10,9 @@ #include #include +#include +#include +#include namespace EMotionFX { @@ -18,5 +21,14 @@ namespace EMotionFX public: static AZStd::string FileToBase64(const char* filePath); static AZ::Data::Asset GetAssetFromActor(const AZ::Data::AssetId& assetId, AZStd::unique_ptr&& actor); + + template + static AZ::Data::Asset CreateActorAssetAndRegister(const AZ::Data::AssetId& assetId, Args&&... args) + { + AZStd::unique_ptr actor = ActorFactory::CreateAndInit(AZStd::forward(args)...); + AZ::Data::Asset actorAsset = TestActorAssets::GetAssetFromActor(assetId, AZStd::move(actor)); + GetEMotionFX().GetActorManager()->RegisterActor(actorAsset); + return AZStd::move(actorAsset); + } }; } // namespace EMotionFX diff --git a/Gems/EMotionFX/Code/Tests/UI/CanAddJointAndChildren.cpp b/Gems/EMotionFX/Code/Tests/UI/CanAddJointAndChildren.cpp index 59506a9d43..32240ba409 100644 --- a/Gems/EMotionFX/Code/Tests/UI/CanAddJointAndChildren.cpp +++ b/Gems/EMotionFX/Code/Tests/UI/CanAddJointAndChildren.cpp @@ -16,13 +16,13 @@ #include #include -#include #include #include #include #include #include +#include namespace EMotionFX { @@ -37,8 +37,11 @@ namespace EMotionFX RecordProperty("test_case_id", "C13048819"); // Create an actor - AutoRegisteredActor actor = ActorFactory::CreateAndInit(5, "SimpleActor"); - ActorInstance* actorInstance = ActorInstance::Create(actor.get()); + AZ::Data::AssetId actorAssetId("{5060227D-B6F4-422E-BF82-41AAC5F228A5}"); + AZ::Data::Asset actorAsset = + TestActorAssets::CreateActorAssetAndRegister(actorAssetId, 5, "SimpleActor"); + ActorInstance* actorInstance = ActorInstance::Create(actorAsset->GetActor()); + const Actor* actor = actorAsset->GetActor(); // Open simulated objects layout EMStudio::GetMainWindow()->ApplicationModeChanged("SimulatedObjects"); diff --git a/Gems/EMotionFX/Code/Tests/UI/CanAddSimulatedObject.cpp b/Gems/EMotionFX/Code/Tests/UI/CanAddSimulatedObject.cpp index e1c4525a9e..0820895db8 100644 --- a/Gems/EMotionFX/Code/Tests/UI/CanAddSimulatedObject.cpp +++ b/Gems/EMotionFX/Code/Tests/UI/CanAddSimulatedObject.cpp @@ -17,7 +17,6 @@ #include #include #include -#include #include #include #include @@ -27,6 +26,7 @@ #include #include +#include #include #include @@ -81,9 +81,9 @@ namespace EMotionFX inputDialog->accept(); // There is one and only one simulated objects - ASSERT_EQ(m_actor->GetSimulatedObjectSetup()->GetNumSimulatedObjects(), 1); + ASSERT_EQ(m_actorAsset->GetActor()->GetSimulatedObjectSetup()->GetNumSimulatedObjects(), 1); // Check it has the correct name - EXPECT_STREQ(m_actor->GetSimulatedObjectSetup()->GetSimulatedObject(0)->GetName().c_str(), objectName); + EXPECT_STREQ(m_actorAsset->GetActor()->GetSimulatedObjectSetup()->GetSimulatedObject(0)->GetName().c_str(), objectName); } @@ -112,14 +112,16 @@ namespace EMotionFX }); ASSERT_NE(addCapsuleColliderAction, addSelectedColliderMenuActions.end()); - size_t numCapsuleColliders = PhysicsSetupUtils::CountColliders(m_actor.get(), PhysicsSetup::SimulatedObjectCollider, false, Physics::ShapeType::Capsule); + size_t numCapsuleColliders = PhysicsSetupUtils::CountColliders( + m_actorAsset->GetActor(), PhysicsSetup::SimulatedObjectCollider, false, Physics::ShapeType::Capsule); (*addCapsuleColliderAction)->trigger(); // Delete the context menu, otherwise there it will still be around during this frame as the Qt event loop has not been run. delete contextMenu; - const size_t numCapsuleCollidersAfterAdd = PhysicsSetupUtils::CountColliders(m_actor.get(), PhysicsSetup::SimulatedObjectCollider, false, Physics::ShapeType::Capsule); + const size_t numCapsuleCollidersAfterAdd = PhysicsSetupUtils::CountColliders( + m_actorAsset->GetActor(), PhysicsSetup::SimulatedObjectCollider, false, Physics::ShapeType::Capsule); ASSERT_EQ(numCapsuleCollidersAfterAdd, numCapsuleColliders + 1); @@ -127,7 +129,7 @@ namespace EMotionFX } protected: - AutoRegisteredActor m_actor; + AZ::Data::Asset m_actorAsset; EMotionFX::SimulatedObjectWidget* m_simulatedObjectWidget = nullptr; EMotionFX::SkeletonOutlinerPlugin* m_skeletonOutliner = nullptr; @@ -144,7 +146,8 @@ namespace EMotionFX { RecordProperty("test_case_id", "C13048820"); - m_actor = ActorFactory::CreateAndInit(1, "CanAddSimulatedObjectActor"); + AZ::Data::AssetId actorAssetId("{5060227D-B6F4-422E-BF82-41AAC5F228A5}"); + m_actorAsset = TestActorAssets::CreateActorAssetAndRegister(actorAssetId, 1, "CanAddSimulatedObjectActor"); CreateSimulateObject("New simulated object"); } @@ -153,9 +156,11 @@ namespace EMotionFX { RecordProperty("test_case_id", "C13048818"); - AutoRegisteredActor actor = ActorFactory::CreateAndInit(2, "CanAddSimulatedObjectWithJointsActor"); - - ActorInstance* actorInstance = ActorInstance::Create(actor.get()); + AZ::Data::AssetId actorAssetId("{5060227D-B6F4-422E-BF82-41AAC5F228A5}"); + AZ::Data::Asset actorAsset = + TestActorAssets::CreateActorAssetAndRegister(actorAssetId, 2, "CanAddSimulatedObjectWithJointsActor"); + Actor* actor = actorAsset->GetActor(); + ActorInstance* actorInstance = ActorInstance::Create(actor); EMStudio::GetMainWindow()->ApplicationModeChanged("SimulatedObjects"); @@ -216,7 +221,9 @@ namespace EMotionFX { RecordProperty("test_case_id", "C13048820a"); - m_actor = ActorFactory::CreateAndInit(5, "CanAddSimulatedObjectActor"); + AZ::Data::AssetId actorAssetId("{5060227D-B6F4-422E-BF82-41AAC5F228A5}"); + m_actorAsset = TestActorAssets::CreateActorAssetAndRegister(actorAssetId, 5, "CanAddSimulatedObjectActor"); + Actor* actor = m_actorAsset->GetActor(); CreateSimulateObject("sim1"); @@ -261,8 +268,8 @@ namespace EMotionFX inputDialog->SetText("sim2"); inputDialog->accept(); - ASSERT_EQ(m_actor->GetSimulatedObjectSetup()->GetNumSimulatedObjects(), 2); - const auto simulatedObject = m_actor->GetSimulatedObjectSetup()->GetSimulatedObject(1); + ASSERT_EQ(actor->GetSimulatedObjectSetup()->GetNumSimulatedObjects(), 2); + const auto simulatedObject = actor->GetSimulatedObjectSetup()->GetSimulatedObject(1); EXPECT_STREQ(simulatedObject->GetName().c_str(), "sim2"); } @@ -270,7 +277,9 @@ namespace EMotionFX { RecordProperty("test_case_id", "C13048816"); - m_actor = ActorFactory::CreateAndInit(5, "CanAddSimulatedObjectActor"); + AZ::Data::AssetId actorAssetId("{5060227D-B6F4-422E-BF82-41AAC5F228A5}"); + m_actorAsset = TestActorAssets::CreateActorAssetAndRegister(actorAssetId, 5, "CanAddSimulatedObjectActor"); + Actor* actor = m_actorAsset->GetActor(); CreateSimulateObject("sim1"); @@ -301,25 +310,25 @@ namespace EMotionFX QAction* addCapsuleColliderAction; ASSERT_TRUE(UIFixture::GetActionFromContextMenu(addCapsuleColliderAction, addSelectedColliderMenu, "Capsule")); - size_t numCapsuleColliders = PhysicsSetupUtils::CountColliders(m_actor.get(), PhysicsSetup::SimulatedObjectCollider, false, Physics::ShapeType::Capsule); + size_t numCapsuleColliders = PhysicsSetupUtils::CountColliders(actor, PhysicsSetup::SimulatedObjectCollider, false, Physics::ShapeType::Capsule); EXPECT_EQ(numCapsuleColliders, 0); addCapsuleColliderAction->trigger(); // Check that a collider has been added. - size_t numCollidersAfterAddCapsule = PhysicsSetupUtils::CountColliders(m_actor.get(), PhysicsSetup::SimulatedObjectCollider, false, Physics::ShapeType::Capsule); + size_t numCollidersAfterAddCapsule = PhysicsSetupUtils::CountColliders(actor, PhysicsSetup::SimulatedObjectCollider, false, Physics::ShapeType::Capsule); ASSERT_EQ(numCollidersAfterAddCapsule, numCapsuleColliders + 1) << "Capsule collider not added."; QAction* addSphereColliderAction; ASSERT_TRUE(UIFixture::GetActionFromContextMenu(addSphereColliderAction, addSelectedColliderMenu, "Sphere")); - const size_t numSphereColliders = PhysicsSetupUtils::CountColliders(m_actor.get(), PhysicsSetup::SimulatedObjectCollider, false, Physics::ShapeType::Sphere); + const size_t numSphereColliders = PhysicsSetupUtils::CountColliders(actor, PhysicsSetup::SimulatedObjectCollider, false, Physics::ShapeType::Sphere); EXPECT_EQ(numSphereColliders, 0); addSphereColliderAction->trigger(); // Check that a second collider has been added. - const size_t numCollidersAfterAddSphere = PhysicsSetupUtils::CountColliders(m_actor.get(), PhysicsSetup::SimulatedObjectCollider, false, Physics::ShapeType::Sphere); + const size_t numCollidersAfterAddSphere = PhysicsSetupUtils::CountColliders(actor, PhysicsSetup::SimulatedObjectCollider, false, Physics::ShapeType::Sphere); ASSERT_EQ(numCollidersAfterAddSphere, numSphereColliders + 1) << "Sphere collider not added."; } @@ -327,7 +336,9 @@ namespace EMotionFX { RecordProperty("test_case_id", "C13048821"); - m_actor = ActorFactory::CreateAndInit(1, "CanRemoveSimulatedObjectActor"); + AZ::Data::AssetId actorAssetId("{5060227D-B6F4-422E-BF82-41AAC5F228A5}"); + m_actorAsset = TestActorAssets::CreateActorAssetAndRegister(actorAssetId, 1, "CanRemoveSimulatedObjectActor"); + Actor* actor = m_actorAsset->GetActor(); CreateSimulateObject("TestObject1"); @@ -350,14 +361,16 @@ namespace EMotionFX ASSERT_TRUE(UIFixture::GetActionFromContextMenu(removeObjectAction, contextMenu, "Remove object")); removeObjectAction->trigger(); - ASSERT_EQ(m_actor->GetSimulatedObjectSetup()->GetNumSimulatedObjects(), 0); + ASSERT_EQ(actor->GetSimulatedObjectSetup()->GetNumSimulatedObjects(), 0); } TEST_F(CanAddSimulatedObjectFixture, CanAddColliderToSimulatedObjectFromInspector) { RecordProperty("test_case_id", "C20385259"); - - m_actor = ActorFactory::CreateAndInit(5, "CanAddSimulatedObjectActor"); + + AZ::Data::AssetId actorAssetId("{5060227D-B6F4-422E-BF82-41AAC5F228A5}"); + m_actorAsset = TestActorAssets::CreateActorAssetAndRegister(actorAssetId, 5, "CanAddSimulatedObjectActor"); + Actor* actor = m_actorAsset->GetActor(); CreateSimulateObject("sim1"); @@ -380,10 +393,12 @@ namespace EMotionFX // Send the left button click directly to the button QTest::mouseClick(addColliderButton, Qt::LeftButton); - const size_t numCapsuleColliders = PhysicsSetupUtils::CountColliders(m_actor.get(), PhysicsSetup::SimulatedObjectCollider, false, Physics::ShapeType::Capsule); + const size_t numCapsuleColliders = + PhysicsSetupUtils::CountColliders(actor, PhysicsSetup::SimulatedObjectCollider, false, Physics::ShapeType::Capsule); EXPECT_EQ(numCapsuleColliders, 0); - const size_t numSphereColliders = PhysicsSetupUtils::CountColliders(m_actor.get(), PhysicsSetup::SimulatedObjectCollider, false, Physics::ShapeType::Sphere); + const size_t numSphereColliders = + PhysicsSetupUtils::CountColliders(actor, PhysicsSetup::SimulatedObjectCollider, false, Physics::ShapeType::Sphere); EXPECT_EQ(numSphereColliders, 0); QMenu* contextMenu = addColliderButton->findChild("EMFX.AddColliderButton.ContextMenu"); @@ -392,13 +407,15 @@ namespace EMotionFX QAction* addCapsuleAction; ASSERT_TRUE(UIFixture::GetActionFromContextMenu(addCapsuleAction, contextMenu, "Add capsule")); addCapsuleAction->trigger(); - const size_t numCollidersAfterAddCapsule = PhysicsSetupUtils::CountColliders(m_actor.get(), PhysicsSetup::SimulatedObjectCollider, false, Physics::ShapeType::Capsule); + const size_t numCollidersAfterAddCapsule = + PhysicsSetupUtils::CountColliders(actor, PhysicsSetup::SimulatedObjectCollider, false, Physics::ShapeType::Capsule); ASSERT_EQ(numCollidersAfterAddCapsule, numCapsuleColliders + 1) << "Capsule collider not added."; QAction* addSphereAction; ASSERT_TRUE(UIFixture::GetActionFromContextMenu(addSphereAction, contextMenu, "Add sphere")); addSphereAction->trigger(); - const size_t numCollidersAfterAddSphere = PhysicsSetupUtils::CountColliders(m_actor.get(), PhysicsSetup::SimulatedObjectCollider, false, Physics::ShapeType::Sphere); + const size_t numCollidersAfterAddSphere = + PhysicsSetupUtils::CountColliders(actor, PhysicsSetup::SimulatedObjectCollider, false, Physics::ShapeType::Sphere); ASSERT_EQ(numCollidersAfterAddSphere, numSphereColliders + 1) << "Sphere collider not added."; } @@ -406,7 +423,9 @@ namespace EMotionFX { RecordProperty("test_case_id", "C13048818"); - m_actor = ActorFactory::CreateAndInit(7, "CanAddSimulatedObjectActor"); + AZ::Data::AssetId actorAssetId("{5060227D-B6F4-422E-BF82-41AAC5F228A5}"); + m_actorAsset = TestActorAssets::CreateActorAssetAndRegister(actorAssetId, 7, "CanAddSimulatedObjectActor"); + Actor* actor = m_actorAsset->GetActor(); CreateSimulateObject("ANY"); @@ -442,7 +461,7 @@ namespace EMotionFX messageBoxPopupHandler.WaitForPopupPressDialogButton(QDialogButtonBox::No); newSimulatedObjectAction->trigger(); - const EMotionFX::SimulatedObject* simulatedObject = m_actor->GetSimulatedObjectSetup()->GetSimulatedObject(0); + const EMotionFX::SimulatedObject* simulatedObject = actor->GetSimulatedObjectSetup()->GetSimulatedObject(0); EXPECT_EQ(simulatedObject->GetNumSimulatedRootJoints(), 1); EXPECT_EQ(simulatedObject->GetNumSimulatedJoints(), 3); } @@ -450,7 +469,9 @@ namespace EMotionFX { RecordProperty("test_case_id", "C13048817"); - m_actor = ActorFactory::CreateAndInit(5, "CanAddSimulatedObjectActor"); + AZ::Data::AssetId actorAssetId("{5060227D-B6F4-422E-BF82-41AAC5F228A5}"); + m_actorAsset = TestActorAssets::CreateActorAssetAndRegister(actorAssetId, 5, "CanAddSimulatedObjectActor"); + Actor* actor = m_actorAsset->GetActor(); EMStudio::GetMainWindow()->ApplicationModeChanged("SimulatedObjects"); @@ -470,7 +491,7 @@ namespace EMotionFX AddCapsuleColliderToJointIndex(3); AddCapsuleColliderToJointIndex(4); - const size_t numCollidersAfterAdd = PhysicsSetupUtils::CountColliders(m_actor.get(), PhysicsSetup::SimulatedObjectCollider); + const size_t numCollidersAfterAdd = PhysicsSetupUtils::CountColliders(actor, PhysicsSetup::SimulatedObjectCollider); EXPECT_EQ(numCollidersAfterAdd, 2); m_indexList.clear(); @@ -498,7 +519,7 @@ namespace EMotionFX removeAction->trigger(); // Check that one of the colliders is now gone. - const size_t numCollidersAfterFirstRemove = PhysicsSetupUtils::CountColliders(m_actor.get(), PhysicsSetup::SimulatedObjectCollider); + const size_t numCollidersAfterFirstRemove = PhysicsSetupUtils::CountColliders(actor, PhysicsSetup::SimulatedObjectCollider); ASSERT_EQ(numCollidersAfterFirstRemove, numCollidersAfterAdd - 1) << "RemoveCollider action in Simulated Object Inspector failed."; // Now do the same thing using the Simulated Object Inspector context menu. @@ -536,7 +557,7 @@ namespace EMotionFX delAction->trigger(); // Check that we have the number of colliders we started we expect. - const size_t numCollidersAfterSecondRemove = PhysicsSetupUtils::CountColliders(m_actor.get(), PhysicsSetup::SimulatedObjectCollider); + const size_t numCollidersAfterSecondRemove = PhysicsSetupUtils::CountColliders(actor, PhysicsSetup::SimulatedObjectCollider); ASSERT_EQ(numCollidersAfterSecondRemove, numCollidersAfterAdd - 2); } diff --git a/Gems/EMotionFX/Code/Tests/UI/CanAddToSimulatedObject.cpp b/Gems/EMotionFX/Code/Tests/UI/CanAddToSimulatedObject.cpp index e27d558664..a10ba352d2 100644 --- a/Gems/EMotionFX/Code/Tests/UI/CanAddToSimulatedObject.cpp +++ b/Gems/EMotionFX/Code/Tests/UI/CanAddToSimulatedObject.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -52,9 +53,11 @@ namespace EMotionFX { RecordProperty("test_case_id", "C14603914"); - AutoRegisteredActor actor = ActorFactory::CreateAndInit(7, "CanAddToSimulatedObjectActor"); - - ActorInstance* actorInstance = ActorInstance::Create(actor.get()); + AZ::Data::AssetId actorAssetId("{5060227D-B6F4-422E-BF82-41AAC5F228A5}"); + AZ::Data::Asset actorAsset = + TestActorAssets::CreateActorAssetAndRegister(actorAssetId, 7, "CanAddToSimulatedObjectActor"); + Actor* actor = actorAsset->GetActor(); + ActorInstance* actorInstance = ActorInstance::Create(actor); // Change the Editor mode to Simulated Objects EMStudio::GetMainWindow()->ApplicationModeChanged("SimulatedObjects"); @@ -165,9 +168,11 @@ namespace EMotionFX }); RecordProperty("test_case_id", "C13291807"); - AutoRegisteredActor actor = ActorFactory::CreateAndInit(7, "CanAddToSimulatedObjectActor"); - - ActorInstance* actorInstance = ActorInstance::Create(actor.get()); + AZ::Data::AssetId actorAssetId("{5060227D-B6F4-422E-BF82-41AAC5F228A5}"); + AZ::Data::Asset actorAsset = + TestActorAssets::CreateActorAssetAndRegister(actorAssetId, 7, "CanAddToSimulatedObjectActor"); + Actor* actor = actorAsset->GetActor(); + ActorInstance* actorInstance = ActorInstance::Create(actor); // Change the Editor mode to Physics EMStudio::GetMainWindow()->ApplicationModeChanged("Physics"); diff --git a/Gems/EMotionFX/Code/Tests/UI/CanChangeParametersInSimulatedObject.cpp b/Gems/EMotionFX/Code/Tests/UI/CanChangeParametersInSimulatedObject.cpp index 510bfb2d83..108a637412 100644 --- a/Gems/EMotionFX/Code/Tests/UI/CanChangeParametersInSimulatedObject.cpp +++ b/Gems/EMotionFX/Code/Tests/UI/CanChangeParametersInSimulatedObject.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -37,9 +38,10 @@ namespace EMotionFX { RecordProperty("test_case_id", "C14519563"); - AutoRegisteredActor actor = ActorFactory::CreateAndInit(7, "CanAddToSimulatedObjectActor"); - - ActorInstance* actorInstance = ActorInstance::Create(actor.get()); + AZ::Data::AssetId actorAssetId("{5060227D-B6F4-422E-BF82-41AAC5F228A5}"); + AZ::Data::Asset actorAsset = + TestActorAssets::CreateActorAssetAndRegister(actorAssetId, 7, "CanAddToSimulatedObjectActor"); + ActorInstance* actorInstance = ActorInstance::Create(actorAsset->GetActor()); // Change the Editor mode to Simulated Objects EMStudio::GetMainWindow()->ApplicationModeChanged("SimulatedObjects"); diff --git a/Gems/EMotionFX/Code/Tests/UI/CanUseFileMenu.cpp b/Gems/EMotionFX/Code/Tests/UI/CanUseFileMenu.cpp index 73a0f1c865..2af3329203 100644 --- a/Gems/EMotionFX/Code/Tests/UI/CanUseFileMenu.cpp +++ b/Gems/EMotionFX/Code/Tests/UI/CanUseFileMenu.cpp @@ -24,10 +24,12 @@ #include #include +#include #include #include #include #include +#include #include #include #include @@ -141,8 +143,10 @@ namespace EMotionFX { if (EMotionFX::GetActorManager().GetNumActorInstances() == 0) { - AutoRegisteredActor actor = ActorFactory::CreateAndInit(2, "CanAddSimulatedObjectWithJointsActor"); - ActorInstance::Create(actor.get()); + AZ::Data::AssetId actorAssetId("{5060227D-B6F4-422E-BF82-41AAC5F228A5}"); + AZ::Data::Asset actorAsset = + TestActorAssets::CreateActorAssetAndRegister(actorAssetId, 2, "CanAddSimulatedObjectWithJointsActor"); + ActorInstance::Create(actorAsset->GetActor()); EXPECT_EQ(EMotionFX::GetActorManager().GetNumActorInstances(), 1) << "Failed to create actor set for reset test."; } @@ -152,15 +156,16 @@ namespace EMotionFX { if (EMotionFX::GetActorManager().GetNumActorInstances() == 0) { - AutoRegisteredActor actor = ActorFactory::CreateAndInit(2, "CanAddSimulatedObjectWithJointsActor"); - ActorInstance::Create(actor.get()); + AZ::Data::AssetId actorAssetId("{5060227D-B6F4-422E-BF82-41AAC5F228A5}"); + AZ::Data::Asset actorAsset = TestActorAssets::CreateActorAssetAndRegister( + actorAssetId, 2, "CanAddSimulatedObjectWithJointsActor"); + ActorInstance::Create(actorAsset->GetActor()); EXPECT_EQ(EMotionFX::GetActorManager().GetNumActorInstances(), 1) << "Failed to create actor set for reset test."; - - actor->SetFileName(filename); + actorAsset->GetActor()->SetFileName(filename); AZStd::string stringFilename = filename; - ExporterLib::SaveActor(stringFilename, actor.get(), MCore::Endian::ENDIAN_LITTLE); + ExporterLib::SaveActor(stringFilename, actorAsset->GetActor(), MCore::Endian::ENDIAN_LITTLE); } } @@ -239,7 +244,6 @@ namespace EMotionFX // Load the actor we just saved, with replaceScene set to true to represent a load. LoadActor(actorFilename.toUtf8().data(), true); - ASSERT_EQ(EMotionFX::GetActorManager().GetNumActorInstances(), 1) << "Failed to load Actor."; // Do it again to verify that number of actors stays the same when replaceScene is true. @@ -696,7 +700,10 @@ namespace EMotionFX TestResetMenuItem(fileMenu); - TestActorMenus(fileMenu); + // Temporarily disable loading actor test. + // This is because the importer command now load actor asset instead of reading from disk. We do not want to add dependency to the asset processor + // in this test. + // TestActorMenus(fileMenu); TestSaveAllMenuItem(fileMenu); } diff --git a/Gems/EMotionFX/Code/Tests/UI/CanUseLayoutMenu.cpp b/Gems/EMotionFX/Code/Tests/UI/CanUseLayoutMenu.cpp index 6fbaf3a028..e615d3c9e0 100644 --- a/Gems/EMotionFX/Code/Tests/UI/CanUseLayoutMenu.cpp +++ b/Gems/EMotionFX/Code/Tests/UI/CanUseLayoutMenu.cpp @@ -17,7 +17,6 @@ #include #include #include -#include #include #include #include diff --git a/Gems/EMotionFX/Code/Tests/UI/ClothColliderTests.cpp b/Gems/EMotionFX/Code/Tests/UI/ClothColliderTests.cpp index 6428a11e6d..d87c35a4fa 100644 --- a/Gems/EMotionFX/Code/Tests/UI/ClothColliderTests.cpp +++ b/Gems/EMotionFX/Code/Tests/UI/ClothColliderTests.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include namespace EMotionFX @@ -102,7 +103,9 @@ namespace EMotionFX const int lastIndex = 6; RecordProperty("test_case_id", "C18970351"); - AutoRegisteredActor actor = ActorFactory::CreateAndInit(numJoints, "RagdollEditTestsActor"); + AZ::Data::AssetId actorAssetId("{5060227D-B6F4-422E-BF82-41AAC5F228A5}"); + AZ::Data::Asset actorAsset = + TestActorAssets::CreateActorAssetAndRegister(actorAssetId, numJoints, "RagdollEditTestsActor"); CreateSkeletonAndModelIndices(); EXPECT_EQ(m_indexList.size(), numJoints); diff --git a/Gems/EMotionFX/Code/Tests/UI/LODSkinnedMeshTests.cpp b/Gems/EMotionFX/Code/Tests/UI/LODSkinnedMeshTests.cpp index 6e5e14e9fc..def8bdab58 100644 --- a/Gems/EMotionFX/Code/Tests/UI/LODSkinnedMeshTests.cpp +++ b/Gems/EMotionFX/Code/Tests/UI/LODSkinnedMeshTests.cpp @@ -80,11 +80,14 @@ namespace EMotionFX DataMembers m_data; }; - AZStd::unique_ptr CreateLODActor(int numLODs) + AZ::Data::Asset CreateLODActor(int numLODs) { - AZStd::unique_ptr actor = ActorFactory::CreateAndInit("LODSkinnedMeshTestsActor"); + AZ::Data::AssetId actorAssetId("{5060227D-B6F4-422E-BF82-41AAC5F228A5}"); + AZ::Data::Asset actorAsset = + TestActorAssets::CreateActorAssetAndRegister(actorAssetId, "LODSkinnedMeshTestsActor"); // Modify the actor to have numLODs LOD levels. + Actor* actor = actorAsset->GetActor(); Mesh* lodMesh = actor->GetMesh(0, 0); StandardMaterial* dummyMat = StandardMaterial::Create("Dummy Material"); actor->AddMaterial(0, dummyMat); // owns the material @@ -97,7 +100,7 @@ namespace EMotionFX actor->AddMaterial(i, dummyMat->Clone()); } - return actor; + return AZStd::move(actorAsset); } class LODPropertyRowWidget @@ -112,9 +115,8 @@ namespace EMotionFX const int numLODs = GetParam(); RecordProperty("test_case_id", "C29202698"); - AutoRegisteredActor actor = CreateLODActor(numLODs); - - ActorInstance* actorInstance = ActorInstance::Create(actor.get()); + AZ::Data::Asset actorAsset = CreateLODActor(numLODs); + ActorInstance* actorInstance = ActorInstance::Create(actorAsset->GetActor()); // Change the Editor mode to Character EMStudio::GetMainWindow()->ApplicationModeChanged("Character"); @@ -166,8 +168,7 @@ namespace EMotionFX gameEntity->SetId(entityId); AZ::Data::AssetId actorAssetId("{85D3EF54-7400-43F8-8A40-F6BCBF534E54}"); - AZStd::unique_ptr actor = CreateLODActor(numLODs); - AZ::Data::Asset actorAsset = TestActorAssets::GetAssetFromActor(actorAssetId, AZStd::move(actor)); + AZ::Data::Asset actorAsset = CreateLODActor(numLODs); gameEntity->CreateComponent(); Integration::ActorComponent::Configuration actorConf; diff --git a/Gems/EMotionFX/Code/Tests/UI/RagdollEditTests.cpp b/Gems/EMotionFX/Code/Tests/UI/RagdollEditTests.cpp index 1c39cde5b6..d097e67d15 100644 --- a/Gems/EMotionFX/Code/Tests/UI/RagdollEditTests.cpp +++ b/Gems/EMotionFX/Code/Tests/UI/RagdollEditTests.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -110,7 +111,9 @@ namespace EMotionFX const int numJoints = 6; RecordProperty("test_case_id", "C3122249"); - AutoRegisteredActor actor = ActorFactory::CreateAndInit(numJoints, "RagdollEditTestsActor"); + AZ::Data::AssetId actorAssetId("{5060227D-B6F4-422E-BF82-41AAC5F228A5}"); + AZ::Data::Asset actorAsset = + TestActorAssets::CreateActorAssetAndRegister(actorAssetId, numJoints, "RagdollEditTestsActor"); CreateSkeletonAndModelIndices(); @@ -138,7 +141,9 @@ namespace EMotionFX const int numJoints = 8; RecordProperty("test_case_id", "C3122248"); - AutoRegisteredActor actor = ActorFactory::CreateAndInit(numJoints, "RagdollEditTestsActor"); + AZ::Data::AssetId actorAssetId("{5060227D-B6F4-422E-BF82-41AAC5F228A5}"); + AZ::Data::Asset actorAsset = + TestActorAssets::CreateActorAssetAndRegister(actorAssetId, numJoints, "RagdollEditTestsActor"); CreateSkeletonAndModelIndices(); EXPECT_EQ(m_indexList.size(), numJoints); diff --git a/Gems/LmbrCentral/Code/Source/Ai/EditorNavigationAreaComponent.cpp b/Gems/LmbrCentral/Code/Source/Ai/EditorNavigationAreaComponent.cpp index 4a290443d0..f723918cf3 100644 --- a/Gems/LmbrCentral/Code/Source/Ai/EditorNavigationAreaComponent.cpp +++ b/Gems/LmbrCentral/Code/Source/Ai/EditorNavigationAreaComponent.cpp @@ -51,7 +51,7 @@ namespace LmbrCentral ->Attribute(AZ::Edit::Attributes::Icon, "Icons/Components/NavigationArea.svg") ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/NavigationArea.svg") ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c)) - ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/nav-area/") + ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/ai/nav-area/") ->Attribute(AZ::Edit::Attributes::AutoExpand, true) ->DataElement(AZ::Edit::UIHandlers::CheckBox, &EditorNavigationAreaComponent::m_exclusion, "Exclusion", "Does this area add or subtract from the Navigation Mesh") ->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorNavigationAreaComponent::OnNavigationAreaChanged) diff --git a/Gems/LmbrCentral/Code/Source/Ai/EditorNavigationSeedComponent.cpp b/Gems/LmbrCentral/Code/Source/Ai/EditorNavigationSeedComponent.cpp index 975eb17da1..47999dbd4b 100644 --- a/Gems/LmbrCentral/Code/Source/Ai/EditorNavigationSeedComponent.cpp +++ b/Gems/LmbrCentral/Code/Source/Ai/EditorNavigationSeedComponent.cpp @@ -36,7 +36,7 @@ namespace LmbrCentral ->Attribute(AZ::Edit::Attributes::Icon, "Icons/Components/NavigationSeed.svg") ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/NavigationSeed.svg") ->Attribute(AZ::Edit::Attributes::AutoExpand, true) - ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/nav-seed/") + ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/ai/nav-seed/") ->DataElement(AZ::Edit::UIHandlers::ComboBox, &EditorNavigationSeedComponent::m_agentType, "Agent Type", "Describes the type of the Entity for navigation purposes.") ->Attribute(AZ::Edit::Attributes::StringList, &PopulateAgentTypeList) ->Attribute("ChangeNotify", &EditorNavigationSeedComponent::OnAgentTypeChanged); diff --git a/Gems/LmbrCentral/Code/Source/Ai/NavigationComponent.cpp b/Gems/LmbrCentral/Code/Source/Ai/NavigationComponent.cpp index 9897895dcd..500d5c17da 100644 --- a/Gems/LmbrCentral/Code/Source/Ai/NavigationComponent.cpp +++ b/Gems/LmbrCentral/Code/Source/Ai/NavigationComponent.cpp @@ -125,7 +125,7 @@ namespace LmbrCentral ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/Navigation.svg") ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c)) ->Attribute(AZ::Edit::Attributes::AutoExpand, true) - ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/navigation/") + ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/ai/navigation/") ->DataElement(AZ::Edit::UIHandlers::Default, &NavigationComponent::m_agentSpeed, "Agent Speed", "The speed of the agent while navigating ") ->DataElement(AZ::Edit::UIHandlers::ComboBox, &NavigationComponent::m_agentType, "Agent Type", diff --git a/Gems/LmbrCentral/Code/Source/Editor/EditorCommentComponent.cpp b/Gems/LmbrCentral/Code/Source/Editor/EditorCommentComponent.cpp index 97f8dc60a2..6c929bd3c4 100644 --- a/Gems/LmbrCentral/Code/Source/Editor/EditorCommentComponent.cpp +++ b/Gems/LmbrCentral/Code/Source/Editor/EditorCommentComponent.cpp @@ -33,7 +33,7 @@ namespace LmbrCentral ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/Comment.svg") ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZStd::vector({ AZ_CRC("Level", 0x9aeacc13), AZ_CRC("Game", 0x232b318c), AZ_CRC("Layer", 0xe4db211a) })) ->Attribute(AZ::Edit::Attributes::AutoExpand, true) - ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/comment/") + ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/editor/comment/") ->DataElement(AZ::Edit::UIHandlers::MultiLineEdit, &EditorCommentComponent::m_comment,"", "Comment") ->Attribute(AZ_CRC("PlaceholderText", 0xa23ec278), "Add comment text here"); } diff --git a/Gems/LmbrCentral/Code/Source/Scripting/EditorTagComponent.cpp b/Gems/LmbrCentral/Code/Source/Scripting/EditorTagComponent.cpp index fa98601a11..90fc0db437 100644 --- a/Gems/LmbrCentral/Code/Source/Scripting/EditorTagComponent.cpp +++ b/Gems/LmbrCentral/Code/Source/Scripting/EditorTagComponent.cpp @@ -38,7 +38,7 @@ namespace LmbrCentral ->Attribute(AZ::Edit::Attributes::Icon, "Icons/Components/Tag.svg") ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/Tag.svg") ->Attribute(AZ::Edit::Attributes::AutoExpand, true) - ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/tag/") + ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/gameplay/tag/") ->DataElement(AZ::Edit::UIHandlers::Default, &EditorTagComponent::m_tags, "Tags", "The tags that will be on this entity by default") ->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorTagComponent::OnTagChanged); } diff --git a/Gems/LmbrCentral/Code/Source/Scripting/SimpleStateComponent.cpp b/Gems/LmbrCentral/Code/Source/Scripting/SimpleStateComponent.cpp index e278f18e8b..29c97cdde1 100644 --- a/Gems/LmbrCentral/Code/Source/Scripting/SimpleStateComponent.cpp +++ b/Gems/LmbrCentral/Code/Source/Scripting/SimpleStateComponent.cpp @@ -204,7 +204,7 @@ namespace LmbrCentral ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c)) ->Attribute(AZ::Edit::Attributes::Icon, "Icons/Components/SimpleState.svg") ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/SimpleState.svg") - ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/simple-state/") + ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/gameplay/simple-state/") ->DataElement(AZ::Edit::UIHandlers::ComboBox, &SimpleStateComponent::m_initialStateName, "Initial state", "The initial active state") ->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ_CRC("RefreshAttributesAndValues", 0xcbc2147c)) ->Attribute(AZ::Edit::Attributes::StringList, &SimpleStateComponent::GetStateNames) diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h b/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h index 32af39a43b..19289e1e42 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h @@ -48,6 +48,7 @@ namespace Multiplayer using NotifyClientMigrationEvent = AZ::Event; using NotifyEntityMigrationEvent = AZ::Event; using ConnectionAcquiredEvent = AZ::Event; + using ServerAcceptanceReceivedEvent = AZ::Event<>; using SessionInitEvent = AZ::Event; using SessionShutdownEvent = AZ::Event; @@ -122,6 +123,10 @@ namespace Multiplayer //! @param handler The ConnectionAcquiredEvent Handler to add virtual void AddConnectionAcquiredHandler(ConnectionAcquiredEvent::Handler& handler) = 0; + //! Adds a ServerAcceptanceReceived Handler which is invoked when the client receives the accept packet from the server. + //! @param handler The ServerAcceptanceReceived Handler to add + virtual void AddServerAcceptanceReceivedHandler(ServerAcceptanceReceivedEvent::Handler& handler) = 0; + //! Adds a SessionInitEvent Handler which is invoked when a new network session starts. //! @param handler The SessionInitEvent Handler to add virtual void AddSessionInitHandler(SessionInitEvent::Handler& handler) = 0; diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugHierarchyReporter.cpp b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugHierarchyReporter.cpp new file mode 100644 index 0000000000..7c38a340eb --- /dev/null +++ b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugHierarchyReporter.cpp @@ -0,0 +1,225 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include "MultiplayerDebugHierarchyReporter.h" + +#include +#include +#include +#include +#include +#include + +#if defined(IMGUI_ENABLED) +#include +#endif + +namespace Multiplayer +{ + MultiplayerDebugHierarchyReporter::MultiplayerDebugHierarchyReporter() + : m_updateDebugOverlay([this]() { UpdateDebugOverlay(); }, AZ::Name("UpdateHierarchyDebug")) + { + CollectHierarchyRoots(); + + AZ::EntitySystemBus::Handler::BusConnect(); + m_updateDebugOverlay.Enqueue(AZ::TimeMs{ 0 }, true); + } + + MultiplayerDebugHierarchyReporter::~MultiplayerDebugHierarchyReporter() + { + AZ::EntitySystemBus::Handler::BusDisconnect(); + } + + // -------------------------------------------------------------------------------------------- + void MultiplayerDebugHierarchyReporter::OnImGuiUpdate() + { +#if defined(IMGUI_ENABLED) + ImGui::Text("Hierarchies"); + ImGui::Separator(); + + for (const auto& root : m_hierarchyRoots) + { + if (const auto* rootComponent = root.second.m_rootComponent) + { + if (rootComponent->IsHierarchicalRoot()) + { + const AZStd::vector& hierarchicalChildren = rootComponent->GetHierarchicalEntities(); + + if (ImGui::TreeNode(rootComponent->GetEntity()->GetName().c_str(), + "[%s] %4zu members", + rootComponent->GetEntity()->GetName().c_str(), + hierarchicalChildren.size())) + { + ImGui::Separator(); + ImGui::Columns(4, "hierarchy_columns"); + ImGui::Text("EntityId"); + ImGui::NextColumn(); + ImGui::Text("NetEntityId"); + ImGui::NextColumn(); + ImGui::Text("Entity Name"); + ImGui::NextColumn(); + ImGui::Text("Role"); + ImGui::NextColumn(); + + ImGui::Separator(); + ImGui::Columns(4, "hierarchy child info"); + + bool firstEntity = true; + for (const AZ::Entity* entity : hierarchicalChildren) + { + ImGui::Text("%s", entity->GetId().ToString().c_str()); + ImGui::NextColumn(); + ImGui::Text("%u", GetMultiplayer()->GetNetworkEntityManager()->GetNetEntityIdById(entity->GetId())); + ImGui::NextColumn(); + ImGui::Text("%s", entity->GetName().c_str()); + ImGui::NextColumn(); + + if (firstEntity) + { + ImGui::Text("Root node"); + } + else if (entity->FindComponent()) + { + ImGui::Text("Inner root node"); + } + else if (entity->FindComponent()) + { + ImGui::Text("Child node"); + } + ImGui::NextColumn(); + + firstEntity = false; + } + + ImGui::Columns(1); + ImGui::TreePop(); + } + } + } + } + + ImGui::Separator(); + if (ImGui::InputFloat("Awareness Radius", &m_awarenessRadius)) + { + CollectHierarchyRoots(); + } + if (ImGui::Button("Refresh")) + { + CollectHierarchyRoots(); + } +#endif + } + + + void MultiplayerDebugHierarchyReporter::UpdateDebugOverlay() + { + if (!m_hierarchyRoots.empty()) + { + if (m_debugDisplay == nullptr) + { + AzFramework::DebugDisplayRequestBus::BusPtr debugDisplayBus; + AzFramework::DebugDisplayRequestBus::Bind(debugDisplayBus, AzFramework::g_defaultSceneEntityDebugDisplayId); + m_debugDisplay = AzFramework::DebugDisplayRequestBus::FindFirstHandler(debugDisplayBus); + } + + const AZ::u32 stateBefore = m_debugDisplay->GetState(); + m_debugDisplay->SetColor(AZ::Colors::White); + + for (const auto& root : m_hierarchyRoots) + { + if (const auto* rootComponent = root.second.m_rootComponent) + { + if (rootComponent->IsHierarchicalRoot()) + { + const AZStd::vector& hierarchicalChildren = rootComponent->GetHierarchicalEntities(); + + azsprintf(m_statusBuffer, "Hierarchy [%s] %u members", rootComponent->GetEntity()->GetName().c_str(), + aznumeric_cast(hierarchicalChildren.size())); + + AZ::Vector3 entityPosition = rootComponent->GetEntity()->GetTransform()->GetWorldTranslation(); + constexpr bool centerText = true; + m_debugDisplay->DrawTextLabel(entityPosition, 1.0f, m_statusBuffer, centerText, 0, 0); + } + } + } + + m_debugDisplay->SetState(stateBefore); + } + } + + void MultiplayerDebugHierarchyReporter::OnEntityActivated(const AZ::EntityId& entityId) + { + if (const AZ::Entity* childEntity = AZ::Interface::Get()->FindEntity(entityId)) + { + if (auto* rootComponent = childEntity->FindComponent()) + { + HierarchyRootInfo info; + info.m_rootComponent = rootComponent; + rootComponent->BindNetworkHierarchyChangedEventHandler(info.m_changedEvent); + rootComponent->BindNetworkHierarchyLeaveEventHandler(info.m_leaveEvent); + + m_hierarchyRoots.insert(AZStd::make_pair(rootComponent, info)); + } + } + } + + void MultiplayerDebugHierarchyReporter::OnEntityDeactivated(const AZ::EntityId& entityId) + { + if (const AZ::Entity* childEntity = AZ::Interface::Get()->FindEntity(entityId)) + { + if (auto* rootComponent = childEntity->FindComponent()) + { + m_hierarchyRoots.erase(rootComponent); + } + } + } + + void MultiplayerDebugHierarchyReporter::CollectHierarchyRoots() + { + m_hierarchyRoots.clear(); + AZ::Sphere awarenessSphere(AZ::Vector3::CreateZero(), m_awarenessRadius); + + const auto viewportContextManager = AZ::Interface::Get(); + if (const auto viewportContext = viewportContextManager->GetDefaultViewportContext()) + { + awarenessSphere.SetCenter(viewportContext->GetCameraTransform().GetTranslation()); + } + + AZStd::vector gatheredEntries; + AZ::Interface::Get()->GetDefaultVisibilityScene()->Enumerate(awarenessSphere, + [&gatheredEntries](const AzFramework::IVisibilityScene::NodeData& nodeData) + { + gatheredEntries.reserve(gatheredEntries.size() + nodeData.m_entries.size()); + for (AzFramework::VisibilityEntry* visEntry : nodeData.m_entries) + { + if (visEntry->m_typeFlags & AzFramework::VisibilityEntry::TypeFlags::TYPE_Entity) + { + gatheredEntries.push_back(visEntry); + } + } + } + ); + + for (const AzFramework::VisibilityEntry* entry : gatheredEntries) + { + const AZ::Entity* entity = static_cast(entry->m_userData); + if (auto* rootComponent = entity->FindComponent()) + { + if (awarenessSphere.GetCenter().GetDistanceEstimate(entity->GetTransform()->GetWorldTranslation()) < m_awarenessRadius) + { + HierarchyRootInfo info; + info.m_rootComponent = rootComponent; + rootComponent->BindNetworkHierarchyChangedEventHandler(info.m_changedEvent); + rootComponent->BindNetworkHierarchyLeaveEventHandler(info.m_leaveEvent); + + m_hierarchyRoots.insert(AZStd::make_pair(rootComponent, info)); + } + } + } + } +} diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugHierarchyReporter.h b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugHierarchyReporter.h new file mode 100644 index 0000000000..2ce7e055a0 --- /dev/null +++ b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugHierarchyReporter.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include +#include +#include + +namespace Multiplayer +{ + /** + * /brief Provides ImGui and debug draw hierarchy information at runtime. + */ + class MultiplayerDebugHierarchyReporter + : public AZ::EntitySystemBus::Handler + { + public: + MultiplayerDebugHierarchyReporter(); + ~MultiplayerDebugHierarchyReporter() override; + + //! Main update loop. + void OnImGuiUpdate(); + + //! Draws hierarchy information over hierarchy root entities. + void UpdateDebugOverlay(); + + //! EntitySystemBus overrides. + //! @{ + void OnEntityActivated(const AZ::EntityId& entityId) override; + void OnEntityDeactivated(const AZ::EntityId& entityId) override; + //! @} + + private: + AZ::ScheduledEvent m_updateDebugOverlay; + + AzFramework::DebugDisplayRequests* m_debugDisplay = nullptr; + + struct HierarchyRootInfo + { + NetworkHierarchyRootComponent* m_rootComponent = nullptr; + NetworkHierarchyChangedEvent::Handler m_changedEvent; + NetworkHierarchyLeaveEvent::Handler m_leaveEvent; + }; + + AZStd::unordered_map m_hierarchyRoots; + void CollectHierarchyRoots(); + + char m_statusBuffer[100] = {}; + + float m_awarenessRadius = 1000.f; + }; +} diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.cpp b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.cpp index 422b2f0cae..b23bc677f0 100644 --- a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.cpp @@ -71,6 +71,7 @@ namespace Multiplayer ImGui::Checkbox("Networking Stats", &m_displayNetworkingStats); ImGui::Checkbox("Multiplayer Stats", &m_displayMultiplayerStats); ImGui::Checkbox("Multiplayer Entity Stats", &m_displayPerEntityStats); + ImGui::Checkbox("Multiplayer Hierarchy Debugger", &m_displayHierarchyDebugger); ImGui::EndMenu(); } } @@ -464,6 +465,29 @@ namespace Multiplayer } } } + + if (m_displayHierarchyDebugger) + { + if (ImGui::Begin("Multiplayer Hierarchy Debugger", &m_displayHierarchyDebugger)) + { + if (m_hierarchyDebugger == nullptr) + { + m_hierarchyDebugger = AZStd::make_unique(); + } + + if (m_hierarchyDebugger) + { + m_hierarchyDebugger->OnImGuiUpdate(); + } + } + } + else + { + if (m_hierarchyDebugger) + { + m_hierarchyDebugger.reset(); + } + } } #endif } diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.h b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.h index 7b3c852f58..9becf1543c 100644 --- a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.h +++ b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.h @@ -8,6 +8,8 @@ #pragma once +#include "MultiplayerDebugHierarchyReporter.h" + #include #include #include @@ -62,5 +64,8 @@ namespace Multiplayer bool m_displayPerEntityStats = false; AZStd::unique_ptr m_reporter; + + bool m_displayHierarchyDebugger = false; + AZStd::unique_ptr m_hierarchyDebugger; }; } diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp index 582cda3eea..a30ab207b4 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp @@ -146,7 +146,6 @@ namespace Multiplayer { // Connect the Editor to the editor server for Multiplayer simulation AZ::Interface::Get()->Connect(remoteAddress.c_str(), remotePort); - AZ::Interface::Get()->SendReadyForEntityUpdates(true); } } } diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp index 377fb1eb56..55a05e33a7 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp @@ -62,6 +62,7 @@ namespace Multiplayer } MultiplayerEditorSystemComponent::MultiplayerEditorSystemComponent() + : m_serverAcceptanceReceivedHandler([this](){OnServerAcceptanceReceived();}) { ; } @@ -70,6 +71,7 @@ namespace Multiplayer { AzFramework::GameEntityContextEventBus::Handler::BusConnect(); AzToolsFramework::EditorEvents::Bus::Handler::BusConnect(); + AZ::Interface::Get()->AddServerAcceptanceReceivedHandler(m_serverAcceptanceReceivedHandler); } void MultiplayerEditorSystemComponent::Deactivate() @@ -143,7 +145,11 @@ namespace Multiplayer // Start the configured server if it's available AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo; - processLaunchInfo.m_commandlineParameters = AZStd::string::format("\"%s\" --editorsv_isDedicated true", serverPath.c_str()); + processLaunchInfo.m_commandlineParameters = AZStd::string::format( + R"("%s" --project-path "%s" --editorsv_isDedicated true --sv_defaultPlayerSpawnAsset "%s")", + serverPath.c_str(), + AZ::Utils::GetProjectPath().c_str(), + static_cast(sv_defaultPlayerSpawnAsset).c_str()); processLaunchInfo.m_showWindow = true; processLaunchInfo.m_processPriority = AzFramework::ProcessPriority::PROCESSPRIORITY_NORMAL; @@ -239,4 +245,12 @@ namespace Multiplayer void MultiplayerEditorSystemComponent::OnGameEntitiesReset() { } + + void MultiplayerEditorSystemComponent::OnServerAcceptanceReceived() + { + // We're now accepting the connection to the EditorServer. + // In normal game clients SendReadyForEntityUpdates will be enabled once the appropriate level's root spawnable is loaded, + // but since we're in Editor, we're already in the level. + AZ::Interface::Get()->SendReadyForEntityUpdates(true); + } } diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h index 6a9e6a79b6..4e4c6b677f 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.h @@ -8,6 +8,8 @@ #pragma once +#include + #include #include @@ -45,6 +47,9 @@ namespace Multiplayer MultiplayerEditorSystemComponent(); ~MultiplayerEditorSystemComponent() override = default; + //! Called once the editor receives the server's accept packet + void OnServerAcceptanceReceived(); + //! AZ::Component overrides. //! @{ void Activate() override; @@ -71,5 +76,7 @@ namespace Multiplayer IEditor* m_editor = nullptr; AzFramework::ProcessWatcher* m_serverProcess = nullptr; AzNetworking::ConnectionId m_editorConnId; + + ServerAcceptanceReceivedEvent::Handler m_serverAcceptanceReceivedHandler; }; } diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp index 37e87ace84..35174e31fb 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp @@ -497,6 +497,8 @@ namespace Multiplayer AZ::Interface::Get()->PerformCommand(commandString.c_str()); AZ::CVarFixedString loadLevelString = "LoadLevel " + packet.GetMap(); AZ::Interface::Get()->PerformCommand(loadLevelString.c_str()); + + m_serverAcceptanceReceivedEvent.Signal(); return true; } @@ -821,6 +823,11 @@ namespace Multiplayer handler.Connect(m_connectionAcquiredEvent); } + void MultiplayerSystemComponent::AddServerAcceptanceReceivedHandler(ServerAcceptanceReceivedEvent::Handler& handler) + { + handler.Connect(m_serverAcceptanceReceivedEvent); + } + void MultiplayerSystemComponent::AddSessionInitHandler(SessionInitEvent::Handler& handler) { handler.Connect(m_initEvent); @@ -1034,7 +1041,7 @@ namespace Multiplayer INetworkEntityManager::EntityList entityList = m_networkEntityManager.CreateEntitiesImmediate(playerPrefabEntityId, NetEntityRole::Authority, AZ::Transform::CreateIdentity(), Multiplayer::AutoActivate::DoNotActivate); NetworkEntityHandle controlledEntity; - if (entityList.size() > 0) + if (!entityList.empty()) { controlledEntity = entityList[0]; } diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h index 316ddd41d5..e46bbb59bc 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h @@ -37,6 +37,8 @@ namespace AzNetworking namespace Multiplayer { + AZ_CVAR_EXTERNED(AZ::CVarFixedString, sv_defaultPlayerSpawnAsset); + //! Multiplayer system component wraps the bridging logic between the game and transport layer. class MultiplayerSystemComponent final : public AZ::Component @@ -116,6 +118,7 @@ namespace Multiplayer void AddConnectionAcquiredHandler(ConnectionAcquiredEvent::Handler& handler) override; void AddSessionInitHandler(SessionInitEvent::Handler& handler) override; void AddSessionShutdownHandler(SessionShutdownEvent::Handler& handler) override; + void AddServerAcceptanceReceivedHandler(ServerAcceptanceReceivedEvent::Handler& handler) override; void SendNotifyClientMigrationEvent(const HostId& hostId, uint64_t userIdentifier, ClientInputId lastClientInputId) override; void SendNotifyEntityMigrationEvent(const ConstNetworkEntityHandle& entityHandle, const HostId& remoteHostId) override; void SendReadyForEntityUpdates(bool readyForEntityUpdates) override; @@ -157,6 +160,7 @@ namespace Multiplayer SessionInitEvent m_initEvent; SessionShutdownEvent m_shutdownEvent; ConnectionAcquiredEvent m_connectionAcquiredEvent; + ServerAcceptanceReceivedEvent m_serverAcceptanceReceivedEvent; ClientDisconnectedEvent m_clientDisconnectedEvent; ClientMigrationStartEvent m_clientMigrationStartEvent; ClientMigrationEndEvent m_clientMigrationEndEvent; diff --git a/Gems/Multiplayer/Code/Tests/CommonBenchmarkSetup.h b/Gems/Multiplayer/Code/Tests/CommonBenchmarkSetup.h index 7f98d22702..5a528ed497 100644 --- a/Gems/Multiplayer/Code/Tests/CommonBenchmarkSetup.h +++ b/Gems/Multiplayer/Code/Tests/CommonBenchmarkSetup.h @@ -328,6 +328,7 @@ namespace Multiplayer void Terminate([[maybe_unused]] AzNetworking::DisconnectReason reason) override {} void AddClientDisconnectedHandler([[maybe_unused]] ClientDisconnectedEvent::Handler& handler) override {} void AddConnectionAcquiredHandler([[maybe_unused]] ConnectionAcquiredEvent::Handler& handler) override {} + void AddServerAcceptanceReceivedHandler([[maybe_unused]] ServerAcceptanceReceivedEvent::Handler& handler) override {} void AddSessionInitHandler([[maybe_unused]] SessionInitEvent::Handler& handler) override {} void AddSessionShutdownHandler([[maybe_unused]] SessionShutdownEvent::Handler& handler) override {} void SendReadyForEntityUpdates([[maybe_unused]] bool readyForEntityUpdates) override {} diff --git a/Gems/Multiplayer/Code/Tests/MockInterfaces.h b/Gems/Multiplayer/Code/Tests/MockInterfaces.h index 44024f67ab..527aeb51bc 100644 --- a/Gems/Multiplayer/Code/Tests/MockInterfaces.h +++ b/Gems/Multiplayer/Code/Tests/MockInterfaces.h @@ -29,9 +29,10 @@ namespace UnitTest MOCK_METHOD1(AddClientDisconnectedHandler, void(AZ::Event<>::Handler&)); MOCK_METHOD1(AddNotifyClientMigrationHandler, void(Multiplayer::NotifyClientMigrationEvent::Handler&)); MOCK_METHOD1(AddNotifyEntityMigrationEventHandler, void(Multiplayer::NotifyEntityMigrationEvent::Handler&)); - MOCK_METHOD1(AddConnectionAcquiredHandler, void(AZ::Event::Handler&)); - MOCK_METHOD1(AddSessionInitHandler, void(AZ::Event::Handler&)); - MOCK_METHOD1(AddSessionShutdownHandler, void(AZ::Event::Handler&)); + MOCK_METHOD1(AddConnectionAcquiredHandler, void(Multiplayer::ConnectionAcquiredEvent::Handler&)); + MOCK_METHOD1(AddServerAcceptanceReceivedHandler, void(Multiplayer::ServerAcceptanceReceivedEvent::Handler&)); + MOCK_METHOD1(AddSessionInitHandler, void(Multiplayer::SessionInitEvent::Handler&)); + MOCK_METHOD1(AddSessionShutdownHandler, void(Multiplayer::SessionShutdownEvent::Handler&)); MOCK_METHOD3(SendNotifyClientMigrationEvent, void(const Multiplayer::HostId&, uint64_t, Multiplayer::ClientInputId)); MOCK_METHOD2(SendNotifyEntityMigrationEvent, void(const Multiplayer::ConstNetworkEntityHandle&, const Multiplayer::HostId&)); MOCK_METHOD1(SendReadyForEntityUpdates, void(bool)); diff --git a/Gems/Multiplayer/Code/multiplayer_debug_files.cmake b/Gems/Multiplayer/Code/multiplayer_debug_files.cmake index 37a1c91640..1d85dd8149 100644 --- a/Gems/Multiplayer/Code/multiplayer_debug_files.cmake +++ b/Gems/Multiplayer/Code/multiplayer_debug_files.cmake @@ -9,6 +9,8 @@ set(FILES Source/Debug/MultiplayerDebugByteReporter.cpp Source/Debug/MultiplayerDebugByteReporter.h + Source/Debug/MultiplayerDebugHierarchyReporter.cpp + Source/Debug/MultiplayerDebugHierarchyReporter.h Source/Debug/MultiplayerDebugPerEntityReporter.cpp Source/Debug/MultiplayerDebugPerEntityReporter.h Source/Debug/MultiplayerDebugModule.cpp diff --git a/Gems/Prefab/PrefabBuilder/CMakeLists.txt b/Gems/Prefab/PrefabBuilder/CMakeLists.txt index 450b8d0408..9aedbd3d99 100644 --- a/Gems/Prefab/PrefabBuilder/CMakeLists.txt +++ b/Gems/Prefab/PrefabBuilder/CMakeLists.txt @@ -13,6 +13,9 @@ endif() ly_add_target( NAME PrefabBuilder.Static STATIC NAMESPACE Gem + INCLUDE_DIRECTORIES + PRIVATE + . FILES_CMAKE prefabbuilder_files.cmake BUILD_DEPENDENCIES @@ -20,6 +23,9 @@ ly_add_target( AZ::AzCore AZ::AzToolsFramework AZ::AssetBuilderSDK + AZ::SceneCore + AZ::SceneData + 3rdParty::RapidJSON ) ly_add_target( @@ -35,7 +41,8 @@ ly_add_target( Gem::PrefabBuilder.Static ) -# the prefab builder only needs to be active in builders +# create an alias for the tool version +ly_create_alias(NAME PrefabBuilder.Tools NAMESPACE Gem TARGETS Gem::PrefabBuilder.Builders) # we automatically add this gem, if it is present, to all our known set of builder applications: ly_enable_gems(GEMS PrefabBuilder) diff --git a/Gems/Prefab/PrefabBuilder/PrefabBuilderModule.cpp b/Gems/Prefab/PrefabBuilder/PrefabBuilderModule.cpp index 762f459d9d..2bf7026549 100644 --- a/Gems/Prefab/PrefabBuilder/PrefabBuilderModule.cpp +++ b/Gems/Prefab/PrefabBuilder/PrefabBuilderModule.cpp @@ -8,6 +8,7 @@ #include #include +#include namespace AZ::Prefab { @@ -22,7 +23,8 @@ namespace AZ::Prefab : Module() { m_descriptors.insert(m_descriptors.end(), { - PrefabBuilderComponent::CreateDescriptor() + PrefabBuilderComponent::CreateDescriptor(), + AZ::SceneAPI::Behaviors::PrefabGroupBehavior::CreateDescriptor() }); } }; diff --git a/Gems/Prefab/PrefabBuilder/PrefabBuilderTests.h b/Gems/Prefab/PrefabBuilder/PrefabBuilderTests.h index 3aea173ba4..2aec317e69 100644 --- a/Gems/Prefab/PrefabBuilder/PrefabBuilderTests.h +++ b/Gems/Prefab/PrefabBuilder/PrefabBuilderTests.h @@ -13,6 +13,7 @@ #include #include #include +#include namespace UnitTest { diff --git a/Gems/Prefab/PrefabBuilder/PrefabGroup/IPrefabGroup.h b/Gems/Prefab/PrefabBuilder/PrefabGroup/IPrefabGroup.h new file mode 100644 index 0000000000..c2f8313b95 --- /dev/null +++ b/Gems/Prefab/PrefabBuilder/PrefabGroup/IPrefabGroup.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#pragma once + +#include +#include +#include + +namespace AZ::SceneAPI::DataTypes +{ + class IPrefabGroup + : public ISceneNodeGroup + { + public: + AZ_RTTI(IPrefabGroup, "{7E50FAEF-3379-4521-99C5-B428FDEE3B7B}", ISceneNodeGroup); + + ~IPrefabGroup() override = default; + virtual AzToolsFramework::Prefab::PrefabDomConstReference GetPrefabDomRef() const = 0; + }; +} diff --git a/Gems/Prefab/PrefabBuilder/PrefabGroup/PrefabBehaviorTests.cpp b/Gems/Prefab/PrefabBuilder/PrefabGroup/PrefabBehaviorTests.cpp new file mode 100644 index 0000000000..856aba979b --- /dev/null +++ b/Gems/Prefab/PrefabBuilder/PrefabGroup/PrefabBehaviorTests.cpp @@ -0,0 +1,161 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace UnitTest +{ + class PrefabBehaviorTests + : public PrefabBuilderTests + { + public: + static void SetUpTestCase() + { + // Allocator needed by SceneCore + if (!AZ::AllocatorInstance().IsReady()) + { + AZ::AllocatorInstance().Create(); + } + AZ::SceneAPI::SceneCoreStandaloneAllocator::Initialize(AZ::Environment::GetInstance()); + } + + static void TearDownTestCase() + { + AZ::SceneAPI::SceneCoreStandaloneAllocator::TearDown(); + AZ::AllocatorInstance().Destroy(); + } + + void SetUp() override + { + PrefabBuilderTests::SetUp(); + m_prefabGroupBehavior = AZStd::make_unique(); + m_prefabGroupBehavior->Activate(); + + // Mocking the asset system replacing the AssetSystem::AssetSystemComponent + AZ::Entity* systemEntity = m_app.FindEntity(AZ::SystemEntityId); + systemEntity->FindComponent()->Deactivate(); + using namespace testing; + ON_CALL(m_assetSystemRequestMock, GetSourceInfoBySourcePath(_, _, _)).WillByDefault([](auto* path, auto& info, auto&) + { + return PrefabBehaviorTests::OnGetSourceInfoBySourcePath(path, info); + }); + m_assetSystemRequestMock.BusConnect(); + } + + void TearDown() override + { + m_assetSystemRequestMock.BusDisconnect(); + + m_prefabGroupBehavior->Deactivate(); + m_prefabGroupBehavior.reset(); + + PrefabBuilderTests::TearDown(); + } + + static bool OnGetSourceInfoBySourcePath(AZStd::string_view sourcePath, AZ::Data::AssetInfo& assetInfo) + { + if (sourcePath == AZStd::string_view("mock")) + { + assetInfo.m_assetId = AZ::Uuid::CreateRandom(); + assetInfo.m_assetType = azrtti_typeid(); + assetInfo.m_relativePath = "mock/path"; + assetInfo.m_sizeBytes = 0; + } + return true; + } + + struct TestPreExportEventContext + { + TestPreExportEventContext() + : m_scene("test_context") + { + using namespace AZ::SceneAPI::Events; + m_preExportEventContext = AZStd::make_unique(m_productList, m_outputDirectory, m_scene, "mock"); + } + + void SetOutputDirectory(AZStd::string outputDirectory) + { + using namespace AZ::SceneAPI::Events; + m_outputDirectory = AZStd::move(outputDirectory); + m_preExportEventContext = AZStd::make_unique(m_productList, m_outputDirectory, m_scene, "mock"); + } + + AZStd::unique_ptr m_preExportEventContext; + AZ::SceneAPI::Events::ExportProductList m_productList; + AZStd::string m_outputDirectory; + AZ::SceneAPI::Containers::Scene m_scene; + }; + + AZStd::unique_ptr m_prefabGroupBehavior; + testing::NiceMock m_assetSystemRequestMock; + }; + + TEST_F(PrefabBehaviorTests, PrefabBehavior_EmptyContextIgnored_Works) + { + auto context = TestPreExportEventContext{}; + + auto result = AZ::SceneAPI::Events::ProcessingResult::Failure; + AZ::SceneAPI::Events::CallProcessorBus::BroadcastResult( + result, + &AZ::SceneAPI::Events::CallProcessorBus::Events::Process, + context.m_preExportEventContext.get()); + + EXPECT_EQ(result, AZ::SceneAPI::Events::ProcessingResult::Ignored); + } + + TEST_F(PrefabBehaviorTests, PrefabBehavior_SimplePrefab_Works) + { + auto context = TestPreExportEventContext{}; + + // check for the file at /mock/fake_prefab.procprefab + AZ::Test::ScopedAutoTempDirectory tempDir; + context.SetOutputDirectory(tempDir.GetDirectory()); + + auto jsonOutcome = AZ::JsonSerializationUtils::ReadJsonString(Data::jsonPrefab); + ASSERT_TRUE(jsonOutcome); + + auto prefabGroup = AZStd::make_shared(); + prefabGroup.get()->SetId(AZ::Uuid::CreateRandom()); + prefabGroup.get()->SetName("fake_prefab"); + prefabGroup.get()->SetPrefabDom(AZStd::move(jsonOutcome.GetValue())); + context.m_scene.GetManifest().AddEntry(prefabGroup); + context.m_scene.SetSource("mock", AZ::Uuid::CreateRandom()); + + auto result = AZ::SceneAPI::Events::ProcessingResult::Failure; + AZ::SceneAPI::Events::CallProcessorBus::BroadcastResult( + result, + &AZ::SceneAPI::Events::CallProcessorBus::Events::Process, + context.m_preExportEventContext.get()); + + EXPECT_EQ(result, AZ::SceneAPI::Events::ProcessingResult::Success); + + AZStd::string pathStr; + AzFramework::StringFunc::Path::ConstructFull(tempDir.GetDirectory(), "mock/fake_prefab.procprefab", pathStr, true); + if (!AZ::IO::SystemFile::Exists(pathStr.c_str())) + { + AZ_Warning("testing", false, "The product asset (%s) is missing", pathStr.c_str()); + } + } +} diff --git a/Gems/Prefab/PrefabBuilder/PrefabGroup/PrefabBehaviorTests.inl b/Gems/Prefab/PrefabBuilder/PrefabGroup/PrefabBehaviorTests.inl new file mode 100644 index 0000000000..b39f782a1e --- /dev/null +++ b/Gems/Prefab/PrefabBuilder/PrefabGroup/PrefabBehaviorTests.inl @@ -0,0 +1,104 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +namespace UnitTest +{ + namespace Data + { + const char* jsonPrefab = R"JSON( + { + "ContainerEntity": { + "Id": "ContainerEntity", + "Name": "test_template_1", + "Components": { + "Component_[12122553907433030840]": { + "$type": "EditorVisibilityComponent", + "Id": 12122553907433030840 + }, + "Component_[5666150279650800686]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 5666150279650800686, + "Parent Entity": "" + }, + "Component_[8790726658974076423]": { + "$type": "EditorOnlyEntityComponent", + "Id": 8790726658974076423 + } + } + }, + "Entities": { + "Entity_[1588652751483]": { + "Id": "Entity_[1588652751483]", + "Name": "root", + "Components": { + "Component_[11872748096995986607]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 11872748096995986607, + "Parent Entity": "ContainerEntity", + "Transform Data": { + "Rotate": [ + 0.0, + 0.10000000149011612, + 180.0 + ] + } + }, + "Component_[12138841758570858610]": { + "$type": "EditorVisibilityComponent", + "Id": 12138841758570858610 + }, + "Component_[15735658354806796004]": { + "$type": "EditorOnlyEntityComponent", + "Id": 15735658354806796004 + } + } + }, + "Entity_[1592947718779]": { + "Id": "Entity_[1592947718779]", + "Name": "cube", + "Components": { + "Component_[2505301170249328189]": { + "$type": "EditorOnlyEntityComponent", + "Id": 2505301170249328189 + }, + "Component_[3716170894544198343]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 3716170894544198343, + "Parent Entity": "Entity_[1588652751483]" + }, + "Component_[5862175558847453681]": { + "$type": "EditorVisibilityComponent", + "Id": 5862175558847453681 + } + } + }, + "Entity_[1597242686075]": { + "Id": "Entity_[1597242686075]", + "Name": "cubeKid", + "Components": { + "Component_[10128771992421174485]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 10128771992421174485, + "Parent Entity": "Entity_[1592947718779]" + }, + "Component_[14936165953779771344]": { + "$type": "EditorVisibilityComponent", + "Id": 14936165953779771344 + }, + "Component_[403416213715997356]": { + "$type": "EditorOnlyEntityComponent", + "Id": 403416213715997356 + } + } + } + } + } + )JSON"; + + } +} diff --git a/Gems/Prefab/PrefabBuilder/PrefabGroup/PrefabGroup.cpp b/Gems/Prefab/PrefabBuilder/PrefabGroup/PrefabGroup.cpp new file mode 100644 index 0000000000..f4dd37b86c --- /dev/null +++ b/Gems/Prefab/PrefabBuilder/PrefabGroup/PrefabGroup.cpp @@ -0,0 +1,136 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace AZ::SceneAPI::SceneData +{ + // PrefabGroup + + PrefabGroup::PrefabGroup() + : m_id(Uuid::CreateNull()) + , m_name() + { + } + + const AZStd::string& PrefabGroup::GetName() const + { + return m_name; + } + + void PrefabGroup::SetName(AZStd::string name) + { + m_name = AZStd::move(name); + } + + const Uuid& PrefabGroup::GetId() const + { + return m_id; + } + + void PrefabGroup::SetId(Uuid id) + { + m_id = AZStd::move(id); + } + + Containers::RuleContainer& PrefabGroup::GetRuleContainer() + { + return m_rules; + } + + const Containers::RuleContainer& PrefabGroup::GetRuleContainerConst() const + { + return m_rules; + } + + DataTypes::ISceneNodeSelectionList& PrefabGroup::GetSceneNodeSelectionList() + { + return m_nodeSelectionList; + } + + const DataTypes::ISceneNodeSelectionList& PrefabGroup::GetSceneNodeSelectionList() const + { + return m_nodeSelectionList; + } + + void PrefabGroup::SetPrefabDom(AzToolsFramework::Prefab::PrefabDom prefabDom) + { + m_prefabDomData = AZStd::make_shared(); + m_prefabDomData->CopyValue(prefabDom); + } + + AzToolsFramework::Prefab::PrefabDomConstReference PrefabGroup::GetPrefabDomRef() const + { + if (m_prefabDomData) + { + return m_prefabDomData->GetValue(); + } + return {}; + } + + void PrefabGroup::Reflect(ReflectContext* context) + { + SerializeContext* serializeContext = azrtti_cast(context); + if (serializeContext) + { + serializeContext->Class() + ->Version(1); + + serializeContext->Class() + ->Version(1) + ->Field("name", &PrefabGroup::m_name) + ->Field("nodeSelectionList", &PrefabGroup::m_nodeSelectionList) + ->Field("rules", &PrefabGroup::m_rules) + ->Field("id", &PrefabGroup::m_id) + ->Field("prefabDomData", &PrefabGroup::m_prefabDomData); + } + + BehaviorContext* behaviorContext = azrtti_cast(context); + if (behaviorContext) + { + auto setPrefabDomData = [](PrefabGroup& self, const AZStd::string& json) + { + auto jsonOutcome = JsonSerializationUtils::ReadJsonString(json); + if (jsonOutcome.IsSuccess()) + { + self.SetPrefabDom(AZStd::move(jsonOutcome.GetValue())); + return true; + } + AZ_Error("prefab", false, "Set PrefabDom failed (%s)", jsonOutcome.GetError().c_str()); + return false; + }; + + auto getPrefabDomData = [](const PrefabGroup& self) -> AZStd::string + { + if (self.GetPrefabDomRef().has_value() == false) + { + return {}; + } + AZStd::string buffer; + JsonSerializationUtils::WriteJsonString(self.GetPrefabDomRef().value(), buffer); + return buffer; + }; + + behaviorContext->Class() + ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All) + ->Attribute(Script::Attributes::Scope, Script::Attributes::ScopeFlags::Common) + ->Attribute(Script::Attributes::Module, "prefab") + ->Property("name", BehaviorValueProperty(&PrefabGroup::m_name)) + ->Property("id", BehaviorValueProperty(&PrefabGroup::m_id)) + ->Property("prefabDomData", getPrefabDomData, setPrefabDomData); + } + } +} diff --git a/Gems/Prefab/PrefabBuilder/PrefabGroup/PrefabGroup.h b/Gems/Prefab/PrefabBuilder/PrefabGroup/PrefabGroup.h new file mode 100644 index 0000000000..e5c47186eb --- /dev/null +++ b/Gems/Prefab/PrefabBuilder/PrefabGroup/PrefabGroup.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace AZ +{ + class ReflectContext; +} + +namespace AZ::SceneAPI::Containers +{ + class Scene; +} + +namespace AZ::SceneAPI::SceneData +{ + class PrefabGroup final + : public DataTypes::IPrefabGroup + { + public: + AZ_RTTI(PrefabGroup, "{99FE3C6F-5B55-4D8B-8013-2708010EC715}", DataTypes::IPrefabGroup); + AZ_CLASS_ALLOCATOR(PrefabGroup, SystemAllocator, 0); + + static void Reflect(AZ::ReflectContext* context); + + PrefabGroup(); + ~PrefabGroup() override = default; + + // DataTypes::IPrefabGroup + AzToolsFramework::Prefab::PrefabDomConstReference GetPrefabDomRef() const override; + const AZStd::string& GetName() const override; + const Uuid& GetId() const override; + Containers::RuleContainer& GetRuleContainer() override; + const Containers::RuleContainer& GetRuleContainerConst() const override; + DataTypes::ISceneNodeSelectionList& GetSceneNodeSelectionList() override; + const DataTypes::ISceneNodeSelectionList& GetSceneNodeSelectionList() const override; + + // Concrete API + void SetId(Uuid id); + void SetName(AZStd::string name); + void SetPrefabDom(AzToolsFramework::Prefab::PrefabDom prefabDom); + + private: + SceneNodeSelectionList m_nodeSelectionList; + Containers::RuleContainer m_rules; + AZStd::string m_name; + Uuid m_id; + AZStd::shared_ptr m_prefabDomData; + }; +} diff --git a/Gems/Prefab/PrefabBuilder/PrefabGroup/PrefabGroupBehavior.cpp b/Gems/Prefab/PrefabBuilder/PrefabGroup/PrefabGroupBehavior.cpp new file mode 100644 index 0000000000..13828e5f8c --- /dev/null +++ b/Gems/Prefab/PrefabBuilder/PrefabGroup/PrefabGroupBehavior.cpp @@ -0,0 +1,251 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace AZ::SceneAPI::Behaviors +{ + // + // ExportEventHandler + // + + struct PrefabGroupBehavior::ExportEventHandler final + : public AZ::SceneAPI::SceneCore::ExportingComponent + { + using PreExportEventContextFunction = AZStd::function; + PreExportEventContextFunction m_preExportEventContextFunction; + AZ::Prefab::PrefabGroupAssetHandler m_prefabGroupAssetHandler; + + ExportEventHandler() = delete; + + ExportEventHandler(PreExportEventContextFunction function) + : m_preExportEventContextFunction(AZStd::move(function)) + { + BindToCall(&ExportEventHandler::PrepareForExport); + AZ::SceneAPI::SceneCore::ExportingComponent::Activate(); + } + + ~ExportEventHandler() + { + AZ::SceneAPI::SceneCore::ExportingComponent::Deactivate(); + } + + Events::ProcessingResult PrepareForExport(Events::PreExportEventContext& context) + { + return m_preExportEventContextFunction(context); + } + }; + + // + // PrefabGroupBehavior + // + + void PrefabGroupBehavior::Activate() + { + m_exportEventHandler = AZStd::make_shared([this](auto& context) + { + return this->OnPrepareForExport(context); + }); + } + + void PrefabGroupBehavior::Deactivate() + { + m_exportEventHandler.reset(); + } + + AZStd::unique_ptr PrefabGroupBehavior::CreateProductAssetData(const SceneData::PrefabGroup* prefabGroup) const + { + using namespace AzToolsFramework::Prefab; + + auto* prefabLoaderInterface = AZ::Interface::Get(); + if (!prefabLoaderInterface) + { + AZ_Error("prefab", false, "Could not get PrefabLoaderInterface"); + return {}; + } + + // write to a UTF-8 string buffer + auto prefabDomRef = prefabGroup->GetPrefabDomRef(); + if (!prefabDomRef) + { + AZ_Error("prefab", false, "PrefabGroup(%s) missing PrefabDom", prefabGroup->GetName().c_str()); + return {}; + } + + const AzToolsFramework::Prefab::PrefabDom& prefabDom = prefabDomRef.value(); + rapidjson::StringBuffer sb; + rapidjson::Writer> writer(sb); + if (prefabDom.Accept(writer) == false) + { + AZ_Error("prefab", false, "Could not write PrefabGroup(%s) to JSON", prefabGroup->GetName().c_str()); + return {}; + } + + // validate the PrefabDom will make a valid Prefab template instance + auto templateId = prefabLoaderInterface->LoadTemplateFromString(sb.GetString(), prefabGroup->GetName().c_str()); + if (templateId == InvalidTemplateId) + { + AZ_Error("prefab", false, "PrefabGroup(%s) Could not write load template", prefabGroup->GetName().c_str()); + return {}; + } + + auto* prefabSystemComponentInterface = AZ::Interface::Get(); + if (!prefabSystemComponentInterface) + { + AZ_Error("prefab", false, "Could not get PrefabSystemComponentInterface"); + return {}; + } + + // create instance to update the asset hints + auto instance = prefabSystemComponentInterface->InstantiatePrefab(templateId); + if (!instance) + { + AZ_Error("prefab", false, "PrefabGroup(%s) Could not instantiate prefab", prefabGroup->GetName().c_str()); + return {}; + } + + auto* instanceToTemplateInterface = AZ::Interface::Get(); + if (!instanceToTemplateInterface) + { + AZ_Error("prefab", false, "Could not get InstanceToTemplateInterface"); + return {}; + } + + // fill out a JSON DOM + auto proceduralPrefab = AZStd::make_unique(rapidjson::kObjectType); + instanceToTemplateInterface->GenerateDomForInstance(*proceduralPrefab.get(), *instance.get()); + return proceduralPrefab; + } + + bool PrefabGroupBehavior::WriteOutProductAsset( + Events::PreExportEventContext& context, + const SceneData::PrefabGroup* prefabGroup, + const rapidjson::Document& doc) const + { + // Retrieve source asset info so we can get a string with the relative path to the asset + bool assetInfoResult; + Data::AssetInfo info; + AZStd::string watchFolder; + AzToolsFramework::AssetSystemRequestBus::BroadcastResult( + assetInfoResult, + &AzToolsFramework::AssetSystemRequestBus::Events::GetSourceInfoBySourcePath, + context.GetScene().GetSourceFilename().c_str(), + info, + watchFolder); + + AZ::IO::FixedMaxPath assetPath(info.m_relativePath); + assetPath.ReplaceFilename(prefabGroup->GetName().c_str()); + + AZStd::string filePath = AZ::SceneAPI::Utilities::FileUtilities::CreateOutputFileName( + assetPath.c_str(), + context.GetOutputDirectory(), + AZ::Prefab::PrefabGroupAssetHandler::s_Extension); + + AZ::IO::FileIOStream fileStream(filePath.c_str(), AZ::IO::OpenMode::ModeWrite); + if (fileStream.IsOpen() == false) + { + AZ_Error("prefab", false, "File path(%s) could not open for write", filePath.c_str()); + return false; + } + + // write to a UTF-8 string buffer + rapidjson::StringBuffer sb; + rapidjson::Writer> writer(sb); + if (doc.Accept(writer) == false) + { + AZ_Error("prefab", false, "PrefabGroup(%s) Could not buffer JSON", prefabGroup->GetName().c_str()); + return false; + } + + const auto bytesWritten = fileStream.Write(sb.GetSize(), sb.GetString()); + if (bytesWritten > 1) + { + AZ::u32 subId = AZ::Crc32(filePath.c_str()); + context.GetProductList().AddProduct( + filePath, + context.GetScene().GetSourceGuid(), + azrtti_typeid(), + {}, + AZStd::make_optional(subId)); + + return true; + } + return false; + } + + Events::ProcessingResult PrefabGroupBehavior::OnPrepareForExport(Events::PreExportEventContext& context) const + { + AZStd::vector prefabGroupCollection; + const Containers::SceneManifest& manifest = context.GetScene().GetManifest(); + + for (size_t i = 0; i < manifest.GetEntryCount(); ++i) + { + const auto* group = azrtti_cast(manifest.GetValue(i).get()); + if (group) + { + prefabGroupCollection.push_back(group); + } + } + + if (prefabGroupCollection.empty()) + { + return AZ::SceneAPI::Events::ProcessingResult::Ignored; + } + + for (const auto* prefabGroup : prefabGroupCollection) + { + auto result = CreateProductAssetData(prefabGroup); + if (!result) + { + return Events::ProcessingResult::Failure; + } + + if (WriteOutProductAsset(context, prefabGroup, *result.get()) == false) + { + return Events::ProcessingResult::Failure; + } + } + + return Events::ProcessingResult::Success; + } + + void PrefabGroupBehavior::Reflect(ReflectContext* context) + { + AZ::SceneAPI::SceneData::PrefabGroup::Reflect(context); + Prefab::ProceduralPrefabAsset::Reflect(context); + + SerializeContext* serializeContext = azrtti_cast(context); + if (serializeContext) + { + serializeContext->Class()->Version(1); + } + } +} diff --git a/Gems/Prefab/PrefabBuilder/PrefabGroup/PrefabGroupBehavior.h b/Gems/Prefab/PrefabBuilder/PrefabGroup/PrefabGroupBehavior.h new file mode 100644 index 0000000000..b57d30695a --- /dev/null +++ b/Gems/Prefab/PrefabBuilder/PrefabGroup/PrefabGroupBehavior.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace AZ +{ + class ReflectContext; +} + +namespace AZ::SceneAPI::Events +{ + class PreExportEventContext; +} + +namespace AZ::SceneAPI::Behaviors +{ + class PrefabGroupBehavior + : public SceneCore::BehaviorComponent + { + public: + AZ_COMPONENT(PrefabGroupBehavior, "{13DC2819-CAC2-4977-91D7-C870087072AB}", SceneCore::BehaviorComponent); + + ~PrefabGroupBehavior() override = default; + + void Activate() override; + void Deactivate() override; + static void Reflect(ReflectContext* context); + + private: + Events::ProcessingResult OnPrepareForExport(Events::PreExportEventContext& context) const; + AZStd::unique_ptr CreateProductAssetData(const SceneData::PrefabGroup* prefabGroup) const; + + bool WriteOutProductAsset( + Events::PreExportEventContext& context, + const SceneData::PrefabGroup* prefabGroup, + const rapidjson::Document& doc) const; + + struct ExportEventHandler; + AZStd::shared_ptr m_exportEventHandler; + }; +} diff --git a/Gems/Prefab/PrefabBuilder/PrefabGroup/PrefabGroupTests.cpp b/Gems/Prefab/PrefabBuilder/PrefabGroup/PrefabGroupTests.cpp new file mode 100644 index 0000000000..9fb935ce9f --- /dev/null +++ b/Gems/Prefab/PrefabBuilder/PrefabGroup/PrefabGroupTests.cpp @@ -0,0 +1,161 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include +#include +#include +#include + +namespace UnitTest +{ + TEST_F(PrefabBuilderTests, PrefabGroup_FindsRequiredReflection_True) + { + using namespace AZ::SceneAPI; + auto* serializeContext = m_app.GetSerializeContext(); + ASSERT_NE(nullptr, serializeContext); + SceneData::PrefabGroup::Reflect(serializeContext); + SceneData::PrefabGroup::Reflect(m_app.GetJsonRegistrationContext()); + ASSERT_NE(nullptr, serializeContext->FindClassData(azrtti_typeid())); + ASSERT_NE(nullptr, serializeContext->FindClassData(azrtti_typeid())); + + auto findElementWithName = [](const AZ::SerializeContext::ClassData* classData, const char* name) + { + auto it = AZStd::find_if(classData->m_elements.begin(), classData->m_elements.end(), [name](const auto& element) + { + return strcmp(element.m_name, name) == 0; + }); + return it != classData->m_elements.end(); + }; + + auto* prefabGroupClassData = serializeContext->FindClassData(azrtti_typeid()); + EXPECT_TRUE(findElementWithName(prefabGroupClassData, "name")); + EXPECT_TRUE(findElementWithName(prefabGroupClassData, "nodeSelectionList")); + EXPECT_TRUE(findElementWithName(prefabGroupClassData, "rules")); + EXPECT_TRUE(findElementWithName(prefabGroupClassData, "id")); + EXPECT_TRUE(findElementWithName(prefabGroupClassData, "prefabDomData")); + + m_app.GetJsonRegistrationContext()->EnableRemoveReflection(); + SceneData::PrefabGroup::Reflect(m_app.GetJsonRegistrationContext()); + } + + TEST_F(PrefabBuilderTests, PrefabGroup_JsonWithPrefabArbitraryPrefab_Works) + { + namespace JSR = AZ::JsonSerializationResult; + using namespace AZ::SceneAPI; + auto* serializeContext = m_app.GetSerializeContext(); + ASSERT_NE(nullptr, serializeContext); + SceneData::PrefabGroup::Reflect(serializeContext); + AZ::Prefab::ProceduralPrefabAsset::Reflect(serializeContext); + SceneData::PrefabGroup::Reflect(m_app.GetJsonRegistrationContext()); + AZ::Prefab::ProceduralPrefabAsset::Reflect(m_app.GetJsonRegistrationContext()); + + // fill out a PrefabGroup using JSON + AZStd::string_view input = R"JSON( + { + "name" : "tester", + "id" : "{49698DBC-B447-49EF-9B56-25BB29342AFB}", + "prefabDomData" : {"foo": "bar"} + })JSON"; + + rapidjson::Document document; + document.Parse(input.data(), input.size()); + ASSERT_FALSE(document.HasParseError()); + + SceneData::PrefabGroup instancePrefabGroup; + EXPECT_EQ(AZ::JsonSerialization::Load(instancePrefabGroup, document).GetOutcome(), JSR::Outcomes::PartialDefaults); + + ASSERT_TRUE(instancePrefabGroup.GetPrefabDomRef().has_value()); + const AzToolsFramework::Prefab::PrefabDom& dom = instancePrefabGroup.GetPrefabDomRef().value(); + EXPECT_TRUE(dom.IsObject()); + EXPECT_TRUE(dom.GetObject().HasMember("foo")); + EXPECT_STREQ(dom.GetObject().FindMember("foo")->value.GetString(), "bar"); + EXPECT_STREQ(instancePrefabGroup.GetName().c_str(), "tester"); + EXPECT_STREQ(instancePrefabGroup.GetId().ToString().c_str(), "{49698DBC-B447-49EF-9B56-25BB29342AFB}"); + + m_app.GetJsonRegistrationContext()->EnableRemoveReflection(); + SceneData::PrefabGroup::Reflect(m_app.GetJsonRegistrationContext()); + AZ::Prefab::ProceduralPrefabAsset::Reflect(m_app.GetJsonRegistrationContext()); + } + + TEST_F(PrefabBuilderTests, PrefabGroup_InvalidPrefabJson_Detected) + { + using namespace AZ::SceneAPI; + + AZStd::string_view input = R"JSON( + { + bad json that will not parse + })JSON"; + + rapidjson::Document document; + document.Parse(input.data(), input.size()); + + SceneData::PrefabGroup prefabGroup; + prefabGroup.SetId(AZ::Uuid::CreateRandom()); + prefabGroup.SetName("tester"); + prefabGroup.SetPrefabDom(AZStd::move(document)); + + const AzToolsFramework::Prefab::PrefabDom& dom = prefabGroup.GetPrefabDomRef().value(); + EXPECT_TRUE(dom.IsNull()); + EXPECT_STREQ("tester", prefabGroup.GetName().c_str()); + } + + struct PrefabBuilderBehaviorTests + : public PrefabBuilderTests + { + void SetUp() override + { + using namespace AZ::SceneAPI; + + PrefabBuilderTests::SetUp(); + SceneData::PrefabGroup::Reflect(m_app.GetSerializeContext()); + SceneData::PrefabGroup::Reflect(m_app.GetBehaviorContext()); + SceneData::PrefabGroup::Reflect(m_app.GetJsonRegistrationContext()); + m_scriptContext = AZStd::make_unique(); + m_scriptContext->BindTo(m_app.GetBehaviorContext()); + } + + void TearDown() override + { + using namespace AZ::SceneAPI; + m_app.GetJsonRegistrationContext()->EnableRemoveReflection(); + SceneData::PrefabGroup::Reflect(m_app.GetJsonRegistrationContext()); + + m_scriptContext.reset(); + PrefabBuilderTests::TearDown(); + } + + void ExpectExecute(AZStd::string_view script) + { + EXPECT_TRUE(m_scriptContext->Execute(script.data())); + } + + AZStd::unique_ptr m_scriptContext; + }; + + TEST_F(PrefabBuilderBehaviorTests, PrefabGroup_PrefabGroupClass_Exists) + { + ExpectExecute("group = PrefabGroup()"); + ExpectExecute("assert(group)"); + ExpectExecute("assert(group.name)"); + ExpectExecute("assert(group.id)"); + ExpectExecute("assert(group.prefabDomData)"); + } + + TEST_F(PrefabBuilderBehaviorTests, PrefabGroup_PrefabGroupAssignment_Works) + { + ExpectExecute("group = PrefabGroup()"); + ExpectExecute("group.name = 'tester'"); + ExpectExecute("group.id = Uuid.CreateString('{AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE}', 0)"); + ExpectExecute("group.prefabDomData = '{\"foo\": \"bar\"}'"); + ExpectExecute("assert(group.name == 'tester')"); + ExpectExecute("assert(tostring(group.id) == '{AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE}')"); + ExpectExecute("assert(group.prefabDomData == '{\\n \"foo\": \"bar\"\\n}')"); + } +} diff --git a/Gems/Prefab/PrefabBuilder/PrefabGroup/ProceduralAssetHandler.cpp b/Gems/Prefab/PrefabBuilder/PrefabGroup/ProceduralAssetHandler.cpp new file mode 100644 index 0000000000..17ed4d9c0c --- /dev/null +++ b/Gems/Prefab/PrefabBuilder/PrefabGroup/ProceduralAssetHandler.cpp @@ -0,0 +1,185 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace AZ::Prefab +{ + // AssetTypeInfoHandler + + class PrefabGroupAssetHandler::AssetTypeInfoHandler final + : public AZ::AssetTypeInfoBus::Handler + { + public: + AZ_CLASS_ALLOCATOR(AssetTypeInfoHandler, AZ::SystemAllocator, 0); + AssetTypeInfoHandler(); + ~AssetTypeInfoHandler() override; + AZ::Data::AssetType GetAssetType() const override; + const char* GetAssetTypeDisplayName() const override; + const char* GetGroup() const override; + const char* GetBrowserIcon() const override; + void GetAssetTypeExtensions(AZStd::vector& extensions) override; + }; + + PrefabGroupAssetHandler::AssetTypeInfoHandler::AssetTypeInfoHandler() + { + AZ::AssetTypeInfoBus::Handler::BusConnect(azrtti_typeid()); + } + + PrefabGroupAssetHandler::AssetTypeInfoHandler::~AssetTypeInfoHandler() + { + AZ::AssetTypeInfoBus::Handler::BusDisconnect(azrtti_typeid()); + } + + AZ::Data::AssetType PrefabGroupAssetHandler::AssetTypeInfoHandler::GetAssetType() const + { + return azrtti_typeid(); + } + + const char* PrefabGroupAssetHandler::AssetTypeInfoHandler::GetAssetTypeDisplayName() const + { + return "Procedural Prefab"; + } + + const char* PrefabGroupAssetHandler::AssetTypeInfoHandler::GetGroup() const + { + return "Prefab"; + } + + const char* PrefabGroupAssetHandler::AssetTypeInfoHandler::GetBrowserIcon() const + { + return "Icons/Components/Box.png"; + } + + void PrefabGroupAssetHandler::AssetTypeInfoHandler::GetAssetTypeExtensions(AZStd::vector& extensions) + { + extensions.push_back(PrefabGroupAssetHandler::s_Extension); + } + + // PrefabGroupAssetHandler + + AZStd::string_view PrefabGroupAssetHandler::s_Extension{ "procprefab" }; + + PrefabGroupAssetHandler::PrefabGroupAssetHandler() + { + auto assetCatalog = AZ::Data::AssetCatalogRequestBus::FindFirstHandler(); + if (assetCatalog) + { + assetCatalog->EnableCatalogForAsset(azrtti_typeid()); + assetCatalog->AddExtension(s_Extension.data()); + } + if (AZ::Data::AssetManager::IsReady()) + { + AZ::Data::AssetManager::Instance().RegisterHandler(this, azrtti_typeid()); + } + m_assetTypeInfoHandler = AZStd::make_shared(); + } + + PrefabGroupAssetHandler::~PrefabGroupAssetHandler() + { + m_assetTypeInfoHandler.reset(); + if (AZ::Data::AssetManager::IsReady()) + { + AZ::Data::AssetManager::Instance().UnregisterHandler(this); + } + } + + AZ::Data::AssetData* PrefabGroupAssetHandler::CreateAsset([[maybe_unused]] const AZ::Data::AssetId& id, const AZ::Data::AssetType& type) + { + if (type != azrtti_typeid()) + { + AZ_Error("prefab", false, "Invalid asset type! Only handle 'ProceduralPrefabAsset'"); + return nullptr; + } + return aznew ProceduralPrefabAsset{}; + } + + void PrefabGroupAssetHandler::DestroyAsset(AZ::Data::AssetData* ptr) + { + // Note: the PrefabLoaderInterface will handle the lifetime of the Prefab Template + delete ptr; + } + + void PrefabGroupAssetHandler::GetHandledAssetTypes(AZStd::vector& assetTypes) + { + assetTypes.push_back(azrtti_typeid()); + } + + AZ::Data::AssetHandler::LoadResult PrefabGroupAssetHandler::LoadAssetData( + const AZ::Data::Asset& asset, + AZStd::shared_ptr stream, + [[maybe_unused]] const AZ::Data::AssetFilterCB& assetLoadFilterCB) + { + using namespace AzToolsFramework::Prefab; + + auto* proceduralPrefabAsset = asset.GetAs(); + if (!proceduralPrefabAsset) + { + AZ_Error("prefab", false, "This should be a ProceduralPrefabAsset type, as this is the only type we process!"); + return LoadResult::Error; + } + + AZStd::string buffer; + buffer.resize(stream->GetLoadedSize()); + stream->Read(stream->GetLoadedSize(), buffer.data()); + + auto jsonOutcome = AZ::JsonSerializationUtils::ReadJsonString(buffer); + if (jsonOutcome.IsSuccess() == false) + { + AZ_Error("prefab", false, "Asset JSON failed to compile %s", jsonOutcome.GetError().c_str()); + return LoadResult::Error; + } + const auto& jsonDoc = jsonOutcome.GetValue(); + + if (jsonDoc.IsObject() == false) + { + return LoadResult::Error; + } + + if (jsonDoc.FindMember("Source") == jsonDoc.MemberEnd()) + { + return LoadResult::Error; + } + const auto& templateName = jsonDoc["Source"]; + + AZStd::string stringJson; + auto stringOutcome = AZ::JsonSerializationUtils::WriteJsonString(jsonDoc, stringJson); + if (stringOutcome.IsSuccess() == false) + { + AZ_Error("prefab", false, "Could not write to JSON string %s", stringOutcome.GetError().c_str()); + return LoadResult::Error; + } + + // prepare the template + auto* prefabLoaderInterface = AZ::Interface::Get(); + if (!prefabLoaderInterface) + { + return LoadResult::Error; + } + + auto templateId = prefabLoaderInterface->LoadTemplateFromString(stringJson.data(), templateName.GetString()); + if (templateId == InvalidTemplateId) + { + return LoadResult::Error; + } + + proceduralPrefabAsset->SetTemplateId(templateId); + proceduralPrefabAsset->SetTemplateName(templateName.GetString()); + return LoadResult::LoadComplete; + } + + AZStd::unique_ptr s_PrefabGroupAssetHandler; +} + diff --git a/Gems/Prefab/PrefabBuilder/PrefabGroup/ProceduralAssetHandler.h b/Gems/Prefab/PrefabBuilder/PrefabGroup/ProceduralAssetHandler.h new file mode 100644 index 0000000000..301f8a467a --- /dev/null +++ b/Gems/Prefab/PrefabBuilder/PrefabGroup/ProceduralAssetHandler.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#pragma once + +#include +#include +#include +#include + +namespace AZ::Prefab +{ + class PrefabGroupAssetHandler final + : public AZ::Data::AssetHandler + { + public: + AZ_CLASS_ALLOCATOR(PrefabGroupAssetHandler, AZ::SystemAllocator, 0); + PrefabGroupAssetHandler(); + ~PrefabGroupAssetHandler() override; + + static AZStd::string_view s_Extension; + + protected: + AZ::Data::AssetData* CreateAsset(const AZ::Data::AssetId& id, const AZ::Data::AssetType& type) override; + void DestroyAsset(AZ::Data::AssetData* ptr) override; + void GetHandledAssetTypes(AZStd::vector& assetTypes) override; + AZ::Data::AssetHandler::LoadResult LoadAssetData( + const AZ::Data::Asset& asset, + AZStd::shared_ptr stream, + const AZ::Data::AssetFilterCB& assetLoadFilterCB) override; + + class AssetTypeInfoHandler; + AZStd::shared_ptr m_assetTypeInfoHandler; + }; +} diff --git a/Gems/Prefab/PrefabBuilder/PrefabGroupTests.cpp b/Gems/Prefab/PrefabBuilder/PrefabGroupTests.cpp new file mode 100644 index 0000000000..a63c0caa69 --- /dev/null +++ b/Gems/Prefab/PrefabBuilder/PrefabGroupTests.cpp @@ -0,0 +1,162 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include +#include + +namespace UnitTest +{ + TEST_F(PrefabBuilderTests, PrefabGroup_FindsRequiredReflection_True) + { + using namespace AZ::SceneAPI; + auto* serializeContext = m_app.GetSerializeContext(); + ASSERT_NE(nullptr, serializeContext); + SceneData::PrefabGroup::Reflect(serializeContext); + ASSERT_NE(nullptr, serializeContext->FindClassData(azrtti_typeid())); + ASSERT_NE(nullptr, serializeContext->FindClassData(azrtti_typeid())); + + auto findElementWithName = [](const AZ::SerializeContext::ClassData* classData, const char* name) + { + auto it = AZStd::find_if(classData->m_elements.begin(), classData->m_elements.end(), [name](const auto& element) + { + return strcmp(element.m_name, name) == 0; + }); + return it != classData->m_elements.end(); + }; + + auto* prefabGroupClassData = serializeContext->FindClassData(azrtti_typeid()); + EXPECT_TRUE(findElementWithName(prefabGroupClassData, "name")); + EXPECT_TRUE(findElementWithName(prefabGroupClassData, "nodeSelectionList")); + EXPECT_TRUE(findElementWithName(prefabGroupClassData, "rules")); + EXPECT_TRUE(findElementWithName(prefabGroupClassData, "id")); + EXPECT_TRUE(findElementWithName(prefabGroupClassData, "prefabDomBuffer")); + } + + TEST_F(PrefabBuilderTests, PrefabGroup_JsonWithPrefabArbitraryPrefab_Works) + { + using namespace AZ::SceneAPI; + auto* serializeContext = m_app.GetSerializeContext(); + ASSERT_NE(nullptr, serializeContext); + SceneData::PrefabGroup::Reflect(serializeContext); + + // fill out a PrefabGroup using JSON + AZStd::string_view input = R"JSON( + { + "name" : "tester", + "id" : "{49698DBC-B447-49EF-9B56-25BB29342AFB}", + "prefabDomBuffer" : "{\"foo\":\"bar\"}" + })JSON"; + + rapidjson::Document document; + document.Parse(input.data(), input.size()); + ASSERT_FALSE(document.HasParseError()); + + SceneData::PrefabGroup instancePrefabGroup; + AZ::JsonSerialization::Load(instancePrefabGroup, document); + + const auto& dom = instancePrefabGroup.GetPrefabDom(); + EXPECT_TRUE(dom.GetObject().HasMember("foo")); + EXPECT_STREQ(dom.GetObject().FindMember("foo")->value.GetString(), "bar"); + EXPECT_STREQ(instancePrefabGroup.GetName().c_str(), "tester"); + EXPECT_STREQ( + instancePrefabGroup.GetId().ToString().c_str(), + "{49698DBC-B447-49EF-9B56-25BB29342AFB}"); + EXPECT_TRUE(instancePrefabGroup.GetPrefabDom().IsObject()); + } + + TEST_F(PrefabBuilderTests, PrefabGroup_InvalidPrefabJson_Detected) + { + using namespace AZ::SceneAPI; + + AZStd::string_view input = R"JSON( + { + bad json that will not parse + })JSON"; + + rapidjson::Document document; + document.Parse(input.data(), input.size()); + + SceneData::PrefabGroup prefabGroup; + prefabGroup.SetId(AZStd::move(AZ::Uuid::CreateRandom())); + prefabGroup.SetName(AZStd::move("tester")); + prefabGroup.SetPrefabDom(AZStd::move(document)); + + const auto& dom = prefabGroup.GetPrefabDom(); + EXPECT_TRUE(dom.IsNull()); + EXPECT_STREQ("tester", prefabGroup.GetName().c_str()); + } + + TEST_F(PrefabBuilderTests, PrefabGroup_InvalidPrefabJsonBuffer_Detected) + { + using namespace AZ::SceneAPI; + + AZStd::string_view inputJson = R"JSON( + { + bad json that will not parse + })JSON"; + + SceneData::PrefabGroup prefabGroup; + prefabGroup.SetId(AZStd::move(AZ::Uuid::CreateRandom())); + prefabGroup.SetName(AZStd::move("tester")); + prefabGroup.SetPrefabDomBuffer(std::move(inputJson)); + + const auto& dom = prefabGroup.GetPrefabDom(); + EXPECT_TRUE(dom.IsNull()); + EXPECT_STREQ("tester", prefabGroup.GetName().c_str()); + } + + struct PrefabBuilderBehaviorTests + : public PrefabBuilderTests + { + void SetUp() override + { + using namespace AZ::SceneAPI; + + PrefabBuilderTests::SetUp(); + SceneData::PrefabGroup::Reflect(m_app.GetSerializeContext()); + SceneData::PrefabGroup::Reflect(m_app.GetBehaviorContext()); + m_scriptContext = AZStd::make_unique(); + m_scriptContext->BindTo(m_app.GetBehaviorContext()); + } + + void TearDown() override + { + m_scriptContext.reset(); + PrefabBuilderTests::TearDown(); + } + + void ExpectExecute(AZStd::string_view script) + { + EXPECT_TRUE(m_scriptContext->Execute(script.data())); + } + + AZStd::unique_ptr m_scriptContext; + }; + + TEST_F(PrefabBuilderBehaviorTests, PrefabGroup_PrefabGroupClass_Exists) + { + ExpectExecute("group = PrefabGroup()"); + ExpectExecute("assert(group)"); + ExpectExecute("assert(group.name)"); + ExpectExecute("assert(group.id)"); + ExpectExecute("assert(group.prefabDomBuffer)"); + } + + TEST_F(PrefabBuilderBehaviorTests, PrefabGroup_PrefabGroupAssignment_Works) + { + ExpectExecute("group = PrefabGroup()"); + ExpectExecute("group.name = 'tester'"); + ExpectExecute("group.id = Uuid.CreateString('{AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE}', 0)"); + ExpectExecute("group.prefabDomBuffer = '{}'"); + ExpectExecute("assert(group.name == 'tester')"); + ExpectExecute("assert(tostring(group.id) == '{AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE}')"); + ExpectExecute("assert(group.prefabDomBuffer == '{}')"); + } +} diff --git a/Gems/Prefab/PrefabBuilder/prefabbuilder_files.cmake b/Gems/Prefab/PrefabBuilder/prefabbuilder_files.cmake index 9cdf90d951..4f3bf860be 100644 --- a/Gems/Prefab/PrefabBuilder/prefabbuilder_files.cmake +++ b/Gems/Prefab/PrefabBuilder/prefabbuilder_files.cmake @@ -9,4 +9,11 @@ set(FILES PrefabBuilderComponent.h PrefabBuilderComponent.cpp + PrefabGroup/IPrefabGroup.h + PrefabGroup/PrefabGroup.cpp + PrefabGroup/PrefabGroup.h + PrefabGroup/PrefabGroupBehavior.cpp + PrefabGroup/PrefabGroupBehavior.h + PrefabGroup/ProceduralAssetHandler.cpp + PrefabGroup/ProceduralAssetHandler.h ) diff --git a/Gems/Prefab/PrefabBuilder/prefabbuilder_tests_files.cmake b/Gems/Prefab/PrefabBuilder/prefabbuilder_tests_files.cmake index 3f1ae0388b..faf2f88b5e 100644 --- a/Gems/Prefab/PrefabBuilder/prefabbuilder_tests_files.cmake +++ b/Gems/Prefab/PrefabBuilder/prefabbuilder_tests_files.cmake @@ -9,4 +9,7 @@ set(FILES PrefabBuilderTests.h PrefabBuilderTests.cpp + PrefabGroup/PrefabGroupTests.cpp + PrefabGroup/PrefabBehaviorTests.cpp + PrefabGroup/PrefabBehaviorTests.inl ) diff --git a/Gems/PythonAssetBuilder/Editor/Scripts/scene_api/scene_data.py b/Gems/PythonAssetBuilder/Editor/Scripts/scene_api/scene_data.py index 2c63e5f90c..2578efeae2 100755 --- a/Gems/PythonAssetBuilder/Editor/Scripts/scene_api/scene_data.py +++ b/Gems/PythonAssetBuilder/Editor/Scripts/scene_api/scene_data.py @@ -104,6 +104,15 @@ class SceneManifest(): self.manifest['values'].append(meshGroup) return meshGroup + def add_prefab_group(self, name, id, json) -> dict: + prefabGroup = {} + prefabGroup['$type'] = '{99FE3C6F-5B55-4D8B-8013-2708010EC715} PrefabGroup' + prefabGroup['name'] = name + prefabGroup['id'] = id + prefabGroup['prefabDomData'] = json + self.manifest['values'].append(prefabGroup) + return prefabGroup + def mesh_group_select_node(self, meshGroup, nodeName): meshGroup['nodeSelectionList']['selectedNodes'].append(nodeName) diff --git a/Gems/SceneProcessing/Code/Source/SceneBuilder/SceneBuilderWorker.cpp b/Gems/SceneProcessing/Code/Source/SceneBuilder/SceneBuilderWorker.cpp index c1a1252c6b..1d3cd60e49 100644 --- a/Gems/SceneProcessing/Code/Source/SceneBuilder/SceneBuilderWorker.cpp +++ b/Gems/SceneProcessing/Code/Source/SceneBuilder/SceneBuilderWorker.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -34,6 +35,9 @@ #include #include +#include +#include +#include #include #include @@ -81,6 +85,93 @@ namespace SceneBuilder return m_cachedFingerprint.c_str(); } + void SceneBuilderWorker::PopulateSourceDependencies(const AZStd::string& manifestJson, AZStd::vector& sourceFileDependencies) + { + auto readJsonOutcome = AZ::JsonSerializationUtils::ReadJsonString(manifestJson); + AZStd::string errorMsg; + if (!readJsonOutcome.IsSuccess()) + { + // This may be an old format xml file. We don't have any dependencies in the old format so there's no point trying to parse an xml + return; + } + + rapidjson::Document document = readJsonOutcome.TakeValue(); + + auto manifestObject = document.GetObject(); + auto valuesIterator = manifestObject.FindMember("values"); + auto valuesArray = valuesIterator->value.GetArray(); + + AZStd::vector paths; + AZ::SceneAPI::Events::AssetImportRequestBus::Broadcast( + &AZ::SceneAPI::Events::AssetImportRequestBus::Events::GetManifestDependencyPaths, paths); + + for (const auto& value : valuesArray) + { + auto object = value.GetObject(); + + for (const auto& path : paths) + { + rapidjson::Pointer pointer(path.c_str()); + + auto dependencyValue = pointer.Get(object); + + if (dependencyValue && dependencyValue->IsString()) + { + AZStd::string dependency = dependencyValue->GetString(); + + sourceFileDependencies.emplace_back(AssetBuilderSDK::SourceFileDependency( + dependency, AZ::Uuid::CreateNull(), AssetBuilderSDK::SourceFileDependency::SourceFileDependencyType::Absolute)); + } + } + } + } + + bool SceneBuilderWorker::ManifestDependencyCheck(const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response) + { + AZStd::string manifestExtension; + AZStd::string generatedManifestExtension; + + AZ::SceneAPI::Events::AssetImportRequestBus::Broadcast( + &AZ::SceneAPI::Events::AssetImportRequestBus::Events::GetManifestExtension, manifestExtension); + AZ::SceneAPI::Events::AssetImportRequestBus::Broadcast( + &AZ::SceneAPI::Events::AssetImportRequestBus::Events::GetGeneratedManifestExtension, generatedManifestExtension); + + if (manifestExtension.empty() || generatedManifestExtension.empty()) + { + AZ_Error("SceneBuilderWorker", false, "Failed to get scene manifest extension"); + return false; + } + + AZ::SettingsRegistryInterface::FixedValueString assetCacheRoot; + AZ::SettingsRegistry::Get()->Get(assetCacheRoot, AZ::SettingsRegistryMergeUtils::FilePathKey_CacheRootFolder); + + auto manifestPath = (AZ::IO::Path(request.m_watchFolder) / (request.m_sourceFile + manifestExtension)); + auto generatedManifestPath = (AZ::IO::Path(assetCacheRoot) / (request.m_sourceFile + generatedManifestExtension)); + + auto populateDependenciesFunc = [&response](const AZStd::string& path) + { + auto readFileOutcome = AZ::Utils::ReadFile(path, AZ::SceneAPI::Containers::SceneManifest::MaxSceneManifestFileSizeInBytes); + if (!readFileOutcome.IsSuccess()) + { + AZ_Error("SceneBuilderWorker", false, "%s", readFileOutcome.GetError().c_str()); + return; + } + + PopulateSourceDependencies(readFileOutcome.TakeValue(), response.m_sourceFileDependencyList); + }; + + if (AZ::IO::FileIOBase::GetInstance()->Exists(manifestPath.Native().c_str())) + { + populateDependenciesFunc(manifestPath.Native()); + } + else if (AZ::IO::FileIOBase::GetInstance()->Exists(generatedManifestPath.Native().c_str())) + { + populateDependenciesFunc(generatedManifestPath.Native()); + } + + return true; + } + void SceneBuilderWorker::CreateJobs(const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response) { // Check for shutdown @@ -118,6 +209,11 @@ namespace SceneBuilder sourceFileDependencyInfo.m_sourceDependencyType = AssetBuilderSDK::SourceFileDependency::SourceFileDependencyType::Wildcards; response.m_sourceFileDependencyList.push_back(sourceFileDependencyInfo); + if (!ManifestDependencyCheck(request, response)) + { + return; + } + response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success; } diff --git a/Gems/SceneProcessing/Code/Source/SceneBuilder/SceneBuilderWorker.h b/Gems/SceneProcessing/Code/Source/SceneBuilder/SceneBuilderWorker.h index 3ecc657a3a..32456f9b2d 100644 --- a/Gems/SceneProcessing/Code/Source/SceneBuilder/SceneBuilderWorker.h +++ b/Gems/SceneProcessing/Code/Source/SceneBuilder/SceneBuilderWorker.h @@ -11,6 +11,7 @@ #include #include #include +#include namespace AssetBuilderSDK { @@ -45,11 +46,15 @@ namespace SceneBuilder public: ~SceneBuilderWorker() override = default; + void CreateJobs(const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response); void ProcessJob(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response); void ShutDown() override; const char* GetFingerprint() const; + static void PopulateSourceDependencies( + const AZStd::string& manifestJson, AZStd::vector& sourceFileDependencies); + static bool ManifestDependencyCheck(const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response); static AZ::Uuid GetUUID(); void PopulateProductDependencies(const AZ::SceneAPI::Events::ExportProduct& exportProduct, const char* watchFolder, AssetBuilderSDK::JobProduct& jobProduct) const; diff --git a/Gems/SceneProcessing/Code/Tests/SceneBuilder/SceneBuilderTests.cpp b/Gems/SceneProcessing/Code/Tests/SceneBuilder/SceneBuilderTests.cpp index 5d0105f080..ba1a186e5a 100644 --- a/Gems/SceneProcessing/Code/Tests/SceneBuilder/SceneBuilderTests.cpp +++ b/Gems/SceneProcessing/Code/Tests/SceneBuilder/SceneBuilderTests.cpp @@ -11,11 +11,14 @@ #include #include #include +#include #include #include #include +#include #include #include +#include using namespace AZ; using namespace SceneBuilder; @@ -193,3 +196,200 @@ TEST_F(SceneBuilderTests, SceneBuilderWorker_ExportProductDependencies_ProductAn TestSuccessCase(exportProduct, expectedPathDependencies, { dependencyId }); } + +struct ImportHandler + : SceneAPI::Events::AssetImportRequestBus::Handler +{ + ImportHandler() + { + BusConnect(); + } + + ~ImportHandler() override + { + BusDisconnect(); + } + + void GetManifestDependencyPaths(AZStd::vector& paths) override + { + paths.emplace_back("/scriptFilename"); + paths.emplace_back("/layer1/layer2/0/target"); + } + + void GetManifestExtension(AZStd::string& result) override + { + result = ".test"; + } + + void GetGeneratedManifestExtension(AZStd::string& result) override + { + result = ".test.gen"; + } +}; + +using SourceDependencyTests = UnitTest::ScopedAllocatorSetupFixture; + +namespace SourceDependencyJson +{ + constexpr const char* TestJson = R"JSON( +{ + "values": [ + { + "$type": "Test1", + "scriptFilename": "a/test/path.png" + }, + { + "$type": "Test2", + "layer1" : { + "layer2" : [ + { + "target": "value.png", + "otherData": "value2.png" + }, + { + "target" : "wrong.png" + } + ] + } + } + ] +} + )JSON"; +} + +TEST_F(SourceDependencyTests, SourceDependencyTest) +{ + ImportHandler handler; + AZStd::vector dependencies; + + SceneBuilderWorker::PopulateSourceDependencies(SourceDependencyJson::TestJson, dependencies); + + ASSERT_EQ(dependencies.size(), 2); + ASSERT_STREQ(dependencies[0].m_sourceFileDependencyPath.c_str(), "a/test/path.png"); + ASSERT_STREQ(dependencies[1].m_sourceFileDependencyPath.c_str(), "value.png"); +} + +struct SettingsRegistryMock : AZ::Interface::Registrar +{ + bool Get(FixedValueString& result, AZStd::string_view) const override + { + result = "cache"; + return true; + } + + void SetApplyPatchSettings(const AZ::JsonApplyPatchSettings& /*applyPatchSettings*/) override{} + void GetApplyPatchSettings(AZ::JsonApplyPatchSettings& /*applyPatchSettings*/) override{} + + MOCK_CONST_METHOD1(GetType, Type (AZStd::string_view)); + MOCK_CONST_METHOD2(Visit, bool (Visitor&, AZStd::string_view)); + MOCK_CONST_METHOD2(Visit, bool (const VisitorCallback&, AZStd::string_view)); + MOCK_METHOD1(RegisterNotifier, NotifyEventHandler (const NotifyCallback&)); + MOCK_METHOD1(RegisterNotifier, NotifyEventHandler (NotifyCallback&&)); + MOCK_METHOD1(RegisterPreMergeEvent, PreMergeEventHandler (const PreMergeEventCallback&)); + MOCK_METHOD1(RegisterPreMergeEvent, PreMergeEventHandler (PreMergeEventCallback&&)); + MOCK_METHOD1(RegisterPostMergeEvent, PostMergeEventHandler (const PostMergeEventCallback&)); + MOCK_METHOD1(RegisterPostMergeEvent, PostMergeEventHandler (PostMergeEventCallback&&)); + MOCK_CONST_METHOD2(Get, bool (bool&, AZStd::string_view)); + MOCK_CONST_METHOD2(Get, bool (s64&, AZStd::string_view)); + MOCK_CONST_METHOD2(Get, bool (u64&, AZStd::string_view)); + MOCK_CONST_METHOD2(Get, bool (double&, AZStd::string_view)); + MOCK_CONST_METHOD2(Get, bool (AZStd::string&, AZStd::string_view)); + MOCK_CONST_METHOD3(GetObject, bool (void*, Uuid, AZStd::string_view)); + MOCK_METHOD2(Set, bool (AZStd::string_view, bool)); + MOCK_METHOD2(Set, bool (AZStd::string_view, s64)); + MOCK_METHOD2(Set, bool (AZStd::string_view, u64)); + MOCK_METHOD2(Set, bool (AZStd::string_view, double)); + MOCK_METHOD2(Set, bool (AZStd::string_view, AZStd::string_view)); + MOCK_METHOD2(Set, bool (AZStd::string_view, const char*)); + MOCK_METHOD3(SetObject, bool (AZStd::string_view, const void*, Uuid)); + MOCK_METHOD1(Remove, bool (AZStd::string_view)); + MOCK_METHOD3(MergeCommandLineArgument, bool (AZStd::string_view, AZStd::string_view, const CommandLineArgumentSettings&)); + MOCK_METHOD2(MergeSettings, bool (AZStd::string_view, Format)); + MOCK_METHOD4(MergeSettingsFile, bool (AZStd::string_view, Format, AZStd::string_view, AZStd::vector*)); + MOCK_METHOD5(MergeSettingsFolder, bool (AZStd::string_view, const Specializations&, AZStd::string_view, AZStd::string_view, AZStd::vector*)); + MOCK_METHOD1(SetUseFileIO, void (bool)); +}; + +struct SourceDependencyMockedIOTests : UnitTest::ScopedAllocatorSetupFixture + , UnitTest::SetRestoreFileIOBaseRAII +{ + SourceDependencyMockedIOTests() + : UnitTest::SetRestoreFileIOBaseRAII(m_ioMock) + { + + } + + void SetUp() override + { + using namespace ::testing; + + ON_CALL(m_ioMock, Open(_, _, _)) + .WillByDefault(Invoke( + [](auto, auto, IO::HandleType& handle) + { + handle = 1234; + return AZ::IO::Result(AZ::IO::ResultCode::Success); + })); + + ON_CALL(m_ioMock, Size(An(), _)).WillByDefault(Invoke([](auto, AZ::u64& size) + { + size = strlen(SourceDependencyJson::TestJson); + return AZ::IO::ResultCode::Success; + })); + + EXPECT_CALL(m_ioMock, Read(_, _, _, _, _)) + .WillRepeatedly(Invoke( + [](auto, void* buffer, auto, auto, AZ::u64* bytesRead) + { + memcpy(buffer, SourceDependencyJson::TestJson, strlen(SourceDependencyJson::TestJson)); + *bytesRead = strlen(SourceDependencyJson::TestJson); + return AZ::IO::ResultCode::Success; + })); + + EXPECT_CALL(m_ioMock, Close(_)).WillRepeatedly(Return(AZ::IO::ResultCode::Success)); + } + + IO::NiceFileIOBaseMock m_ioMock; +}; + +TEST_F(SourceDependencyMockedIOTests, RegularManifestHasPriority) +{ + ImportHandler handler; + SettingsRegistryMock settingsRegistry; + + AssetBuilderSDK::CreateJobsRequest request; + AssetBuilderSDK::CreateJobsResponse response; + + request.m_sourceFile = "file.fbx"; + + using namespace ::testing; + + AZStd::string genPath = AZStd::string("cache").append(1, AZ_TRAIT_OS_PATH_SEPARATOR).append("file.fbx.test.gen"); + + EXPECT_CALL(m_ioMock, Exists(StrEq("file.fbx.test"))).WillRepeatedly(Return(true)); + EXPECT_CALL(m_ioMock, Exists(StrEq(genPath.c_str()))).Times(Exactly(0)); + + ASSERT_TRUE(SceneBuilderWorker::ManifestDependencyCheck(request, response)); + ASSERT_EQ(response.m_sourceFileDependencyList.size(), 2); +} + +TEST_F(SourceDependencyMockedIOTests, GeneratedManifestTest) +{ + ImportHandler handler; + SettingsRegistryMock settingsRegistry; + + AssetBuilderSDK::CreateJobsRequest request; + AssetBuilderSDK::CreateJobsResponse response; + + request.m_sourceFile = "file.fbx"; + + using namespace ::testing; + + AZStd::string genPath = AZStd::string("cache").append(1, AZ_TRAIT_OS_PATH_SEPARATOR).append("file.fbx.test.gen"); + + EXPECT_CALL(m_ioMock, Exists(StrEq("file.fbx.test"))).WillRepeatedly(Return(false)); + EXPECT_CALL(m_ioMock, Exists(StrEq(genPath.c_str()))).WillRepeatedly(Return(true)); + + ASSERT_TRUE(SceneBuilderWorker::ManifestDependencyCheck(request, response)); + ASSERT_EQ(response.m_sourceFileDependencyList.size(), 2); +} diff --git a/Gems/ScriptCanvas/Code/Builder/ScriptCanvasBuilderWorkerUtility.cpp b/Gems/ScriptCanvas/Code/Builder/ScriptCanvasBuilderWorkerUtility.cpp index 0853789b19..7d4cfec909 100644 --- a/Gems/ScriptCanvas/Code/Builder/ScriptCanvasBuilderWorkerUtility.cpp +++ b/Gems/ScriptCanvas/Code/Builder/ScriptCanvasBuilderWorkerUtility.cpp @@ -110,13 +110,12 @@ namespace ScriptCanvasBuilder const ScriptCanvas::Translation::Result translationResult = TranslateToLua(request); auto isSuccessOutcome = translationResult.IsSuccess(ScriptCanvas::Translation::TargetFlags::Lua); - if (!isSuccessOutcome.IsSuccess()) { return AZ::Failure(isSuccessOutcome.TakeError()); } - auto& translation = translationResult.m_translations.find(ScriptCanvas::Translation::TargetFlags::Lua)->second; + auto& translation = translationResult.m_translations.find(ScriptCanvas::Translation::TargetFlags::Lua)->second; AZ::Data::Asset asset; scriptAssetId.m_subId = AZ::ScriptAsset::CompiledAssetSubId; asset.Create(scriptAssetId); @@ -481,6 +480,7 @@ namespace ScriptCanvasBuilder buildEntity->Activate(); } + AZ_Assert(buildEntity->GetState() == AZ::Entity::State::Active, "build entity not active"); return sourceGraph; } diff --git a/Gems/ScriptCanvas/Code/Editor/Components/EditorScriptCanvasComponent.cpp b/Gems/ScriptCanvas/Code/Editor/Components/EditorScriptCanvasComponent.cpp index 2708f95f92..043e034062 100644 --- a/Gems/ScriptCanvas/Code/Editor/Components/EditorScriptCanvasComponent.cpp +++ b/Gems/ScriptCanvas/Code/Editor/Components/EditorScriptCanvasComponent.cpp @@ -170,7 +170,7 @@ namespace ScriptCanvasEditor ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c)) ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("UI", 0x27ff46b0)) ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Level", 0x9aeacc13)) - ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/script-canvas/") + ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/scripting/script-canvas/") ->DataElement(AZ::Edit::UIHandlers::Default, &EditorScriptCanvasComponent::m_scriptCanvasAssetHolder, "Script Canvas Asset", "Script Canvas asset associated with this component") ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) ->DataElement(AZ::Edit::UIHandlers::Default, &EditorScriptCanvasComponent::m_variableOverrides, "Properties", "Script Canvas Graph Properties") diff --git a/Gems/ScriptCanvas/Code/Editor/Components/GraphUpgrade.cpp b/Gems/ScriptCanvas/Code/Editor/Components/GraphUpgrade.cpp index e87c82e186..0e5f11fa40 100644 --- a/Gems/ScriptCanvas/Code/Editor/Components/GraphUpgrade.cpp +++ b/Gems/ScriptCanvas/Code/Editor/Components/GraphUpgrade.cpp @@ -519,7 +519,7 @@ namespace ScriptCanvasEditor if (validationResults.HasErrors()) { - AZ::Interface::Get()->GraphNeedsManualUpgrade(sm->m_asset.GetId()); + sm->MarkError("Failed to Parse"); for (auto& err : validationResults.GetEvents()) { @@ -686,7 +686,7 @@ namespace ScriptCanvasEditor void EditorGraphUpgradeMachine::OnComplete(IState::ExitStatus exitStatus) { - UpgradeNotifications::Bus::Broadcast(&UpgradeNotifications::OnGraphUpgradeComplete, m_asset, exitStatus == IState::ExitStatus::Skipped); + UpgradeNotificationsBus::Broadcast(&UpgradeNotifications::OnGraphUpgradeComplete, m_asset, exitStatus == IState::ExitStatus::Skipped); m_asset = {}; } @@ -735,7 +735,7 @@ namespace ScriptCanvasEditor { AZ::SystemTickBus::Handler::BusDisconnect(); - OnComplete(exitStatus); + OnComplete(m_error.empty() ? exitStatus : IState::ExitStatus::Skipped); } } diff --git a/Gems/ScriptCanvas/Code/Editor/Include/ScriptCanvas/Bus/EditorScriptCanvasBus.h b/Gems/ScriptCanvas/Code/Editor/Include/ScriptCanvas/Bus/EditorScriptCanvasBus.h index 4497968ffc..7e7b600081 100644 --- a/Gems/ScriptCanvas/Code/Editor/Include/ScriptCanvas/Bus/EditorScriptCanvasBus.h +++ b/Gems/ScriptCanvas/Code/Editor/Include/ScriptCanvas/Bus/EditorScriptCanvasBus.h @@ -215,21 +215,6 @@ namespace ScriptCanvasEditor using EditorLoggingComponentNotificationBus = AZ::EBus; - class IUpgradeRequests - { - public: - AZ_TYPE_INFO(IUpgradeRequests, "{D25318F2-4DDA-4E76-98CB-6D561BB6234D}"); - - using AssetList = AZStd::list; - virtual AssetList& GetAssetsToUpgrade() = 0; - - virtual void ClearGraphsThatNeedUpgrade() = 0; - virtual void GraphNeedsManualUpgrade(const AZ::Data::AssetId&) = 0; - virtual const AZStd::vector& GetGraphsThatNeedManualUpgrade() const = 0; - virtual bool IsUpgrading() = 0; - virtual void SetIsUpgrading(bool isUpgrading) = 0; - }; - class UpgradeNotifications : public AZ::EBusTraits { @@ -237,15 +222,11 @@ namespace ScriptCanvasEditor static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple; - using Bus = AZ::EBus; - virtual void OnUpgradeStart() {} - virtual void OnUpgradeComplete() {} virtual void OnUpgradeCancelled() {} virtual void OnGraphUpgradeComplete(AZ::Data::Asset&, bool skipped = false) { (void)skipped; } }; - - + using UpgradeNotificationsBus = AZ::EBus; } diff --git a/Gems/ScriptCanvas/Code/Editor/Include/ScriptCanvas/Components/GraphUpgrade.h b/Gems/ScriptCanvas/Code/Editor/Include/ScriptCanvas/Components/GraphUpgrade.h index fb6e109156..817b883695 100644 --- a/Gems/ScriptCanvas/Code/Editor/Include/ScriptCanvas/Components/GraphUpgrade.h +++ b/Gems/ScriptCanvas/Code/Editor/Include/ScriptCanvas/Components/GraphUpgrade.h @@ -133,18 +133,23 @@ namespace ScriptCanvasEditor bool GetVerbose() const; + const AZStd::string GetError() const { return m_error; } + void SetVerbose(bool isVerbose); const AZStd::string& GetDebugPrefix() const; void SetDebugPrefix(AZStd::string_view); + void MarkError(AZStd::string_view error) { m_error = error; } + AZStd::shared_ptr m_currentState = nullptr; AZStd::vector> m_states; private: bool m_isVerbose = true; AZStd::string m_debugPrefix; + AZStd::string m_error; }; //! This state machine will collect and share a variety of data from the EditorGraph diff --git a/Gems/ScriptCanvas/Code/Editor/SystemComponent.cpp b/Gems/ScriptCanvas/Code/Editor/SystemComponent.cpp index 4b60fd357c..f901915fdb 100644 --- a/Gems/ScriptCanvas/Code/Editor/SystemComponent.cpp +++ b/Gems/ScriptCanvas/Code/Editor/SystemComponent.cpp @@ -6,46 +6,35 @@ * */ - -#include -#include -#include #include +#include +#include +#include +#include #include - #include - +#include #include +#include #include - -#include - +#include +#include #include - -#include #include #include -#include - +#include +#include +#include +#include +#include #include +#include +#include #include #include #include #include -#include -#include - -#include - -#include -#include - #include -#include -#include - -#include -#include namespace ScriptCanvasEditor { @@ -54,6 +43,7 @@ namespace ScriptCanvasEditor SystemComponent::SystemComponent() { AzToolsFramework::AssetSeedManagerRequests::Bus::Handler::BusConnect(); + m_versionExplorer = AZStd::make_unique(); } SystemComponent::~SystemComponent() @@ -61,7 +51,6 @@ namespace ScriptCanvasEditor AzToolsFramework::UnregisterViewPane(LyViewPane::ScriptCanvas); AzToolsFramework::EditorContextMenuBus::Handler::BusDisconnect(); AzToolsFramework::EditorEvents::Bus::Handler::BusDisconnect(); - AzFramework::AssetCatalogEventBus::Handler::BusDisconnect(); AzToolsFramework::AssetSeedManagerRequests::Bus::Handler::BusDisconnect(); } @@ -141,17 +130,12 @@ namespace ScriptCanvasEditor { if (userSettings->m_showUpgradeDialog) { - AzFramework::AssetCatalogEventBus::Handler::BusConnect(); } else { m_upgradeDisabled = true; } } - else - { - AzFramework::AssetCatalogEventBus::Handler::BusConnect(); - } } void SystemComponent::NotifyRegisterViews() @@ -172,7 +156,6 @@ namespace ScriptCanvasEditor AzToolsFramework::EditorEvents::Bus::Handler::BusDisconnect(); ScriptCanvasExecutionBus::Handler::BusDisconnect(); SystemRequestBus::Handler::BusDisconnect(); - AzFramework::AssetCatalogEventBus::Handler::BusDisconnect(); m_jobContext.reset(); m_jobManager.reset(); @@ -397,64 +380,6 @@ namespace ScriptCanvasEditor return reporter; } - void SystemComponent::OnCatalogLoaded(const char* /*catalogFile*/) - { - // Enumerate all ScriptCanvas assets - AZ::Data::AssetCatalogRequestBus::Broadcast(&AZ::Data::AssetCatalogRequestBus::Events::EnumerateAssets, - nullptr, - [this](const AZ::Data::AssetId, const AZ::Data::AssetInfo& assetInfo) { - - if (assetInfo.m_assetType == azrtti_typeid()) - { - AddAssetToUpgrade(assetInfo); - } - }, - nullptr - ); - } - - void SystemComponent::OnCatalogAssetAdded(const AZ::Data::AssetId& assetId) - { - if (IsUpgrading()) - { - return; - } - - auto assetInfo = ScriptCanvasEditor::AssetHelpers::GetAssetInfo(assetId); - AddAssetToUpgrade(assetInfo); - } - - void SystemComponent::OnCatalogAssetRemoved(const AZ::Data::AssetId& assetId, const AZ::Data::AssetInfo& /*assetInfo*/) - { - if (IsUpgrading()) - { - return; - } - - AZStd::erase_if(m_assetsToConvert, [assetId](const AZ::Data::AssetInfo& assetToConvert) - { - return assetToConvert.m_assetId == assetId; - } - ); - } - - void SystemComponent::AddAssetToUpgrade(const AZ::Data::AssetInfo& assetInfo) - { - auto query = AZStd::find_if(m_assetsToConvert.begin(), m_assetsToConvert.end(), [assetInfo](const AZ::Data::AssetInfo& assetToConvert) - { - return assetToConvert.m_assetId == assetInfo.m_assetId; - } - ); - - if (query == m_assetsToConvert.end()) - { - if (assetInfo.m_assetType == azrtti_typeid()) - { - m_assetsToConvert.push_back(assetInfo); - } - } - } - AzToolsFramework::AssetSeedManagerRequests::AssetTypePairs SystemComponent::GetAssetTypeMapping() { return { diff --git a/Gems/ScriptCanvas/Code/Editor/SystemComponent.h b/Gems/ScriptCanvas/Code/Editor/SystemComponent.h index 3e18262c98..6f85b3c5a6 100644 --- a/Gems/ScriptCanvas/Code/Editor/SystemComponent.h +++ b/Gems/ScriptCanvas/Code/Editor/SystemComponent.h @@ -9,23 +9,20 @@ #pragma once -#include -#include -#include - #include -#include +#include +#include #include - +#include #include +#include #include #include +#include #include - -#include - -#include -#include +#include +#include +#include namespace ScriptCanvasEditor { @@ -36,9 +33,7 @@ namespace ScriptCanvasEditor , private AzToolsFramework::AssetBrowser::AssetBrowserInteractionNotificationBus::Handler , private ScriptCanvasExecutionBus::Handler , private AZ::UserSettingsNotificationBus::Handler - , private AzFramework::AssetCatalogEventBus::Handler , private AZ::Data::AssetBus::MultiHandler - , private AZ::Interface::Registrar , private AzToolsFramework::AssetSeedManagerRequests::Bus::Handler , private AzToolsFramework::EditorContextMenuBus::Handler { @@ -96,74 +91,30 @@ namespace ScriptCanvasEditor void OnUserSettingsActivated() override; //////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////// - // AzFramework::AssetCatalogEventBus::Handler... - void OnCatalogLoaded(const char* /*catalogFile*/) override; - void OnCatalogAssetAdded(const AZ::Data::AssetId& /*assetId*/) override; - void OnCatalogAssetRemoved(const AZ::Data::AssetId& /*assetId*/, const AZ::Data::AssetInfo& /*assetInfo*/) override; - //////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////// // AssetSeedManagerRequests::Bus::Handler... AzToolsFramework::AssetSeedManagerRequests::AssetTypePairs GetAssetTypeMapping() override; //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// - // IUpgradeRequests... - void ClearGraphsThatNeedUpgrade() override - { - m_assetsThatNeedManualUpgrade.clear(); - } - - IUpgradeRequests::AssetList& GetAssetsToUpgrade() override - { - return m_assetsToConvert; - } - - void GraphNeedsManualUpgrade(const AZ::Data::AssetId& assetId) override - { - if (AZStd::find(m_assetsThatNeedManualUpgrade.begin(), m_assetsThatNeedManualUpgrade.end(), assetId) == m_assetsThatNeedManualUpgrade.end()) - { - m_assetsThatNeedManualUpgrade.push_back(assetId); - } - } - - const AZStd::vector& GetGraphsThatNeedManualUpgrade() const override - { - return m_assetsThatNeedManualUpgrade; - } - - bool IsUpgrading() override - { - return m_isUpgrading; - } - - void SetIsUpgrading(bool isUpgrading) override - { - m_isUpgrading = isUpgrading; - } - - //////////////////////////////////////////////////////////////////////// - + private: SystemComponent(const SystemComponent&) = delete; void FilterForScriptCanvasEnabledEntities(AzToolsFramework::EntityIdList& sourceList, AzToolsFramework::EntityIdList& targetList); void PopulateEditorCreatableTypes(); - void AddAssetToUpgrade(const AZ::Data::AssetInfo& assetInfo); - + AZStd::unique_ptr m_jobManager; AZStd::unique_ptr m_jobContext; + AZStd::unique_ptr m_versionExplorer; AZStd::unordered_set m_creatableTypes; AssetTracker m_assetTracker; - IUpgradeRequests::AssetList m_assetsToConvert; AZStd::vector m_assetsThatNeedManualUpgrade; bool m_isUpgrading = false; bool m_upgradeDisabled = false; - }; } diff --git a/Gems/ScriptCanvas/Code/Editor/View/Widgets/NodePalette/NodePaletteModel.cpp b/Gems/ScriptCanvas/Code/Editor/View/Widgets/NodePalette/NodePaletteModel.cpp index b29992f9b1..047a07cd0b 100644 --- a/Gems/ScriptCanvas/Code/Editor/View/Widgets/NodePalette/NodePaletteModel.cpp +++ b/Gems/ScriptCanvas/Code/Editor/View/Widgets/NodePalette/NodePaletteModel.cpp @@ -829,12 +829,12 @@ namespace ScriptCanvasEditor NodePaletteModel::NodePaletteModel() : m_paletteId(AZ::Entity::MakeId()) { - UpgradeNotifications::Bus::Handler::BusConnect(); + UpgradeNotificationsBus::Handler::BusConnect(); } NodePaletteModel::~NodePaletteModel() { - UpgradeNotifications::Bus::Handler::BusDisconnect(); + UpgradeNotificationsBus::Handler::BusDisconnect(); DisconnectLambdas(); diff --git a/Gems/ScriptCanvas/Code/Editor/View/Widgets/NodePalette/NodePaletteModel.h b/Gems/ScriptCanvas/Code/Editor/View/Widgets/NodePalette/NodePaletteModel.h index b27bfd97f1..28d627af01 100644 --- a/Gems/ScriptCanvas/Code/Editor/View/Widgets/NodePalette/NodePaletteModel.h +++ b/Gems/ScriptCanvas/Code/Editor/View/Widgets/NodePalette/NodePaletteModel.h @@ -61,7 +61,7 @@ namespace ScriptCanvasEditor class NodePaletteModel : public GraphCanvas::CategorizerInterface - , UpgradeNotifications::Bus::Handler + , UpgradeNotificationsBus::Handler { public: typedef AZStd::unordered_map< ScriptCanvas::NodeTypeIdentifier, NodePaletteModelInformation* > NodePaletteRegistry; @@ -104,10 +104,6 @@ namespace ScriptCanvasEditor { DisconnectLambdas(); } - void OnUpgradeComplete() override - { - ConnectLambdas(); - } // Asset Node Support void OnRowsInserted(const QModelIndex& parentIndex, int first, int last); diff --git a/Gems/ScriptCanvas/Code/Editor/View/Widgets/ScriptCanvasNodePaletteDockWidget.cpp b/Gems/ScriptCanvas/Code/Editor/View/Widgets/ScriptCanvasNodePaletteDockWidget.cpp index 96b0ac353f..9e51c1896f 100644 --- a/Gems/ScriptCanvas/Code/Editor/View/Widgets/ScriptCanvasNodePaletteDockWidget.cpp +++ b/Gems/ScriptCanvas/Code/Editor/View/Widgets/ScriptCanvasNodePaletteDockWidget.cpp @@ -148,7 +148,7 @@ namespace ScriptCanvasEditor , m_assetModel(assetModel) , m_categorizer(nodePaletteModel) { - UpgradeNotifications::Bus::Handler::BusConnect(); + UpgradeNotificationsBus::Handler::BusConnect(); if (m_assetModel) { @@ -166,7 +166,7 @@ namespace ScriptCanvasEditor AzFramework::AssetCatalogEventBus::Handler::BusDisconnect(); AZ::Data::AssetBus::MultiHandler::BusDisconnect(); - UpgradeNotifications::Bus::Handler::BusDisconnect(); + UpgradeNotificationsBus::Handler::BusDisconnect(); } @@ -386,15 +386,6 @@ namespace ScriptCanvasEditor AzFramework::AssetCatalogEventBus::Handler::BusDisconnect(); } - void ScriptCanvasRootPaletteTreeItem::OnUpgradeComplete() - { - ConnectLambdas(); - - AzFramework::AssetCatalogEventBus::Handler::BusConnect(); - - TraverseTree(); - } - void ScriptCanvasRootPaletteTreeItem::OnUpgradeCancelled() { if (!AzFramework::AssetCatalogEventBus::Handler::BusIsConnected()) diff --git a/Gems/ScriptCanvas/Code/Editor/View/Widgets/ScriptCanvasNodePaletteDockWidget.h b/Gems/ScriptCanvas/Code/Editor/View/Widgets/ScriptCanvasNodePaletteDockWidget.h index 7b2e9293bd..45645ceb33 100644 --- a/Gems/ScriptCanvas/Code/Editor/View/Widgets/ScriptCanvasNodePaletteDockWidget.h +++ b/Gems/ScriptCanvas/Code/Editor/View/Widgets/ScriptCanvasNodePaletteDockWidget.h @@ -57,7 +57,7 @@ namespace ScriptCanvasEditor : public GraphCanvas::NodePaletteTreeItem , AzFramework::AssetCatalogEventBus::Handler , AZ::Data::AssetBus::MultiHandler - , UpgradeNotifications::Bus::Handler + , UpgradeNotificationsBus::Handler , AZ::SystemTickBus::Handler { public: @@ -92,7 +92,6 @@ namespace ScriptCanvasEditor // UpgradeNotifications::Bus void OnUpgradeStart() override; - void OnUpgradeComplete() override; void OnUpgradeCancelled() override; //// diff --git a/Gems/ScriptCanvas/Code/Editor/View/Widgets/VariablePanel/GraphVariablesTableView.cpp b/Gems/ScriptCanvas/Code/Editor/View/Widgets/VariablePanel/GraphVariablesTableView.cpp index 91f85ae36a..5b78c62bac 100644 --- a/Gems/ScriptCanvas/Code/Editor/View/Widgets/VariablePanel/GraphVariablesTableView.cpp +++ b/Gems/ScriptCanvas/Code/Editor/View/Widgets/VariablePanel/GraphVariablesTableView.cpp @@ -411,10 +411,7 @@ namespace ScriptCanvasEditor case Qt::TextAlignmentRole: { - if (index.column() == ColumnIndex::Type) - { - return QVariant(Qt::AlignLeft | Qt::AlignVCenter); - } + return QVariant(Qt::AlignLeft | Qt::AlignVCenter); } break; @@ -880,6 +877,11 @@ namespace ScriptCanvasEditor return tr(m_columnNames[section]); } + if (role == Qt::TextAlignmentRole) + { + return QVariant(Qt::AlignLeft | Qt::AlignVCenter); + } + return QAbstractItemModel::headerData(section, orientation, role); } diff --git a/Gems/ScriptCanvas/Code/Editor/View/Windows/MainWindow.cpp b/Gems/ScriptCanvas/Code/Editor/View/Windows/MainWindow.cpp index 289643d59c..1ad8b58317 100644 --- a/Gems/ScriptCanvas/Code/Editor/View/Windows/MainWindow.cpp +++ b/Gems/ScriptCanvas/Code/Editor/View/Windows/MainWindow.cpp @@ -135,7 +135,6 @@ #include #include #include -#include #include @@ -705,80 +704,7 @@ namespace ScriptCanvasEditor m_autoSaveTimer.setSingleShot(true); connect(&m_autoSaveTimer, &QTimer::timeout, this, &MainWindow::OnAutoSave); - UpdateMenuState(false); - - PromptForUpgrade(); - } - - void MainWindow::PromptForUpgrade() - { - static bool displayUpgradePrompt = false; // Set to true if you need the upgrade dialog to show up on ScriptCanvas editor open - if (displayUpgradePrompt && m_showUpgradeTool) - { - QTimer::singleShot(1.f, this, [this]() - { - UpgradeTool* upgradeTool = aznew UpgradeTool(this); - - QPoint centerPoint = frameGeometry().center(); - - upgradeTool->adjustSize(); - upgradeTool->move(centerPoint.x() - upgradeTool->width() / 2, centerPoint.y() - upgradeTool->height() / 2); - - if (upgradeTool->exec() == QDialog::Accepted) - { - // Manual correction - size_t assetsThatNeedManualInspection = AZ::Interface::Get()->GetGraphsThatNeedManualUpgrade().size(); - QString message = QObject::tr("%1 Graph(s) upgraded
%2 graph(s) did not require upgrade").arg(upgradeTool->UpgradedGraphCount()).arg(upgradeTool->SkippedGraphCount()); - if (assetsThatNeedManualInspection > 0) - { - message.append(QObject::tr("
%1 graph(s) that need manual corrections. You will be prompted to review them after you close this dialog.
").arg(assetsThatNeedManualInspection)); - } - - auto settingsRegistry = AZ::SettingsRegistry::Get(); - - AZ::IO::Path outputFileName; - settingsRegistry->Get(outputFileName.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectLogPath); - outputFileName /= "ScriptCanvasUpgradeReport.html"; - // Report - AZStd::string urlToReport = AZStd::string::format("For more information see the Upgrade Report.", outputFileName.c_str()); - message.append(QObject::tr("
%1").arg(urlToReport.c_str())); - - // Backup - if (upgradeTool->HasBackup()) - { - AZ::IO::Path outputFileName2; - settingsRegistry->Get(outputFileName2.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectUserPath); - outputFileName2 /= "ScriptCanvas_BACKUP"; - - AZStd::string backupPath = AZStd::string::format("
Open the Backup Folder.", outputFileName.c_str()); - message.append(QObject::tr("%1").arg(backupPath.c_str())); - } - - // Done upgrading, show details - QMessageBox mb(QMessageBox::Information, - QObject::tr("Upgrade Complete"), - message, - QMessageBox::Ok, this); - mb.setTextFormat(Qt::RichText); - - centerPoint = frameGeometry().center(); - - mb.adjustSize(); - mb.move(centerPoint.x() - width() / 2, centerPoint.y() - height() / 2); - - mb.exec(); - - // If there are graphs that need manual correction, show the helper - if (assetsThatNeedManualInspection > 0) - { - UpgradeHelper* upgradeHelper = new UpgradeHelper(this); - upgradeHelper->show(); - } - } - - }); - } } MainWindow::~MainWindow() @@ -3510,18 +3436,20 @@ namespace ScriptCanvasEditor void MainWindow::RunUpgradeTool() { - VersionExplorer* versionExplorer = aznew VersionExplorer(this); + using namespace VersionExplorer; + auto versionExplorer = aznew VersionExplorer::Controller(this); versionExplorer->exec(); - // Manual correction - size_t assetsThatNeedManualInspection = AZ::Interface::Get()->GetGraphsThatNeedManualUpgrade().size(); - - // If there are graphs that need manual correction, show the helper - if (assetsThatNeedManualInspection > 0) + const ModificationResults* result = nullptr; + ModelRequestsBus::BroadcastResult(result, &ModelRequestsTraits::GetResults); + if (result && !result->m_failures.empty()) { + // If there are graphs that need manual correction, show the helper UpgradeHelper* upgradeHelper = new UpgradeHelper(this); upgradeHelper->show(); } + + delete versionExplorer; } void MainWindow::OnShowValidationErrors() diff --git a/Gems/ScriptCanvas/Code/Editor/View/Windows/MainWindow.h b/Gems/ScriptCanvas/Code/Editor/View/Windows/MainWindow.h index 30e84f3fa8..2c84412e28 100644 --- a/Gems/ScriptCanvas/Code/Editor/View/Windows/MainWindow.h +++ b/Gems/ScriptCanvas/Code/Editor/View/Windows/MainWindow.h @@ -51,8 +51,7 @@ #include -#include -#include +#include #if SCRIPTCANVAS_EDITOR #include @@ -99,30 +98,25 @@ namespace ScriptCanvasEditor class ScriptCanvasAssetBrowserModel : public AzToolsFramework::AssetBrowser::AssetBrowserFilterModel - , private UpgradeNotifications::Bus::Handler + , private UpgradeNotificationsBus::Handler { public: explicit ScriptCanvasAssetBrowserModel(QObject* parent = nullptr) : AzToolsFramework::AssetBrowser::AssetBrowserFilterModel(parent) { - UpgradeNotifications::Bus::Handler::BusConnect(); + UpgradeNotificationsBus::Handler::BusConnect(); } ~ScriptCanvasAssetBrowserModel() override { - UpgradeNotifications::Bus::Handler::BusDisconnect(); + UpgradeNotificationsBus::Handler::BusDisconnect(); } void OnUpgradeStart() override { AzToolsFramework::AssetBrowser::AssetBrowserComponentNotificationBus::Handler::BusDisconnect(); } - - void OnUpgradeComplete() override - { - AzToolsFramework::AssetBrowser::AssetBrowserComponentNotificationBus::Handler::BusConnect(); - } }; class OnSaveToast @@ -806,7 +800,5 @@ namespace ScriptCanvasEditor Workspace* m_workspace; void OnSaveCallback(bool saveSuccess, AZ::Data::AssetPtr, AZ::Data::AssetId previousFileAssetId); - - void PromptForUpgrade(); }; } diff --git a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Controller.cpp b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Controller.cpp new file mode 100644 index 0000000000..dbcd176ee5 --- /dev/null +++ b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Controller.cpp @@ -0,0 +1,629 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace ScriptCanvasEditor +{ + namespace VersionExplorer + { + Controller::Controller(QWidget* parent) + : AzQtComponents::StyledDialog(parent) + , m_view(new Ui::View()) + { + m_view->setupUi(this); + m_view->tableWidget->horizontalHeader()->setVisible(false); + m_view->tableWidget->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); + m_view->tableWidget->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Fixed); + m_view->tableWidget->setColumnWidth(3, 22); + m_view->textEdit->setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded); + m_view->textEdit->setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOn); + connect(m_view->scanButton, &QPushButton::pressed, this, &Controller::OnButtonPressScan); + connect(m_view->closeButton, &QPushButton::pressed, this, &Controller::OnButtonPressClose); + m_view->upgradeAllButton->setVisible(false); + connect(m_view->upgradeAllButton, &QPushButton::pressed, this, &Controller::OnButtonPressUpgrade); + m_view->progressBar->setValue(0); + m_view->progressBar->setVisible(false); + + UpgradeNotificationsBus::Handler::BusConnect(); + ModelNotificationsBus::Handler::BusConnect(); + } + + Controller::~Controller() + { + delete m_view; + } + + void Controller::AddLogEntries() + { + const AZStd::vector* logs = nullptr; + LogBus::BroadcastResult(logs, &LogTraits::GetEntries); + if (!logs || logs->empty()) + { + return; + } + + const QTextCursor oldCursor = m_view->textEdit->textCursor(); + QScrollBar* scrollBar = m_view->textEdit->verticalScrollBar(); + + m_view->textEdit->moveCursor(QTextCursor::End); + QTextCursor textCursor = m_view->textEdit->textCursor(); + + for (auto& entry : *logs) + { + textCursor.insertText("\n"); + textCursor.insertText(entry.c_str()); + } + + scrollBar->setValue(scrollBar->maximum()); + m_view->textEdit->moveCursor(QTextCursor::StartOfLine); + LogBus::Broadcast(&LogTraits::Clear); + } + + void Controller::EnableAllUpgradeButtons() + { + for (int row = 0; row < m_view->tableWidget->rowCount(); ++row) + { + if (QPushButton* button = qobject_cast(m_view->tableWidget->cellWidget(row, ColumnAction))) + { + button->setEnabled(true); + } + } + } + + QList Controller::FindTableItems(const AZ::Data::AssetInfo& info) + { + return m_view->tableWidget->findItems(info.m_relativePath.c_str(), Qt::MatchFlag::MatchExactly); + } + + void Controller::OnButtonPressClose() + { + reject(); + } + + void Controller::OnButtonPressScan() + { + // \todo move to another file + auto isUpToDate = [this](AZ::Data::Asset asset) + { + AZ::Entity* scriptCanvasEntity = nullptr; + + if (asset.GetType() == azrtti_typeid()) + { + ScriptCanvasAsset* scriptCanvasAsset = asset.GetAs(); + if (!scriptCanvasAsset) + { + AZ_Warning + (ScriptCanvas::k_VersionExplorerWindow.data() + , false + , "InspectAsset: %s, AsestData failed to return ScriptCanvasAsset" + , asset.GetHint().c_str()); + return true; + } + + scriptCanvasEntity = scriptCanvasAsset->GetScriptCanvasEntity(); + AZ_Assert(scriptCanvasEntity, "The Script Canvas asset must have a valid entity"); + } + + auto graphComponent = scriptCanvasEntity->FindComponent(); + AZ_Assert(graphComponent, "The Script Canvas entity must have a Graph component"); + return !m_view->forceUpgrade->isChecked() && graphComponent->GetVersion().IsLatest(); + }; + + ScanConfiguration config; + config.reportFilteredGraphs = !m_view->onlyShowOutdated->isChecked(); + config.filter = isUpToDate; + + SetLoggingPreferences(); + ModelRequestsBus::Broadcast(&ModelRequestsTraits::Scan, config); + } + + void Controller::OnButtonPressUpgrade() + { + OnButtonPressUpgradeImplementation({}); + } + + void Controller::OnButtonPressUpgradeImplementation(const AZ::Data::AssetInfo& assetInfo) + { + auto simpleUpdate = [this](AZ::Data::Asset asset) + { + if (asset.GetType() == azrtti_typeid()) + { + ScriptCanvasAsset* scriptCanvasAsset = asset.GetAs(); + AZ_Assert(scriptCanvasAsset, "Unable to get the asset of ScriptCanvasAsset, but received type: %s" + , azrtti_typeid().template ToString().c_str()); + if (!scriptCanvasAsset) + { + return; + } + + AZ::Entity* scriptCanvasEntity = scriptCanvasAsset->GetScriptCanvasEntity(); + AZ_Assert(scriptCanvasEntity, "View::UpgradeGraph The Script Canvas asset must have a valid entity"); + if (!scriptCanvasEntity) + { + return; + } + + AZ::Entity* queryEntity = nullptr; + AZ::ComponentApplicationBus::BroadcastResult(queryEntity, &AZ::ComponentApplicationRequests::FindEntity, scriptCanvasEntity->GetId()); + if (queryEntity) + { + if (queryEntity->GetState() == AZ::Entity::State::Active) + { + queryEntity->Deactivate(); + } + + scriptCanvasEntity = queryEntity; + } + + if (scriptCanvasEntity->GetState() == AZ::Entity::State::Constructed) + { + scriptCanvasEntity->Init(); + } + + if (scriptCanvasEntity->GetState() == AZ::Entity::State::Init) + { + scriptCanvasEntity->Activate(); + } + + AZ_Assert(scriptCanvasEntity->GetState() == AZ::Entity::State::Active, "Graph entity is not active"); + auto graphComponent = scriptCanvasEntity->FindComponent(); + AZ_Assert(graphComponent, "The Script Canvas entity must have a Graph component"); + if (graphComponent) + { + graphComponent->UpgradeGraph + (asset + , m_view->forceUpgrade->isChecked() ? Graph::UpgradeRequest::Forced : Graph::UpgradeRequest::IfOutOfDate + , m_view->verbose->isChecked()); + } + } + }; + + auto onReadyOnlyFile = [this]()->bool + { + int result = QMessageBox::No; + QMessageBox mb + (QMessageBox::Warning + , QObject::tr("Failed to Save Upgraded File") + , QObject::tr("The upgraded file could not be saved because the file is read only.\n" + "Do you want to make it writeable and overwrite it?") + , QMessageBox::YesToAll | QMessageBox::Yes | QMessageBox::No + , this); + result = mb.exec(); + return result == QMessageBox::YesToAll; + }; + + SetLoggingPreferences(); + ModifyConfiguration config; + config.modifySingleAsset = assetInfo; + config.modification = simpleUpdate; + config.onReadOnlyFile = onReadyOnlyFile; + config.backupGraphBeforeModification = m_view->makeBackupCheckbox->isChecked(); + ModelRequestsBus::Broadcast(&ModelRequestsTraits::Modify, config); + } + + void Controller::OnButtonPressUpgradeSingle(const AZ::Data::AssetInfo& assetInfo) + { + OnButtonPressUpgradeImplementation(assetInfo); + } + + void Controller::OnUpgradeModificationBegin([[maybe_unused]] const ModifyConfiguration& config, const AZ::Data::AssetInfo& info) + { + for (auto* item : FindTableItems(info)) + { + int row = item->row(); + SetRowBusy(row); + m_view->tableWidget->setCellWidget(row, ColumnAction, nullptr); + } + } + + void Controller::OnUpgradeModificationEnd + ( [[maybe_unused]] const ModifyConfiguration& config + , const AZ::Data::AssetInfo& info + , ModificationResult result) + { + if (result.errorMessage.empty()) + { + VE_LOG("Successfully modified %s", result.assetInfo.m_relativePath.c_str()); + } + else + { + VE_LOG("Failed to modify %s: %s", result.assetInfo.m_relativePath.c_str(), result.errorMessage.data()); + } + + for (auto* item : FindTableItems(info)) + { + int row = item->row(); + + if (result.errorMessage.empty()) + { + SetRowSucceeded(row); + } + else + { + SetRowFailed(row, ""); + + if (QPushButton* button = qobject_cast(m_view->tableWidget->cellWidget(row, ColumnAction))) + { + button->setEnabled(false); + } + } + } + + m_view->progressBar->setVisible(true); + ++m_handledAssetCount; + m_view->progressBar->setValue(m_handledAssetCount); + AddLogEntries(); + } + + void Controller::OnGraphUpgradeComplete(AZ::Data::Asset& asset, bool skipped) + { + ModificationResult result; + result.asset = asset; + AZ::Data::AssetCatalogRequestBus::BroadcastResult + ( result.assetInfo, &AZ::Data::AssetCatalogRequests::GetAssetInfoById, asset.GetId()); + + if (skipped) + { + result.errorMessage = "Failed in editor upgrade state machine - check logs"; + } + + ModificationNotificationsBus::Broadcast(&ModificationNotificationsTraits::ModificationComplete, result); + } + + void Controller::OnScanBegin(size_t assetCount) + { + m_handledAssetCount = 0; + m_view->tableWidget->setRowCount(0); + m_view->progressBar->setVisible(true); + m_view->progressBar->setRange(0, aznumeric_cast(assetCount)); + m_view->progressBar->setValue(0); + m_view->scanButton->setEnabled(false); + m_view->upgradeAllButton->setEnabled(false); + m_view->onlyShowOutdated->setEnabled(false); + + QString spinnerText = QStringLiteral("Scan in progress - gathering graphs that can be updated"); + m_view->spinner->SetText(spinnerText); + SetSpinnerIsBusy(true); + } + + void Controller::OnScanComplete(const ScanResult& result) + { + m_view->onlyShowOutdated->setEnabled(true); + + QString spinnerText = QStringLiteral("Scan Complete"); + spinnerText.append(QString::asprintf(" - Discovered: %zu, Failed: %zu, Upgradeable: %zu, Up-to-date: %zu" + , result.m_catalogAssets.size() + , result.m_loadErrors.size() + , result.m_unfiltered.size() + , result.m_filteredAssets.size())); + + m_view->spinner->SetText(spinnerText); + SetSpinnerIsBusy(false); + m_view->progressBar->setVisible(false); + EnableAllUpgradeButtons(); + + if (!result.m_unfiltered.empty()) + { + m_view->upgradeAllButton->setEnabled(true); + } + } + + void Controller::OnScanFilteredGraph(const AZ::Data::AssetInfo& info) + { + OnScannedGraph(info, Filtered::Yes); + } + + void Controller::OnScannedGraph(const AZ::Data::AssetInfo& assetInfo, [[maybe_unused]] Filtered filtered) + { + const int rowIndex = m_view->tableWidget->rowCount(); + + if (filtered == Filtered::No || !m_view->onlyShowOutdated->isChecked()) + { + m_view->tableWidget->insertRow(rowIndex); + QTableWidgetItem* rowName = new QTableWidgetItem(tr(assetInfo.m_relativePath.c_str())); + m_view->tableWidget->setItem(rowIndex, static_cast(ColumnAsset), rowName); + SetRowSucceeded(rowIndex); + + if (filtered == Filtered::No) + { + QPushButton* upgradeButton = new QPushButton(this); + upgradeButton->setText("Upgrade"); + upgradeButton->setEnabled(false); + SetRowBusy(rowIndex); + + connect + ( upgradeButton + , &QPushButton::pressed + , this + , [this, assetInfo]() + { + this->OnButtonPressUpgradeSingle(assetInfo); + }); + + m_view->tableWidget->setCellWidget(rowIndex, static_cast(ColumnAction), upgradeButton); + } + + char resolvedBuffer[AZ_MAX_PATH_LEN] = { 0 }; + AZStd::string path = AZStd::string::format("@devroot@/%s", assetInfo.m_relativePath.c_str()); + AZ::IO::FileIOBase::GetInstance()->ResolvePath(path.c_str(), resolvedBuffer, AZ_MAX_PATH_LEN); + AZ::StringFunc::Path::GetFullPath(resolvedBuffer, path); + AZ::StringFunc::Path::Normalize(path); + + bool result = false; + AZ::Data::AssetInfo info; + AZStd::string watchFolder; + QByteArray assetNameUtf8 = assetInfo.m_relativePath.c_str(); + AzToolsFramework::AssetSystemRequestBus::BroadcastResult + ( result + , &AzToolsFramework::AssetSystemRequestBus::Events::GetSourceInfoBySourcePath + , assetNameUtf8 + , info + , watchFolder); + AZ_Error + ( ScriptCanvas::k_VersionExplorerWindow.data() + , result + , "Failed to locate asset info for '%s'.", assetNameUtf8.constData()); + + QToolButton* browseButton = new QToolButton(this); + browseButton->setToolTip(AzQtComponents::fileBrowserActionName()); + browseButton->setIcon(QIcon(":/stylesheet/img/UI20/browse-edit.svg")); + + QString absolutePath = QDir(watchFolder.c_str()).absoluteFilePath(info.m_relativePath.c_str()); + connect(browseButton, &QPushButton::clicked, [absolutePath] { + AzQtComponents::ShowFileOnDesktop(absolutePath); + }); + + m_view->tableWidget->setCellWidget(rowIndex, static_cast(ColumnBrowse), browseButton); + } + + OnScannedGraphResult(assetInfo); + } + + void Controller::OnScannedGraphResult([[maybe_unused]] const AZ::Data::AssetInfo& info) + { + m_view->progressBar->setValue(aznumeric_cast(m_handledAssetCount)); + ++m_handledAssetCount; + AddLogEntries(); + } + + void Controller::OnScanLoadFailure(const AZ::Data::AssetInfo& info) + { + const int rowIndex = m_view->tableWidget->rowCount(); + m_view->tableWidget->insertRow(rowIndex); + QTableWidgetItem* rowName = new QTableWidgetItem + ( tr(AZStd::string::format("Load Error: %s", info.m_relativePath.c_str()).c_str())); + m_view->tableWidget->setItem(rowIndex, static_cast(ColumnAsset), rowName); + SetRowFailed(rowIndex, "Load failed"); + OnScannedGraphResult(info); + } + + void Controller::OnScanUnFilteredGraph(const AZ::Data::AssetInfo& info) + { + OnScannedGraph(info, Filtered::No); + } + + void Controller::OnUpgradeBegin + ( const ModifyConfiguration& config + , [[maybe_unused]] const WorkingAssets& assets) + { + QString spinnerText = QStringLiteral("Upgrade in progress - "); + if (config.modifySingleAsset.m_assetId.IsValid()) + { + spinnerText.append(" single graph"); + + if (assets.size() == 1) + { + for (auto* item : FindTableItems(assets.front().info)) + { + int row = item->row(); + SetRowBusy(row); + } + } + } + else + { + for (int row = 0; row < m_view->tableWidget->rowCount(); ++row) + { + if (QPushButton* button = qobject_cast(m_view->tableWidget->cellWidget(row, ColumnAction))) + { + button->setEnabled(false); + } + + SetRowBusy(row); + } + + spinnerText.append(" all scanned graphs"); + } + + m_view->spinner->SetText(spinnerText); + SetSpinnerIsBusy(true); + } + + void Controller::SetLoggingPreferences() + { + LogBus::Broadcast(&LogTraits::SetVerbose, m_view->verbose->isChecked()); + LogBus::Broadcast(&LogTraits::SetVersionExporerExclusivity, m_view->updateReportingOnly->isChecked()); + } + + void Controller::SetSpinnerIsBusy(bool isBusy) + { + m_view->spinner->SetIsBusy(isBusy); + m_view->spinner->SetBusyIconSize(16); + } + + void Controller::OnUpgradeComplete(const ModificationResults& result) + { + QString spinnerText = QStringLiteral("Upgrade Complete - "); + spinnerText.append(QString::asprintf(" - Upgraded: %zu, Failed: %zu" + , result.m_successes.size() + , result.m_failures.size())); + m_view->spinner->SetText(spinnerText); + SetSpinnerIsBusy(false); + AddLogEntries(); + EnableAllUpgradeButtons(); + m_view->scanButton->setEnabled(true); + } + + void Controller::OnUpgradeDependenciesGathered(const AZ::Data::AssetInfo& info, Result result) + { + for (auto* item : FindTableItems(info)) + { + int row = item->row(); + + if (result == Result::Success) + { + SetRowSucceeded(row); + } + else + { + SetRowFailed(row, ""); + } + + if (QPushButton* button = qobject_cast(m_view->tableWidget->cellWidget(row, ColumnAction))) + { + button->setEnabled(true); + } + } + + m_view->progressBar->setVisible(true); + ++m_handledAssetCount; + m_view->progressBar->setValue(m_handledAssetCount); + AddLogEntries(); + } + + void Controller::OnUpgradeDependencySortBegin + ( [[maybe_unused]] const ModifyConfiguration& config + , const WorkingAssets& assets) + { + m_handledAssetCount = 0; + m_view->progressBar->setVisible(true); + m_view->progressBar->setRange(0, aznumeric_caster(assets.size())); + m_view->progressBar->setValue(0); + m_view->scanButton->setEnabled(false); + m_view->upgradeAllButton->setEnabled(false); + m_view->onlyShowOutdated->setEnabled(false); + + for (int row = 0; row != m_view->tableWidget->rowCount(); ++row) + { + if (QPushButton* button = qobject_cast(m_view->tableWidget->cellWidget(row, ColumnAction))) + { + button->setEnabled(false); + SetRowBusy(row); + } + } + + QString spinnerText = QStringLiteral("Upgrade in progress - gathering dependencies for the scanned graphs"); + m_view->spinner->SetText(spinnerText); + SetSpinnerIsBusy(true); + } + + void Controller::OnUpgradeDependencySortEnd + ( [[maybe_unused]] const ModifyConfiguration& config + , const WorkingAssets& assets + , [[maybe_unused]] const AZStd::vector& sortedOrder) + { + m_handledAssetCount = 0; + m_view->progressBar->setRange(0, aznumeric_caster(assets.size())); + m_view->progressBar->setValue(0); + m_view->progressBar->setVisible(true); + + for (int row = 0; row != m_view->tableWidget->rowCount(); ++row) + { + if (QPushButton* button = qobject_cast(m_view->tableWidget->cellWidget(row, ColumnAction))) + { + button->setEnabled(false); + SetRowPending(row); + } + } + + QString spinnerText = QStringLiteral("Upgrade in progress - gathering dependencies is complete"); + m_view->spinner->SetText(spinnerText); + SetSpinnerIsBusy(false); + AddLogEntries(); + } + + void Controller::SetRowBusy(int index) + { + if (index >= m_view->tableWidget->rowCount()) + { + return; + } + + AzQtComponents::StyledBusyLabel* busy = new AzQtComponents::StyledBusyLabel(this); + busy->SetBusyIconSize(16); + m_view->tableWidget->setCellWidget(index, ColumnStatus, busy); + } + + void Controller::SetRowFailed(int index, AZStd::string_view message) + { + if (index >= m_view->tableWidget->rowCount()) + { + return; + } + + QToolButton* doneButton = new QToolButton(this); + doneButton->setIcon(QIcon(":/stylesheet/img/UI20/titlebar-close.svg")); + doneButton->setToolTip(message.data()); + m_view->tableWidget->setCellWidget(index, ColumnStatus, doneButton); + } + + void Controller::SetRowPending(int index) + { + m_view->tableWidget->removeCellWidget(index, ColumnStatus); + } + + void Controller::SetRowsBusy() + { + for (int i = 0; i != m_view->tableWidget->rowCount(); ++i) + { + SetRowBusy(i); + } + } + + void Controller::SetRowSucceeded(int index) + { + if (index >= m_view->tableWidget->rowCount()) + { + return; + } + + QToolButton* doneButton = new QToolButton(this); + doneButton->setIcon(QIcon(":/stylesheet/img/UI20/checkmark-menu.svg")); + m_view->tableWidget->setCellWidget(index, ColumnStatus, doneButton); + } + } +} diff --git a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Controller.h b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Controller.h new file mode 100644 index 0000000000..9842ea3da4 --- /dev/null +++ b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Controller.h @@ -0,0 +1,108 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#if !defined(Q_MOC_RUN) +#include +#include +#include +#include +#include +#include +#include +#include +AZ_PUSH_DISABLE_WARNING(4244 4251 4800, "-Wunknown-warning-option") +#include +AZ_POP_DISABLE_WARNING +#endif + +class QPushButton; +class QTableWidgetItem; + +namespace Ui +{ + class View; +} + +namespace AzQtComponents +{ + class StyledBusyLabel; +} + +namespace ScriptCanvasEditor +{ + namespace VersionExplorer + { + //! A tool that collects and upgrades all Script Canvas graphs in the asset catalog + //! Handles display change notifications, handles state change notifications, sends control requests + class Controller + : public AzQtComponents::StyledDialog + , private UpgradeNotificationsBus::Handler + , private ModelNotificationsBus::Handler + { + Q_OBJECT + + public: + AZ_CLASS_ALLOCATOR(Controller, AZ::SystemAllocator, 0); + + explicit Controller(QWidget* parent = nullptr); + ~Controller(); + + private: + static constexpr int ColumnAsset = 0; + static constexpr int ColumnAction = 1; + static constexpr int ColumnBrowse = 2; + static constexpr int ColumnStatus = 3; + + Ui::View* m_view = nullptr; + int m_handledAssetCount = 0; + + void AddLogEntries(); + void EnableAllUpgradeButtons(); + QList FindTableItems(const AZ::Data::AssetInfo& assetInfo); + + void OnButtonPressClose(); + void OnButtonPressScan(); + void OnButtonPressUpgrade(); + void OnButtonPressUpgradeImplementation(const AZ::Data::AssetInfo& assetInfo); + void OnButtonPressUpgradeSingle(const AZ::Data::AssetInfo& assetInfo); + + void OnGraphUpgradeComplete(AZ::Data::Asset&, bool skipped) override; + + void OnScanBegin(size_t assetCount) override; + void OnScanComplete(const ScanResult& result) override; + void OnScanFilteredGraph(const AZ::Data::AssetInfo& info) override; + void OnScanLoadFailure(const AZ::Data::AssetInfo& info) override; + void OnScanUnFilteredGraph(const AZ::Data::AssetInfo& info) override; + enum class Filtered { No, Yes }; + void OnScannedGraph(const AZ::Data::AssetInfo& info, Filtered filtered); + void OnScannedGraphResult(const AZ::Data::AssetInfo& info); + + // for single operation UI updates, just check the assets size, or note it on the request + void OnUpgradeBegin(const ModifyConfiguration& config, const WorkingAssets& assets) override; + void OnUpgradeComplete(const ModificationResults& results) override; + void OnUpgradeDependenciesGathered(const AZ::Data::AssetInfo& info, Result result) override; + void OnUpgradeDependencySortBegin(const ModifyConfiguration& config, const WorkingAssets& assets) override; + void OnUpgradeDependencySortEnd + ( const ModifyConfiguration& config + , const WorkingAssets& assets + , const AZStd::vector& sortedOrder) override; + void OnUpgradeModificationBegin(const ModifyConfiguration& config, const AZ::Data::AssetInfo& info) override; + void OnUpgradeModificationEnd(const ModifyConfiguration& config, const AZ::Data::AssetInfo& info, ModificationResult result) override; + + void SetLoggingPreferences(); + void SetSpinnerIsBusy(bool isBusy); + void SetRowBusy(int index); + void SetRowFailed(int index, AZStd::string_view message); + void SetRowPending(int index); + void SetRowsBusy(); + void SetRowSucceeded(int index); + }; + } +} diff --git a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/FileSaver.cpp b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/FileSaver.cpp new file mode 100644 index 0000000000..f067feeca4 --- /dev/null +++ b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/FileSaver.cpp @@ -0,0 +1,233 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace FileSaverCpp +{ + class FileEventHandler + : public AZ::IO::FileIOEventBus::Handler + { + public: + int m_errorCode = 0; + AZStd::string m_fileName; + + FileEventHandler() + { + BusConnect(); + } + + ~FileEventHandler() + { + BusDisconnect(); + } + + void OnError(const AZ::IO::SystemFile* /*file*/, const char* fileName, int errorCode) override + { + m_errorCode = errorCode; + + if (fileName) + { + m_fileName = fileName; + } + } + }; +} + +namespace ScriptCanvasEditor +{ + namespace VersionExplorer + { + FileSaver::FileSaver + ( AZStd::function onReadOnlyFile + , AZStd::function onComplete) + : m_onReadOnlyFile(onReadOnlyFile) + , m_onComplete(onComplete) + {} + + void FileSaver::PerformMove + ( AZStd::string tmpFileName + , AZStd::string target + , size_t remainingAttempts) + { + FileSaverCpp::FileEventHandler fileEventHandler; + + if (remainingAttempts == 0) + { + AZ::SystemTickBus::QueueFunction([this, tmpFileName]() + { + FileSaveResult result; + result.fileSaveError = "Failed to move updated file from temporary location to tmpFileName destination"; + result.tempFileRemovalError = RemoveTempFile(tmpFileName); + m_onComplete(result); + }); + } + else if (remainingAttempts == 2) + { + auto streamer = AZ::Interface::Get(); + // before the last attempt, flush all the caches + AZ::IO::FileRequestPtr flushRequest = streamer->FlushCaches(); + streamer->SetRequestCompleteCallback(flushRequest + , [this, remainingAttempts, tmpFileName, target]([[maybe_unused]] AZ::IO::FileRequestHandle request) + { + // One last try + AZ::SystemTickBus::QueueFunction( + [this, remainingAttempts, tmpFileName, target]() { PerformMove(tmpFileName, target, remainingAttempts - 1); }); + }); + streamer->QueueRequest(flushRequest); + } + else + { + // the actual move attempt + auto moveResult = AZ::IO::SmartMove(tmpFileName.c_str(), target.c_str()); + if (moveResult.GetResultCode() == AZ::IO::ResultCode::Success) + { + auto streamer = AZ::Interface::Get(); + AZ::IO::FileRequestPtr flushRequest = streamer->FlushCache(target.c_str()); + // Bump the slice asset up in the asset processor's queue. + AzFramework::AssetSystemRequestBus::Broadcast(&AzFramework::AssetSystem::AssetSystemRequests::EscalateAssetBySearchTerm, target.c_str()); + AZ::SystemTickBus::QueueFunction([this, tmpFileName]() + { + FileSaveResult result; + result.tempFileRemovalError = RemoveTempFile(tmpFileName); + m_onComplete(result); + }); + } + else + { + AZ_Warning(ScriptCanvas::k_VersionExplorerWindow.data(), false, "moving converted file to tmpFileName destination failed: %s, trying again", target.c_str()); + auto streamer = AZ::Interface::Get(); + AZ::IO::FileRequestPtr flushRequest = streamer->FlushCache(target.c_str()); + streamer->SetRequestCompleteCallback(flushRequest, [this, tmpFileName, target, remainingAttempts]([[maybe_unused]] AZ::IO::FileRequestHandle request) + { + // Continue saving. + AZ::SystemTickBus::QueueFunction([this, tmpFileName, target, remainingAttempts]() { PerformMove(tmpFileName, target, remainingAttempts - 1); }); + }); + streamer->QueueRequest(flushRequest); + } + } + } + + void FileSaver::OnSourceFileReleased(AZ::Data::Asset asset) + { + AZStd::string relativePath, fullPath; + AZ::Data::AssetCatalogRequestBus::BroadcastResult(relativePath, &AZ::Data::AssetCatalogRequests::GetAssetPathById, asset.GetId()); + bool fullPathFound = false; + AzToolsFramework::AssetSystemRequestBus::BroadcastResult(fullPathFound, &AzToolsFramework::AssetSystemRequestBus::Events::GetFullSourcePathFromRelativeProductPath, relativePath, fullPath); + AZStd::string tmpFileName; + // here we are saving the graph to a temp file instead of the original file and then copying the temp file to the original file. + // This ensures that AP will not a get a file change notification on an incomplete graph file causing it to fail processing. Temp files are ignored by AP. + if (!AZ::IO::CreateTempFileName(fullPath.c_str(), tmpFileName)) + { + FileSaveResult result; + result.fileSaveError = "Failure to create temporary file name"; + m_onComplete(result); + return; + } + + bool tempSavedSucceeded = false; + AZ::IO::FileIOStream fileStream(tmpFileName.c_str(), AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeText); + if (fileStream.IsOpen()) + { + if (asset.GetType() == azrtti_typeid()) + { + ScriptCanvasEditor::ScriptCanvasAssetHandler handler; + tempSavedSucceeded = handler.SaveAssetData(asset, &fileStream); + } + + fileStream.Close(); + } + + if (!tempSavedSucceeded) + { + FileSaveResult result; + result.fileSaveError = "Save asset data to temporary file failed"; + m_onComplete(result); + return; + } + + AzToolsFramework::SourceControlCommandBus::Broadcast + ( &AzToolsFramework::SourceControlCommandBus::Events::RequestEdit + , fullPath.c_str() + , true + , [this, fullPath, tmpFileName]([[maybe_unused]] bool success, const AzToolsFramework::SourceControlFileInfo& info) + { + constexpr const size_t k_maxAttemps = 10; + + if (!info.IsReadOnly()) + { + PerformMove(tmpFileName, fullPath, k_maxAttemps); + } + else if (m_onReadOnlyFile && m_onReadOnlyFile()) + { + AZ::IO::SystemFile::SetWritable(info.m_filePath.c_str(), true); + PerformMove(tmpFileName, fullPath, k_maxAttemps); + } + else + { + FileSaveResult result; + result.fileSaveError = "Source file was and remained read-only"; + result.tempFileRemovalError = RemoveTempFile(tmpFileName); + m_onComplete(result); + } + }); + } + + AZStd::string FileSaver::RemoveTempFile(AZStd::string_view tempFile) + { + AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance(); + if (!fileIO) + { + return "GraphUpgradeComplete: No FileIO instance"; + } + + if (fileIO->Exists(tempFile.data()) && !fileIO->Remove(tempFile.data())) + { + return AZStd::string::format("Failed to remove temporary file: %s", tempFile.data()); + } + + return ""; + } + + void FileSaver::Save(AZ::Data::Asset asset) + { + AZStd::string relativePath, fullPath; + AZ::Data::AssetCatalogRequestBus::BroadcastResult(relativePath, &AZ::Data::AssetCatalogRequests::GetAssetPathById, asset.GetId()); + bool fullPathFound = false; + AzToolsFramework::AssetSystemRequestBus::BroadcastResult + (fullPathFound + , &AzToolsFramework::AssetSystemRequestBus::Events::GetFullSourcePathFromRelativeProductPath + , relativePath, fullPath); + + if (!fullPathFound) + { + FileSaveResult result; + result.fileSaveError = "Full source path not found"; + m_onComplete(result); + } + else + { + auto streamer = AZ::Interface::Get(); + AZ::IO::FileRequestPtr flushRequest = streamer->FlushCache(fullPath); + streamer->SetRequestCompleteCallback(flushRequest, [this, asset]([[maybe_unused]] AZ::IO::FileRequestHandle request) + { + this->OnSourceFileReleased(asset); + }); + streamer->QueueRequest(flushRequest); + } + } + } +} diff --git a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/FileSaver.h b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/FileSaver.h new file mode 100644 index 0000000000..dd333927ed --- /dev/null +++ b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/FileSaver.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include + +namespace ScriptCanvasEditor +{ + namespace VersionExplorer + { + struct FileSaveResult + { + AZStd::string fileSaveError; + AZStd::string tempFileRemovalError; + }; + + class FileSaver + { + public: + AZ_CLASS_ALLOCATOR(FileSaver, AZ::SystemAllocator, 0); + + FileSaver + ( AZStd::function onReadOnlyFile + , AZStd::function onComplete); + + void Save(AZ::Data::Asset asset); + + private: + AZStd::function m_onComplete; + AZStd::function m_onReadOnlyFile; + + void OnSourceFileReleased(AZ::Data::Asset asset); + + void PerformMove + ( AZStd::string source + , AZStd::string target + , size_t remainingAttempts); + + AZStd::string RemoveTempFile(AZStd::string_view tempFile); + }; + } +} diff --git a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/LogTraits.h b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/LogTraits.h new file mode 100644 index 0000000000..47cebe7324 --- /dev/null +++ b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/LogTraits.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#pragma once + +#include +#include + +#define VE_LOG(...) LogBus::Broadcast(&LogTraits::Entry, __VA_ARGS__); + +namespace ScriptCanvasEditor +{ + namespace VersionExplorer + { + class LogTraits + : public AZ::EBusTraits + { + public: + virtual void Activate() = 0; + virtual void Clear() = 0; + virtual void Deactivate() = 0; + virtual void Entry(const char* format, ...) = 0; + virtual const AZStd::vector* GetEntries() const = 0; + virtual void SetVersionExporerExclusivity(bool enabled) = 0; + virtual void SetVerbose(bool verbosity) = 0; + }; + using LogBus = AZ::EBus; + } +} diff --git a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Model.cpp b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Model.cpp new file mode 100644 index 0000000000..d8d242adbb --- /dev/null +++ b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Model.cpp @@ -0,0 +1,175 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include +#include +#include +#include +#include + +namespace ModifierCpp +{ + +} + +namespace ScriptCanvasEditor +{ + namespace VersionExplorer + { + EditorKeepAlive::EditorKeepAlive() + { + ISystem* system = nullptr; + CrySystemRequestBus::BroadcastResult(system, &CrySystemRequestBus::Events::GetCrySystem); + + if (system) + { + m_edKeepEditorActive = system->GetIConsole()->GetCVar("ed_KeepEditorActive"); + + if (m_edKeepEditorActive) + { + m_keepEditorActive = m_edKeepEditorActive->GetIVal(); + m_edKeepEditorActive->Set(1); + } + } + } + + EditorKeepAlive::~EditorKeepAlive() + { + if (m_edKeepEditorActive) + { + m_edKeepEditorActive->Set(m_keepEditorActive); + } + } + + Model::Model() + { + ModelRequestsBus::Handler::BusConnect(); + } + + void Model::CacheSettings() + { + m_settingsCache = AZStd::make_unique(); + ScriptCanvas::Grammar::g_saveRawTranslationOuputToFile = false; + ScriptCanvas::Grammar::g_printAbstractCodeModel = false; + ScriptCanvas::Grammar::g_saveRawTranslationOuputToFile = false; + } + + const ModificationResults* Model::GetResults() + { + return !IsWorking() ? &m_modResults : nullptr; + } + + void Model::Idle() + { + m_state = State::Idle; + m_keepEditorAlive.reset(); + m_log.Deactivate(); + } + + bool Model::IsReadyToModify() const + { + if (IsWorking()) + { + return false; + } + + if (!m_scanner) + { + return false; + } + + return !m_scanner->GetResult().m_unfiltered.empty(); + } + + bool Model::IsWorking() const + { + return m_state != State::Idle; + } + + void Model::Modify(const ModifyConfiguration& modification) + { + if (!IsReadyToModify()) + { + AZ_Warning(ScriptCanvas::k_VersionExplorerWindow.data(), false, "Explorer is not ready to modify graphs."); + return; + } + + if (modification.modifySingleAsset.m_assetId.IsValid()) + { + const auto& results = m_scanner->GetResult(); + auto iter = AZStd::find_if + ( results.m_unfiltered.begin() + , results.m_unfiltered.end() + , [&modification](const auto& candidate) + { + return candidate.info.m_assetId == modification.modifySingleAsset.m_assetId; + }); + + if (iter == results.m_unfiltered.end()) + { + AZ_Warning(ScriptCanvas::k_VersionExplorerWindow.data(), false, "Requested upgrade graph not found in scanned list."); + return; + } + + + m_state = State::ModifySingle; + m_modifier = AZStd::make_unique(modification, WorkingAssets{ *iter }, [this]() { OnModificationComplete(); }); + } + else + { + auto results = m_scanner->TakeResult(); + m_state = State::ModifyAll; + m_modifier = AZStd::make_unique(modification, AZStd::move(results.m_unfiltered), [this]() { OnModificationComplete(); }); + } + + m_modResults = {}; + m_log.Activate(); + m_keepEditorAlive = AZStd::make_unique(); + } + + void Model::OnModificationComplete() + { + ModelNotificationsBus::Broadcast(&ModelNotificationsTraits::OnUpgradeComplete, m_modifier->GetResult()); + m_modifier.reset(); + + if (m_state == State::ModifyAll) + { + m_scanner.reset(); + } + + Idle(); + } + + void Model::OnScanComplete() + { + ModelNotificationsBus::Broadcast(&ModelNotificationsTraits::OnScanComplete, m_scanner->GetResult()); + Idle(); + } + + void Model::Scan(const ScanConfiguration& config) + { + if (IsWorking()) + { + AZ_Warning(ScriptCanvas::k_VersionExplorerWindow.data(), false, "Explorer is already working"); + return; + } + + m_state = State::Scanning; + m_log.Activate(); + m_keepEditorAlive = AZStd::make_unique(); + m_scanner = AZStd::make_unique(config, [this](){ OnScanComplete(); }); + } + + void Model::RestoreSettings() + { + m_settingsCache.reset(); + } + } +} diff --git a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Model.h b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Model.h new file mode 100644 index 0000000000..ab0f710870 --- /dev/null +++ b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Model.h @@ -0,0 +1,81 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +struct ICVar; + +namespace ScriptCanvasEditor +{ + namespace VersionExplorer + { + //! Scoped utility to set and restore the "ed_KeepEditorActive" CVar in order to allow + //! the upgrade tool to work even if the editor is not in the foreground + class EditorKeepAlive + { + public: + EditorKeepAlive(); + ~EditorKeepAlive(); + + private: + int m_keepEditorActive; + ICVar* m_edKeepEditorActive; + }; + + //! Handles model change requests, state queries; sends state change notifications + class Model + : public ModelRequestsBus::Handler + { + public: + AZ_CLASS_ALLOCATOR(Model, AZ::SystemAllocator, 0); + + Model(); + + const ModificationResults* GetResults() override; + + void Modify(const ModifyConfiguration& modification) override; + + void Scan(const ScanConfiguration& config) override; + + private: + enum class State + { + Idle, + Scanning, + ModifyAll, + ModifySingle + }; + + State m_state = State::Idle; + Log m_log; + + // these two are managed by the same class because the modifer will only operate on the results of the scanner + AZStd::unique_ptr m_modifier; + AZStd::unique_ptr m_scanner; + AZStd::unique_ptr m_settingsCache; + AZStd::unique_ptr m_keepEditorAlive; + + ModificationResults m_modResults; + + void CacheSettings(); + void Idle(); + bool IsReadyToModify() const; + bool IsWorking() const; + void OnModificationComplete(); + void OnScanComplete(); + void RestoreSettings(); + }; + } +} diff --git a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/ModelTraits.h b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/ModelTraits.h new file mode 100644 index 0000000000..01eb200542 --- /dev/null +++ b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/ModelTraits.h @@ -0,0 +1,108 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#pragma once + +#include +#include + +namespace ScriptCanvasEditor +{ + namespace VersionExplorer + { + struct WorkingAsset + { + AZ::Data::Asset asset; + AZ::Data::AssetInfo info; + }; + + using WorkingAssets = AZStd::vector; + + struct ModifyConfiguration + { + AZStd::function)> modification; + AZStd::function onReadOnlyFile; + AZ::Data::AssetInfo modifySingleAsset; + bool backupGraphBeforeModification = false; + bool successfulDependencyUpgradeRequired = true; + }; + + struct ModificationResult + { + AZ::Data::Asset asset; + AZ::Data::AssetInfo assetInfo; + AZStd::string errorMessage; + }; + + struct ModificationResults + { + AZStd::vector m_successes; + AZStd::vector m_failures; + }; + + struct ScanConfiguration + { + AZStd::function)> filter; + bool reportFilteredGraphs = false; + }; + + struct ScanResult + { + AZStd::vector m_catalogAssets; + WorkingAssets m_unfiltered; + AZStd::vector m_filteredAssets; + AZStd::vector m_loadErrors; + }; + + enum Result + { + Failure, + Success + }; + + class ModificationNotificationsTraits + : public AZ::EBusTraits + { + public: + virtual void ModificationComplete(const ModificationResult& result) = 0; + }; + using ModificationNotificationsBus = AZ::EBus; + + class ModelRequestsTraits + : public AZ::EBusTraits + { + public: + virtual const ModificationResults* GetResults() = 0; + virtual void Modify(const ModifyConfiguration& modification) = 0; + virtual void Scan(const ScanConfiguration& filter) = 0; + }; + using ModelRequestsBus = AZ::EBus; + + class ModelNotificationsTraits + : public AZ::EBusTraits + { + public: + virtual void OnScanBegin(size_t assetCount) = 0; + virtual void OnScanComplete(const ScanResult& result) = 0; + virtual void OnScanFilteredGraph(const AZ::Data::AssetInfo& info) = 0; + virtual void OnScanLoadFailure(const AZ::Data::AssetInfo& info) = 0; + virtual void OnScanUnFilteredGraph(const AZ::Data::AssetInfo& info) = 0; + + virtual void OnUpgradeBegin(const ModifyConfiguration& config, const WorkingAssets& assets) = 0; + virtual void OnUpgradeComplete(const ModificationResults& results) = 0; + virtual void OnUpgradeDependenciesGathered(const AZ::Data::AssetInfo& info, Result result) = 0; + virtual void OnUpgradeDependencySortBegin(const ModifyConfiguration& config, const WorkingAssets& assets) = 0; + virtual void OnUpgradeDependencySortEnd + ( const ModifyConfiguration& config + , const WorkingAssets& assets + , const AZStd::vector& sortedOrder) = 0; + virtual void OnUpgradeModificationBegin(const ModifyConfiguration& config, const AZ::Data::AssetInfo& info) = 0; + virtual void OnUpgradeModificationEnd(const ModifyConfiguration& config, const AZ::Data::AssetInfo& info, ModificationResult result) = 0; + }; + using ModelNotificationsBus = AZ::EBus; + } +} diff --git a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Modifier.cpp b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Modifier.cpp new file mode 100644 index 0000000000..f3da1ba309 --- /dev/null +++ b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Modifier.cpp @@ -0,0 +1,405 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include +#include +#include + +namespace ModifierCpp +{ + +} + +namespace ScriptCanvasEditor +{ + namespace VersionExplorer + { + Modifier::Modifier + ( const ModifyConfiguration& modification + , WorkingAssets&& assets + , AZStd::function onComplete) + : m_state(State::GatheringDependencies) + , m_config(modification) + , m_assets(assets) + , m_onComplete(onComplete) + { + AZ_Assert(m_config.modification, "No modification function provided"); + ModelNotificationsBus::Broadcast(&ModelNotificationsTraits::OnUpgradeBegin, modification, m_assets); + AZ::SystemTickBus::Handler::BusConnect(); + } + + const AZ::Data::AssetInfo& Modifier::GetCurrentAsset() const + { + return m_state == State::GatheringDependencies + ? m_assets[m_assetIndex].info + : m_assets[m_dependencyOrderedAssetIndicies[m_assetIndex]].info; + } + + AZStd::unordered_set& Modifier::GetOrCreateDependencyIndexSet() + { + auto iter = m_dependencies.find(m_assetIndex); + if (iter == m_dependencies.end()) + { + iter = m_dependencies.insert_or_assign(m_assetIndex, AZStd::unordered_set()).first; + } + + return iter->second; + } + + const ModificationResults& Modifier::GetResult() const + { + return m_results; + } + + void Modifier::GatherDependencies() + { + AZ::SerializeContext* serializeContext{}; + AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext); + AZ_Assert(serializeContext, "SerializeContext is required to enumerate dependent assets in the ScriptCanvas file"); + + bool anyFailures = false; + auto asset = LoadAsset(); + + if (asset + && asset.GetAs() + && asset.GetAs()->GetScriptCanvasGraph() + && asset.GetAs()->GetScriptCanvasGraph()->GetGraphData()) + { + auto graphData = asset.GetAs()->GetScriptCanvasGraph()->GetGraphData(); + + auto dependencyGrabber = [this] + ( void* instancePointer + , const AZ::SerializeContext::ClassData* classData + , [[maybe_unused]] const AZ::SerializeContext::ClassElement* classElement) + { + if (auto azTypeId = classData->m_azRtti->GetTypeId(); + azTypeId == azrtti_typeid>()) + { + const auto* subgraphAsset = + reinterpret_cast*>(instancePointer); + if (subgraphAsset->GetId().IsValid()) + { + if (auto iter = m_assetInfoIndexById.find(subgraphAsset->GetId().m_guid); iter != m_assetInfoIndexById.end()) + { + // insert the index of the dependency into the set that belongs to this asset + GetOrCreateDependencyIndexSet().insert(iter->second); + } + } + } + // always continue, make note of the script canvas dependencies + return true; + }; + + if (!serializeContext->EnumerateInstanceConst + ( graphData + , azrtti_typeid() + , dependencyGrabber + , {} + , AZ::SerializeContext::ENUM_ACCESS_FOR_READ + , nullptr + , nullptr)) + { + anyFailures = true; + VE_LOG("Modifier: ERROR - Failed to gather dependencies from graph data: %s" + , GetCurrentAsset().m_relativePath.c_str()) + } + } + else + { + anyFailures = true; + VE_LOG("Modifier: ERROR - Failed to load asset %s for modification, even though it scanned properly" + , GetCurrentAsset().m_relativePath.c_str()); + } + + ModelNotificationsBus::Broadcast + ( &ModelNotificationsTraits::OnUpgradeDependenciesGathered + , GetCurrentAsset() + , anyFailures ? Result::Failure : Result::Success); + + // Flush asset database events to ensure no asset references are held by closures queued on Ebuses. + AZ::Data::AssetManager::Instance().DispatchEvents(); + } + + AZ::Data::Asset Modifier::LoadAsset() + { + AZ::Data::Asset asset = AZ::Data::AssetManager::Instance().GetAsset + ( GetCurrentAsset().m_assetId + , azrtti_typeid() + , AZ::Data::AssetLoadBehavior::PreLoad); + + asset.BlockUntilLoadComplete(); + + if (asset.IsReady()) + { + return asset; + } + else + { + return {}; + } + } + + void Modifier::ModificationComplete(const ModificationResult& result) + { + m_result = result; + + if (result.errorMessage.empty()) + { + SaveModifiedGraph(result); + } + else + { + ReportModificationError(result.errorMessage); + } + } + + void Modifier::ModifyCurrentAsset() + { + m_result = {}; + m_result.assetInfo = GetCurrentAsset(); + + ModelNotificationsBus::Broadcast(&ModelNotificationsTraits::OnUpgradeModificationBegin, m_config, GetCurrentAsset()); + + if (auto asset = LoadAsset()) + { + ModificationNotificationsBus::Handler::BusConnect(); + m_modifyState = ModifyState::InProgress; + m_config.modification(asset); + } + else + { + ReportModificationError("Failed to load during modification"); + } + } + + void Modifier::ModifyNextAsset() + { + ModelNotificationsBus::Broadcast + ( &ModelNotificationsTraits::OnUpgradeModificationEnd, m_config, GetCurrentAsset(), m_result); + ModificationNotificationsBus::Handler::BusDisconnect(); + m_modifyState = ModifyState::Idle; + ++m_assetIndex; + m_result = {}; + } + + void Modifier::ReportModificationError(AZStd::string_view report) + { + m_result.asset = {}; + m_result.errorMessage = report; + m_results.m_failures.push_back(m_result); + ModifyNextAsset(); + } + + void Modifier::ReportModificationSuccess() + { + m_results.m_successes.push_back(m_result.assetInfo); + ModifyNextAsset(); + } + + void Modifier::ReportSaveResult() + { + AZStd::lock_guard lock(m_mutex); + m_fileSaver.reset(); + + if (m_fileSaveResult.fileSaveError.empty()) + { + ReportModificationSuccess(); + } + else + { + ReportModificationError(m_fileSaveResult.fileSaveError); + } + + m_fileSaveResult = {}; + m_modifyState = ModifyState::Idle; + } + + void Modifier::OnFileSaveComplete(const FileSaveResult& result) + { + if (!result.tempFileRemovalError.empty()) + { + VE_LOG + ( "Temporary file not removed for %s: %s" + , m_result.assetInfo.m_relativePath.c_str() + , result.tempFileRemovalError.c_str()); + } + + AZStd::lock_guard lock(m_mutex); + m_modifyState = ModifyState::ReportResult; + m_fileSaver.reset(); + m_fileSaveResult = result; + } + + void Modifier::OnSystemTick() + { + switch (m_state) + { + case State::GatheringDependencies: + TickGatherDependencies(); + break; + + case State::ModifyingGraphs: + TickUpdateGraph(); + break; + } + + AZ::Data::AssetManager::Instance().DispatchEvents(); + AZ::SystemTickBus::ExecuteQueuedEvents(); + } + + void Modifier::SaveModifiedGraph(const ModificationResult& result) + { + m_modifyState = ModifyState::Saving; + m_fileSaver = AZStd::make_unique + ( m_config.onReadOnlyFile + , [this](const FileSaveResult& result) { OnFileSaveComplete(result); }); + m_fileSaver->Save(result.asset); + } + + void Modifier::SortGraphsByDependencies() + { + m_dependencyOrderedAssetIndicies.reserve(m_assets.size()); + Sorter sorter; + sorter.modifier = this; + sorter.Sort(); + } + + ModificationResults&& Modifier::TakeResult() + { + return AZStd::move(m_results); + } + + void Modifier::TickGatherDependencies() + { + if (m_assetIndex == 0) + { + if (m_config.successfulDependencyUpgradeRequired) + { + ModelNotificationsBus::Broadcast(&ModelNotificationsTraits::OnUpgradeDependencySortBegin, m_config, m_assets); + m_assetInfoIndexById.reserve(m_assets.size()); + + for (size_t index = 0; index != m_assets.size(); ++index) + { + m_assetInfoIndexById.insert({ m_assets[index].info.m_assetId.m_guid, index }); + } + } + else + { + m_dependencyOrderedAssetIndicies.reserve(m_assets.size()); + + for (size_t index = 0; index != m_assets.size(); ++index) + { + m_dependencyOrderedAssetIndicies.push_back(index); + } + + // go straight into ModifyinGraphs + m_assetIndex = m_assets.size(); + } + } + + if (m_assetIndex == m_assets.size()) + { + if (m_config.successfulDependencyUpgradeRequired) + { + SortGraphsByDependencies(); + ModelNotificationsBus::Broadcast + ( &ModelNotificationsTraits::OnUpgradeDependencySortEnd + , m_config + , m_assets + , m_dependencyOrderedAssetIndicies); + } + + m_assetIndex = 0; + m_state = State::ModifyingGraphs; + } + else + { + GatherDependencies(); + ++m_assetIndex; + } + } + + void Modifier::TickUpdateGraph() + { + if (m_assetIndex == m_assets.size()) + { + VE_LOG("Modifier: Complete."); + AZ::SystemTickBus::Handler::BusDisconnect(); + + if (m_onComplete) + { + m_onComplete(); + } + } + else + { + AZStd::lock_guard lock(m_mutex); + + switch (m_modifyState) + { + case ScriptCanvasEditor::VersionExplorer::Modifier::ModifyState::Idle: + ModifyCurrentAsset(); + break; + case ScriptCanvasEditor::VersionExplorer::Modifier::ModifyState::ReportResult: + ReportSaveResult(); + break; + default: + break; + } + } + } + + const AZStd::unordered_set* Modifier::Sorter::GetDependencies(size_t index) const + { + auto iter = modifier->m_dependencies.find(index); + return iter != modifier->m_dependencies.end() ? &iter->second : nullptr; + } + + void Modifier::Sorter::Sort() + { + for (size_t index = 0; index != modifier->m_assets.size(); ++index) + { + Visit(index); + } + } + + void Modifier::Sorter::Visit(size_t index) + { + if (markedPermanent.contains(index)) + { + return; + } + + if (markedTemporary.contains(index)) + { + AZ_Error + (ScriptCanvas::k_VersionExplorerWindow.data() + , false + , "Modifier: Dependency sort has failed during, circular dependency detected for Asset: %s" + , modifier->GetCurrentAsset().m_relativePath.c_str()); + return; + } + + markedTemporary.insert(index); + + if (auto dependencies = GetDependencies(index)) + { + for (auto& dependency : *dependencies) + { + Visit(dependency); + } + } + + markedTemporary.erase(index); + markedPermanent.insert(index); + modifier->m_dependencyOrderedAssetIndicies.push_back(index); + } + } +} diff --git a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Modifier.h b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Modifier.h new file mode 100644 index 0000000000..981a1eb746 --- /dev/null +++ b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Modifier.h @@ -0,0 +1,103 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include +#include +#include + +namespace ScriptCanvasEditor +{ + namespace VersionExplorer + { + class Modifier + : private AZ::SystemTickBus::Handler + , private ModificationNotificationsBus::Handler + { + public: + AZ_CLASS_ALLOCATOR(Modifier, AZ::SystemAllocator, 0); + + Modifier + ( const ModifyConfiguration& modification + , WorkingAssets&& assets + , AZStd::function onComplete); + + const ModificationResults& GetResult() const; + ModificationResults&& TakeResult(); + + private: + friend class Sorter; + + struct Sorter + { + Modifier* modifier; + AZStd::unordered_set markedPermanent; + AZStd::unordered_set markedTemporary; + void Sort(); + + private: + void Visit(size_t index); + const AZStd::unordered_set* GetDependencies(size_t index) const; + }; + + enum class State + { + GatheringDependencies, + ModifyingGraphs + }; + + enum class ModifyState + { + Idle, + InProgress, + Saving, + ReportResult + }; + + AZStd::recursive_mutex m_mutex; + + // the two states reside in this class because the modification is only complete if the new source file saves out + State m_state = State::GatheringDependencies; + ModifyState m_modifyState = ModifyState::Idle; + size_t m_assetIndex = 0; + AZStd::function m_onComplete; + // asset infos in scanned order + WorkingAssets m_assets; + // dependency sorted order indices into the asset vector + AZStd::vector m_dependencyOrderedAssetIndicies; + // dependency indices by asset info index (only exist if graphs have them) + AZStd::unordered_map> m_dependencies; + AZStd::unordered_map m_assetInfoIndexById; + AZStd::vector m_failures; + ModifyConfiguration m_config; + ModificationResult m_result; + ModificationResults m_results; + AZStd::unique_ptr m_fileSaver; + FileSaveResult m_fileSaveResult; + + void GatherDependencies(); + const AZ::Data::AssetInfo& GetCurrentAsset() const; + AZStd::unordered_set& GetOrCreateDependencyIndexSet(); + AZ::Data::Asset LoadAsset(); + void ModifyCurrentAsset(); + void ModifyNextAsset(); + void ModificationComplete(const ModificationResult& result) override; + void ReportModificationError(AZStd::string_view report); + void ReportModificationSuccess(); + void ReportSaveResult(); + void SaveModifiedGraph(const ModificationResult& result); + void SortGraphsByDependencies(); + void OnFileSaveComplete(const FileSaveResult& result); + void OnSystemTick() override; + void TickGatherDependencies(); + void TickUpdateGraph(); + }; + } +} diff --git a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Scanner.cpp b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Scanner.cpp new file mode 100644 index 0000000000..bc69f6e634 --- /dev/null +++ b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Scanner.cpp @@ -0,0 +1,118 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include + +namespace ScriptCanvasEditor +{ + namespace VersionExplorer + { + Scanner::Scanner(const ScanConfiguration& config, AZStd::function onComplete) + : m_config(config) + , m_onComplete(onComplete) + { + AZ::Data::AssetCatalogRequestBus::Broadcast + ( &AZ::Data::AssetCatalogRequestBus::Events::EnumerateAssets + , nullptr + , [this](const AZ::Data::AssetId, const AZ::Data::AssetInfo& assetInfo) + { + if (assetInfo.m_assetType == azrtti_typeid()) + { + m_result.m_catalogAssets.push_back(assetInfo); + } + } + , nullptr); + + ModelNotificationsBus::Broadcast(&ModelNotificationsTraits::OnScanBegin, m_result.m_catalogAssets.size()); + AZ::SystemTickBus::Handler::BusConnect(); + } + + void Scanner::FilterAsset(AZ::Data::Asset asset) + { + if (m_config.filter && m_config.filter(asset)) + { + VE_LOG("Scanner: Excluded: %s ", GetCurrentAsset().m_relativePath.c_str()); + m_result.m_filteredAssets.push_back(GetCurrentAsset()); + ModelNotificationsBus::Broadcast(&ModelNotificationsTraits::OnScanFilteredGraph, GetCurrentAsset()); + } + else + { + VE_LOG("Scanner: Included: %s ", GetCurrentAsset().m_relativePath.c_str()); + m_result.m_unfiltered.push_back({ asset, GetCurrentAsset() }); + ModelNotificationsBus::Broadcast(&ModelNotificationsTraits::OnScanUnFilteredGraph, GetCurrentAsset()); + } + } + + const AZ::Data::AssetInfo& Scanner::GetCurrentAsset() const + { + return m_result.m_catalogAssets[m_catalogAssetIndex]; + } + + const ScanResult& Scanner::GetResult() const + { + return m_result; + } + + AZ::Data::Asset Scanner::LoadAsset() + { + AZ::Data::Asset asset = AZ::Data::AssetManager::Instance().GetAsset + ( GetCurrentAsset().m_assetId + , azrtti_typeid() + , AZ::Data::AssetLoadBehavior::PreLoad); + + asset.BlockUntilLoadComplete(); + + if (asset.IsReady()) + { + return asset; + } + else + { + return {}; + } + } + + void Scanner::OnSystemTick() + { + if (m_catalogAssetIndex == m_result.m_catalogAssets.size()) + { + VE_LOG("Scanner: Complete."); + AZ::SystemTickBus::Handler::BusDisconnect(); + + if (m_onComplete) + { + m_onComplete(); + } + } + else + { + if (auto asset = LoadAsset()) + { + VE_LOG("Scanner: Loaded: %s ", GetCurrentAsset().m_relativePath.c_str()); + FilterAsset(asset); + } + else + { + VE_LOG("Scanner: Failed to load: %s ", GetCurrentAsset().m_relativePath.c_str()); + m_result.m_loadErrors.push_back(GetCurrentAsset()); + ModelNotificationsBus::Broadcast(&ModelNotificationsTraits::OnScanLoadFailure, GetCurrentAsset()); + } + + VE_LOG("Scanner: scan of %s complete", GetCurrentAsset().m_relativePath.c_str()); + ++m_catalogAssetIndex; + } + } + + ScanResult&& Scanner::TakeResult() + { + return AZStd::move(m_result); + } + } +} diff --git a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Scanner.h b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Scanner.h new file mode 100644 index 0000000000..fb030845c7 --- /dev/null +++ b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Scanner.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include +#include + +namespace ScriptCanvasEditor +{ + namespace VersionExplorer + { + class Scanner + : private AZ::SystemTickBus::Handler + { + public: + AZ_CLASS_ALLOCATOR(Scanner, AZ::SystemAllocator, 0); + + Scanner(const ScanConfiguration& config, AZStd::function onComplete); + + const ScanResult& GetResult() const; + ScanResult&& TakeResult(); + + private: + size_t m_catalogAssetIndex = 0; + AZStd::function m_onComplete; + ScanConfiguration m_config; + ScanResult m_result; + + void FilterAsset(AZ::Data::Asset); + const AZ::Data::AssetInfo& GetCurrentAsset() const; + AZ::Data::Asset LoadAsset(); + void OnSystemTick() override; + }; + } +} diff --git a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/UpgradeHelper.cpp b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/UpgradeHelper.cpp index 9670bb2f1f..14bc1c4dc8 100644 --- a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/UpgradeHelper.cpp +++ b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/UpgradeHelper.cpp @@ -6,37 +6,32 @@ * */ +#include +#include #include #include -#include #include -#include #include -#include "UpgradeHelper.h" - #include #include #include #include - #include #include - +#include +#include #include #include #include - -#include - +#include #include +#include +#include #include #include - #include #include -#include -#include namespace ScriptCanvasEditor { @@ -52,40 +47,47 @@ namespace ScriptCanvasEditor m_ui->tableWidget->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); int rows = 0; - auto& graphsToUpgrade = AZ::Interface::Get()->GetGraphsThatNeedManualUpgrade(); - for (auto& assetId : graphsToUpgrade) + const VersionExplorer::ModificationResults* result = nullptr; + VersionExplorer::ModelRequestsBus::BroadcastResult(result, &VersionExplorer::ModelRequestsTraits::GetResults); + + if (result && !result->m_failures.empty()) { - auto assetInfo = ScriptCanvasEditor::AssetHelpers::GetAssetInfo(assetId); - m_ui->tableWidget->insertRow(rows); + for (auto& failedUpdate : result->m_failures) + { + auto& assetInfo = failedUpdate.assetInfo; + auto assetId = assetInfo.m_assetId; - connect(m_ui->closeButton, &QPushButton::pressed, this, &QDialog::accept); - connect(m_ui->tableWidget, &QTableWidget::itemDoubleClicked, this, [this, rows, assetId](QTableWidgetItem* item) - { - if (item && item->data(Qt::UserRole).toInt() == rows) + m_ui->tableWidget->insertRow(rows); + + connect(m_ui->closeButton, &QPushButton::pressed, this, &QDialog::accept); + connect(m_ui->tableWidget, &QTableWidget::itemDoubleClicked, this, [this, rows, assetId](QTableWidgetItem* item) { - OpenGraph(assetId); + if (item && item->data(Qt::UserRole).toInt() == rows) + { + OpenGraph(assetId); + } } - } - ); + ); + + auto openGraph = [this, assetId] { + OpenGraph(assetId); + }; - auto openGraph = [this, assetId] { - OpenGraph(assetId); - }; + QTableWidgetItem* rowName = new QTableWidgetItem(tr(assetInfo.m_relativePath.c_str())); + rowName->setData(Qt::UserRole, rows); + m_ui->tableWidget->setItem(rows, 0, rowName); - QTableWidgetItem* rowName = new QTableWidgetItem(tr(assetInfo.m_relativePath.c_str())); - rowName->setData(Qt::UserRole, rows); - m_ui->tableWidget->setItem(rows, 0, rowName); + QToolButton* rowGoToButton = new QToolButton(this); + rowGoToButton->setIcon(QIcon(":/stylesheet/img/UI20/open-in-internal-app.svg")); + rowGoToButton->setToolTip("Open Graph"); - QToolButton* rowGoToButton = new QToolButton(this); - rowGoToButton->setIcon(QIcon(":/stylesheet/img/UI20/open-in-internal-app.svg")); - rowGoToButton->setToolTip("Open Graph"); - - connect(rowGoToButton, &QToolButton::clicked, openGraph); + connect(rowGoToButton, &QToolButton::clicked, openGraph); - m_ui->tableWidget->setCellWidget(rows, 1, rowGoToButton); + m_ui->tableWidget->setCellWidget(rows, 1, rowGoToButton); - ++rows; + ++rows; + } } } diff --git a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/UpgradeTool.cpp b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/UpgradeTool.cpp deleted file mode 100644 index f1832927c5..0000000000 --- a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/UpgradeTool.cpp +++ /dev/null @@ -1,669 +0,0 @@ -/* - * Copyright (c) Contributors to the Open 3D Engine Project. - * For complete copyright and license terms please see the LICENSE at the root of this distribution. - * - * SPDX-License-Identifier: Apache-2.0 OR MIT - * - */ - -#include -#include - -#include "UpgradeTool.h" - -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include - -#include - -#include -#include -#include - -#include -#include - -#include "UpgradeHelper.h" - -namespace ScriptCanvasEditor -{ - UpgradeTool::UpgradeTool(QWidget* parent /*= nullptr*/) - : AzQtComponents::StyledDialog(parent) - , m_ui(new Ui::UpgradeTool()) - { - m_ui->setupUi(this); - - AzQtComponents::CheckBox::applyToggleSwitchStyle(m_ui->doNotAskCheckbox); - AzQtComponents::CheckBox::applyToggleSwitchStyle(m_ui->makeBackupCheckbox); - - m_ui->progressFrame->setVisible(false); - - connect(m_ui->upgradeButton, &QPushButton::pressed, this, &UpgradeTool::OnUpgrade); - connect(m_ui->notNowButton, &QPushButton::pressed, this, &UpgradeTool::OnNoThanks); - - UpgradeNotifications::Bus::Handler::BusConnect(); - AZ::Debug::TraceMessageBus::Handler::BusConnect(); - - resize(700, 100); - } - - void UpgradeTool::OnNoThanks() - { - DisconnectBuses(); - - UpdateSettings(); - - reject(); - } - - void UpgradeTool::UpdateSettings() - { - auto userSettings = AZ::UserSettings::CreateFind(AZ_CRC("ScriptCanvasPreviewSettings", 0x1c5a2965), AZ::UserSettings::CT_LOCAL); - if (userSettings) - { - userSettings->m_showUpgradeDialog = !m_ui->doNotAskCheckbox->isChecked(); - - AZ::UserSettingsOwnerRequestBus::Event(AZ::UserSettings::CT_LOCAL, &AZ::UserSettingsOwnerRequests::SaveSettings); - } - } - - UpgradeTool::~UpgradeTool() - { - DisconnectBuses(); - } - - void UpgradeTool::DisconnectBuses() - { - UpgradeNotifications::Bus::Handler::BusDisconnect(); - - AZ::SystemTickBus::Handler::BusDisconnect(); - AZ::Data::AssetBus::MultiHandler::BusDisconnect(); - AZ::Debug::TraceMessageBus::Handler::BusDisconnect(); - } - - void UpgradeTool::closeEvent(QCloseEvent* event) - { - // m_keepEditorAlive.reset(); - - DisconnectBuses(); - - UpgradeNotifications::Bus::Broadcast(&UpgradeNotifications::OnUpgradeCancelled); - - AzQtComponents::StyledDialog::closeEvent(event); - } - - bool UpgradeTool::HasBackup() const - { - return m_ui->makeBackupCheckbox->isChecked(); - } - - void UpgradeTool::OnUpgrade() - { - setWindowFlag(Qt::WindowCloseButtonHint, false); - - // m_keepEditorAlive = AZStd::make_unique(); - - UpdateSettings(); - - UpgradeNotifications::Bus::Broadcast(&UpgradeNotifications::OnUpgradeStart); - - m_assetsToUpgrade.clear(); - - IUpgradeRequests* upgradeRequests = AZ::Interface::Get(); - m_assetsToUpgrade = upgradeRequests->GetAssetsToUpgrade(); - - AZ::SystemTickBus::Handler::BusConnect(); - - if (m_ui->makeBackupCheckbox->isChecked()) - { - if (!DoBackup()) - { - // There was a problem, ask if the user wants to keep going or abort - QMessageBox mb(QMessageBox::Warning, - QObject::tr("Backup Failed"), - QObject::tr("Failed to backup your Script Canvas graphs, do you want to proceed with upgrade?"), - QMessageBox::Yes | QMessageBox::No, this); - - if (mb.exec() == QMessageBox::Yes) - { - DoUpgrade(); - } - } - } - else - { - DoUpgrade(); - } - } - - bool UpgradeTool::DoBackup() - { - if (!m_assetsToUpgrade.empty()) - { - m_state = UpgradeState::Backup; - - m_inProgressAsset = m_assetsToUpgrade.begin(); - - m_ui->progressFrame->setVisible(true); - m_ui->progressBar->setRange(0, aznumeric_cast(m_assetsToUpgrade.size())); - - m_ui->spinner->SetIsBusy(true); - m_ui->spinner->SetBusyIconSize(32); - - m_ui->upgradeButton->setEnabled(false); - m_ui->notNowButton->setEnabled(false); - m_ui->doNotAskCheckbox->setEnabled(false); - m_ui->makeBackupCheckbox->setEnabled(false); - - // Make the folder for the backup - - QDateTime theTime = QDateTime::currentDateTime(); - QString subFolder = theTime.toString("yyyy-MM-dd [HH.mm.ss]"); - - auto settingsRegistry = AZ::SettingsRegistry::Get(); - m_backupPath.clear(); - settingsRegistry->Get(m_backupPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectUserPath); - - m_backupPath = m_backupPath / "ScriptCanvas_BACKUP" / subFolder.toUtf8().data(); - - if (!AZ::IO::FileIOBase::GetInstance()->Exists(m_backupPath.c_str())) - { - if (AZ::IO::FileIOBase::GetInstance()->CreatePath(m_backupPath.c_str()) != AZ::IO::ResultCode::Success) - { - AZ_Error("Script Canvas", false, "Failed to create backup folder %s", m_backupPath.c_str()); - - return false; - } - } - } - - return true; - } - - void UpgradeTool::BackupComplete() - { - m_currentAssetIndex = 0; - m_ui->progressBar->setValue(0); - - DoUpgrade(); - } - - void UpgradeTool::DoUpgrade() - { - m_state = UpgradeState::Upgrade; - - if (!m_assetsToUpgrade.empty()) - { - m_ui->progressFrame->setVisible(true); - m_ui->progressBar->setRange(0, aznumeric_cast(m_assetsToUpgrade.size())); - - m_ui->spinner->SetIsBusy(true); - m_ui->spinner->SetBusyIconSize(32); - - m_ui->upgradeButton->setEnabled(false); - m_ui->notNowButton->setEnabled(false); - m_ui->doNotAskCheckbox->setEnabled(false); - m_ui->makeBackupCheckbox->setEnabled(false); - - m_inProgressAsset = m_assetsToUpgrade.begin(); - } - } - - void UpgradeTool::OnAssetReady(AZ::Data::Asset asset) - { - // Start asset upgrade job when current asset is unassigned only - // If current asset is present, there is ongoing progress handling this asset already - if (IsOnReadyAssetForCurrentProcess(asset.GetId())) - { - m_inProgress = true; - m_currentAsset = asset; - m_scriptCanvasEntity = AssetUpgradeJob(asset); - if (!m_scriptCanvasEntity) - { - ResetUpgradeCurrentAsset(); - } - } - } - - void UpgradeTool::OnAssetError(AZ::Data::Asset asset) - { - // Reset upgrade target when script canvas entity is unassigned only. - // If script canvas entity is present, we should let the conversion progress finish itself. - if (IsCurrentProcessFreeToAbort(asset.GetId())) - { - AZ_TracePrintf("Script Canvas", "Asset fails to get load: %s\n", asset.GetHint().c_str()); - ResetUpgradeCurrentAsset(); - } - } - - void UpgradeTool::OnAssetUnloaded(const AZ::Data::AssetId assetId, const AZ::Data::AssetType) - { - // Reset upgrade target when script canvas entity is unassigned only. - // If script canvas entity is present, we should let the conversion progress finish itself. - if (IsCurrentProcessFreeToAbort(assetId)) - { - AZ_TracePrintf("Script Canvas", "Asset gets unloaded: %s\n", m_inProgressAsset->m_relativePath.c_str()); - ResetUpgradeCurrentAsset(); - } - } - - - void UpgradeTool::OnGraphUpgradeComplete(AZ::Data::Asset& asset, bool skipped /*=false*/) - { - if (!skipped) - { - AZStd::string relativePath, fullPath; - AZ::Data::AssetCatalogRequestBus::BroadcastResult(relativePath, &AZ::Data::AssetCatalogRequests::GetAssetPathById, asset.GetId()); - - bool fullPathFound = false; - AzToolsFramework::AssetSystemRequestBus::BroadcastResult(fullPathFound, &AzToolsFramework::AssetSystemRequestBus::Events::GetFullSourcePathFromRelativeProductPath, relativePath, fullPath); - - AZStd::string tmpFileName; - bool tmpFilesaved = false; - - // here we are saving the graph to a temp file instead of the original file and then copying the temp file to the original file. - // This ensures that AP will not a get a file change notification on an incomplete graph file causing it to fail processing. Temp files are ignored by AP. - if (AZ::IO::CreateTempFileName(fullPath.c_str(), tmpFileName)) - { - AZ::IO::FileIOStream fileStream(tmpFileName.c_str(), AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeText); - - if (fileStream.IsOpen()) - { - if (asset.GetType() == azrtti_typeid()) - { - tmpFilesaved = AZ::Utils::SaveObjectToStream(fileStream, AZ::DataStream::ST_XML, &asset.GetAs()->GetScriptCanvasData()); - } - - fileStream.Close(); - } - - using SCCommandBus = AzToolsFramework::SourceControlCommandBus; - SCCommandBus::Broadcast(&SCCommandBus::Events::RequestEdit, fullPath.c_str(), true, - [this, &asset, fullPath, tmpFileName, tmpFilesaved](bool /*success*/, const AzToolsFramework::SourceControlFileInfo& info) - { - if (!info.IsReadOnly()) - { - if (tmpFilesaved) - { - PerformMove(asset, tmpFileName, fullPath); - } - } - else - { - if (m_overwriteAll) - { - MakeWriteable(info); - - if (tmpFilesaved) - { - PerformMove(asset, tmpFileName, fullPath); - } - } - else - { - int result = QMessageBox::No; - if (!m_overwriteAll) - { - QMessageBox mb(QMessageBox::Warning, - QObject::tr("Failed to Save Upgraded File"), - QObject::tr("The upgraded file could not be saved because the file is read only.\nDo you want to make it writeable and overwrite it?"), - QMessageBox::YesToAll | QMessageBox::Yes | QMessageBox::No, this); - - result = mb.exec(); - if (result == QMessageBox::YesToAll) - { - m_overwriteAll = true; - } - } - - if (result == QMessageBox::Yes || m_overwriteAll) - { - MakeWriteable(info); - - if (tmpFilesaved) - { - PerformMove(asset, tmpFileName, fullPath); - } - } - - } - } - }); - } - } - else - { - // We skipped the upgrade (it's up to date), just mark it complete - AZ::SystemTickBus::QueueFunction([this, asset]() { UpgradeComplete(asset, true); }); - - } - - } - - void UpgradeTool::MakeWriteable(const AzToolsFramework::SourceControlFileInfo& info) - { - AZ::IO::SystemFile::SetWritable(info.m_filePath.c_str(), true); - } - - void UpgradeTool::PerformMove(AZ::Data::Asset& asset, const AZStd::string& source, const AZStd::string& target) - { - auto moveResult = AZ::IO::SmartMove(source.c_str(), target.c_str()); - if (moveResult.GetResultCode() == AZ::IO::ResultCode::Success) - { - // Bump the slice asset up in the asset processor's queue. - AzFramework::AssetSystemRequestBus::Broadcast(&AzFramework::AssetSystem::AssetSystemRequests::EscalateAssetBySearchTerm, target.c_str()); - - AZ::SystemTickBus::QueueFunction([this, &asset]() { UpgradeComplete(asset); }); - } - else - { - auto streamer = AZ::Interface::Get(); - AZ::IO::FileRequestPtr flushRequest = streamer->FlushCache(target.c_str()); - streamer->SetRequestCompleteCallback(flushRequest, [this, &asset, source, target]([[maybe_unused]] AZ::IO::FileRequestHandle request) - { - // Continue saving. - AZ::SystemTickBus::QueueFunction([this, &asset, source, target]() { RetryMove(asset, source, target); }); - }); - streamer->QueueRequest(flushRequest); - } - } - - bool UpgradeTool::IsOnReadyAssetForCurrentProcess(const AZ::Data::AssetId& assetId) - { - return !m_currentAsset && m_inProgressAsset != m_assetsToUpgrade.end() && m_inProgressAsset->m_assetId == assetId; - } - - bool UpgradeTool::IsCurrentProcessFreeToAbort(const AZ::Data::AssetId& assetId) - { - return !m_scriptCanvasEntity && m_inProgressAsset != m_assetsToUpgrade.end() && m_inProgressAsset->m_assetId == assetId; - } - - bool UpgradeTool::IsUpgradeCompleteForAllAssets() - { - return !m_inProgress && !m_currentAsset && !m_scriptCanvasEntity && m_inProgressAsset == m_assetsToUpgrade.end(); - } - - bool UpgradeTool::IsUpgradeCompleteForCurrentAsset() - { - return !m_inProgress && !m_currentAsset && !m_scriptCanvasEntity && m_inProgressAsset != m_assetsToUpgrade.end(); - } - - void UpgradeTool::ResetUpgradeCurrentAsset() - { - AZ::Data::AssetBus::MultiHandler::BusDisconnect(m_currentAsset.GetId()); - - if (m_scriptCanvasEntity) - { - m_scriptCanvasEntity->Deactivate(); - m_scriptCanvasEntity = nullptr; - } - - if (m_inProgressAsset != m_assetsToUpgrade.end()) - { - m_inProgressAsset = m_assetsToUpgrade.erase(m_inProgressAsset); - } - - m_currentAsset.Release(); - m_currentAsset = {}; - m_inProgress = false; - } - - void UpgradeTool::OnSystemTick() - { - switch (m_state) - { - case UpgradeTool::UpgradeState::Upgrade: - - if (IsUpgradeCompleteForCurrentAsset()) - { - m_inProgress = true; - AZ::Data::AssetInfo& assetToUpgrade = *m_inProgressAsset; - - if (!AZ::Data::AssetBus::MultiHandler::BusIsConnectedId(assetToUpgrade.m_assetId)) - { - AZ::Data::AssetBus::MultiHandler::BusConnect(assetToUpgrade.m_assetId); - } - - auto asset = AZ::Data::AssetManager::Instance().GetAsset(assetToUpgrade.m_assetId, assetToUpgrade.m_assetType, AZ::Data::AssetLoadBehavior::Default); - asset.BlockUntilLoadComplete(); - - auto streamer = AZ::Interface::Get(); - AZ::IO::FileRequestPtr flushRequest = streamer->FlushCache(assetToUpgrade.m_relativePath); - streamer->SetRequestCompleteCallback(flushRequest, []([[maybe_unused]] AZ::IO::FileRequestHandle request) - { - }); - streamer->QueueRequest(flushRequest); - - if (asset.IsReady() || asset.GetStatus() == AZ::Data::AssetData::AssetStatus::ReadyPreNotify) - { - m_currentAsset = asset; - m_scriptCanvasEntity = AssetUpgradeJob(asset); - if (!m_scriptCanvasEntity) - { - ResetUpgradeCurrentAsset(); - } - } - - m_ui->spinner->SetText(QObject::tr("%1").arg(assetToUpgrade.m_relativePath.c_str())); - } - else if (IsUpgradeCompleteForAllAssets()) - { - FinalizeUpgrade(); - } - break; - - case UpgradeTool::UpgradeState::Backup: - - if (m_inProgressAsset != m_assetsToUpgrade.end()) - { - AZ::Data::AssetInfo& assetToBackup = *m_inProgressAsset; - - m_ui->spinner->SetText(QObject::tr("%1").arg(assetToBackup.m_relativePath.c_str())); - - BackupAsset(assetToBackup); - - m_ui->progressBar->setValue(aznumeric_cast(++m_currentAssetIndex)); - } - else - { - BackupComplete(); - } - break; - - default: - break; - } - - AZ::Data::AssetManager::Instance().DispatchEvents(); - AZ::SystemTickBus::ExecuteQueuedEvents(); - - } - - void UpgradeTool::BackupAsset(const AZ::Data::AssetInfo& assetInfo) - { - AZ::IO::FixedMaxPath sourceFilePath; - - // Using this to get the watch folder - AZStd::string watchFolder; - AZ::Data::AssetInfo sourceFileAssetInfo; - bool sourceInfoFound{}; - AzToolsFramework::AssetSystemRequestBus::BroadcastResult(sourceInfoFound, - &AzToolsFramework::AssetSystemRequestBus::Events::GetSourceInfoBySourcePath, - assetInfo.m_relativePath.c_str(), sourceFileAssetInfo, watchFolder); - if (sourceInfoFound) - { - sourceFilePath = AZ::IO::FixedMaxPath(AZStd::string_view(watchFolder)) / sourceFileAssetInfo.m_relativePath; - } - - const auto targetFilePath = m_backupPath / sourceFileAssetInfo.m_relativePath; - - if (AZ::IO::FileIOBase::GetInstance()->Copy(sourceFilePath.c_str(), targetFilePath.c_str()) != AZ::IO::ResultCode::Error) - { - AZ_TracePrintf("Script Canvas", "Backup: %s -> %s\n", sourceFilePath.c_str(), targetFilePath.c_str()); - } - else - { - AZ_TracePrintf("Script Canvas", "(Error) Failed to create backup: %s -> %s\n", sourceFilePath.c_str(), targetFilePath.c_str()); - } - - ++m_inProgressAsset; - } - - void UpgradeTool::UpgradeComplete(const AZ::Data::Asset& asset, bool skipped /*= false*/) - { - m_ui->progressBar->setValue(aznumeric_cast(++m_currentAssetIndex)); - - ResetUpgradeCurrentAsset(); - - if (!skipped) - { - AZStd::string filename; - AzFramework::StringFunc::Path::GetFileName(asset.GetHint().c_str(), filename); - AZ_TracePrintf("Script Canvas", "%s -> Upgraded and Saved!\n", filename.c_str()); - } - } - - void UpgradeTool::FinalizeUpgrade() - { - setWindowFlag(Qt::WindowCloseButtonHint, true); - - AZ::SystemTickBus::Handler::BusDisconnect(); - - m_currentAsset = {}; - - SaveLog(); - - UpgradeNotifications::Bus::Broadcast(&UpgradeNotifications::OnUpgradeComplete); - - AZ_TracePrintf("Script Canvas", "\nUpgrade Complete!\n"); - - DisconnectBuses(); - - accept(); - } - - void UpgradeTool::SaveLog() - { - AZStd::string outputFileName = AZStd::string::format("@log@/ScriptCanvasUpgradeReport.html"); - - char resolvedBuffer[AZ_MAX_PATH_LEN] = { 0 }; - AZ::IO::FileIOBase::GetInstance()->ResolvePath(outputFileName.c_str(), resolvedBuffer, AZ_MAX_PATH_LEN); - - AZStd::string endPath = resolvedBuffer; - AZ::StringFunc::Path::Normalize(endPath); - - AZ::IO::SystemFile outputFile; - if (!outputFile.Open(endPath.c_str(), - AZ::IO::SystemFile::SF_OPEN_CREATE | AZ::IO::SystemFile::SF_OPEN_WRITE_ONLY)) - { - AZ_Error("Script Canvas", false, "Failed to open file for writing: %s", endPath.c_str()); - return; - } - - QDateTime theTime = QDateTime::currentDateTime(); - AZStd::string timeStamp = theTime.toString("yyyy-MM-dd [HH.mm.ss]").toUtf8().data(); - - AZStd::string header = "\n\n\n\n\n"; - header.append(AZStd::string::format("Log captured: %s
\n", timeStamp.c_str()).c_str()); - - outputFile.Write(header.c_str(), header.size()); - for (auto& log : m_logs) - { - AZStd::string logText = AZStd::string::format("%s
", log.c_str()); - AzFramework::StringFunc::Replace(logText, "\n", "
\n"); - outputFile.Write(logText.data(), logText.size()); - } - AZStd::string footer = "\n\n"; - outputFile.Write(footer.c_str(), footer.size()); - - - - outputFile.Close(); - } - - AZ::Entity* UpgradeTool::AssetUpgradeJob(AZ::Data::Asset&) - { - return nullptr; - } - - void UpgradeTool::RetryMove(AZ::Data::Asset& asset, const AZStd::string& source, const AZStd::string& target) - { - auto moveResult = AZ::IO::SmartMove(source.c_str(), target.c_str()); - if (moveResult.GetResultCode() == AZ::IO::ResultCode::Success) - { - // Bump the slice asset up in the asset processor's queue. - AzFramework::AssetSystemRequestBus::Broadcast(&AzFramework::AssetSystem::AssetSystemRequests::EscalateAssetBySearchTerm, target.c_str()); - - AZ::SystemTickBus::QueueFunction([this, &asset]() { UpgradeComplete(asset); }); - } - else - { - auto streamer = AZ::Interface::Get(); - AZ::IO::FileRequestPtr flushRequest = streamer->FlushCache(target.c_str()); - streamer->SetRequestCompleteCallback(flushRequest, [this, &asset, source, target]([[maybe_unused]] AZ::IO::FileRequestHandle request) - { - // Continue saving. - AZ::SystemTickBus::QueueFunction([this, &asset, source, target]() { RetryMove(asset, source, target); }); - }); - streamer->QueueRequest(flushRequest); - - - //AZ::SystemTickBus::QueueFunction([this, &asset, source, target]() { RetryMove(asset, source, target); }); - } - } - - void UpgradeTool::CaptureLogFromTraceBus(const char* /*window*/, const char* message) - { - AZStd::string msg = message; - if (msg.ends_with("\n")) - { - msg = msg.substr(0, msg.size() - 1); - } - - m_logs.push_back(msg); - } - - bool UpgradeTool::OnPreError(const char* window, const char* /*fileName*/, int /*line*/, const char* /*func*/, const char* message) - { - AZStd::string msg = AZStd::string::format("(Error): %s
", message); - CaptureLogFromTraceBus(window, msg.c_str()); - - return false; - } - - bool UpgradeTool::OnPreWarning(const char* window, const char* /*fileName*/, int /*line*/, const char* /*func*/, const char* message) - { - AZStd::string msg = AZStd::string::format("(Warning): %s
", message); - CaptureLogFromTraceBus(window, msg.c_str()); - - return false; - } - - bool UpgradeTool::OnException(const char* message) - { - AZStd::string msg = AZStd::string::format("(Exception): %s
", message); - CaptureLogFromTraceBus("Script Canvas", msg.c_str()); - - return false; - } - - bool UpgradeTool::OnPrintf(const char* window, const char* message) - { - CaptureLogFromTraceBus(window, message); - return false; - } - -#include - -} diff --git a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/UpgradeTool.h b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/UpgradeTool.h deleted file mode 100644 index 8a2034bf4f..0000000000 --- a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/UpgradeTool.h +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright (c) Contributors to the Open 3D Engine Project. - * For complete copyright and license terms please see the LICENSE at the root of this distribution. - * - * SPDX-License-Identifier: Apache-2.0 OR MIT - * - */ - -#pragma once - -#if !defined(Q_MOC_RUN) -#include - -AZ_PUSH_DISABLE_WARNING(4244 4251 4800, "-Wunknown-warning-option") -#include -AZ_POP_DISABLE_WARNING - -#include -#include - -#include - -#include - -#include - -#include -#include -#include -#include -#endif - -class QPushButton; - -namespace Ui -{ - class UpgradeTool; -} - -namespace ScriptCanvasEditor -{ - class KeepEditorAlive; - - //! A tool that collects and upgrades all Script Canvas graphs in the asset catalog - class UpgradeTool - : public AzQtComponents::StyledDialog - , private AZ::SystemTickBus::Handler - , private AZ::Data::AssetBus::MultiHandler - , private UpgradeNotifications::Bus::Handler - , private AZ::Debug::TraceMessageBus::Handler - { - Q_OBJECT - - public: - AZ_CLASS_ALLOCATOR(UpgradeTool, AZ::SystemAllocator, 0); - - UpgradeTool(QWidget* parent = nullptr); - ~UpgradeTool(); - - size_t& UpgradedGraphCount() { return m_upgradedAssets; } - size_t& SkippedGraphCount() { return m_skippedAssets; } - - bool HasBackup() const; - - private: - - bool IsOnReadyAssetForCurrentProcess(const AZ::Data::AssetId& assetId); - bool IsCurrentProcessFreeToAbort(const AZ::Data::AssetId& assetId); - bool IsUpgradeCompleteForAllAssets(); - bool IsUpgradeCompleteForCurrentAsset(); - void ResetUpgradeCurrentAsset(); - - void OnUpgrade(); - void OnNoThanks(); - void UpdateSettings(); - - enum class UpgradeState - { - Inactive, - Backup, - Upgrade - }; - UpgradeState m_state = UpgradeState::Inactive; - - bool DoBackup(); - void BackupAsset(const AZ::Data::AssetInfo& assetInfo); - void BackupComplete(); - - void DoUpgrade(); - void UpgradeComplete(const AZ::Data::Asset&, bool skipped = false); - - AZ::Entity* AssetUpgradeJob(AZ::Data::Asset& asset); - - // SystemTickBus::Handler - void OnSystemTick() override; - // - - // AssetBus::Handler - void OnAssetReady(AZ::Data::Asset asset) override; - void OnAssetError(AZ::Data::Asset asset) override; - void OnAssetUnloaded(const AZ::Data::AssetId assetId, const AZ::Data::AssetType assetType) override; - // - - // AZ::Debug::TranceMessageBus::Handler - bool OnException(const char* /*message*/) override; - bool OnPrintf(const char* /*window*/, const char* /*message*/) override; - bool OnPreError(const char* /*window*/, const char* /*fileName*/, int /*line*/, const char* /*func*/, const char* /*message*/) override; - bool OnPreWarning(const char* /*window*/, const char* /*fileName*/, int /*line*/, const char* /*func*/, const char* /*message*/) override; - // - - void CaptureLogFromTraceBus(const char* window, const char* message); - - void OnGraphUpgradeComplete(AZ::Data::Asset&, bool skipped = false) override; - - void RetryMove(AZ::Data::Asset& asset, const AZStd::string& source, const AZStd::string& target); - - void SaveLog(); - - bool m_inProgress = false; - size_t m_currentAssetIndex = 0; - - size_t m_upgradedAssets = 0; - size_t m_skippedAssets = 0; - - IUpgradeRequests::AssetList m_assetsToUpgrade; - IUpgradeRequests::AssetList::iterator m_inProgressAsset; - - AZ::Data::Asset m_currentAsset; - - AZStd::unique_ptr m_ui; - AZStd::recursive_mutex m_mutex; - - // AZStd::unique_ptr m_keepEditorAlive; - - AZStd::vector m_logs; - - AZ::Entity* m_scriptCanvasEntity = nullptr; - - AZ::IO::Path m_backupPath; - - void FinalizeUpgrade(); - void DisconnectBuses(); - - void closeEvent(QCloseEvent* event) override; - - bool m_overwriteAll = false; - void MakeWriteable(const AzToolsFramework::SourceControlFileInfo& info); - void PerformMove(AZ::Data::Asset& asset, const AZStd::string& source, const AZStd::string& target); - }; -} diff --git a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/UpgradeTool.ui b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/UpgradeTool.ui deleted file mode 100644 index 78a63ce727..0000000000 --- a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/UpgradeTool.ui +++ /dev/null @@ -1,260 +0,0 @@ - - - UpgradeTool - - - Qt::WindowModal - - - - 0 - 0 - 801 - 328 - - - - - 0 - 0 - - - - Script Canvas Upgrade - - - - 5 - - - 5 - - - 5 - - - 5 - - - 5 - - - - - - - - - - 0 - 0 - - - - <html><head/><body><p>Script Canvas graphs are now compiled into Lua by the Asset Processor! In order to complete this upgrade, all graphs in your current project must be updated.</p><p>This upgrading is making changes to the underlying Script Canvas graph format. Depending on the number of scripts you've created this process could take a few minutes to complete. </p><p>This upgrade is required for any projects saved on 1.26 or earlier.</p><p>If you choose &quot;Not now&quot;, your project may not work correctly.</p></body></html> - - - false - - - true - - - - - - - - 0 - 80 - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - true - - - - 0 - 0 - - - - - 0 - 0 - - - - false - - - background-color: rgb(47, 47, 47); - - - QFrame::NoFrame - - - QFrame::Raised - - - - - - - 0 - 0 - - - - - 32 - 32 - - - - false - - - - - - - - 0 - 0 - - - - - - - 24 - - - true - - - - - - - - - - - - - Qt::Vertical - - - QSizePolicy::MinimumExpanding - - - - 20 - 0 - - - - - - - - - - - - Backup my source graphs - - - true - - - - - - - Do not ask me again - - - - - - - - 0 - 0 - - - - Qt::LeftToRight - - - Upgrade - - - - - - - - 0 - 0 - - - - Qt::LeftToRight - - - Not now - - - false - - - - - - - - - - - - - - - - AzQtComponents::StyledBusyLabel - QWidget -
AzQtComponents/Components/StyledBusyLabel.h
- 1 -
-
- - -
diff --git a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/VersionExplorer.cpp b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/VersionExplorer.cpp deleted file mode 100644 index f400f84d51..0000000000 --- a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/VersionExplorer.cpp +++ /dev/null @@ -1,996 +0,0 @@ -/* - * Copyright (c) Contributors to the Open 3D Engine Project. - * For complete copyright and license terms please see the LICENSE at the root of this distribution. - * - * SPDX-License-Identifier: Apache-2.0 OR MIT - * - */ - - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace VersionExplorerCpp -{ - class FileEventHandler - : public AZ::IO::FileIOEventBus::Handler - { - public: - int m_errorCode = 0; - AZStd::string m_fileName; - - FileEventHandler() - { - BusConnect(); - } - - ~FileEventHandler() - { - BusDisconnect(); - } - - void OnError(const AZ::IO::SystemFile* /*file*/, const char* fileName, int errorCode) override - { - m_errorCode = errorCode; - - if (fileName) - { - m_fileName = fileName; - } - } - }; -} - -namespace ScriptCanvasEditor -{ - EditorKeepAlive::EditorKeepAlive() - { - ISystem* system = nullptr; - CrySystemRequestBus::BroadcastResult(system, &CrySystemRequestBus::Events::GetCrySystem); - - m_edKeepEditorActive = system->GetIConsole()->GetCVar("ed_KeepEditorActive"); - - if (m_edKeepEditorActive) - { - m_keepEditorActive = m_edKeepEditorActive->GetIVal(); - m_edKeepEditorActive->Set(1); - } - } - - EditorKeepAlive::~EditorKeepAlive() - { - if (m_edKeepEditorActive) - { - m_edKeepEditorActive->Set(m_keepEditorActive); - } - } - - VersionExplorer::VersionExplorer(QWidget* parent /*= nullptr*/) - : AzQtComponents::StyledDialog(parent) - , m_ui(new Ui::VersionExplorer()) - { - m_ui->setupUi(this); - - m_ui->tableWidget->horizontalHeader()->setVisible(false); - m_ui->tableWidget->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); - m_ui->tableWidget->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Fixed); - m_ui->tableWidget->setColumnWidth(3, 22); - - m_ui->textEdit->setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded); - m_ui->textEdit->setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOn); - - connect(m_ui->scanButton, &QPushButton::pressed, this, &VersionExplorer::OnScan); - connect(m_ui->closeButton, &QPushButton::pressed, this, &VersionExplorer::OnClose); - connect(m_ui->upgradeAllButton, &QPushButton::pressed, this, &VersionExplorer::OnUpgradeAll); - - m_ui->progressBar->setValue(0); - m_ui->progressBar->setVisible(false); - - m_keepEditorAlive = AZStd::make_unique(); - m_inspectingAsset = m_assetsToInspect.end(); - - } - - VersionExplorer::~VersionExplorer() - { - AZ::SystemTickBus::Handler::BusDisconnect(); - - UpgradeNotifications::Bus::Handler::BusDisconnect(); - AZ::Debug::TraceMessageBus::Handler::BusDisconnect(); - - } - - void VersionExplorer::Log(const char* format, ...) - { - if (m_ui->verbose->isChecked()) - { - char sBuffer[2048]; - va_list ArgList; - va_start(ArgList, format); - azvsnprintf(sBuffer, sizeof(sBuffer), format, ArgList); - sBuffer[sizeof(sBuffer) - 1] = '\0'; - va_end(ArgList); - - AZ_TracePrintf(ScriptCanvas::k_VersionExplorerWindow.data(), "%s\n", sBuffer); - } - } - - void VersionExplorer::OnClose() - { - reject(); - } - - bool VersionExplorer::IsUpgrading() const - { - return m_inProgressAsset != m_assetsToUpgrade.end() && m_inProgress; - } - - void VersionExplorer::OnSystemTick() - { - switch (m_state) - { - case ProcessState::Scan: - - if (!m_inProgress && m_inspectingAsset != m_assetsToInspect.end()) - { - m_inProgress = true; - AZ::Data::AssetInfo& assetToUpgrade = *m_inspectingAsset; - m_currentAsset = AZ::Data::AssetManager::Instance().GetAsset(assetToUpgrade.m_assetId, assetToUpgrade.m_assetType, AZ::Data::AssetLoadBehavior::PreLoad); - Log("SystemTick::ProcessState::Scan: %s pre-blocking load hint", m_currentAsset.GetHint().c_str()); - m_currentAsset.BlockUntilLoadComplete(); - if (m_currentAsset.IsReady()) - { - // The asset is ready, grab its info - m_inProgress = true; - InspectAsset(m_currentAsset, assetToUpgrade); - } - else - { - m_ui->tableWidget->insertRow(static_cast(m_currentAssetRowIndex)); - QTableWidgetItem* rowName = new QTableWidgetItem - ( tr(AZStd::string::format("Error: %s", assetToUpgrade.m_relativePath.c_str()).c_str())); - m_ui->tableWidget->setItem(static_cast(m_currentAssetRowIndex), static_cast(ColumnAsset), rowName); - ++m_currentAssetRowIndex; - - Log("SystemTick::ProcessState::Scan: %s post-blocking load, problem loading asset", assetToUpgrade.m_relativePath.c_str()); - ++m_failedAssets; - ScanComplete(m_currentAsset); - } - } - break; - - case ProcessState::Upgrade: - { - AZStd::lock_guard lock(m_mutex); - if (m_upgradeComplete) - { - ++m_upgradeAssetIndex; - m_inProgress = false; - m_ui->progressBar->setVisible(true); - m_ui->progressBar->setValue(m_upgradeAssetIndex); - - if (m_scriptCanvasEntity) - { - m_scriptCanvasEntity->Deactivate(); - m_scriptCanvasEntity = nullptr; - } - - GraphUpgradeCompleteUIUpdate(m_upgradeAsset, m_upgradeResult, m_upgradeMessage); - - if (!m_isUpgradingSingleGraph) - { - if (m_inProgressAsset != m_assetsToUpgrade.end()) - { - m_inProgressAsset = m_assetsToUpgrade.erase(m_inProgressAsset); - } - - if (m_inProgressAsset == m_assetsToUpgrade.end()) - { - FinalizeUpgrade(); - } - } - else - { - m_inProgressAsset = m_assetsToUpgrade.erase(m_inProgressAsset); - m_inProgress = false; - m_state = ProcessState::Inactive; - m_settingsCache.reset(); - AZ::SystemTickBus::Handler::BusDisconnect(); - AZ::Debug::TraceMessageBus::Handler::BusDisconnect(); - } - - m_isUpgradingSingleGraph = false; - - if (m_assetsToUpgrade.empty()) - { - m_ui->upgradeAllButton->setEnabled(false); - } - - m_upgradeComplete = false; - } - - if (!IsUpgrading() && m_state == ProcessState::Upgrade) - { - AZStd::string errorMessage = BackupGraph(*m_inProgressAsset); - // Make the backup - if (errorMessage.empty()) - { - Log("SystemTick::ProcessState::Upgrade: Backup Success %s ", m_inProgressAsset->GetHint().c_str()); - QList items = m_ui->tableWidget->findItems(m_inProgressAsset->GetHint().c_str(), Qt::MatchFlag::MatchExactly); - if (!items.isEmpty()) - { - for (auto* item : items) - { - int row = item->row(); - AzQtComponents::StyledBusyLabel* spinner = qobject_cast(m_ui->tableWidget->cellWidget(row, ColumnStatus)); - spinner->SetIsBusy(true); - } - } - - // Upgrade the graph - UpgradeGraph(*m_inProgressAsset); - } - else - { - Log("SystemTick::ProcessState::Upgrade: Backup Failed %s ", m_inProgressAsset->GetHint().c_str()); - GraphUpgradeComplete(*m_inProgressAsset, OperationResult::Failure, errorMessage); - } - - } - break; - } - default: - break; - } - - FlushLogs(); - - AZ::Data::AssetManager::Instance().DispatchEvents(); - AZ::SystemTickBus::ExecuteQueuedEvents(); - } - - // Backup - - void VersionExplorer::OnUpgradeAll() - { - m_state = ProcessState::Upgrade; - m_settingsCache = AZStd::make_unique(); - ScriptCanvas::Grammar::g_saveRawTranslationOuputToFile = false; - ScriptCanvas::Grammar::g_printAbstractCodeModel = false; - ScriptCanvas::Grammar::g_saveRawTranslationOuputToFile = false; - AZ::Interface::Get()->SetIsUpgrading(true); - AZ::Interface::Get()->ClearGraphsThatNeedUpgrade(); - m_inProgressAsset = m_assetsToUpgrade.begin(); - AZ::Debug::TraceMessageBus::Handler::BusConnect(); - AZ::SystemTickBus::Handler::BusConnect(); - m_ui->progressBar->setVisible(true); - m_ui->progressBar->setRange(0, aznumeric_cast(m_assetsToUpgrade.size())); - m_ui->progressBar->setValue(m_upgradeAssetIndex); - m_keepEditorAlive = AZStd::make_unique(); - } - - AZStd::string VersionExplorer::BackupGraph(const AZ::Data::Asset& asset) - { - if (!m_ui->makeBackupCheckbox->isChecked()) - { - // considered a success - return ""; - } - - auto fileIoBase = AZ::IO::FileIOBase::GetInstance(); - auto settingsRegistry = AZ::SettingsRegistry::Get(); - - QDateTime theTime = QDateTime::currentDateTime(); - QString subFolder = theTime.toString("yyyy-MM-dd [HH.mm.ss]"); - - AZ::IO::FixedMaxPath projectUserPath; - settingsRegistry->Get(projectUserPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectUserPath); - - AZ::IO::FixedMaxPath backupPath = projectUserPath / "ScriptCanvas_BACKUP" / subFolder.toUtf8().data(); - - if (!fileIoBase->Exists(backupPath.c_str())) - { - if (fileIoBase->CreatePath(backupPath.c_str()) != AZ::IO::ResultCode::Success) - { - AZ_Error(ScriptCanvas::k_VersionExplorerWindow.data(), false, "Failed to create backup folder %s", backupPath.c_str()); - return "Failed to create backup folder"; - } - } - - AZStd::string watchFolder; - AZ::Data::AssetInfo assetInfo; - - AZ::IO::FixedMaxPath sourceFilePath; - bool sourceInfoFound{}; - AzToolsFramework::AssetSystemRequestBus::BroadcastResult(sourceInfoFound, - &AzToolsFramework::AssetSystemRequestBus::Events::GetSourceInfoBySourcePath, - asset.GetHint().c_str(), assetInfo, watchFolder); - if (sourceInfoFound) - { - sourceFilePath = AZ::IO::FixedMaxPath(AZStd::string_view(watchFolder)) / assetInfo.m_relativePath; - } - else - { - AZ_Warning(ScriptCanvas::k_VersionExplorerWindow.data(), false, "VersionExplorer::BackupGraph: Failed to find file: %s", asset.GetHint().c_str()); - return "Failed to find source file"; - } - - const AZ::IO::FixedMaxPath targetFilePath = backupPath / assetInfo.m_relativePath; - - if (fileIoBase->Copy(sourceFilePath.c_str(), targetFilePath.c_str()) != AZ::IO::ResultCode::Success) - { - AZ_Warning(ScriptCanvas::k_VersionExplorerWindow.data(), false, "VersionExplorer::BackupGraph: Error creating backup: %s ---> %s\n", sourceFilePath.c_str(), targetFilePath.c_str()); - return "Failed to copy source file to backup location"; - } - - Log("VersionExplorer::BackupGraph: Backed up: %s ---> %s\n", sourceFilePath.c_str(), targetFilePath.c_str()); - return ""; - } - - void VersionExplorer::UpgradeGraph(const AZ::Data::Asset& asset) - { - m_inProgress = true; - m_upgradeComplete = false; - Log("UpgradeGraph %s ", m_inProgressAsset->GetHint().c_str()); - m_ui->spinner->SetText(QObject::tr("Upgrading: %1").arg(asset.GetHint().c_str())); - m_scriptCanvasEntity = nullptr; - - UpgradeNotifications::Bus::Handler::BusConnect(); - - if (asset.GetType() == azrtti_typeid()) - { - ScriptCanvasAsset* scriptCanvasAsset = asset.GetAs(); - AZ_Assert(scriptCanvasAsset, "Unable to get the asset of ScriptCanvasAsset, but received type: %s" - , azrtti_typeid().template ToString().c_str()); - - if (!scriptCanvasAsset) - { - return; - } - - AZ::Entity* scriptCanvasEntity = scriptCanvasAsset->GetScriptCanvasEntity(); - AZ_Assert(scriptCanvasEntity, "VersionExplorer::UpgradeGraph The Script Canvas asset must have a valid entity"); - if (!scriptCanvasEntity) - { - return; - } - - AZ::Entity* queryEntity = nullptr; - AZ::ComponentApplicationBus::BroadcastResult(queryEntity, &AZ::ComponentApplicationRequests::FindEntity, scriptCanvasEntity->GetId()); - if (queryEntity) - { - if (queryEntity->GetState() == AZ::Entity::State::Active) - { - queryEntity->Deactivate(); - } - - scriptCanvasEntity = queryEntity; - } - - if (scriptCanvasEntity->GetState() == AZ::Entity::State::Constructed) - { - scriptCanvasEntity->Init(); - } - - if (scriptCanvasEntity->GetState() == AZ::Entity::State::Init) - { - scriptCanvasEntity->Activate(); - } - - AZ_Assert(scriptCanvasEntity->GetState() == AZ::Entity::State::Active, "Graph entity is not active"); - auto graphComponent = scriptCanvasEntity->FindComponent(); - AZ_Assert(graphComponent, "The Script Canvas entity must have a Graph component"); - - if (graphComponent) - { - m_scriptCanvasEntity = scriptCanvasEntity; - - graphComponent->UpgradeGraph - ( asset - , m_ui->forceUpgrade->isChecked() ? Graph::UpgradeRequest::Forced : Graph::UpgradeRequest::IfOutOfDate - , m_ui->verbose->isChecked()); - } - } - - AZ_Assert(m_scriptCanvasEntity, "The ScriptCanvas asset should have an entity"); - } - - void VersionExplorer::OnGraphUpgradeComplete(AZ::Data::Asset& asset, bool /*skipped*/ /*= false*/) - { - AZStd::string relativePath, fullPath; - AZ::Data::AssetCatalogRequestBus::BroadcastResult(relativePath, &AZ::Data::AssetCatalogRequests::GetAssetPathById, asset.GetId()); - bool fullPathFound = false; - AzToolsFramework::AssetSystemRequestBus::BroadcastResult(fullPathFound, &AzToolsFramework::AssetSystemRequestBus::Events::GetFullSourcePathFromRelativeProductPath, relativePath, fullPath); - if (!fullPathFound) - { - AZ_Error(ScriptCanvas::k_VersionExplorerWindow.data(), false, "Full source path not found for %s", relativePath.c_str()); - } - - auto streamer = AZ::Interface::Get(); - AZ::IO::FileRequestPtr flushRequest = streamer->FlushCache(fullPath); - streamer->SetRequestCompleteCallback(flushRequest, [this, asset]([[maybe_unused]] AZ::IO::FileRequestHandle request) - { - this->OnSourceFileReleased(asset); - }); - streamer->QueueRequest(flushRequest); - } - - void VersionExplorer::OnSourceFileReleased(AZ::Data::Asset asset) - { - AZStd::string relativePath, fullPath; - AZ::Data::AssetCatalogRequestBus::BroadcastResult(relativePath, &AZ::Data::AssetCatalogRequests::GetAssetPathById, asset.GetId()); - bool fullPathFound = false; - AzToolsFramework::AssetSystemRequestBus::BroadcastResult(fullPathFound, &AzToolsFramework::AssetSystemRequestBus::Events::GetFullSourcePathFromRelativeProductPath, relativePath, fullPath); - m_tmpFileName.clear(); - AZStd::string tmpFileName; - // here we are saving the graph to a temp file instead of the original file and then copying the temp file to the original file. - // This ensures that AP will not a get a file change notification on an incomplete graph file causing it to fail processing. Temp files are ignored by AP. - if (!AZ::IO::CreateTempFileName(fullPath.c_str(), tmpFileName)) - { - GraphUpgradeComplete(asset, OperationResult::Failure, "Failure to create temporary file name"); - return; - } - - bool tempSavedSucceeded = false; - AZ::IO::FileIOStream fileStream(tmpFileName.c_str(), AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeText); - if (fileStream.IsOpen()) - { - if (asset.GetType() == azrtti_typeid()) - { - ScriptCanvasEditor::ScriptCanvasAssetHandler handler; - tempSavedSucceeded = handler.SaveAssetData(asset, &fileStream); - } - - fileStream.Close(); - } - - // attempt to remove temporary file no matter what - m_tmpFileName = tmpFileName; - if (!tempSavedSucceeded) - { - GraphUpgradeComplete(asset, OperationResult::Failure, "Save asset data to temporary file failed"); - return; - } - - using SCCommandBus = AzToolsFramework::SourceControlCommandBus; - SCCommandBus::Broadcast(&SCCommandBus::Events::RequestEdit, fullPath.c_str(), true, - [this, asset, fullPath, tmpFileName]([[maybe_unused]] bool success, const AzToolsFramework::SourceControlFileInfo& info) - { - constexpr const size_t k_maxAttemps = 10; - - if (!info.IsReadOnly()) - { - PerformMove(asset, tmpFileName, fullPath, k_maxAttemps); - } - else - { - if (m_overwriteAll) - { - AZ::IO::SystemFile::SetWritable(info.m_filePath.c_str(), true); - PerformMove(asset, tmpFileName, fullPath, k_maxAttemps); - } - else - { - int result = QMessageBox::No; - if (!m_overwriteAll) - { - QMessageBox mb(QMessageBox::Warning, - QObject::tr("Failed to Save Upgraded File"), - QObject::tr("The upgraded file could not be saved because the file is read only.\nDo you want to make it writeable and overwrite it?"), - QMessageBox::YesToAll | QMessageBox::Yes | QMessageBox::No, this); - - result = mb.exec(); - if (result == QMessageBox::YesToAll) - { - m_overwriteAll = true; - } - } - - if (result == QMessageBox::Yes || m_overwriteAll) - { - AZ::IO::SystemFile::SetWritable(info.m_filePath.c_str(), true); - PerformMove(asset, tmpFileName, fullPath, k_maxAttemps); - } - } - } - }); - } - - void VersionExplorer::PerformMove(AZ::Data::Asset asset, AZStd::string source, AZStd::string target - , size_t remainingAttempts) - { - VersionExplorerCpp::FileEventHandler fileEventHandler; - - if (remainingAttempts == 0) - { - // all attempts failed, give up - AZ_Warning(ScriptCanvas::k_VersionExplorerWindow.data(), false, "moving converted file to source destination failed: %s. giving up", target.c_str()); - GraphUpgradeComplete(asset, OperationResult::Failure, "Failed to move updated file from backup to source destination"); - } - else if (remainingAttempts == 2) - { - // before the final attempt, flush all caches - AZ_Warning(ScriptCanvas::k_VersionExplorerWindow.data(), false, "moving converted file to source destination failed: %s, trying again", target.c_str()); - auto streamer = AZ::Interface::Get(); - AZ::IO::FileRequestPtr flushRequest = streamer->FlushCaches(); - streamer->SetRequestCompleteCallback(flushRequest - , [this, asset, remainingAttempts, source, target]([[maybe_unused]] AZ::IO::FileRequestHandle request) - { - // Continue saving. - AZ::SystemTickBus::QueueFunction( - [this, asset, remainingAttempts, source, target](){ PerformMove(asset, source, target, remainingAttempts - 1); }); - }); - streamer->QueueRequest(flushRequest); - } - else - { - // the actual move attempt - auto moveResult = AZ::IO::SmartMove(source.c_str(), target.c_str()); - if (moveResult.GetResultCode() == AZ::IO::ResultCode::Success) - { - m_tmpFileName.clear(); - auto streamer = AZ::Interface::Get(); - AZ::IO::FileRequestPtr flushRequest = streamer->FlushCache(target.c_str()); - // Bump the slice asset up in the asset processor's queue. - AzFramework::AssetSystemRequestBus::Broadcast(&AzFramework::AssetSystem::AssetSystemRequests::EscalateAssetBySearchTerm, target.c_str()); - AZ::SystemTickBus::QueueFunction([this, asset]() - { - GraphUpgradeComplete(asset, OperationResult::Success, ""); - }); - } - else - { - AZ_Warning(ScriptCanvas::k_VersionExplorerWindow.data(), false, "moving converted file to source destination failed: %s, trying again", target.c_str()); - auto streamer = AZ::Interface::Get(); - AZ::IO::FileRequestPtr flushRequest = streamer->FlushCache(target.c_str()); - streamer->SetRequestCompleteCallback(flushRequest, [this, asset, source, target, remainingAttempts]([[maybe_unused]] AZ::IO::FileRequestHandle request) - { - // Continue saving. - AZ::SystemTickBus::QueueFunction([this, asset, source, target, remainingAttempts]() { PerformMove(asset, source, target, remainingAttempts - 1); }); - }); - streamer->QueueRequest(flushRequest); - } - } - } - - void VersionExplorer::GraphUpgradeComplete - ( const AZ::Data::Asset asset, OperationResult result, AZStd::string_view message) - { - AZStd::lock_guard lock(m_mutex); - m_upgradeComplete = true; - m_upgradeResult = result; - m_upgradeMessage = message; - m_upgradeAsset = asset; - - if (!m_tmpFileName.empty()) - { - AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance(); - AZ_Assert(fileIO, "GraphUpgradeComplete: No FileIO instance"); - - if (fileIO->Exists(m_tmpFileName.c_str()) && !fileIO->Remove(m_tmpFileName.c_str())) - { - AZ_TracePrintf(ScriptCanvas::k_VersionExplorerWindow.data(), "Failed to remove temporary file: %s", m_tmpFileName.c_str()); - } - } - - if (m_upgradeResult == OperationResult::Failure) - { - AZ::Interface::Get()->GraphNeedsManualUpgrade(asset.GetId()); - } - - m_tmpFileName.clear(); - } - - void VersionExplorer::GraphUpgradeCompleteUIUpdate - ( const AZ::Data::Asset asset, OperationResult result, AZStd::string_view message) - { - QString text = asset.GetHint().c_str(); - QList items = m_ui->tableWidget->findItems(text, Qt::MatchFlag::MatchExactly); - - if (!items.isEmpty()) - { - for (auto* item : items) - { - int row = item->row(); - QTableWidgetItem* label = m_ui->tableWidget->item(row, ColumnAsset); - QString assetName = asset.GetHint().c_str(); - - if (label->text().compare(assetName) == 0) - { - m_ui->tableWidget->removeCellWidget(row, ColumnAction); - m_ui->tableWidget->removeCellWidget(row, ColumnStatus); - - QToolButton* doneButton = new QToolButton(this); - doneButton->setToolTip("Upgrade complete"); - if (result == OperationResult::Success) - { - doneButton->setIcon(QIcon(":/stylesheet/img/UI20/checkmark-menu.svg")); - } - else - { - doneButton->setIcon(QIcon(":/stylesheet/img/UI20/titlebar-close.svg")); - doneButton->setToolTip(message.data()); - } - - m_ui->tableWidget->setCellWidget(row, ColumnStatus, doneButton); - } - } - } - } - - void VersionExplorer::FinalizeUpgrade() - { - Log("FinalizeUpgrade!"); - m_inProgress = false; - m_assetsToUpgrade.clear(); - m_ui->upgradeAllButton->setEnabled(false); - m_ui->onlyShowOutdated->setEnabled(true); - m_keepEditorAlive.reset(); - m_ui->progressBar->setVisible(false); - - // Manual correction - size_t assetsThatNeedManualInspection = AZ::Interface::Get()->GetGraphsThatNeedManualUpgrade().size(); - if (assetsThatNeedManualInspection > 0) - { - m_ui->spinner->SetText("Some graphs will require manual corrections, you will be prompted to review them upon closing this dialog"); - } - else - { - m_ui->spinner->SetText("Upgrade complete."); - } - - AZ::SystemTickBus::Handler::BusDisconnect(); - AZ::Debug::TraceMessageBus::Handler::BusDisconnect(); - UpgradeNotifications::Bus::Handler::BusDisconnect(); - AZ::Interface::Get()->SetIsUpgrading(false); - m_settingsCache.reset(); - } - - // Scanning - - void VersionExplorer::OnScan() - { - m_assetsToUpgrade.clear(); - m_assetsToInspect.clear(); - m_ui->tableWidget->setRowCount(0); - m_inspectedAssets = 0; - m_currentAssetRowIndex = 0; - IUpgradeRequests* upgradeRequests = AZ::Interface::Get(); - m_assetsToInspect = upgradeRequests->GetAssetsToUpgrade(); - DoScan(); - } - - void VersionExplorer::DoScan() - { - m_state = ProcessState::Scan; - m_settingsCache = AZStd::make_unique(); - ScriptCanvas::Grammar::g_saveRawTranslationOuputToFile = false; - ScriptCanvas::Grammar::g_printAbstractCodeModel = false; - ScriptCanvas::Grammar::g_saveRawTranslationOuputToFile = false; - - AZ::SystemTickBus::Handler::BusConnect(); - AZ::Debug::TraceMessageBus::Handler::BusConnect(); - - if (!m_assetsToInspect.empty()) - { - m_discoveredAssets = m_assetsToInspect.size(); - m_failedAssets = 0; - m_inspectedAssets = 0; - m_currentAssetRowIndex = 0; - m_ui->progressFrame->setVisible(true); - m_ui->progressBar->setVisible(true); - m_ui->progressBar->setRange(0, aznumeric_cast(m_assetsToInspect.size())); - m_ui->progressBar->setValue(0); - - m_ui->spinner->SetIsBusy(true); - m_ui->spinner->SetBusyIconSize(32); - - m_ui->scanButton->setEnabled(false); - m_ui->upgradeAllButton->setEnabled(false); - m_ui->onlyShowOutdated->setEnabled(false); - - m_inspectingAsset = m_assetsToInspect.begin(); - m_keepEditorAlive = AZStd::make_unique(); - } - } - - void VersionExplorer::BackupComplete() - { - m_currentAssetRowIndex = 0; - m_ui->progressBar->setValue(0); - DoScan(); - } - - void VersionExplorer::InspectAsset(AZ::Data::Asset& asset, AZ::Data::AssetInfo& assetInfo) - { - Log("InspectAsset: %s", asset.GetHint().c_str()); - AZ::Entity* scriptCanvasEntity = nullptr; - if (asset.GetType() == azrtti_typeid()) - { - ScriptCanvasAsset* scriptCanvasAsset = asset.GetAs(); - if (!scriptCanvasAsset) - { - Log("InspectAsset: %s, AsestData failed to return ScriptCanvasAsset", asset.GetHint().c_str()); - return; - } - - scriptCanvasEntity = scriptCanvasAsset->GetScriptCanvasEntity(); - AZ_Assert(scriptCanvasEntity, "The Script Canvas asset must have a valid entity"); - } - - auto graphComponent = scriptCanvasEntity->FindComponent(); - AZ_Assert(graphComponent, "The Script Canvas entity must have a Graph component"); - - bool onlyShowOutdatedGraphs = m_ui->onlyShowOutdated->isChecked(); - bool forceUpgrade = m_ui->forceUpgrade->isChecked(); - ScriptCanvas::VersionData graphVersion = graphComponent->GetVersion(); - - - if (!forceUpgrade && onlyShowOutdatedGraphs && graphVersion.IsLatest()) - { - ScanComplete(asset); - Log("InspectAsset: %s, is at latest", asset.GetHint().c_str()); - return; - } - - m_ui->tableWidget->insertRow(static_cast(m_currentAssetRowIndex)); - QTableWidgetItem* rowName = new QTableWidgetItem(tr(asset.GetHint().c_str())); - m_ui->tableWidget->setItem(static_cast(m_currentAssetRowIndex), static_cast(ColumnAsset), rowName); - - if (forceUpgrade || !graphComponent->GetVersion().IsLatest()) - { - m_assetsToUpgrade.push_back(asset); - - AzQtComponents::StyledBusyLabel* spinner = new AzQtComponents::StyledBusyLabel(this); - spinner->SetBusyIconSize(16); - - QPushButton* rowGoToButton = new QPushButton(this); - rowGoToButton->setText("Upgrade"); - rowGoToButton->setEnabled(false); - - connect(rowGoToButton, &QPushButton::clicked, [this, spinner, rowGoToButton, assetInfo] { - - AZ::SystemTickBus::QueueFunction([this, rowGoToButton, spinner, assetInfo]() { - // Queue the process state change because we can't connect to the SystemTick bus in a Qt lambda - UpgradeSingle(rowGoToButton, spinner, assetInfo); - }); - - AZ::SystemTickBus::ExecuteQueuedEvents(); - - }); - - m_ui->tableWidget->setCellWidget(static_cast(m_currentAssetRowIndex), static_cast(ColumnAction), rowGoToButton); - m_ui->tableWidget->setCellWidget(static_cast(m_currentAssetRowIndex), static_cast(ColumnStatus), spinner); - } - - char resolvedBuffer[AZ_MAX_PATH_LEN] = { 0 }; - AZStd::string path = AZStd::string::format("@engroot@/%s", asset.GetHint().c_str()); - AZ::IO::FileIOBase::GetInstance()->ResolvePath(path.c_str(), resolvedBuffer, AZ_MAX_PATH_LEN); - AZ::StringFunc::Path::GetFullPath(resolvedBuffer, path); - AZ::StringFunc::Path::Normalize(path); - - bool result = false; - AZ::Data::AssetInfo info; - AZStd::string watchFolder; - QByteArray assetNameUtf8 = asset.GetHint().c_str(); - AzToolsFramework::AssetSystemRequestBus::BroadcastResult(result, &AzToolsFramework::AssetSystemRequestBus::Events::GetSourceInfoBySourcePath, assetNameUtf8, info, watchFolder); - - AZ_Error(ScriptCanvas::k_VersionExplorerWindow.data(), result, "Failed to locate asset info for '%s'.", assetNameUtf8.constData()); - - QToolButton* browseButton = new QToolButton(this); - browseButton->setToolTip(AzQtComponents::fileBrowserActionName()); - browseButton->setIcon(QIcon(":/stylesheet/img/UI20/browse-edit.svg")); - - QString absolutePath = QDir(watchFolder.c_str()).absoluteFilePath(info.m_relativePath.c_str()); - connect(browseButton, &QPushButton::clicked, [absolutePath] { - AzQtComponents::ShowFileOnDesktop(absolutePath); - }); - - m_ui->tableWidget->setCellWidget(static_cast(m_currentAssetRowIndex), static_cast(ColumnBrowse), browseButton); - ScanComplete(asset); - ++m_inspectedAssets; - ++m_currentAssetRowIndex; - } - - void VersionExplorer::UpgradeSingle - ( QPushButton* rowGoToButton - , AzQtComponents::StyledBusyLabel* spinner - , AZ::Data::AssetInfo assetInfo) - { - AZ::Data::Asset asset = AZ::Data::AssetManager::Instance().GetAsset - ( assetInfo.m_assetId, assetInfo.m_assetType, AZ::Data::AssetLoadBehavior::PreLoad); - - if (asset) - { - asset.BlockUntilLoadComplete(); - - if (asset.IsReady()) - { - AZ::Interface::Get()->SetIsUpgrading(true); - m_isUpgradingSingleGraph = true; - m_logs.clear(); - m_ui->textEdit->clear(); - spinner->SetIsBusy(true); - rowGoToButton->setEnabled(false); - - m_inProgressAsset = AZStd::find_if(m_assetsToUpgrade.begin(), m_assetsToUpgrade.end() - , [asset](const UpgradeAssets::value_type& assetToUpgrade) - { - return assetToUpgrade.GetId() == asset.GetId(); - }); - - m_state = ProcessState::Upgrade; - AZ::SystemTickBus::Handler::BusConnect(); - } - } - } - - void VersionExplorer::ScanComplete(const AZ::Data::Asset& asset) - { - Log("ScanComplete: %s", asset.GetHint().c_str()); - m_inProgress = false; - m_ui->progressBar->setValue(aznumeric_cast(m_currentAssetRowIndex)); - m_ui->scanButton->setEnabled(true); - - m_inspectingAsset = m_assetsToInspect.erase(m_inspectingAsset); - FlushLogs(); - - if (m_inspectingAsset == m_assetsToInspect.end()) - { - AZ::SystemTickBus::QueueFunction([this]() { FinalizeScan(); }); - - if (!m_assetsToUpgrade.empty()) - { - m_ui->upgradeAllButton->setEnabled(true); - } - } - } - - void VersionExplorer::FinalizeScan() - { - Log("FinalizeScan()"); - - m_ui->spinner->SetIsBusy(false); - m_ui->onlyShowOutdated->setEnabled(true); - - // Enable all the Upgrade buttons - for (int row = 0; row < m_ui->tableWidget->rowCount(); ++row) - { - QPushButton* button = qobject_cast(m_ui->tableWidget->cellWidget(row, ColumnAction)); - if (button) - { - button->setEnabled(true); - } - } - - QString spinnerText = QStringLiteral("Scan Complete"); - if (m_assetsToUpgrade.empty()) - { - spinnerText.append(" - No graphs require upgrade!"); - } - else - { - spinnerText.append(QString::asprintf(" - Discovered: %zu, Inspected: %zu, Failed: %zu, Upgradeable: %zu" - , m_discoveredAssets, m_inspectedAssets, m_failedAssets, m_assetsToUpgrade.size())); - } - - - m_ui->spinner->SetText(spinnerText); - m_ui->progressBar->setVisible(false); - - if (!m_assetsToUpgrade.empty()) - { - m_ui->upgradeAllButton->setEnabled(true); - } - - AZ::SystemTickBus::Handler::BusDisconnect(); - AZ::Debug::TraceMessageBus::Handler::BusDisconnect(); - UpgradeNotifications::Bus::Handler::BusDisconnect(); - - m_keepEditorAlive.reset(); - m_settingsCache.reset(); - m_state = ProcessState::Inactive; - } - - void VersionExplorer::FlushLogs() - { - if (m_logs.empty()) - { - return; - } - - const QTextCursor oldCursor = m_ui->textEdit->textCursor(); - QScrollBar* scrollBar = m_ui->textEdit->verticalScrollBar(); - - m_ui->textEdit->moveCursor(QTextCursor::End); - QTextCursor textCursor = m_ui->textEdit->textCursor(); - - while (!m_logs.empty()) - { - auto line = "\n" + m_logs.front(); - - m_logs.pop_front(); - - textCursor.insertText(line.c_str()); - } - - scrollBar->setValue(scrollBar->maximum()); - m_ui->textEdit->moveCursor(QTextCursor::StartOfLine); - - } - - bool VersionExplorer::CaptureLogFromTraceBus(const char* window, const char* message) - { - if (m_ui->updateReportingOnly->isChecked() && window != ScriptCanvas::k_VersionExplorerWindow) - { - return true; - } - - AZStd::string msg = message; - if (msg.ends_with("\n")) - { - msg = msg.substr(0, msg.size() - 1); - } - - m_logs.push_back(msg); - return m_ui->updateReportingOnly->isChecked(); - } - - bool VersionExplorer::OnPreError(const char* window, const char* /*fileName*/, int /*line*/, const char* /*func*/, const char* message) - { - AZStd::string msg = AZStd::string::format("(Error): %s", message); - return CaptureLogFromTraceBus(window, msg.c_str()); - } - - bool VersionExplorer::OnPreWarning(const char* window, const char* /*fileName*/, int /*line*/, const char* /*func*/, const char* message) - { - AZStd::string msg = AZStd::string::format("(Warning): %s", message); - return CaptureLogFromTraceBus(window, msg.c_str()); - } - - bool VersionExplorer::OnException(const char* message) - { - AZStd::string msg = AZStd::string::format("(Exception): %s", message); - return CaptureLogFromTraceBus("Script Canvas", msg.c_str()); - } - - bool VersionExplorer::OnPrintf(const char* window, const char* message) - { - return CaptureLogFromTraceBus(window, message); - } - - void VersionExplorer::closeEvent(QCloseEvent* event) - { - m_keepEditorAlive.reset(); - - AzQtComponents::StyledDialog::closeEvent(event); - } - -#include - -} diff --git a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/VersionExplorer.h b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/VersionExplorer.h deleted file mode 100644 index acb28a7dba..0000000000 --- a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/VersionExplorer.h +++ /dev/null @@ -1,180 +0,0 @@ -/* - * Copyright (c) Contributors to the Open 3D Engine Project. - * For complete copyright and license terms please see the LICENSE at the root of this distribution. - * - * SPDX-License-Identifier: Apache-2.0 OR MIT - * - */ - -#pragma once - -#if !defined(Q_MOC_RUN) -#include - -AZ_PUSH_DISABLE_WARNING(4244 4251 4800, "-Wunknown-warning-option") -#include -AZ_POP_DISABLE_WARNING - -#include -#include -#include - -#include -#include - -#include -#include -#include -#endif - -class QPushButton; - -namespace Ui -{ - class VersionExplorer; -} - -namespace AzQtComponents -{ - class StyledBusyLabel; -} - -namespace ScriptCanvasEditor -{ - //! Scoped utility to set and restore the "ed_KeepEditorActive" CVar in order to allow - //! the upgrade tool to work even if the editor is not in the foreground - class EditorKeepAlive - { - public: - EditorKeepAlive(); - ~EditorKeepAlive(); - - private: - int m_keepEditorActive; - ICVar* m_edKeepEditorActive; - }; - - //! A tool that collects and upgrades all Script Canvas graphs in the asset catalog - class VersionExplorer - : public AzQtComponents::StyledDialog - , private AZ::SystemTickBus::Handler - , private UpgradeNotifications::Bus::Handler - , private AZ::Debug::TraceMessageBus::Handler - { - Q_OBJECT - - public: - AZ_CLASS_ALLOCATOR(VersionExplorer, AZ::SystemAllocator, 0); - - explicit VersionExplorer(QWidget* parent = nullptr); - ~VersionExplorer(); - - private: - - static constexpr int ColumnAsset = 0; - static constexpr int ColumnAction = 1; - static constexpr int ColumnBrowse = 2; - static constexpr int ColumnStatus = 3; - - void OnScan(); - void OnClose(); - - enum class ProcessState - { - Inactive, - Backup, - Scan, - Upgrade, - }; - ProcessState m_state = ProcessState::Inactive; - - void DoScan(); - void ScanComplete(const AZ::Data::Asset&); - - void InspectAsset(AZ::Data::Asset& asset, AZ::Data::AssetInfo& assetInfo); - - void OnUpgradeAll(); - - // SystemTickBus::Handler - void OnSystemTick() override; - // - - // AZ::Debug::TranceMessageBus::Handler - bool OnException(const char* /*message*/) override; - bool OnPrintf(const char* /*window*/, const char* /*message*/) override; - bool OnPreError(const char* /*window*/, const char* /*fileName*/, int /*line*/, const char* /*func*/, const char* /*message*/) override; - bool OnPreWarning(const char* /*window*/, const char* /*fileName*/, int /*line*/, const char* /*func*/, const char* /*message*/) override; - // - - bool CaptureLogFromTraceBus(const char* window, const char* message); - - enum class OperationResult - { - Success, - Failure, - }; - - void GraphUpgradeComplete(const AZ::Data::Asset, OperationResult result, AZStd::string_view message); - - bool IsUpgrading() const; - - bool m_inProgress = false; - // scan fields - size_t m_currentAssetRowIndex = 0; - size_t m_inspectedAssets = 0; - size_t m_failedAssets = 0; - size_t m_discoveredAssets = 0; - - IUpgradeRequests::AssetList m_assetsToInspect; - IUpgradeRequests::AssetList::iterator m_inspectingAsset; - using UpgradeAssets = AZStd::vector>; - UpgradeAssets m_assetsToUpgrade; - UpgradeAssets::iterator m_inProgressAsset; - - AZ::Data::Asset m_currentAsset; - - AZStd::unique_ptr m_ui; - - AZStd::unique_ptr m_settingsCache; - - // upgrade fields - AZStd::recursive_mutex m_mutex; - bool m_upgradeComplete = false; - AZ::Data::Asset m_upgradeAsset; - int m_upgradeAssetIndex = 0; - OperationResult m_upgradeResult; - AZStd::string m_upgradeMessage; - AZStd::string m_tmpFileName; - - AZStd::unique_ptr m_keepEditorAlive; - - AZStd::deque m_logs; - - AZ::Entity* m_scriptCanvasEntity = nullptr; - - bool m_isUpgradingSingleGraph = false; - - void UpgradeSingle(QPushButton* item, AzQtComponents::StyledBusyLabel* spinner, AZ::Data::AssetInfo assetInfo); - - void FlushLogs(); - - void FinalizeUpgrade(); - void FinalizeScan(); - - void BackupComplete(); - AZStd::string BackupGraph(const AZ::Data::Asset&); - void UpgradeGraph(const AZ::Data::Asset&); - - void GraphUpgradeCompleteUIUpdate(const AZ::Data::Asset asset, OperationResult result, AZStd::string_view message); - void OnGraphUpgradeComplete(AZ::Data::Asset&, bool skipped = false) override; - - void OnSourceFileReleased(AZ::Data::Asset asset); - - void closeEvent(QCloseEvent* event) override; - - bool m_overwriteAll = false; - void PerformMove(AZ::Data::Asset asset, AZStd::string source, AZStd::string target, size_t remainingAttempts); - - void Log(const char* format, ...); - }; -} diff --git a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/VersionExplorerLog.cpp b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/VersionExplorerLog.cpp new file mode 100644 index 0000000000..c1faedc3a3 --- /dev/null +++ b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/VersionExplorerLog.cpp @@ -0,0 +1,100 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include + +namespace ScriptCanvasEditor +{ + namespace VersionExplorer + { + void Log::Activate() + { + AZ::Debug::TraceMessageBus::Handler::BusConnect(); + LogBus::Handler::BusConnect(); + } + + void Log::Clear() + { + m_logs.clear(); + } + + bool Log::CaptureFromTraceBus(const char* window, const char* message) + { + if (m_isExclusiveReportingEnabled && window != ScriptCanvas::k_VersionExplorerWindow) + { + return true; + } + + AZStd::string msg = message; + if (msg.ends_with("\n")) + { + msg = msg.substr(0, msg.size() - 1); + } + + m_logs.push_back(msg); + return m_isExclusiveReportingEnabled; + } + + void Log::Deactivate() + { + AZ::Debug::TraceMessageBus::Handler::BusDisconnect(); + } + + void Log::Entry(const char* format, ...) + { + if (m_isVerbose) + { + char sBuffer[2048]; + va_list ArgList; + va_start(ArgList, format); + azvsnprintf(sBuffer, sizeof(sBuffer), format, ArgList); + sBuffer[sizeof(sBuffer) - 1] = '\0'; + va_end(ArgList); + AZ_TracePrintf(ScriptCanvas::k_VersionExplorerWindow.data(), "%s\n", sBuffer); + } + } + + const AZStd::vector* Log::GetEntries() const + { + return &m_logs; + } + + void Log::SetVersionExporerExclusivity(bool enabled) + { + m_isExclusiveReportingEnabled = enabled; + } + + void Log::SetVerbose(bool verbosity) + { + m_isVerbose = verbosity; + } + + bool Log::OnPreError(const char* window, const char* /*fileName*/, int /*line*/, const char* /*func*/, const char* message) + { + AZStd::string msg = AZStd::string::format("(Error): %s", message); + return CaptureFromTraceBus(window, msg.c_str()); + } + + bool Log::OnPreWarning(const char* window, const char* /*fileName*/, int /*line*/, const char* /*func*/, const char* message) + { + AZStd::string msg = AZStd::string::format("(Warning): %s", message); + return CaptureFromTraceBus(window, msg.c_str()); + } + + bool Log::OnException(const char* message) + { + AZStd::string msg = AZStd::string::format("(Exception): %s", message); + return CaptureFromTraceBus("Script Canvas", msg.c_str()); + } + + bool Log::OnPrintf(const char* window, const char* message) + { + return CaptureFromTraceBus(window, message); + } + } +} diff --git a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/VersionExplorerLog.h b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/VersionExplorerLog.h new file mode 100644 index 0000000000..7399b085da --- /dev/null +++ b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/VersionExplorerLog.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include +#include + +namespace ScriptCanvasEditor +{ + namespace VersionExplorer + { + class Log + : private AZ::Debug::TraceMessageBus::Handler + , private LogBus::Handler + { + public: + AZ_CLASS_ALLOCATOR(Log, AZ::SystemAllocator, 0); + + void Activate() override; + void Clear() override; + void Deactivate(); + void Entry(const char* format, ...) override; + const AZStd::vector* GetEntries() const override; + void SetVersionExporerExclusivity(bool enabled) override; + void SetVerbose(bool verbosity) override; + + private: + bool m_isExclusiveReportingEnabled = false; + bool m_isVerbose = false; + AZStd::vector m_logs; + + bool CaptureFromTraceBus(const char* window, const char* message); + bool OnException(const char* /*message*/) override; + bool OnPrintf(const char* /*window*/, const char* /*message*/) override; + bool OnPreError(const char* /*window*/, const char* /*fileName*/, int /*line*/, const char* /*func*/, const char* /*message*/) override; + bool OnPreWarning(const char* /*window*/, const char* /*fileName*/, int /*line*/, const char* /*func*/, const char* /*message*/) override; + }; + } +} diff --git a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/VersionExplorer.ui b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/View.ui similarity index 99% rename from Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/VersionExplorer.ui rename to Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/View.ui index 0cd67bcf22..f549c821d6 100644 --- a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/VersionExplorer.ui +++ b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/View.ui @@ -1,7 +1,7 @@ - VersionExplorer - + View + Qt::WindowModal diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/AbstractCodeModel.cpp b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/AbstractCodeModel.cpp index 4509e7e907..7d76dbc27d 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/AbstractCodeModel.cpp +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/AbstractCodeModel.cpp @@ -4320,6 +4320,12 @@ namespace ScriptCanvas { AZ_Assert(execution->GetSymbol() != Symbol::FunctionDefinition, "Function definition input is not handled in AbstractCodeModel::ParseInputDatum"); + if (!input.GetDataType().IsValid()) + { + AddError(nullptr, aznew Internal::ParseError(execution->GetNodeId(), ParseErrors::InvalidDataTypeInInput)); + return; + } + auto nodes = execution->GetId().m_node->GetConnectedNodes(input); if (nodes.empty()) { diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Core/ReceiveScriptEvent.cpp b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Core/ReceiveScriptEvent.cpp index 5a2eb80187..c548176af5 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Core/ReceiveScriptEvent.cpp +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Core/ReceiveScriptEvent.cpp @@ -67,7 +67,7 @@ namespace ScriptCanvas void ReceiveScriptEvent::PopulateAsset(AZ::Data::Asset asset, SlotIdMapping& populationMapping) { - if (CreateHandler(asset)) + if (InitializeDefinition(asset)) { if (!CreateEbus()) { @@ -140,7 +140,6 @@ namespace ScriptCanvas } } - void ReceiveScriptEvent::InitializeEvent(AZ::Data::Asset asset, int eventIndex, SlotIdMapping& populationMapping) { if (!m_handler) @@ -353,6 +352,13 @@ namespace ScriptCanvas AZStd::optional ReceiveScriptEvent::GetEventIndex(AZStd::string eventName) const { + if (!m_handler) + { + const_cast(this)->InitializeDefinition(m_asset); + const_cast(this)->CreateEbus(); + } + + AZ_Error("ScriptCanvas", m_handler != nullptr, "GetEventIndex called and handler was not created"); return m_handler ? AZStd::optional(m_handler->GetFunctionIndex(eventName.c_str())) : AZStd::nullopt; } @@ -490,15 +496,10 @@ namespace ScriptCanvas return m_definition.IsAddressRequired() || (slot && slot->GetDataType().IsValid()); } - bool ReceiveScriptEvent::CreateHandler(AZ::Data::Asset asset) + bool ReceiveScriptEvent::InitializeDefinition(AZ::Data::Asset asset) { AZStd::lock_guard lock(m_mutex); - if (m_handler) - { - return true; - } - if (!asset) { return false; @@ -518,12 +519,11 @@ namespace ScriptCanvas } return true; - } void ReceiveScriptEvent::OnScriptEventReady(const AZ::Data::Asset& asset) { - if (CreateHandler(asset)) + if (InitializeDefinition(asset)) { CompleteInitialize(asset); } @@ -531,7 +531,7 @@ namespace ScriptCanvas bool ReceiveScriptEvent::CreateEbus() { - if (!m_ebus) + if (!m_ebus || !m_handler) { AZ::BehaviorContext* behaviorContext = nullptr; AZ::ComponentApplicationBus::BroadcastResult(behaviorContext, &AZ::ComponentApplicationBus::Events::GetBehaviorContext); @@ -547,13 +547,11 @@ namespace ScriptCanvas AZ_Assert(m_ebus, "Behavior Context EBus does not exist: %s", m_definition.GetName().c_str()); AZ_Assert(m_ebus->m_createHandler, "The ebus %s has no create handler!", m_definition.GetName().c_str()); AZ_Assert(m_ebus->m_destroyHandler, "The ebus %s has no destroy handler!", m_definition.GetName().c_str()); - AZ_Verify(m_ebus->m_createHandler->InvokeResult(m_handler, &m_definition), "Behavior Context EBus handler creation failed %s", m_definition.GetName().c_str()); - AZ_Assert(m_handler, "Ebus create handler failed %s", m_definition.GetName().c_str()); } - return true; + return m_ebus != nullptr && m_handler != nullptr; } bool ReceiveScriptEvent::IsOutOfDate(const VersionData& graphVersion) const @@ -597,7 +595,6 @@ namespace ScriptCanvas } } - AZStd::string ReceiveScriptEvent::GetUpdateString() const { if (m_ebus) diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Core/ReceiveScriptEvent.h b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Core/ReceiveScriptEvent.h index 0f8d673629..a41d0ba3f9 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Core/ReceiveScriptEvent.h +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Core/ReceiveScriptEvent.h @@ -101,7 +101,7 @@ namespace ScriptCanvas Internal::ScriptEventEntry ConfigureEbusEntry(const ScriptEvents::Method& methodDefinition, const AZ::BehaviorEBusHandler::BusForwarderEvent& event, SlotIdMapping& populationMapping); - bool CreateHandler(AZ::Data::Asset asset); + bool InitializeDefinition(AZ::Data::Asset asset); void CompleteInitialize(AZ::Data::Asset asset); void PopulateAsset(AZ::Data::Asset asset, SlotIdMapping& populationMapping); bool m_eventInitComplete = false; @@ -120,7 +120,6 @@ namespace ScriptCanvas bool m_autoConnectToGraphOwner = true; bool m_connected; - }; } } diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Core/ScriptEventBase.cpp b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Core/ScriptEventBase.cpp index 3b01a8fa7c..e4cbfc94f7 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Core/ScriptEventBase.cpp +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Core/ScriptEventBase.cpp @@ -122,11 +122,8 @@ namespace ScriptCanvas void ScriptEventBase::OnActivate() { - if (!m_asset || m_asset.GetStatus() == AZ::Data::AssetData::AssetStatus::NotLoaded) - { - m_asset = AZ::Data::AssetManager::Instance().GetAsset(m_scriptEventAssetId, AZ::Data::AssetLoadBehavior::PreLoad); - m_asset.BlockUntilLoadComplete(); - } + m_asset = AZ::Data::AssetManager::Instance().GetAsset(m_scriptEventAssetId, AZ::Data::AssetLoadBehavior::PreLoad); + m_asset.BlockUntilLoadComplete(); } void ScriptEventBase::OnAssetReady(AZ::Data::Asset asset) diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Results/ErrorText.h b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Results/ErrorText.h index da9a1ebc1f..a803ea862e 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Results/ErrorText.h +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Results/ErrorText.h @@ -41,6 +41,7 @@ namespace ScriptCanvas constexpr const char* InactiveGraph = "This graph defines no functions, it is never activated, and will never execute. Add a Start node or connect an event handler or define functions."; constexpr const char* InfiniteLoopWritingToVariable = "infinite loop when writing to variable"; constexpr const char* InfiniteSelfActivationLoop = "infinite loop when activating the entity that owns this graph"; + constexpr const char* InvalidDataTypeInInput = "invalid data type in input slot"; constexpr const char* MetaDataNeedsTupleTypeIdForEventCall = "Meta data needs an AzTypeId for Tuple if an EBus call returns multiple types to ScriptCanvas"; constexpr const char* MetaDataRequiredForEventCall = "Meta data is required for ACM node that is an EBus call"; constexpr const char* MissingFalseExecutionSlotOnIf = "A 'False' Execution output slot is required in an If branch node"; diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Translation/GraphToLua.cpp b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Translation/GraphToLua.cpp index 9a1ef73126..ee7eb704bb 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Translation/GraphToLua.cpp +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Translation/GraphToLua.cpp @@ -246,8 +246,7 @@ namespace ScriptCanvas } else { - ErrorList errors = { AZStd::string("provide translation failure details") }; - return AZ::Failure(errors); + return AZ::Failure(translation.MoveErrors()); } } @@ -874,7 +873,13 @@ namespace ScriptCanvas AZStd::optional eventIndex = ebusHandling->m_node->GetEventIndex(nameAndEventThread.first); if (!eventIndex) { - AddError(nullptr, aznew Internal::ParseError(ebusHandling->m_node->GetEntityId(), AZStd::string::format("EBus handler did not return a valid index for event %s", nameAndEventThread.first.c_str()))); + AddError(nullptr, + aznew Internal::ParseError( + ebusHandling->m_node->GetEntityId() + , AZStd::string::format + ( "EBus Handler %s did not return a valid index for event %s" + , ebusHandling->m_ebusName.c_str() + , nameAndEventThread.first.c_str()))); return; } diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Translation/GraphToX.cpp b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Translation/GraphToX.cpp index 09315ec4b0..d21fb354b1 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Translation/GraphToX.cpp +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Translation/GraphToX.cpp @@ -107,27 +107,9 @@ namespace ScriptCanvas m_translationDuration = AZStd::chrono::microseconds(AZStd::chrono::system_clock::now() - m_translationStartTime).count(); } - AZStd::string GraphToX::ResolveScope(const AZStd::vector& namespaces) - { - AZStd::string resolution; - - if (!namespaces.empty()) - { - resolution = Grammar::ToIdentifier(namespaces[0]); - - for (size_t index = 1; index < namespaces.size(); ++index) - { - resolution += m_configuration.m_lexicalScopeDelimiter; - resolution += Grammar::ToIdentifier(namespaces[index]); - } - } - - return resolution; - } - - void GraphToX::SingleLineComment(Writer& writer) + AZStd::vector&& GraphToX::MoveErrors() { - writer.Write(m_configuration.m_singleLineComment); + return AZStd::move(m_errors); } void GraphToX::OpenBlockComment(Writer& writer) @@ -160,6 +142,29 @@ namespace ScriptCanvas writer.Indent(); } + AZStd::string GraphToX::ResolveScope(const AZStd::vector& namespaces) + { + AZStd::string resolution; + + if (!namespaces.empty()) + { + resolution = Grammar::ToIdentifier(namespaces[0]); + + for (size_t index = 1; index < namespaces.size(); ++index) + { + resolution += m_configuration.m_lexicalScopeDelimiter; + resolution += Grammar::ToIdentifier(namespaces[index]); + } + } + + return resolution; + } + + void GraphToX::SingleLineComment(Writer& writer) + { + writer.Write(m_configuration.m_singleLineComment); + } + void GraphToX::WriteCopyright(Writer& writer) { OpenBlockComment(writer); diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Translation/GraphToX.h b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Translation/GraphToX.h index 062a58efe1..c4e189679a 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Translation/GraphToX.h +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Translation/GraphToX.h @@ -51,12 +51,13 @@ namespace ScriptCanvas AZStd::string_view GetGraphName() const; AZStd::string_view GetFullPath() const; AZStd::sys_time_t GetTranslationDuration() const; - AZStd::string ResolveScope(const AZStd::vector& namespaces); - void SingleLineComment(Writer& writer); + AZStd::vector&& MoveErrors(); void OpenBlockComment(Writer& writer); void OpenFunctionBlock(Writer& writer); void OpenNamespace(Writer& writer, AZStd::string_view ns); void OpenScope(Writer& writer); + AZStd::string ResolveScope(const AZStd::vector& namespaces); + void SingleLineComment(Writer& writer); void WriteCopyright(Writer& writer); void WriteDoNotModify(Writer& writer); void WriteLastWritten(Writer& writer); diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Translation/TranslationResult.cpp b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Translation/TranslationResult.cpp index dd8b42730d..c39cac13a3 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Translation/TranslationResult.cpp +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Translation/TranslationResult.cpp @@ -72,6 +72,15 @@ namespace ScriptCanvas resultString += entry->GetDescription(); } + for (const auto& errors : m_errors) + { + for (auto& entry : errors.second) + { + resultString += "* "; + resultString += entry->GetDescription(); + } + } + return resultString; } diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Translation/TranslationResult.h b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Translation/TranslationResult.h index 0cde56d5dd..bd616fbfbb 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Translation/TranslationResult.h +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Translation/TranslationResult.h @@ -12,10 +12,11 @@ #include #include #include -#include #include -#include +#include +#include #include +#include namespace AZ { @@ -85,7 +86,7 @@ namespace ScriptCanvas AZStd::sys_time_t m_duration; }; - using ErrorList = AZStd::vector; + using ErrorList = AZStd::vector; using Errors = AZStd::unordered_map; using Translations = AZStd::unordered_map; @@ -102,7 +103,6 @@ namespace ScriptCanvas const AZStd::sys_time_t m_translationDuration; Result(AZStd::string invalidSourceInfo); - Result(Result&& source); Result(Grammar::AbstractCodeModelConstPtr model); Result(Grammar::AbstractCodeModelConstPtr model, Translations&& translations, Errors&& errors); diff --git a/Gems/ScriptCanvas/Code/scriptcanvasgem_editor_files.cmake b/Gems/ScriptCanvas/Code/scriptcanvasgem_editor_files.cmake index 52b1cc4772..609f7cd454 100644 --- a/Gems/ScriptCanvas/Code/scriptcanvasgem_editor_files.cmake +++ b/Gems/ScriptCanvas/Code/scriptcanvasgem_editor_files.cmake @@ -259,15 +259,24 @@ set(FILES Editor/View/Windows/ScriptCanvasContextMenus.cpp Editor/View/Windows/ScriptCanvasContextMenus.h Editor/View/Windows/ScriptCanvasEditorResources.qrc - Editor/View/Windows/Tools/UpgradeTool/UpgradeHelper.h + Editor/View/Windows/Tools/UpgradeTool/Controller.cpp + Editor/View/Windows/Tools/UpgradeTool/Controller.h + Editor/View/Windows/Tools/UpgradeTool/FileSaver.cpp + Editor/View/Windows/Tools/UpgradeTool/FileSaver.h + Editor/View/Windows/Tools/UpgradeTool/LogTraits.h + Editor/View/Windows/Tools/UpgradeTool/Model.cpp + Editor/View/Windows/Tools/UpgradeTool/Model.h + Editor/View/Windows/Tools/UpgradeTool/Modifier.cpp + Editor/View/Windows/Tools/UpgradeTool/Modifier.h + Editor/View/Windows/Tools/UpgradeTool/ModelTraits.h + Editor/View/Windows/Tools/UpgradeTool/Scanner.cpp + Editor/View/Windows/Tools/UpgradeTool/Scanner.h Editor/View/Windows/Tools/UpgradeTool/UpgradeHelper.cpp + Editor/View/Windows/Tools/UpgradeTool/UpgradeHelper.h Editor/View/Windows/Tools/UpgradeTool/UpgradeHelper.ui - Editor/View/Windows/Tools/UpgradeTool/UpgradeTool.cpp - Editor/View/Windows/Tools/UpgradeTool/UpgradeTool.h - Editor/View/Windows/Tools/UpgradeTool/UpgradeTool.ui - Editor/View/Windows/Tools/UpgradeTool/VersionExplorer.h - Editor/View/Windows/Tools/UpgradeTool/VersionExplorer.cpp - Editor/View/Windows/Tools/UpgradeTool/VersionExplorer.ui + Editor/View/Windows/Tools/UpgradeTool/VersionExplorerLog.cpp + Editor/View/Windows/Tools/UpgradeTool/VersionExplorerLog.h + Editor/View/Windows/Tools/UpgradeTool/View.ui Editor/Framework/ScriptCanvasGraphUtilities.inl Editor/Framework/ScriptCanvasGraphUtilities.h Editor/Framework/ScriptCanvasTraceUtilities.h diff --git a/Gems/ScriptCanvasTesting/Assets/ScriptCanvas/UnitTests/LY_SC_UnitTest_MultipleOut.scriptcanvas b/Gems/ScriptCanvasTesting/Assets/ScriptCanvas/UnitTests/LY_SC_UnitTest_MultipleOut.scriptcanvas index 1d316c4dc0..1770d0e6c2 100644 --- a/Gems/ScriptCanvasTesting/Assets/ScriptCanvas/UnitTests/LY_SC_UnitTest_MultipleOut.scriptcanvas +++ b/Gems/ScriptCanvasTesting/Assets/ScriptCanvas/UnitTests/LY_SC_UnitTest_MultipleOut.scriptcanvas @@ -1,5227 +1,3238 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +{ + "Type": "JsonSerialization", + "Version": 1, + "ClassName": "ScriptCanvasData", + "ClassData": { + "m_scriptCanvas": { + "Id": { + "id": 20005528169920 + }, + "Name": "LY_SC_UnitTest_MultipleOut", + "Components": { + "Component_[14476721853276197761]": { + "$type": "EditorGraphVariableManagerComponent", + "Id": 14476721853276197761, + "m_variableData": { + "m_nameVariableMap": [ + { + "Key": { + "m_id": "{0D12DC58-0CED-4E69-9234-7B03F80A0008}" + }, + "Value": { + "Datum": { + "scriptCanvasType": { + "m_type": 0 + }, + "isNullPointer": false, + "$type": "bool", + "value": false, + "label": "Boolean" + }, + "VariableId": { + "m_id": "{0D12DC58-0CED-4E69-9234-7B03F80A0008}" + }, + "VariableName": "Exec 1" + } + }, + { + "Key": { + "m_id": "{2ACB3F3A-6D87-4EBC-AD66-F8702F8F62DD}" + }, + "Value": { + "Datum": { + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 3.0, + "label": "Three" + }, + "VariableId": { + "m_id": "{2ACB3F3A-6D87-4EBC-AD66-F8702F8F62DD}" + }, + "VariableName": "Three" + } + }, + { + "Key": { + "m_id": "{69CFFC85-A02B-4538-8704-D5EB9D768BFA}" + }, + "Value": { + "Datum": { + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 0.0, + "label": "Number" + }, + "VariableId": { + "m_id": "{69CFFC85-A02B-4538-8704-D5EB9D768BFA}" + }, + "VariableName": "counter" + } + }, + { + "Key": { + "m_id": "{84E8E7E7-7A51-4DB2-8E03-ECEB4E448C2B}" + }, + "Value": { + "Datum": { + "scriptCanvasType": { + "m_type": 0 + }, + "isNullPointer": false, + "$type": "bool", + "value": false, + "label": "Boolean" + }, + "VariableId": { + "m_id": "{84E8E7E7-7A51-4DB2-8E03-ECEB4E448C2B}" + }, + "VariableName": "Exec 3" + } + }, + { + "Key": { + "m_id": "{DB5D6C5D-FD69-474C-BBBE-4984C5715FA8}" + }, + "Value": { + "Datum": { + "scriptCanvasType": { + "m_type": 0 + }, + "isNullPointer": false, + "$type": "bool", + "value": false, + "label": "Boolean" + }, + "VariableId": { + "m_id": "{DB5D6C5D-FD69-474C-BBBE-4984C5715FA8}" + }, + "VariableName": "Exec 2" + } + } + ] + } + }, + "Component_[16621147190561896838]": { + "$type": "{4D755CA9-AB92-462C-B24F-0B3376F19967} Graph", + "Id": 16621147190561896838, + "m_graphData": { + "m_nodes": [ + { + "Id": { + "id": 20044182875584 + }, + "Name": "SC-Node(Start)", + "Components": { + "Component_[10424525019465666117]": { + "$type": "Start", + "Id": 10424525019465666117, + "Slots": [ + { + "id": { + "m_id": "{25061884-0633-4061-B583-0796CDC9B215}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "toolTip": "Signaled when the entity that owns this graph is fully activated.", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + } + ] + } + } + }, + { + "Id": { + "id": 20014118104512 + }, + "Name": "SC-Node(OperatorAdd)", + "Components": { + "Component_[12328748346053958726]": { + "$type": "OperatorAdd", + "Id": 12328748346053958726, + "Slots": [ + { + "id": { + "m_id": "{AC7B582F-C1DE-4C3C-ACC9-D729BE959BF5}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{5E73CCFA-3E3B-44B5-9E9E-FD9250FCE078}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{B20F1E7F-B2BA-43E4-AD32-D58B8CFA5B57}" + }, + "DynamicTypeOverride": 3, + "contracts": [ + { + "$type": "SlotTypeContract" + }, + null, + { + "$type": "MathOperatorContract", + "NativeTypes": [ + { + "m_type": 3 + }, + { + "m_type": 6 + }, + { + "m_type": 8 + }, + { + "m_type": 9 + }, + { + "m_type": 10 + }, + { + "m_type": 11 + }, + { + "m_type": 12 + }, + { + "m_type": 14 + }, + { + "m_type": 15 + } + ] + } + ], + "slotName": "Number", + "toolTip": "An operand to use in performing the specified Operation", + "DisplayDataType": { + "m_type": 3 + }, + "DisplayGroup": { + "Value": 1114760223 + }, + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DynamicGroup": { + "Value": 1114760223 + }, + "DataType": 1, + "IsReference": true, + "VariableReference": { + "m_id": "{69CFFC85-A02B-4538-8704-D5EB9D768BFA}" + } + }, + { + "id": { + "m_id": "{265B8942-C187-4D83-A017-20BEE053B96D}" + }, + "DynamicTypeOverride": 3, + "contracts": [ + { + "$type": "SlotTypeContract" + }, + null, + { + "$type": "MathOperatorContract", + "NativeTypes": [ + { + "m_type": 3 + }, + { + "m_type": 6 + }, + { + "m_type": 8 + }, + { + "m_type": 9 + }, + { + "m_type": 10 + }, + { + "m_type": 11 + }, + { + "m_type": 12 + }, + { + "m_type": 14 + }, + { + "m_type": 15 + } + ] + } + ], + "slotName": "Number", + "toolTip": "An operand to use in performing the specified Operation", + "DisplayDataType": { + "m_type": 3 + }, + "DisplayGroup": { + "Value": 1114760223 + }, + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DynamicGroup": { + "Value": 1114760223 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{5B25184E-0BC4-44A8-9539-772AC75432B4}" + }, + "DynamicTypeOverride": 3, + "contracts": [ + { + "$type": "SlotTypeContract" + }, + { + "$type": "MathOperatorContract", + "NativeTypes": [ + { + "m_type": 3 + }, + { + "m_type": 6 + }, + { + "m_type": 8 + }, + { + "m_type": 9 + }, + { + "m_type": 10 + }, + { + "m_type": 11 + }, + { + "m_type": 12 + }, + { + "m_type": 14 + }, + { + "m_type": 15 + } + ] + } + ], + "slotName": "Result", + "toolTip": "The result of the specified operation", + "DisplayDataType": { + "m_type": 3 + }, + "DisplayGroup": { + "Value": 1114760223 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DynamicGroup": { + "Value": 1114760223 + }, + "DataType": 1, + "IsReference": true, + "VariableReference": { + "m_id": "{69CFFC85-A02B-4538-8704-D5EB9D768BFA}" + } + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 0.0, + "label": "Number" + }, + { + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 1.0, + "label": "Number" + } + ] + } + } + }, + { + "Id": { + "id": 20022708039104 + }, + "Name": "SC-Node(OperatorAdd)", + "Components": { + "Component_[12328748346053958726]": { + "$type": "OperatorAdd", + "Id": 12328748346053958726, + "Slots": [ + { + "id": { + "m_id": "{AC7B582F-C1DE-4C3C-ACC9-D729BE959BF5}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{5E73CCFA-3E3B-44B5-9E9E-FD9250FCE078}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{B20F1E7F-B2BA-43E4-AD32-D58B8CFA5B57}" + }, + "DynamicTypeOverride": 3, + "contracts": [ + { + "$type": "SlotTypeContract" + }, + null, + { + "$type": "MathOperatorContract", + "NativeTypes": [ + { + "m_type": 3 + }, + { + "m_type": 6 + }, + { + "m_type": 8 + }, + { + "m_type": 9 + }, + { + "m_type": 10 + }, + { + "m_type": 11 + }, + { + "m_type": 12 + }, + { + "m_type": 14 + }, + { + "m_type": 15 + } + ] + } + ], + "slotName": "Number", + "toolTip": "An operand to use in performing the specified Operation", + "DisplayDataType": { + "m_type": 3 + }, + "DisplayGroup": { + "Value": 1114760223 + }, + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DynamicGroup": { + "Value": 1114760223 + }, + "DataType": 1, + "IsReference": true, + "VariableReference": { + "m_id": "{69CFFC85-A02B-4538-8704-D5EB9D768BFA}" + } + }, + { + "id": { + "m_id": "{265B8942-C187-4D83-A017-20BEE053B96D}" + }, + "DynamicTypeOverride": 3, + "contracts": [ + { + "$type": "SlotTypeContract" + }, + null, + { + "$type": "MathOperatorContract", + "NativeTypes": [ + { + "m_type": 3 + }, + { + "m_type": 6 + }, + { + "m_type": 8 + }, + { + "m_type": 9 + }, + { + "m_type": 10 + }, + { + "m_type": 11 + }, + { + "m_type": 12 + }, + { + "m_type": 14 + }, + { + "m_type": 15 + } + ] + } + ], + "slotName": "Number", + "toolTip": "An operand to use in performing the specified Operation", + "DisplayDataType": { + "m_type": 3 + }, + "DisplayGroup": { + "Value": 1114760223 + }, + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DynamicGroup": { + "Value": 1114760223 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{5B25184E-0BC4-44A8-9539-772AC75432B4}" + }, + "DynamicTypeOverride": 3, + "contracts": [ + { + "$type": "SlotTypeContract" + }, + { + "$type": "MathOperatorContract", + "NativeTypes": [ + { + "m_type": 3 + }, + { + "m_type": 6 + }, + { + "m_type": 8 + }, + { + "m_type": 9 + }, + { + "m_type": 10 + }, + { + "m_type": 11 + }, + { + "m_type": 12 + }, + { + "m_type": 14 + }, + { + "m_type": 15 + } + ] + } + ], + "slotName": "Result", + "toolTip": "The result of the specified operation", + "DisplayDataType": { + "m_type": 3 + }, + "DisplayGroup": { + "Value": 1114760223 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DynamicGroup": { + "Value": 1114760223 + }, + "DataType": 1, + "IsReference": true, + "VariableReference": { + "m_id": "{69CFFC85-A02B-4538-8704-D5EB9D768BFA}" + } + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 0.0, + "label": "Number" + }, + { + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 1.0, + "label": "Number" + } + ] + } + } + }, + { + "Id": { + "id": 20061362744768 + }, + "Name": "SC-Node(OperatorAdd)", + "Components": { + "Component_[12328748346053958726]": { + "$type": "OperatorAdd", + "Id": 12328748346053958726, + "Slots": [ + { + "id": { + "m_id": "{AC7B582F-C1DE-4C3C-ACC9-D729BE959BF5}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{5E73CCFA-3E3B-44B5-9E9E-FD9250FCE078}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{B20F1E7F-B2BA-43E4-AD32-D58B8CFA5B57}" + }, + "DynamicTypeOverride": 3, + "contracts": [ + { + "$type": "SlotTypeContract" + }, + null, + { + "$type": "MathOperatorContract", + "NativeTypes": [ + { + "m_type": 3 + }, + { + "m_type": 6 + }, + { + "m_type": 8 + }, + { + "m_type": 9 + }, + { + "m_type": 10 + }, + { + "m_type": 11 + }, + { + "m_type": 12 + }, + { + "m_type": 14 + }, + { + "m_type": 15 + } + ] + } + ], + "slotName": "Number", + "toolTip": "An operand to use in performing the specified Operation", + "DisplayDataType": { + "m_type": 3 + }, + "DisplayGroup": { + "Value": 1114760223 + }, + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DynamicGroup": { + "Value": 1114760223 + }, + "DataType": 1, + "IsReference": true, + "VariableReference": { + "m_id": "{69CFFC85-A02B-4538-8704-D5EB9D768BFA}" + } + }, + { + "id": { + "m_id": "{265B8942-C187-4D83-A017-20BEE053B96D}" + }, + "DynamicTypeOverride": 3, + "contracts": [ + { + "$type": "SlotTypeContract" + }, + null, + { + "$type": "MathOperatorContract", + "NativeTypes": [ + { + "m_type": 3 + }, + { + "m_type": 6 + }, + { + "m_type": 8 + }, + { + "m_type": 9 + }, + { + "m_type": 10 + }, + { + "m_type": 11 + }, + { + "m_type": 12 + }, + { + "m_type": 14 + }, + { + "m_type": 15 + } + ] + } + ], + "slotName": "Number", + "toolTip": "An operand to use in performing the specified Operation", + "DisplayDataType": { + "m_type": 3 + }, + "DisplayGroup": { + "Value": 1114760223 + }, + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DynamicGroup": { + "Value": 1114760223 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{5B25184E-0BC4-44A8-9539-772AC75432B4}" + }, + "DynamicTypeOverride": 3, + "contracts": [ + { + "$type": "SlotTypeContract" + }, + { + "$type": "MathOperatorContract", + "NativeTypes": [ + { + "m_type": 3 + }, + { + "m_type": 6 + }, + { + "m_type": 8 + }, + { + "m_type": 9 + }, + { + "m_type": 10 + }, + { + "m_type": 11 + }, + { + "m_type": 12 + }, + { + "m_type": 14 + }, + { + "m_type": 15 + } + ] + } + ], + "slotName": "Result", + "toolTip": "The result of the specified operation", + "DisplayDataType": { + "m_type": 3 + }, + "DisplayGroup": { + "Value": 1114760223 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DynamicGroup": { + "Value": 1114760223 + }, + "DataType": 1, + "IsReference": true, + "VariableReference": { + "m_id": "{69CFFC85-A02B-4538-8704-D5EB9D768BFA}" + } + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 0.0, + "label": "Number" + }, + { + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 1.0, + "label": "Number" + } + ] + } + } + }, + { + "Id": { + "id": 20009823137216 + }, + "Name": "SC-Node(EqualTo)", + "Components": { + "Component_[13545476611799837370]": { + "$type": "EqualTo", + "Id": 13545476611799837370, + "Slots": [ + { + "id": { + "m_id": "{3A36FFEC-1706-4E84-AF24-9CA117ED9A08}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Result", + "DisplayDataType": { + "m_type": 0 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{71837FF2-8438-4225-AA73-B017D5097FE7}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "toolTip": "Signal to perform the evaluation when desired.", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{85BEF751-FEBE-457B-B73A-F9334E248174}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "True", + "toolTip": "Signaled if the result of the operation is true.", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{F17CFAD4-F1FB-4E17-B466-DDEAE1D0C4D7}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "False", + "toolTip": "Signaled if the result of the operation is false.", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{F89D9F4D-8840-4420-BB13-A62BE5E0248C}" + }, + "DynamicTypeOverride": 3, + "contracts": [ + { + "$type": "SlotTypeContract" + }, + null + ], + "slotName": "Value A", + "DisplayDataType": { + "m_type": 3 + }, + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DynamicGroup": { + "Value": 3545012108 + }, + "DataType": 1, + "IsReference": true, + "VariableReference": { + "m_id": "{69CFFC85-A02B-4538-8704-D5EB9D768BFA}" + } + }, + { + "id": { + "m_id": "{11C3CEF3-4EED-405E-88CB-B25B55C28D5D}" + }, + "DynamicTypeOverride": 3, + "contracts": [ + { + "$type": "SlotTypeContract" + }, + null + ], + "slotName": "Value B", + "DisplayDataType": { + "m_type": 3 + }, + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DynamicGroup": { + "Value": 3545012108 + }, + "DataType": 1, + "IsReference": true, + "VariableReference": { + "m_id": "{2ACB3F3A-6D87-4EBC-AD66-F8702F8F62DD}" + } + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 0.0, + "label": "Value A" + }, + { + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 0.0, + "label": "Value B" + } + ] + } + } + }, + { + "Id": { + "id": 20065657712064 + }, + "Name": "SC-Node(EqualTo)", + "Components": { + "Component_[13545476611799837370]": { + "$type": "EqualTo", + "Id": 13545476611799837370, + "Slots": [ + { + "id": { + "m_id": "{3A36FFEC-1706-4E84-AF24-9CA117ED9A08}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Result", + "DisplayDataType": { + "m_type": 0 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{71837FF2-8438-4225-AA73-B017D5097FE7}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "toolTip": "Signal to perform the evaluation when desired.", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{85BEF751-FEBE-457B-B73A-F9334E248174}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "True", + "toolTip": "Signaled if the result of the operation is true.", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{F17CFAD4-F1FB-4E17-B466-DDEAE1D0C4D7}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "False", + "toolTip": "Signaled if the result of the operation is false.", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{F89D9F4D-8840-4420-BB13-A62BE5E0248C}" + }, + "DynamicTypeOverride": 3, + "contracts": [ + { + "$type": "SlotTypeContract" + }, + null + ], + "slotName": "Value A", + "DisplayDataType": { + "m_type": 3 + }, + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DynamicGroup": { + "Value": 3545012108 + }, + "DataType": 1, + "IsReference": true, + "VariableReference": { + "m_id": "{69CFFC85-A02B-4538-8704-D5EB9D768BFA}" + } + }, + { + "id": { + "m_id": "{11C3CEF3-4EED-405E-88CB-B25B55C28D5D}" + }, + "DynamicTypeOverride": 3, + "contracts": [ + { + "$type": "SlotTypeContract" + }, + null + ], + "slotName": "Value B", + "DisplayDataType": { + "m_type": 3 + }, + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DynamicGroup": { + "Value": 3545012108 + }, + "DataType": 1, + "IsReference": true, + "VariableReference": { + "m_id": "{2ACB3F3A-6D87-4EBC-AD66-F8702F8F62DD}" + } + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 0.0, + "label": "Value A" + }, + { + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 0.0, + "label": "Value B" + } + ] + } + } + }, + { + "Id": { + "id": 20069952679360 + }, + "Name": "SC-Node(EqualTo)", + "Components": { + "Component_[13545476611799837370]": { + "$type": "EqualTo", + "Id": 13545476611799837370, + "Slots": [ + { + "id": { + "m_id": "{3A36FFEC-1706-4E84-AF24-9CA117ED9A08}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Result", + "DisplayDataType": { + "m_type": 0 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{71837FF2-8438-4225-AA73-B017D5097FE7}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "toolTip": "Signal to perform the evaluation when desired.", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{85BEF751-FEBE-457B-B73A-F9334E248174}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "True", + "toolTip": "Signaled if the result of the operation is true.", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{F17CFAD4-F1FB-4E17-B466-DDEAE1D0C4D7}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "False", + "toolTip": "Signaled if the result of the operation is false.", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{F89D9F4D-8840-4420-BB13-A62BE5E0248C}" + }, + "DynamicTypeOverride": 3, + "contracts": [ + { + "$type": "SlotTypeContract" + }, + null + ], + "slotName": "Value A", + "DisplayDataType": { + "m_type": 3 + }, + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DynamicGroup": { + "Value": 3545012108 + }, + "DataType": 1, + "IsReference": true, + "VariableReference": { + "m_id": "{69CFFC85-A02B-4538-8704-D5EB9D768BFA}" + } + }, + { + "id": { + "m_id": "{11C3CEF3-4EED-405E-88CB-B25B55C28D5D}" + }, + "DynamicTypeOverride": 3, + "contracts": [ + { + "$type": "SlotTypeContract" + }, + null + ], + "slotName": "Value B", + "DisplayDataType": { + "m_type": 3 + }, + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DynamicGroup": { + "Value": 3545012108 + }, + "DataType": 1, + "IsReference": true, + "VariableReference": { + "m_id": "{2ACB3F3A-6D87-4EBC-AD66-F8702F8F62DD}" + } + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 0.0, + "label": "Value A" + }, + { + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 0.0, + "label": "Value B" + } + ] + } + } + }, + { + "Id": { + "id": 20048477842880 + }, + "Name": "SC Node(SetVariable)", + "Components": { + "Component_[14507547243373092664]": { + "$type": "SetVariableNode", + "Id": 14507547243373092664, + "Slots": [ + { + "id": { + "m_id": "{A23CADFC-70B4-45EB-8178-D9570EE659ED}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "toolTip": "When signaled sends the variable referenced by this node to a Data Output slot", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{69C4E25F-AD39-4CE9-9E19-87DC8C6B43AB}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "toolTip": "Signaled after the referenced variable has been pushed to the Data Output slot", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{87AF01EE-69E7-441F-BE3D-8DBD95475AF9}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + }, + null + ], + "slotName": "Boolean", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{D0C36DFD-8ED8-4E64-8765-556123BAF3C1}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Boolean", + "DisplayDataType": { + "m_type": 0 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DataType": 1 + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 0 + }, + "isNullPointer": false, + "$type": "bool", + "value": true, + "label": "Boolean" + } + ], + "m_variableId": { + "m_id": "{DB5D6C5D-FD69-474C-BBBE-4984C5715FA8}" + }, + "m_variableDataInSlotId": { + "m_id": "{87AF01EE-69E7-441F-BE3D-8DBD95475AF9}" + }, + "m_variableDataOutSlotId": { + "m_id": "{D0C36DFD-8ED8-4E64-8765-556123BAF3C1}" + } + } + } + }, + { + "Id": { + "id": 20057067777472 + }, + "Name": "SC-Node(Print)", + "Components": { + "Component_[17364567436915815279]": { + "$type": "Print", + "Id": 17364567436915815279, + "Slots": [ + { + "id": { + "m_id": "{DE4E1FFE-8F36-4272-81AD-9973CC9DC197}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "toolTip": "Input signal", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{32561DF2-D512-49DD-8DCC-5FB951122DF5}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + } + ], + "m_format": "Hello", + "m_unresolvedString": [ + "Hello" + ] + } + } + }, + { + "Id": { + "id": 20052772810176 + }, + "Name": "SC Node(SetVariable)", + "Components": { + "Component_[18169977638437689378]": { + "$type": "SetVariableNode", + "Id": 18169977638437689378, + "Slots": [ + { + "id": { + "m_id": "{F65A6861-482D-4014-8532-31F31E9F64AC}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "toolTip": "When signaled sends the variable referenced by this node to a Data Output slot", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{D4DD8502-2960-4E11-81F5-3CCAC7FCFBCE}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "toolTip": "Signaled after the referenced variable has been pushed to the Data Output slot", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{0E4216CC-2501-4ED7-8B85-343813337526}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + }, + null + ], + "slotName": "Boolean", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{B150FE31-45A9-4E45-929A-AE9799BA8E31}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Boolean", + "DisplayDataType": { + "m_type": 0 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DataType": 1 + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 0 + }, + "isNullPointer": false, + "$type": "bool", + "value": true, + "label": "Boolean" + } + ], + "m_variableId": { + "m_id": "{0D12DC58-0CED-4E69-9234-7B03F80A0008}" + }, + "m_variableDataInSlotId": { + "m_id": "{0E4216CC-2501-4ED7-8B85-343813337526}" + }, + "m_variableDataOutSlotId": { + "m_id": "{B150FE31-45A9-4E45-929A-AE9799BA8E31}" + } + } + } + }, + { + "Id": { + "id": 20018413071808 + }, + "Name": "SC Node(SetVariable)", + "Components": { + "Component_[2433126846967649500]": { + "$type": "SetVariableNode", + "Id": 2433126846967649500, + "Slots": [ + { + "id": { + "m_id": "{EA436CD0-DBC5-4DE6-B658-BA88DB2DD8FB}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "toolTip": "When signaled sends the variable referenced by this node to a Data Output slot", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{EF2E6C1F-3960-45D1-AC20-B49B92785FF9}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "toolTip": "Signaled after the referenced variable has been pushed to the Data Output slot", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{B2EB9354-2D69-4A25-990E-E321FDF0EDDA}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + }, + null + ], + "slotName": "Boolean", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{FD662FEC-08F9-410B-BAA9-1AF2D8229766}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Boolean", + "DisplayDataType": { + "m_type": 0 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DataType": 1 + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 0 + }, + "isNullPointer": false, + "$type": "bool", + "value": true, + "label": "Boolean" + } + ], + "m_variableId": { + "m_id": "{84E8E7E7-7A51-4DB2-8E03-ECEB4E448C2B}" + }, + "m_variableDataInSlotId": { + "m_id": "{B2EB9354-2D69-4A25-990E-E321FDF0EDDA}" + }, + "m_variableDataOutSlotId": { + "m_id": "{FD662FEC-08F9-410B-BAA9-1AF2D8229766}" + } + } + } + }, + { + "Id": { + "id": 20027003006400 + }, + "Name": "SC-Node(Expect True)", + "Components": { + "Component_[4200368161720158321]": { + "$type": "{E42861BD-1956-45AE-8DD7-CCFC1E3E5ACF} Method", + "Id": 4200368161720158321, + "Slots": [ + { + "isVisibile": false, + "id": { + "m_id": "{982114AA-80C2-479A-BB0E-040B8C119422}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + }, + null + ], + "slotName": "EntityID: 0", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{58E5597B-3073-45DD-86B7-E14097E3EC0B}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + }, + null + ], + "slotName": "Candidate", + "toolTip": "a value that must be true", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1, + "IsReference": true, + "VariableReference": { + "m_id": "{0D12DC58-0CED-4E69-9234-7B03F80A0008}" + } + }, + { + "id": { + "m_id": "{232AB412-02EB-4201-BA96-BEC573A758EF}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + }, + null + ], + "slotName": "Report", + "toolTip": "additional notes for the test report", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{9E9BE7E3-A17F-4603-8F71-59130F2D0E7E}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{CAFD7A46-861D-4156-A15B-38E798044A36}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 1 + }, + "isNullPointer": false, + "$type": "EntityId", + "value": { + "id": 4276206253 + } + }, + { + "scriptCanvasType": { + "m_type": 0 + }, + "isNullPointer": false, + "$type": "bool", + "value": false, + "label": "Candidate" + }, + { + "scriptCanvasType": { + "m_type": 5 + }, + "isNullPointer": false, + "$type": "{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9} AZStd::string", + "value": "", + "label": "Report" + } + ], + "methodType": 2, + "methodName": "Expect True", + "className": "Unit Testing", + "resultSlotIDs": [ + {} + ], + "prettyClassName": "Unit Testing" + } + } + }, + { + "Id": { + "id": 20035592940992 + }, + "Name": "SC-Node(Expect True)", + "Components": { + "Component_[4200368161720158321]": { + "$type": "{E42861BD-1956-45AE-8DD7-CCFC1E3E5ACF} Method", + "Id": 4200368161720158321, + "Slots": [ + { + "isVisibile": false, + "id": { + "m_id": "{982114AA-80C2-479A-BB0E-040B8C119422}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + }, + null + ], + "slotName": "EntityID: 0", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{58E5597B-3073-45DD-86B7-E14097E3EC0B}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + }, + null + ], + "slotName": "Candidate", + "toolTip": "a value that must be true", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1, + "IsReference": true, + "VariableReference": { + "m_id": "{0D12DC58-0CED-4E69-9234-7B03F80A0008}" + } + }, + { + "id": { + "m_id": "{232AB412-02EB-4201-BA96-BEC573A758EF}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + }, + null + ], + "slotName": "Report", + "toolTip": "additional notes for the test report", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{9E9BE7E3-A17F-4603-8F71-59130F2D0E7E}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{CAFD7A46-861D-4156-A15B-38E798044A36}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 1 + }, + "isNullPointer": false, + "$type": "EntityId", + "value": { + "id": 4276206253 + } + }, + { + "scriptCanvasType": { + "m_type": 0 + }, + "isNullPointer": false, + "$type": "bool", + "value": false, + "label": "Candidate" + }, + { + "scriptCanvasType": { + "m_type": 5 + }, + "isNullPointer": false, + "$type": "{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9} AZStd::string", + "value": "", + "label": "Report" + } + ], + "methodType": 2, + "methodName": "Expect True", + "className": "Unit Testing", + "resultSlotIDs": [ + {} + ], + "prettyClassName": "Unit Testing" + } + } + }, + { + "Id": { + "id": 20039887908288 + }, + "Name": "SC-Node(Expect True)", + "Components": { + "Component_[4200368161720158321]": { + "$type": "{E42861BD-1956-45AE-8DD7-CCFC1E3E5ACF} Method", + "Id": 4200368161720158321, + "Slots": [ + { + "isVisibile": false, + "id": { + "m_id": "{982114AA-80C2-479A-BB0E-040B8C119422}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + }, + null + ], + "slotName": "EntityID: 0", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{58E5597B-3073-45DD-86B7-E14097E3EC0B}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + }, + null + ], + "slotName": "Candidate", + "toolTip": "a value that must be true", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1, + "IsReference": true, + "VariableReference": { + "m_id": "{84E8E7E7-7A51-4DB2-8E03-ECEB4E448C2B}" + } + }, + { + "id": { + "m_id": "{232AB412-02EB-4201-BA96-BEC573A758EF}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + }, + null + ], + "slotName": "Report", + "toolTip": "additional notes for the test report", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{9E9BE7E3-A17F-4603-8F71-59130F2D0E7E}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{CAFD7A46-861D-4156-A15B-38E798044A36}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 1 + }, + "isNullPointer": false, + "$type": "EntityId", + "value": { + "id": 4276206253 + } + }, + { + "scriptCanvasType": { + "m_type": 0 + }, + "isNullPointer": false, + "$type": "bool", + "value": false, + "label": "Candidate" + }, + { + "scriptCanvasType": { + "m_type": 5 + }, + "isNullPointer": false, + "$type": "{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9} AZStd::string", + "value": "", + "label": "Report" + } + ], + "methodType": 2, + "methodName": "Expect True", + "className": "Unit Testing", + "resultSlotIDs": [ + {} + ], + "prettyClassName": "Unit Testing" + } + } + }, + { + "Id": { + "id": 20031297973696 + }, + "Name": "SC-Node(Mark Complete)", + "Components": { + "Component_[4383017650724706609]": { + "$type": "{E42861BD-1956-45AE-8DD7-CCFC1E3E5ACF} Method", + "Id": 4383017650724706609, + "Slots": [ + { + "isVisibile": false, + "id": { + "m_id": "{716C2DBF-F48F-48DC-A1B0-2A0D05ED2DC3}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + }, + null + ], + "slotName": "EntityID: 0", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{39BDE16A-0CBB-46D1-9C26-24F85A7183FC}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + }, + null + ], + "slotName": "Report", + "toolTip": "additional notes for the test report", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{496BD222-4D88-4AD0-992E-3873EB3E51F9}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{AD0780F8-7FB5-480D-B7C0-9CC8208223C4}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 1 + }, + "isNullPointer": false, + "$type": "EntityId", + "value": { + "id": 4276206253 + } + }, + { + "scriptCanvasType": { + "m_type": 5 + }, + "isNullPointer": false, + "$type": "{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9} AZStd::string", + "value": "", + "label": "Report" + } + ], + "methodType": 2, + "methodName": "Mark Complete", + "className": "Unit Testing", + "resultSlotIDs": [ + {} + ], + "prettyClassName": "Unit Testing" + } + } + } + ], + "m_connections": [ + { + "Id": { + "id": 20074247646656 + }, + "Name": "srcEndpoint=(On Graph Start: Out), destEndpoint=(Print: In)", + "Components": { + "Component_[9544956642945817670]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 9544956642945817670, + "sourceEndpoint": { + "nodeId": { + "id": 20044182875584 + }, + "slotId": { + "m_id": "{25061884-0633-4061-B583-0796CDC9B215}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 20057067777472 + }, + "slotId": { + "m_id": "{DE4E1FFE-8F36-4272-81AD-9973CC9DC197}" + } + } + } + } + }, + { + "Id": { + "id": 20078542613952 + }, + "Name": "srcEndpoint=(Print: Out), destEndpoint=(Set Variable: In)", + "Components": { + "Component_[5846765668694970625]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 5846765668694970625, + "sourceEndpoint": { + "nodeId": { + "id": 20057067777472 + }, + "slotId": { + "m_id": "{32561DF2-D512-49DD-8DCC-5FB951122DF5}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 20052772810176 + }, + "slotId": { + "m_id": "{F65A6861-482D-4014-8532-31F31E9F64AC}" + } + } + } + } + }, + { + "Id": { + "id": 20082837581248 + }, + "Name": "srcEndpoint=(Print: Out), destEndpoint=(Set Variable: In)", + "Components": { + "Component_[12462935459010319786]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 12462935459010319786, + "sourceEndpoint": { + "nodeId": { + "id": 20057067777472 + }, + "slotId": { + "m_id": "{32561DF2-D512-49DD-8DCC-5FB951122DF5}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 20048477842880 + }, + "slotId": { + "m_id": "{A23CADFC-70B4-45EB-8178-D9570EE659ED}" + } + } + } + } + }, + { + "Id": { + "id": 20087132548544 + }, + "Name": "srcEndpoint=(Print: Out), destEndpoint=(Set Variable: In)", + "Components": { + "Component_[15367103920447716135]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 15367103920447716135, + "sourceEndpoint": { + "nodeId": { + "id": 20057067777472 + }, + "slotId": { + "m_id": "{32561DF2-D512-49DD-8DCC-5FB951122DF5}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 20018413071808 + }, + "slotId": { + "m_id": "{EA436CD0-DBC5-4DE6-B658-BA88DB2DD8FB}" + } + } + } + } + }, + { + "Id": { + "id": 20091427515840 + }, + "Name": "srcEndpoint=(Add (+): Out), destEndpoint=(Equal To (==): In)", + "Components": { + "Component_[1770958354285950060]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 1770958354285950060, + "sourceEndpoint": { + "nodeId": { + "id": 20061362744768 + }, + "slotId": { + "m_id": "{5E73CCFA-3E3B-44B5-9E9E-FD9250FCE078}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 20009823137216 + }, + "slotId": { + "m_id": "{71837FF2-8438-4225-AA73-B017D5097FE7}" + } + } + } + } + }, + { + "Id": { + "id": 20095722483136 + }, + "Name": "srcEndpoint=(Add (+): Out), destEndpoint=(Equal To (==): In)", + "Components": { + "Component_[15742434548170508999]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 15742434548170508999, + "sourceEndpoint": { + "nodeId": { + "id": 20014118104512 + }, + "slotId": { + "m_id": "{5E73CCFA-3E3B-44B5-9E9E-FD9250FCE078}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 20065657712064 + }, + "slotId": { + "m_id": "{71837FF2-8438-4225-AA73-B017D5097FE7}" + } + } + } + } + }, + { + "Id": { + "id": 20100017450432 + }, + "Name": "srcEndpoint=(Add (+): Out), destEndpoint=(Equal To (==): In)", + "Components": { + "Component_[5888192785906240625]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 5888192785906240625, + "sourceEndpoint": { + "nodeId": { + "id": 20022708039104 + }, + "slotId": { + "m_id": "{5E73CCFA-3E3B-44B5-9E9E-FD9250FCE078}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 20069952679360 + }, + "slotId": { + "m_id": "{71837FF2-8438-4225-AA73-B017D5097FE7}" + } + } + } + } + }, + { + "Id": { + "id": 20104312417728 + }, + "Name": "srcEndpoint=(Set Variable: Out), destEndpoint=(Add (+): In)", + "Components": { + "Component_[14484850856034595321]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 14484850856034595321, + "sourceEndpoint": { + "nodeId": { + "id": 20048477842880 + }, + "slotId": { + "m_id": "{69C4E25F-AD39-4CE9-9E19-87DC8C6B43AB}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 20014118104512 + }, + "slotId": { + "m_id": "{AC7B582F-C1DE-4C3C-ACC9-D729BE959BF5}" + } + } + } + } + }, + { + "Id": { + "id": 20108607385024 + }, + "Name": "srcEndpoint=(Set Variable: Out), destEndpoint=(Add (+): In)", + "Components": { + "Component_[13948374526923268574]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 13948374526923268574, + "sourceEndpoint": { + "nodeId": { + "id": 20052772810176 + }, + "slotId": { + "m_id": "{D4DD8502-2960-4E11-81F5-3CCAC7FCFBCE}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 20061362744768 + }, + "slotId": { + "m_id": "{AC7B582F-C1DE-4C3C-ACC9-D729BE959BF5}" + } + } + } + } + }, + { + "Id": { + "id": 20112902352320 + }, + "Name": "srcEndpoint=(Set Variable: Out), destEndpoint=(Add (+): In)", + "Components": { + "Component_[17650800042816564839]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 17650800042816564839, + "sourceEndpoint": { + "nodeId": { + "id": 20018413071808 + }, + "slotId": { + "m_id": "{EF2E6C1F-3960-45D1-AC20-B49B92785FF9}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 20022708039104 + }, + "slotId": { + "m_id": "{AC7B582F-C1DE-4C3C-ACC9-D729BE959BF5}" + } + } + } + } + }, + { + "Id": { + "id": 20117197319616 + }, + "Name": "srcEndpoint=(Expect True: Out), destEndpoint=(Expect True: In)", + "Components": { + "Component_[9927163777207365219]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 9927163777207365219, + "sourceEndpoint": { + "nodeId": { + "id": 20035592940992 + }, + "slotId": { + "m_id": "{CAFD7A46-861D-4156-A15B-38E798044A36}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 20039887908288 + }, + "slotId": { + "m_id": "{9E9BE7E3-A17F-4603-8F71-59130F2D0E7E}" + } + } + } + } + }, + { + "Id": { + "id": 20121492286912 + }, + "Name": "srcEndpoint=(Expect True: Out), destEndpoint=(Expect True: In)", + "Components": { + "Component_[6569354793892923961]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 6569354793892923961, + "sourceEndpoint": { + "nodeId": { + "id": 20039887908288 + }, + "slotId": { + "m_id": "{CAFD7A46-861D-4156-A15B-38E798044A36}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 20027003006400 + }, + "slotId": { + "m_id": "{9E9BE7E3-A17F-4603-8F71-59130F2D0E7E}" + } + } + } + } + }, + { + "Id": { + "id": 20125787254208 + }, + "Name": "srcEndpoint=(Equal To (==): True), destEndpoint=(Expect True: In)", + "Components": { + "Component_[8734972779133126833]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 8734972779133126833, + "sourceEndpoint": { + "nodeId": { + "id": 20009823137216 + }, + "slotId": { + "m_id": "{85BEF751-FEBE-457B-B73A-F9334E248174}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 20035592940992 + }, + "slotId": { + "m_id": "{9E9BE7E3-A17F-4603-8F71-59130F2D0E7E}" + } + } + } + } + }, + { + "Id": { + "id": 20130082221504 + }, + "Name": "srcEndpoint=(Equal To (==): True), destEndpoint=(Expect True: In)", + "Components": { + "Component_[13233443675773627954]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 13233443675773627954, + "sourceEndpoint": { + "nodeId": { + "id": 20065657712064 + }, + "slotId": { + "m_id": "{85BEF751-FEBE-457B-B73A-F9334E248174}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 20035592940992 + }, + "slotId": { + "m_id": "{9E9BE7E3-A17F-4603-8F71-59130F2D0E7E}" + } + } + } + } + }, + { + "Id": { + "id": 20134377188800 + }, + "Name": "srcEndpoint=(Equal To (==): True), destEndpoint=(Expect True: In)", + "Components": { + "Component_[8564488729834246542]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 8564488729834246542, + "sourceEndpoint": { + "nodeId": { + "id": 20069952679360 + }, + "slotId": { + "m_id": "{85BEF751-FEBE-457B-B73A-F9334E248174}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 20035592940992 + }, + "slotId": { + "m_id": "{9E9BE7E3-A17F-4603-8F71-59130F2D0E7E}" + } + } + } + } + }, + { + "Id": { + "id": 20138672156096 + }, + "Name": "srcEndpoint=(Expect True: Out), destEndpoint=(Mark Complete: In)", + "Components": { + "Component_[17252410557193957278]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 17252410557193957278, + "sourceEndpoint": { + "nodeId": { + "id": 20027003006400 + }, + "slotId": { + "m_id": "{CAFD7A46-861D-4156-A15B-38E798044A36}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 20031297973696 + }, + "slotId": { + "m_id": "{496BD222-4D88-4AD0-992E-3873EB3E51F9}" + } + } + } + } + } + ] + }, + "m_assetType": "{3E2AC8CD-713F-453E-967F-29517F331784}", + "versionData": { + "_grammarVersion": 1, + "_runtimeVersion": 1, + "_fileVersion": 1 + }, + "m_variableCounter": 3, + "GraphCanvasData": [ + { + "Key": { + "id": 20005528169920 + }, + "Value": { + "ComponentData": { + "{5F84B500-8C45-40D1-8EFC-A5306B241444}": { + "$type": "SceneComponentSaveData", + "ViewParams": { + "Scale": 0.7164653965189688, + "AnchorX": -554.109130859375, + "AnchorY": -334.977783203125 + } + } + } + } + }, + { + "Key": { + "id": 20009823137216 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "MathNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 1520.0, + 80.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{4B494FF9-7CD7-496F-9778-8E01DAAE393B}" + } + } + } + }, + { + "Key": { + "id": 20014118104512 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "MathNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 880.0, + 280.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{DF978315-8A60-40C7-8889-A1C2EF190012}" + } + } + } + }, + { + "Key": { + "id": 20018413071808 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "SetVariableNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 560.0, + 420.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData", + "SubStyle": ".setVariable" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{C1E23F0C-0829-4CBC-9176-29CF13B83640}" + } + } + } + }, + { + "Key": { + "id": 20022708039104 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "MathNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 920.0, + 540.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{16FFB986-E605-47C3-8BAC-C5449F871465}" + } + } + } + }, + { + "Key": { + "id": 20027003006400 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "MethodNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 2600.0, + 280.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData", + "SubStyle": ".method" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{F5ACF848-9D4E-45DA-AA58-42DE9695AAB0}" + } + } + } + }, + { + "Key": { + "id": 20031297973696 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "MethodNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 2900.0, + 280.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData", + "SubStyle": ".method" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{456E2F70-A915-450F-B570-57C61FD8713C}" + } + } + } + }, + { + "Key": { + "id": 20035592940992 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "MethodNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 1980.0, + 280.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData", + "SubStyle": ".method" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{ED35AAD0-F976-4FA9-B6C7-08ED30452D33}" + } + } + } + }, + { + "Key": { + "id": 20039887908288 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "MethodNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 2280.0, + 280.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData", + "SubStyle": ".method" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{D2CF914D-DFC1-40B6-884F-85EBD5DF95B6}" + } + } + } + }, + { + "Key": { + "id": 20044182875584 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "TimeNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 0.0, + 260.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{8133F38D-4D68-4244-8999-BE5E019A24E0}" + } + } + } + }, + { + "Key": { + "id": 20048477842880 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "SetVariableNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 560.0, + 260.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData", + "SubStyle": ".setVariable" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{377A822F-6220-45A6-91CB-A3E5F8E9ACFB}" + } + } + } + }, + { + "Key": { + "id": 20052772810176 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "SetVariableNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 560.0, + 60.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData", + "SubStyle": ".setVariable" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{EF322638-4ADC-43B3-9398-105F49D40D96}" + } + } + } + }, + { + "Key": { + "id": 20057067777472 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "StringNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 200.0, + 260.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{6A6126C8-7C7E-48C1-A8A5-D26B9EA59578}" + } + } + } + }, + { + "Key": { + "id": 20061362744768 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "MathNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 880.0, + 60.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{01CF2659-C0C0-4D6B-9C48-B47989D98148}" + } + } + } + }, + { + "Key": { + "id": 20065657712064 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "MathNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 1500.0, + 300.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{F8D64912-B290-443F-90A1-891A893D9F3D}" + } + } + } + }, + { + "Key": { + "id": 20069952679360 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "MathNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 1540.0, + 560.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{A380E8D9-CEA5-44CF-A9DC-69A3C0115256}" + } + } + } + } + ], + "StatisticsHelper": { + "InstanceCounter": [ + { + "Key": 1244476766431948410, + "Value": 3 + }, + { + "Key": 2150378447039925262, + "Value": 1 + }, + { + "Key": 3117476785392655547, + "Value": 3 + }, + { + "Key": 4199610336680704683, + "Value": 1 + }, + { + "Key": 8132426562032687196, + "Value": 1 + }, + { + "Key": 10204019744198319120, + "Value": 1 + }, + { + "Key": 10684225535275896474, + "Value": 1 + }, + { + "Key": 12033332465728181077, + "Value": 3 + }, + { + "Key": 12096037962474910421, + "Value": 1 + } + ] + } + } + } + } + } +} \ No newline at end of file diff --git a/Gems/ScriptCanvasTesting/Code/Tests/ScriptCanvas_RuntimeInterpreted.cpp b/Gems/ScriptCanvasTesting/Code/Tests/ScriptCanvas_RuntimeInterpreted.cpp index 00705bfbaa..9d75d3ed9d 100644 --- a/Gems/ScriptCanvasTesting/Code/Tests/ScriptCanvas_RuntimeInterpreted.cpp +++ b/Gems/ScriptCanvasTesting/Code/Tests/ScriptCanvas_RuntimeInterpreted.cpp @@ -779,7 +779,7 @@ TEST_F(ScriptCanvasTestFixture, InterpretedPrintConnectedInput) TEST_F(ScriptCanvasTestFixture, InterpretedPrintFormatEmptyValue) { - RunUnitTestGraph("LY_SC_UnitTest_PrintFormatEmptyValue", ExecutionMode::Interpreted); + ExpectParseError("LY_SC_UnitTest_PrintFormatEmptyValue"); } TEST_F(ScriptCanvasTestFixture, InterpretedProperties) @@ -819,7 +819,7 @@ TEST_F(ScriptCanvasTestFixture, InterpretedStringFormat) TEST_F(ScriptCanvasTestFixture, InterpretedStringFormatEmptyValue) { - RunUnitTestGraph("LY_SC_UnitTest_StringFormatEmptyValue", ExecutionMode::Interpreted); + ExpectParseError("LY_SC_UnitTest_StringFormatEmptyValue"); } TEST_F(ScriptCanvasTestFixture, InterpretedStringFormatWithRepeatedValueName) diff --git a/Gems/StartingPointInput/Code/Source/InputConfigurationComponent.cpp b/Gems/StartingPointInput/Code/Source/InputConfigurationComponent.cpp index 215efd4656..427856f3a3 100644 --- a/Gems/StartingPointInput/Code/Source/InputConfigurationComponent.cpp +++ b/Gems/StartingPointInput/Code/Source/InputConfigurationComponent.cpp @@ -56,7 +56,7 @@ namespace StartingPointInput ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Editor/Icons/Components/Viewport/InputConfig.svg") ->Attribute(AZ::Edit::Attributes::PrimaryAssetType, AZ::AzTypeInfo::Uuid()) ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game")) - ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/input/") + ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/gameplay/input/") ->DataElement(AZ::Edit::UIHandlers::Default, &InputConfigurationComponent::m_inputEventBindingsAsset, "Input to event bindings", "Asset containing input to event binding information.") ->Attribute(AZ::Edit::Attributes::AutoExpand, true) diff --git a/Gems/Terrain/Assets/Materials/Terrain/DefaultTerrainMacroMaterial.material b/Gems/Terrain/Assets/Materials/Terrain/DefaultTerrainMacroMaterial.material new file mode 100644 index 0000000000..afaf7e1947 --- /dev/null +++ b/Gems/Terrain/Assets/Materials/Terrain/DefaultTerrainMacroMaterial.material @@ -0,0 +1,8 @@ +{ + "description": "", + "materialType": "TerrainMacroMaterial.materialtype", + "parentMaterial": "", + "propertyLayoutVersion": 1, + "properties": { + } +} diff --git a/Gems/Terrain/Assets/Materials/Terrain/TerrainMacroMaterial.materialtype b/Gems/Terrain/Assets/Materials/Terrain/TerrainMacroMaterial.materialtype new file mode 100644 index 0000000000..0a4f6d0229 --- /dev/null +++ b/Gems/Terrain/Assets/Materials/Terrain/TerrainMacroMaterial.materialtype @@ -0,0 +1,46 @@ +{ + "description": "A material for providing terrain with low-fidelity color and normals. This material will get blended with surface detail materials.", + "propertyLayout": { + "version": 1, + "groups": [ + { + "id": "settings", + "displayName": "Settings" + } + ], + "properties": { + "macroColor": [ + { + "id": "useTexture", + "displayName": "Use Texture", + "description": "Whether to use the texture.", + "type": "Bool", + "defaultValue": true + } + + ], + "macroNormal": [ + { + "id": "useTexture", + "displayName": "Use Texture", + "description": "Whether to use the texture.", + "type": "Bool", + "defaultValue": true + } + ] + } + }, + "shaders": [ + { + "file": "../../Shaders/Terrain/TerrainPBR_ForwardPass.shader" + }, + { + "file": "../../Shaders/Terrain/Terrain_Shadowmap.shader" + }, + { + "file": "../../Shaders/Terrain/Terrain_DepthPass.shader" + } + ], + "functors": [ + ] +} diff --git a/Gems/Terrain/Code/Source/Components/TerrainSurfaceDataSystemComponent.cpp b/Gems/Terrain/Code/Source/Components/TerrainSurfaceDataSystemComponent.cpp index 04c94b24d6..0346ff7281 100644 --- a/Gems/Terrain/Code/Source/Components/TerrainSurfaceDataSystemComponent.cpp +++ b/Gems/Terrain/Code/Source/Components/TerrainSurfaceDataSystemComponent.cpp @@ -156,9 +156,19 @@ namespace Terrain point.m_entityId = GetEntityId(); point.m_position = AZ::Vector3(inPosition.GetX(), inPosition.GetY(), terrainHeight); point.m_normal = terrain->GetNormal(inPosition); + + // Always add a "terrain" or "terrainHole" tag. const AZ::Crc32 terrainTag = isHole ? SurfaceData::Constants::s_terrainHoleTagCrc : SurfaceData::Constants::s_terrainTagCrc; SurfaceData::AddMaxValueForMasks(point.m_masks, terrainTag, 1.0f); + + // Add all of the surface tags that the terrain has at this point. + AzFramework::SurfaceData::OrderedSurfaceTagWeightSet surfaceWeights; + terrain->GetSurfaceWeights(point.m_position, surfaceWeights); + for (auto& tag : surfaceWeights) + { + SurfaceData::AddMaxValueForMasks(point.m_masks, tag.m_surfaceType, tag.m_weight); + } surfacePointList.push_back(point); } // Only one handler should exist. diff --git a/Gems/Terrain/Code/Source/Components/TerrainSurfaceGradientListComponent.cpp b/Gems/Terrain/Code/Source/Components/TerrainSurfaceGradientListComponent.cpp index 2fbd6a4814..958e175cf3 100644 --- a/Gems/Terrain/Code/Source/Components/TerrainSurfaceGradientListComponent.cpp +++ b/Gems/Terrain/Code/Source/Components/TerrainSurfaceGradientListComponent.cpp @@ -12,6 +12,7 @@ #include #include +#include namespace Terrain { @@ -33,7 +34,8 @@ namespace Terrain ->Attribute(AZ::Edit::Attributes::AutoExpand, true) ->DataElement( - AZ::Edit::UIHandlers::Default, &TerrainSurfaceGradientMapping::m_gradientEntityId, "Gradient Entity", "ID of Entity providing a gradient.") + AZ::Edit::UIHandlers::Default, &TerrainSurfaceGradientMapping::m_gradientEntityId, + "Gradient Entity", "ID of Entity providing a gradient.") ->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::AttributesAndValues) ->UIElement("GradientPreviewer", "Previewer") ->Attribute(AZ::Edit::Attributes::NameLabelOverride, "") @@ -68,7 +70,8 @@ namespace Terrain ->Attribute(AZ::Edit::Attributes::AutoExpand, true) ->DataElement( - AZ::Edit::UIHandlers::Default, &TerrainSurfaceGradientListConfig::m_gradientSurfaceMappings, "Gradient to Surface Mappings", "Maps Gradient Entities to Surfaces.") + AZ::Edit::UIHandlers::Default, &TerrainSurfaceGradientListConfig::m_gradientSurfaceMappings, + "Gradient to Surface Mappings", "Maps Gradient Entities to Surfaces.") ; } } @@ -109,12 +112,36 @@ namespace Terrain void TerrainSurfaceGradientListComponent::Activate() { + LmbrCentral::DependencyNotificationBus::Handler::BusConnect(GetEntityId()); Terrain::TerrainAreaSurfaceRequestBus::Handler::BusConnect(GetEntityId()); + + // Make sure we get update notifications whenever this entity or any dependent gradient entity changes in any way. + // We'll use that to notify the terrain system that the surface information needs to be refreshed. + m_dependencyMonitor.Reset(); + m_dependencyMonitor.ConnectOwner(GetEntityId()); + m_dependencyMonitor.ConnectDependency(GetEntityId()); + + for (auto& surfaceMapping : m_configuration.m_gradientSurfaceMappings) + { + if (surfaceMapping.m_gradientEntityId != GetEntityId()) + { + m_dependencyMonitor.ConnectDependency(surfaceMapping.m_gradientEntityId); + } + } + + // Notify that the area has changed. + OnCompositionChanged(); } void TerrainSurfaceGradientListComponent::Deactivate() { + m_dependencyMonitor.Reset(); + Terrain::TerrainAreaSurfaceRequestBus::Handler::BusDisconnect(); + LmbrCentral::DependencyNotificationBus::Handler::BusDisconnect(); + + // Since this surface data will no longer exist, notify the terrain system to refresh the area. + OnCompositionChanged(); } bool TerrainSurfaceGradientListComponent::ReadInConfig(const AZ::ComponentConfig* baseConfig) @@ -137,7 +164,9 @@ namespace Terrain return false; } - void TerrainSurfaceGradientListComponent::GetSurfaceWeights(const AZ::Vector3& inPosition, AzFramework::SurfaceData::OrderedSurfaceTagWeightSet& outSurfaceWeights) const + void TerrainSurfaceGradientListComponent::GetSurfaceWeights( + const AZ::Vector3& inPosition, + AzFramework::SurfaceData::OrderedSurfaceTagWeightSet& outSurfaceWeights) const { outSurfaceWeights.clear(); @@ -146,7 +175,8 @@ namespace Terrain for (const auto& mapping : m_configuration.m_gradientSurfaceMappings) { float weight = 0.0f; - GradientSignal::GradientRequestBus::EventResult(weight, mapping.m_gradientEntityId, &GradientSignal::GradientRequestBus::Events::GetValue, params); + GradientSignal::GradientRequestBus::EventResult(weight, + mapping.m_gradientEntityId, &GradientSignal::GradientRequestBus::Events::GetValue, params); AzFramework::SurfaceData::SurfaceTagWeight tagWeight; tagWeight.m_surfaceType = mapping.m_surfaceTag; @@ -154,4 +184,10 @@ namespace Terrain outSurfaceWeights.emplace(tagWeight); } } + + void TerrainSurfaceGradientListComponent::OnCompositionChanged() + { + TerrainSystemServiceRequestBus::Broadcast(&TerrainSystemServiceRequestBus::Events::RefreshArea, GetEntityId()); + } + } // namespace Terrain diff --git a/Gems/Terrain/Code/Source/Components/TerrainSurfaceGradientListComponent.h b/Gems/Terrain/Code/Source/Components/TerrainSurfaceGradientListComponent.h index 799038354c..bb30029f35 100644 --- a/Gems/Terrain/Code/Source/Components/TerrainSurfaceGradientListComponent.h +++ b/Gems/Terrain/Code/Source/Components/TerrainSurfaceGradientListComponent.h @@ -11,6 +11,8 @@ #include #include #include +#include +#include #include #include @@ -47,6 +49,7 @@ namespace Terrain class TerrainSurfaceGradientListComponent : public AZ::Component , public Terrain::TerrainAreaSurfaceRequestBus::Handler + , private LmbrCentral::DependencyNotificationBus::Handler { public: template @@ -72,6 +75,11 @@ namespace Terrain void GetSurfaceWeights(const AZ::Vector3& inPosition, AzFramework::SurfaceData::OrderedSurfaceTagWeightSet& outSurfaceWeights) const override; private: + ////////////////////////////////////////////////////////////////////////// + // LmbrCentral::DependencyNotificationBus + void OnCompositionChanged() override; + TerrainSurfaceGradientListConfig m_configuration; + LmbrCentral::DependencyMonitor m_dependencyMonitor; }; } // namespace Terrain diff --git a/Gems/Terrain/Code/Source/Components/TerrainWorldRendererComponent.cpp b/Gems/Terrain/Code/Source/Components/TerrainWorldRendererComponent.cpp index f3eed41717..9cd9ce9fb2 100644 --- a/Gems/Terrain/Code/Source/Components/TerrainWorldRendererComponent.cpp +++ b/Gems/Terrain/Code/Source/Components/TerrainWorldRendererComponent.cpp @@ -13,8 +13,6 @@ #include #include -#include - #include #include #include @@ -135,17 +133,13 @@ namespace Terrain { m_terrainFeatureProcessor = scene->EnableFeatureProcessor(); } - - AzFramework::Terrain::TerrainDataNotificationBus::Handler::BusConnect(); m_terrainRendererActive = true; } void TerrainWorldRendererComponent::Deactivate() { // On component deactivation, unregister the feature processor and remove it from the default scene. - m_terrainRendererActive = false; - AzFramework::Terrain::TerrainDataNotificationBus::Handler::BusDisconnect(); if (AZ::RPI::Scene* scene = GetScene(); scene) { @@ -178,69 +172,4 @@ namespace Terrain } return false; } - - void TerrainWorldRendererComponent::OnTerrainDataDestroyBegin() - { - // If the terrain is being destroyed, remove all existing terrain data from the feature processor. - - if (m_terrainFeatureProcessor) - { - m_terrainFeatureProcessor->RemoveTerrainData(); - } - } - - void TerrainWorldRendererComponent::OnTerrainDataChanged([[maybe_unused]] const AZ::Aabb& dirtyRegion, [[maybe_unused]] TerrainDataChangedMask dataChangedMask) - { - // Block other threads from accessing the surface data bus while we are in GetValue (which may call into the SurfaceData bus). - // We lock our surface data mutex *before* checking / setting "isRequestInProgress" so that we prevent race conditions - // that create false detection of cyclic dependencies when multiple requests occur on different threads simultaneously. - // (One case where this was previously able to occur was in rapid updating of the Preview widget on the - // GradientSurfaceDataComponent in the Editor when moving the threshold sliders back and forth rapidly) - auto& surfaceDataContext = SurfaceData::SurfaceDataSystemRequestBus::GetOrCreateContext(false); - typename SurfaceData::SurfaceDataSystemRequestBus::Context::DispatchLockGuard scopeLock(surfaceDataContext.m_contextMutex); - - AZ::Vector2 queryResolution = AZ::Vector2(1.0f); - AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult( - queryResolution, &AzFramework::Terrain::TerrainDataRequests::GetTerrainHeightQueryResolution); - - AZ::Aabb worldBounds = AZ::Aabb::CreateNull(); - AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult( - worldBounds, &AzFramework::Terrain::TerrainDataRequests::GetTerrainAabb); - - - AZ::Transform transform = AZ::Transform::CreateTranslation(worldBounds.GetCenter()); - - uint32_t width = aznumeric_cast( - (float)worldBounds.GetXExtent() / queryResolution.GetX()); - uint32_t height = aznumeric_cast( - (float)worldBounds.GetYExtent() / queryResolution.GetY()); - AZStd::vector pixels; - pixels.resize_no_construct(width * height); - const uint32_t pixelDataSize = width * height * sizeof(float); - memset(pixels.data(), 0, pixelDataSize); - - for (uint32_t y = 0; y < height; y++) - { - for (uint32_t x = 0; x < width; x++) - { - bool terrainExists = true; - float terrainHeight = 0.0f; - AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult( - terrainHeight, &AzFramework::Terrain::TerrainDataRequests::GetHeightFromFloats, - (x * queryResolution.GetX()) + worldBounds.GetMin().GetX(), - (y * queryResolution.GetY()) + worldBounds.GetMin().GetY(), - AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT, - &terrainExists); - - pixels[(y * width) + x] = - (terrainHeight - worldBounds.GetMin().GetZ()) / worldBounds.GetExtents().GetZ(); - } - } - - if (m_terrainFeatureProcessor) - { - m_terrainFeatureProcessor->UpdateTerrainData(transform, worldBounds, queryResolution.GetX(), width, height, pixels); - } - } - } diff --git a/Gems/Terrain/Code/Source/Components/TerrainWorldRendererComponent.h b/Gems/Terrain/Code/Source/Components/TerrainWorldRendererComponent.h index cd2a4ea5aa..354b2fde34 100644 --- a/Gems/Terrain/Code/Source/Components/TerrainWorldRendererComponent.h +++ b/Gems/Terrain/Code/Source/Components/TerrainWorldRendererComponent.h @@ -9,8 +9,6 @@ #pragma once #include -#include -#include namespace LmbrCentral { @@ -54,7 +52,6 @@ namespace Terrain class TerrainWorldRendererComponent : public AZ::Component - , public AzFramework::Terrain::TerrainDataNotificationBus::Handler { public: template @@ -77,8 +74,6 @@ namespace Terrain bool WriteOutConfig(AZ::ComponentConfig* outBaseConfig) const override; protected: - void OnTerrainDataDestroyBegin() override; - void OnTerrainDataChanged(const AZ::Aabb& dirtyRegion, TerrainDataChangedMask dataChangedMask) override; AZ::RPI::Scene* GetScene() const; diff --git a/Gems/Terrain/Code/Source/EditorTerrainModule.cpp b/Gems/Terrain/Code/Source/EditorTerrainModule.cpp index 9107ea8541..94c6f5c3c6 100644 --- a/Gems/Terrain/Code/Source/EditorTerrainModule.cpp +++ b/Gems/Terrain/Code/Source/EditorTerrainModule.cpp @@ -14,6 +14,7 @@ #include #include #include +#include namespace Terrain { @@ -24,6 +25,7 @@ namespace Terrain { Terrain::EditorTerrainHeightGradientListComponent::CreateDescriptor(), Terrain::EditorTerrainLayerSpawnerComponent::CreateDescriptor(), + Terrain::EditorTerrainMacroMaterialComponent::CreateDescriptor(), Terrain::EditorTerrainSurfaceGradientListComponent::CreateDescriptor(), Terrain::EditorTerrainSystemComponent::CreateDescriptor(), Terrain::EditorTerrainWorldComponent::CreateDescriptor(), diff --git a/Gems/Terrain/Code/Source/TerrainModule.cpp b/Gems/Terrain/Code/Source/TerrainModule.cpp index 6aeddc3cd5..878c6b44c8 100644 --- a/Gems/Terrain/Code/Source/TerrainModule.cpp +++ b/Gems/Terrain/Code/Source/TerrainModule.cpp @@ -18,6 +18,7 @@ #include #include #include +#include namespace Terrain { @@ -31,6 +32,7 @@ namespace Terrain TerrainWorldRendererComponent::CreateDescriptor(), TerrainHeightGradientListComponent::CreateDescriptor(), TerrainLayerSpawnerComponent::CreateDescriptor(), + TerrainMacroMaterialComponent::CreateDescriptor(), TerrainSurfaceGradientListComponent::CreateDescriptor(), TerrainSurfaceDataSystemComponent::CreateDescriptor(), }); diff --git a/Gems/Terrain/Code/Source/TerrainRenderer/Components/TerrainMacroMaterialComponent.cpp b/Gems/Terrain/Code/Source/TerrainRenderer/Components/TerrainMacroMaterialComponent.cpp new file mode 100644 index 0000000000..55653c64fa --- /dev/null +++ b/Gems/Terrain/Code/Source/TerrainRenderer/Components/TerrainMacroMaterialComponent.cpp @@ -0,0 +1,287 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace Terrain +{ + AZ::Data::AssetId TerrainMacroMaterialConfig::s_macroMaterialTypeAssetId{}; + + void TerrainMacroMaterialConfig::Reflect(AZ::ReflectContext* context) + { + AZ::SerializeContext* serialize = azrtti_cast(context); + if (serialize) + { + serialize->Class() + ->Version(1) + ->Field("MacroMaterial", &TerrainMacroMaterialConfig::m_materialAsset) + ; + + // The edit context for this appears in EditorTerrainMacroMaterialComponent.cpp. + } + } + + AZ::Data::AssetId TerrainMacroMaterialConfig::GetTerrainMacroMaterialTypeAssetId() + { + // Get the Asset ID for the TerrainMacroMaterial material type and store it in a class static so that we don't have to look it + // up again. + if (!s_macroMaterialTypeAssetId.IsValid()) + { + AZ::Data::AssetCatalogRequestBus::BroadcastResult( + s_macroMaterialTypeAssetId, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath, TerrainMacroMaterialTypeAsset, + azrtti_typeid(), false); + AZ_Assert(s_macroMaterialTypeAssetId.IsValid(), "The asset '%s' couldn't be found.", TerrainMacroMaterialTypeAsset); + } + + return s_macroMaterialTypeAssetId; + } + + bool TerrainMacroMaterialConfig::IsMaterialTypeCorrect(const AZ::Data::AssetId& assetId) + { + // We'll verify that whatever material we try to load has this material type as a dependency, as a way to implicitly detect + // that we're only trying to use terrain macro materials even before we load the asset. + auto macroMaterialTypeAssetId = GetTerrainMacroMaterialTypeAssetId(); + + // Get the dependencies for the requested asset. + AZ::Outcome, AZStd::string> result; + AZ::Data::AssetCatalogRequestBus::BroadcastResult( + result, &AZ::Data::AssetCatalogRequestBus::Events::GetDirectProductDependencies, assetId); + + // If any of the dependencies match the TerrainMacroMaterial materialtype asset, then this should be the correct type of material. + if (result) + { + for (auto& dependency : result.GetValue()) + { + if (dependency.m_assetId == macroMaterialTypeAssetId) + { + return true; + } + } + } + + // Didn't have the expected dependency, so it must not be the right material type. + return false; + } + + AZ::Outcome TerrainMacroMaterialConfig::ValidateMaterialAsset(void* newValue, const AZ::Uuid& valueType) + { + if (azrtti_typeid>() != valueType) + { + AZ_Assert(false, "Unexpected value type"); + return AZ::Failure(AZStd::string("Unexpectedly received something other than a material asset for the MacroMaterial!")); + } + + auto newMaterialAsset = *static_cast*>(newValue); + + if (!IsMaterialTypeCorrect(newMaterialAsset.GetId())) + { + return AZ::Failure(AZStd::string::format( + "The selected MacroMaterial ('%s') needs to use the TerrainMacroMaterial material type.", + newMaterialAsset.GetHint().c_str())); + } + + return AZ::Success(); + } + + + void TerrainMacroMaterialComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services) + { + services.push_back(AZ_CRC_CE("TerrainMacroMaterialProviderService")); + } + + void TerrainMacroMaterialComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& services) + { + services.push_back(AZ_CRC_CE("TerrainMacroMaterialProviderService")); + } + + void TerrainMacroMaterialComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& services) + { + services.push_back(AZ_CRC_CE("AxisAlignedBoxShapeService")); + } + + void TerrainMacroMaterialComponent::Reflect(AZ::ReflectContext* context) + { + TerrainMacroMaterialConfig::Reflect(context); + + AZ::SerializeContext* serialize = azrtti_cast(context); + if (serialize) + { + serialize->Class() + ->Version(0) + ->Field("Configuration", &TerrainMacroMaterialComponent::m_configuration) + ; + } + } + + TerrainMacroMaterialComponent::TerrainMacroMaterialComponent(const TerrainMacroMaterialConfig& configuration) + : m_configuration(configuration) + { + } + + void TerrainMacroMaterialComponent::Activate() + { + // Clear out our shape bounds and make sure the material is queued to load. + m_cachedShapeBounds = AZ::Aabb::CreateNull(); + m_configuration.m_materialAsset.QueueLoad(); + + // Don't mark our material as active until it's finished loading and is valid. + m_macroMaterialActive = false; + + // Listen for the material asset to complete loading. + AZ::Data::AssetBus::Handler::BusConnect(m_configuration.m_materialAsset.GetId()); + } + + void TerrainMacroMaterialComponent::Deactivate() + { + TerrainMacroMaterialRequestBus::Handler::BusDisconnect(); + + AZ::Data::AssetBus::Handler::BusDisconnect(); + m_configuration.m_materialAsset.Release(); + + m_macroMaterialInstance.reset(); + + // Send out any notifications as appropriate based on the macro material destruction. + HandleMaterialStateChange(); + } + + bool TerrainMacroMaterialComponent::ReadInConfig(const AZ::ComponentConfig* baseConfig) + { + if (auto config = azrtti_cast(baseConfig)) + { + m_configuration = *config; + return true; + } + return false; + } + + bool TerrainMacroMaterialComponent::WriteOutConfig(AZ::ComponentConfig* outBaseConfig) const + { + if (auto config = azrtti_cast(outBaseConfig)) + { + *config = m_configuration; + return true; + } + return false; + } + + void TerrainMacroMaterialComponent::OnShapeChanged([[maybe_unused]] ShapeComponentNotifications::ShapeChangeReasons reasons) + { + // This should only get called while the macro material is active. If it gets called while the macro material isn't active, + // we've got a bug where we haven't managed the bus connections properly. + AZ_Assert(m_macroMaterialActive, "The ShapeComponentNotificationBus connection is out of sync with the material load."); + + AZ::Aabb oldShapeBounds = m_cachedShapeBounds; + + LmbrCentral::ShapeComponentRequestsBus::EventResult( + m_cachedShapeBounds, GetEntityId(), &LmbrCentral::ShapeComponentRequestsBus::Events::GetEncompassingAabb); + + TerrainMacroMaterialNotificationBus::Broadcast( + &TerrainMacroMaterialNotificationBus::Events::OnTerrainMacroMaterialRegionChanged, + GetEntityId(), oldShapeBounds, m_cachedShapeBounds); + } + + void TerrainMacroMaterialComponent::HandleMaterialStateChange() + { + // We only want our component to appear active during the time that the macro material is loaded and valid. The logic below + // will handle all transition possibilities to notify if we've become active, inactive, or just changed. We'll also only + // keep a valid up-to-date copy of the shape bounds while the material is valid, since we don't need it any other time. + + bool wasPreviouslyActive = m_macroMaterialActive; + bool isNowActive = (m_macroMaterialInstance != nullptr); + + // Set our state to active or inactive, based on whether or not the macro material instance is now valid. + m_macroMaterialActive = isNowActive; + + // Handle the different inactive/active transition possibilities. + + if (!wasPreviouslyActive && !isNowActive) + { + // Do nothing, we haven't yet successfully loaded a valid material. + } + else if (!wasPreviouslyActive && isNowActive) + { + // We've transitioned from inactive to active, so send out a message saying that we've been created and start tracking the + // overall shape bounds. + + // Get the current shape bounds. + LmbrCentral::ShapeComponentRequestsBus::EventResult( + m_cachedShapeBounds, GetEntityId(), &LmbrCentral::ShapeComponentRequestsBus::Events::GetEncompassingAabb); + + // Start listening for terrain macro material requests. + TerrainMacroMaterialRequestBus::Handler::BusConnect(GetEntityId()); + + // Start listening for shape changes. + LmbrCentral::ShapeComponentNotificationsBus::Handler::BusConnect(GetEntityId()); + + TerrainMacroMaterialNotificationBus::Broadcast( + &TerrainMacroMaterialNotificationBus::Events::OnTerrainMacroMaterialCreated, GetEntityId(), m_macroMaterialInstance, + m_cachedShapeBounds); + } + else if (wasPreviouslyActive && !isNowActive) + { + // Stop listening to macro material requests or shape changes, and send out a notification that we no longer have a valid + // macro material. + + TerrainMacroMaterialRequestBus::Handler::BusDisconnect(); + LmbrCentral::ShapeComponentNotificationsBus::Handler::BusDisconnect(); + + m_cachedShapeBounds = AZ::Aabb::CreateNull(); + + TerrainMacroMaterialNotificationBus::Broadcast( + &TerrainMacroMaterialNotificationBus::Events::OnTerrainMacroMaterialDestroyed, GetEntityId()); + } + else + { + // We were active both before and after, so just send out a material changed event. + + TerrainMacroMaterialNotificationBus::Broadcast( + &TerrainMacroMaterialNotificationBus::Events::OnTerrainMacroMaterialChanged, GetEntityId(), m_macroMaterialInstance); + } + } + + void TerrainMacroMaterialComponent::OnAssetReady(AZ::Data::Asset asset) + { + m_configuration.m_materialAsset = asset; + + if (m_configuration.m_materialAsset.Get()->GetMaterialTypeAsset().GetId() == + TerrainMacroMaterialConfig::GetTerrainMacroMaterialTypeAssetId()) + { + m_macroMaterialInstance = AZ::RPI::Material::FindOrCreate(m_configuration.m_materialAsset); + } + else + { + AZ_Error("Terrain", false, "Material '%s' has the wrong material type.", m_configuration.m_materialAsset.GetHint().c_str()); + m_macroMaterialInstance.reset(); + } + + // Clear the material asset reference to make sure we don't prevent hot-reloading. + m_configuration.m_materialAsset.Release(); + + HandleMaterialStateChange(); + } + + void TerrainMacroMaterialComponent::OnAssetReloaded(AZ::Data::Asset asset) + { + OnAssetReady(asset); + } + + void TerrainMacroMaterialComponent::GetTerrainMacroMaterialData( + AZ::Data::Instance& macroMaterial, AZ::Aabb& macroMaterialRegion) + { + macroMaterial = m_macroMaterialInstance; + macroMaterialRegion = m_cachedShapeBounds; + } +} diff --git a/Gems/Terrain/Code/Source/TerrainRenderer/Components/TerrainMacroMaterialComponent.h b/Gems/Terrain/Code/Source/TerrainRenderer/Components/TerrainMacroMaterialComponent.h new file mode 100644 index 0000000000..b60f6b2a94 --- /dev/null +++ b/Gems/Terrain/Code/Source/TerrainRenderer/Components/TerrainMacroMaterialComponent.h @@ -0,0 +1,92 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + + +namespace LmbrCentral +{ + template + class EditorWrappedComponentBase; +} + +namespace Terrain +{ + class TerrainMacroMaterialConfig + : public AZ::ComponentConfig + { + public: + AZ_CLASS_ALLOCATOR(TerrainMacroMaterialConfig, AZ::SystemAllocator, 0); + AZ_RTTI(TerrainMacroMaterialConfig, "{9DBAFFF0-FD20-4594-8884-E3266D8CCAC8}", AZ::ComponentConfig); + static void Reflect(AZ::ReflectContext* context); + + AZ::Data::Asset m_materialAsset = { AZ::Data::AssetLoadBehavior::QueueLoad }; + + static AZ::Data::AssetId GetTerrainMacroMaterialTypeAssetId(); + static bool IsMaterialTypeCorrect(const AZ::Data::AssetId&); + AZ::Outcome ValidateMaterialAsset(void* newValue, const AZ::Uuid& valueType); + + private: + static inline constexpr const char* TerrainMacroMaterialTypeAsset = "materials/terrain/terrainmacromaterial.azmaterialtype"; + static AZ::Data::AssetId s_macroMaterialTypeAssetId; + + }; + + class TerrainMacroMaterialComponent + : public AZ::Component + , public TerrainMacroMaterialRequestBus::Handler + , private LmbrCentral::ShapeComponentNotificationsBus::Handler + , private AZ::Data::AssetBus::Handler + { + public: + template + friend class LmbrCentral::EditorWrappedComponentBase; + AZ_COMPONENT(TerrainMacroMaterialComponent, "{F82379FB-E2AE-4F75-A6F4-1AE5F5DA42E8}"); + static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services); + static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& services); + static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& services); + static void Reflect(AZ::ReflectContext* context); + + TerrainMacroMaterialComponent(const TerrainMacroMaterialConfig& configuration); + TerrainMacroMaterialComponent() = default; + ~TerrainMacroMaterialComponent() = default; + + ////////////////////////////////////////////////////////////////////////// + // AZ::Component interface implementation + void Activate() override; + void Deactivate() override; + bool ReadInConfig(const AZ::ComponentConfig* baseConfig) override; + bool WriteOutConfig(AZ::ComponentConfig* outBaseConfig) const override; + + void GetTerrainMacroMaterialData(AZ::Data::Instance& macroMaterial, AZ::Aabb& macroMaterialRegion) override; + + private: + //////////////////////////////////////////////////////////////////////// + // ShapeComponentNotificationsBus + void OnShapeChanged(ShapeComponentNotifications::ShapeChangeReasons reasons) override; + + ////////////////////////////////////////////////////////////////////////// + // AZ::Data::AssetBus::Handler + void OnAssetReady(AZ::Data::Asset asset) override; + void OnAssetReloaded(AZ::Data::Asset asset) override; + + void HandleMaterialStateChange(); + + TerrainMacroMaterialConfig m_configuration; + AZ::Aabb m_cachedShapeBounds; + AZ::Data::Instance m_macroMaterialInstance; + bool m_macroMaterialActive{ false }; + }; +} diff --git a/Gems/Terrain/Code/Source/TerrainRenderer/EditorComponents/EditorTerrainMacroMaterialComponent.cpp b/Gems/Terrain/Code/Source/TerrainRenderer/EditorComponents/EditorTerrainMacroMaterialComponent.cpp new file mode 100644 index 0000000000..07472d1b85 --- /dev/null +++ b/Gems/Terrain/Code/Source/TerrainRenderer/EditorComponents/EditorTerrainMacroMaterialComponent.cpp @@ -0,0 +1,52 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include +#include + +namespace Terrain +{ + void EditorTerrainMacroMaterialComponent::Reflect(AZ::ReflectContext* context) + { + BaseClassType::ReflectSubClass( + context, 1, + &LmbrCentral::EditorWrappedComponentBaseVersionConverter + ); + + AZ::SerializeContext* serializeContext = azrtti_cast(context); + + if (serializeContext) + { + AZ::EditContext* editContext = serializeContext->GetEditContext(); + + // The edit context for TerrainMacroMaterialConfig is specified here to make it easier to add custom filtering to the + // asset picker for the material asset so that we can eventually only display materials that inherit from the proper + // material type. + if (editContext) + { + editContext + ->Class( + "Terrain Macro Material Component", "Provide a terrain macro material for a region of the world") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) + ->Attribute(AZ::Edit::Attributes::AutoExpand, true) + + ->DataElement( + AZ::Edit::UIHandlers::Default, &TerrainMacroMaterialConfig::m_materialAsset, "Macro Material", + "Terrain macro material for use by any terrain inside the bounding box on this entity.") + // This is disabled until ChangeValidate can support the Asset type. :( + //->Attribute(AZ::Edit::Attributes::ChangeValidate, &TerrainMacroMaterialConfig::ValidateMaterialAsset) + ; + } + } + + } +} diff --git a/Gems/Terrain/Code/Source/TerrainRenderer/EditorComponents/EditorTerrainMacroMaterialComponent.h b/Gems/Terrain/Code/Source/TerrainRenderer/EditorComponents/EditorTerrainMacroMaterialComponent.h new file mode 100644 index 0000000000..a2fddf5768 --- /dev/null +++ b/Gems/Terrain/Code/Source/TerrainRenderer/EditorComponents/EditorTerrainMacroMaterialComponent.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include +#include + +namespace Terrain +{ + class EditorTerrainMacroMaterialComponent + : public LmbrCentral::EditorWrappedComponentBase + { + public: + using BaseClassType = LmbrCentral::EditorWrappedComponentBase; + AZ_EDITOR_COMPONENT(EditorTerrainMacroMaterialComponent, "{24D87D5F-6845-4F1F-81DC-05B4CEBA3EF4}", BaseClassType); + static void Reflect(AZ::ReflectContext* context); + + static constexpr const char* const s_categoryName = "Terrain"; + static constexpr const char* const s_componentName = "Terrain Macro Material"; + static constexpr const char* const s_componentDescription = "Provides a macro material for a region to the terrain renderer"; + static constexpr const char* const s_icon = "Editor/Icons/Components/TerrainLayerRenderer.svg"; + static constexpr const char* const s_viewportIcon = "Editor/Icons/Components/Viewport/TerrainLayerRenderer.svg"; + static constexpr const char* const s_helpUrl = ""; + }; +} diff --git a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.cpp b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.cpp index 59984a1b92..560fe1eb60 100644 --- a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.cpp +++ b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.cpp @@ -10,40 +10,41 @@ #include #include +#include #include #include +#include #include #include -#include +#include + #include #include #include #include -#include -#include #include #include -#include +#include #include #include + #include #include #include #include -#include -#include -#include -#include -#include + #include +#include + namespace Terrain { namespace { [[maybe_unused]] const char* TerrainFPName = "TerrainFeatureProcessor"; + const char* TerrainHeightmapChars = "TerrainHeightmap"; } namespace MaterialInputs @@ -71,7 +72,9 @@ namespace Terrain void TerrainFeatureProcessor::Activate() { m_areaData = {}; + m_dirtyRegion = AZ::Aabb::CreateNull(); Initialize(); + AzFramework::Terrain::TerrainDataNotificationBus::Handler::BusConnect(); } void TerrainFeatureProcessor::Initialize() @@ -99,13 +102,16 @@ namespace Terrain AZ_Error(TerrainFPName, false, "Failed to create Terrain render buffers!"); return; } + OnTerrainDataChanged(AZ::Aabb::CreateNull(), TerrainDataChangedMask::HeightData); } void TerrainFeatureProcessor::Deactivate() { + AzFramework::Terrain::TerrainDataNotificationBus::Handler::BusDisconnect(); + AZ::RPI::MaterialReloadNotificationBus::Handler::BusDisconnect(); + m_patchModel = {}; m_areaData = {}; - AZ::RPI::MaterialReloadNotificationBus::Handler::BusDisconnect(); } void TerrainFeatureProcessor::Render(const AZ::RPI::FeatureProcessor::RenderPacket& packet) @@ -113,51 +119,126 @@ namespace Terrain ProcessSurfaces(packet); } - void TerrainFeatureProcessor::UpdateTerrainData( - const AZ::Transform& transform, - const AZ::Aabb& worldBounds, - float sampleSpacing, - uint32_t width, uint32_t height, const AZStd::vector& heightData) + void TerrainFeatureProcessor::OnTerrainDataDestroyBegin() { - if (!worldBounds.IsValid()) + m_areaData = {}; + } + + void TerrainFeatureProcessor::OnTerrainDataChanged(const AZ::Aabb& dirtyRegion, TerrainDataChangedMask dataChangedMask) + { + if (dataChangedMask != TerrainDataChangedMask::HeightData && dataChangedMask != TerrainDataChangedMask::Settings) { return; } + AZ::Aabb worldBounds = AZ::Aabb::CreateNull(); + AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult( + worldBounds, &AzFramework::Terrain::TerrainDataRequests::GetTerrainAabb); + + const AZ::Aabb& regionToUpdate = dirtyRegion.IsValid() ? dirtyRegion : worldBounds; + + m_dirtyRegion.AddAabb(regionToUpdate); + m_dirtyRegion.Clamp(worldBounds); + + AZ::Transform transform = AZ::Transform::CreateTranslation(worldBounds.GetCenter()); + + AZ::Vector2 queryResolution = AZ::Vector2(1.0f); + AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult( + queryResolution, &AzFramework::Terrain::TerrainDataRequests::GetTerrainHeightQueryResolution); + m_areaData.m_transform = transform; m_areaData.m_heightScale = worldBounds.GetZExtent(); m_areaData.m_terrainBounds = worldBounds; - m_areaData.m_heightmapImageHeight = height; - m_areaData.m_heightmapImageWidth = width; - m_areaData.m_sampleSpacing = sampleSpacing; + m_areaData.m_heightmapImageWidth = aznumeric_cast(worldBounds.GetXExtent() / queryResolution.GetX()); + m_areaData.m_heightmapImageHeight = aznumeric_cast(worldBounds.GetYExtent() / queryResolution.GetY()); + m_areaData.m_updateWidth = aznumeric_cast(m_dirtyRegion.GetXExtent() / queryResolution.GetX()); + m_areaData.m_updateHeight = aznumeric_cast(m_dirtyRegion.GetYExtent() / queryResolution.GetY()); + // Currently query resolution is multidimensional but the rendering system only supports this changing in one dimension. + m_areaData.m_sampleSpacing = queryResolution.GetX(); + m_areaData.m_propertiesDirty = true; + } + + void TerrainFeatureProcessor::UpdateTerrainData() + { + static const AZ::Name TerrainHeightmapName = AZ::Name(TerrainHeightmapChars); + + uint32_t width = m_areaData.m_updateWidth; + uint32_t height = m_areaData.m_updateHeight; + const AZ::Aabb& worldBounds = m_areaData.m_terrainBounds; + float queryResolution = m_areaData.m_sampleSpacing; + + AZ::RHI::Size worldSize = AZ::RHI::Size(m_areaData.m_heightmapImageWidth, m_areaData.m_heightmapImageHeight, 1); + + if (!m_areaData.m_heightmapImage || m_areaData.m_heightmapImage->GetDescriptor().m_size != worldSize) + { + // World size changed, so the whole world needs updating. + width = worldSize.m_width; + height = worldSize.m_height; + m_dirtyRegion = worldBounds; + + AZ::Data::Instance imagePool = AZ::RPI::ImageSystemInterface::Get()->GetSystemAttachmentPool(); + AZ::RHI::ImageDescriptor imageDescriptor = AZ::RHI::ImageDescriptor::Create2D( + AZ::RHI::ImageBindFlags::ShaderRead, width, height, AZ::RHI::Format::R16_UNORM + ); + m_areaData.m_heightmapImage = AZ::RPI::AttachmentImage::Create(*imagePool.get(), imageDescriptor, TerrainHeightmapName, nullptr, nullptr); + AZ_Error(TerrainFPName, m_areaData.m_heightmapImage, "Failed to initialize the heightmap image!"); + } + + AZStd::vector pixels; + pixels.reserve(width * height); - // Create heightmap image data { - m_areaData.m_propertiesDirty = true; + // Block other threads from accessing the surface data bus while we are in GetHeightFromFloats (which may call into the SurfaceData bus). + // We lock our surface data mutex *before* checking / setting "isRequestInProgress" so that we prevent race conditions + // that create false detection of cyclic dependencies when multiple requests occur on different threads simultaneously. + // (One case where this was previously able to occur was in rapid updating of the Preview widget on the + // GradientSurfaceDataComponent in the Editor when moving the threshold sliders back and forth rapidly) - AZ::RHI::Size imageSize; - imageSize.m_width = width; - imageSize.m_height = height; + auto& surfaceDataContext = SurfaceData::SurfaceDataSystemRequestBus::GetOrCreateContext(false); + typename SurfaceData::SurfaceDataSystemRequestBus::Context::DispatchLockGuard scopeLock(surfaceDataContext.m_contextMutex); - AZStd::vector uint16Heights; - uint16Heights.reserve(heightData.size()); - for (float sampleHeight : heightData) + for (uint32_t y = 0; y < height; y++) { - float clampedSample = AZ::GetClamp(sampleHeight, 0.0f, 1.0f); - constexpr uint16_t MaxUint16 = 0xFFFF; - uint16Heights.push_back(aznumeric_cast(clampedSample * MaxUint16)); + for (uint32_t x = 0; x < width; x++) + { + bool terrainExists = true; + float terrainHeight = 0.0f; + AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult( + terrainHeight, &AzFramework::Terrain::TerrainDataRequests::GetHeightFromFloats, + (x * queryResolution) + m_dirtyRegion.GetMin().GetX(), + (y * queryResolution) + m_dirtyRegion.GetMin().GetY(), + AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT, + &terrainExists); + + float clampedHeight = AZ::GetClamp((terrainHeight - worldBounds.GetMin().GetZ()) / worldBounds.GetExtents().GetZ(), 0.0f, 1.0f); + float expandedHeight = AZStd::roundf(clampedHeight * AZStd::numeric_limits::max()); + uint16_t uint16Height = aznumeric_cast(expandedHeight); + + pixels.push_back(uint16Height); + } } - - AZ::Data::Instance streamingImagePool = AZ::RPI::ImageSystemInterface::Get()->GetSystemStreamingPool(); - m_areaData.m_heightmapImage = AZ::RPI::StreamingImage::CreateFromCpuData(*streamingImagePool, - AZ::RHI::ImageDimension::Image2D, - imageSize, - AZ::RHI::Format::R16_UNORM, - (uint8_t*)uint16Heights.data(), - heightData.size() * sizeof(uint16_t)); - AZ_Error(TerrainFPName, m_areaData.m_heightmapImage, "Failed to initialize the heightmap image!"); } + if (m_areaData.m_heightmapImage) + { + const float left = (m_dirtyRegion.GetMin().GetX() - worldBounds.GetMin().GetX()) / queryResolution; + const float top = (m_dirtyRegion.GetMin().GetY() - worldBounds.GetMin().GetY()) / queryResolution; + AZ::RHI::ImageUpdateRequest imageUpdateRequest; + imageUpdateRequest.m_imageSubresourcePixelOffset.m_left = aznumeric_cast(left); + imageUpdateRequest.m_imageSubresourcePixelOffset.m_top = aznumeric_cast(top); + imageUpdateRequest.m_sourceSubresourceLayout.m_bytesPerRow = width * sizeof(uint16_t); + imageUpdateRequest.m_sourceSubresourceLayout.m_bytesPerImage = width * height * sizeof(uint16_t); + imageUpdateRequest.m_sourceSubresourceLayout.m_rowCount = height; + imageUpdateRequest.m_sourceSubresourceLayout.m_size.m_width = width; + imageUpdateRequest.m_sourceSubresourceLayout.m_size.m_height = height; + imageUpdateRequest.m_sourceSubresourceLayout.m_size.m_depth = 1; + imageUpdateRequest.m_sourceData = pixels.data(); + imageUpdateRequest.m_image = m_areaData.m_heightmapImage->GetRHIImage(); + + m_areaData.m_heightmapImage->UpdateImageContents(imageUpdateRequest); + } + + m_dirtyRegion = AZ::Aabb::CreateNull(); } void TerrainFeatureProcessor::ProcessSurfaces(const FeatureProcessor::RenderPacket& process) @@ -169,8 +250,10 @@ namespace Terrain return; } - if (m_areaData.m_propertiesDirty && m_materialInstance) + if (m_areaData.m_propertiesDirty && m_materialInstance && m_materialInstance->CanCompile()) { + UpdateTerrainData(); + m_areaData.m_propertiesDirty = false; m_sectorData.clear(); diff --git a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.h b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.h index 32f88565c2..d8df9b328c 100644 --- a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.h +++ b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainFeatureProcessor.h @@ -9,24 +9,12 @@ #pragma once #include -#include -#include -#include -#include -#include -#include +#include -#include +#include +#include #include -#include -#include -#include -#include -#include -#include -#include -#include #include namespace AZ::RPI @@ -35,6 +23,7 @@ namespace AZ::RPI { class AsyncAssetLoader; } + class Material; class Model; } @@ -43,6 +32,7 @@ namespace Terrain class TerrainFeatureProcessor final : public AZ::RPI::FeatureProcessor , private AZ::RPI::MaterialReloadNotificationBus::Handler + , private AzFramework::Terrain::TerrainDataNotificationBus::Handler { public: AZ_RTTI(TerrainFeatureProcessor, "{D7DAC1F9-4A9F-4D3C-80AE-99579BF8AB1C}", AZ::RPI::FeatureProcessor); @@ -54,26 +44,13 @@ namespace Terrain TerrainFeatureProcessor() = default; ~TerrainFeatureProcessor() = default; - // AZ::Component overrides... + // AZ::RPI::FeatureProcessor overrides... void Activate() override; void Deactivate() override; - - // AZ::RPI::FeatureProcessor overrides... void Render(const AZ::RPI::FeatureProcessor::RenderPacket& packet) override; - // AZ::RPI::MaterialReloadNotificationBus::Handler overrides... - void OnMaterialReinitialized(const AZ::Data::Instance& material) override; - void SetWorldSize(AZ::Vector2 sizeInMeters); - void UpdateTerrainData(const AZ::Transform& transform, const AZ::Aabb& worldBounds, float sampleSpacing, - uint32_t width, uint32_t height, const AZStd::vector& heightData); - - void RemoveTerrainData() - { - m_areaData = {}; - } - private: struct ShaderTerrainData // Must align with struct in Object Srg @@ -104,10 +81,19 @@ namespace Terrain AZStd::vector m_indices; }; + // AZ::RPI::MaterialReloadNotificationBus::Handler overrides... + void OnMaterialReinitialized(const AZ::Data::Instance& material) override; + + // AzFramework::Terrain::TerrainDataNotificationBus overrides... + void OnTerrainDataDestroyBegin() override; + void OnTerrainDataChanged(const AZ::Aabb& dirtyRegion, TerrainDataChangedMask dataChangedMask) override; + void Initialize(); void InitializeTerrainPatch(uint16_t gridSize, float gridSpacing, PatchData& patchdata); bool InitializePatchModel(); + void UpdateTerrainData(); + void ProcessSurfaces(const FeatureProcessor::RenderPacket& process); AZ::Outcome> CreateBufferAsset( @@ -132,14 +118,17 @@ namespace Terrain AZ::Transform m_transform{ AZ::Transform::CreateIdentity() }; AZ::Aabb m_terrainBounds{ AZ::Aabb::CreateNull() }; float m_heightScale{ 0.0f }; - AZ::Data::Instance m_heightmapImage; + AZ::Data::Instance m_heightmapImage; uint32_t m_heightmapImageWidth{ 0 }; uint32_t m_heightmapImageHeight{ 0 }; + uint32_t m_updateWidth{ 0 }; + uint32_t m_updateHeight{ 0 }; bool m_propertiesDirty{ true }; float m_sampleSpacing{ 0.0f }; }; TerrainAreaData m_areaData; + AZ::Aabb m_dirtyRegion{ AZ::Aabb::CreateNull() }; struct SectorData { diff --git a/Gems/Terrain/Code/Source/TerrainRenderer/TerrainMacroMaterialBus.h b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainMacroMaterialBus.h new file mode 100644 index 0000000000..c0618a7f66 --- /dev/null +++ b/Gems/Terrain/Code/Source/TerrainRenderer/TerrainMacroMaterialBus.h @@ -0,0 +1,78 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include +#include +#include + +#include + +namespace Terrain +{ + /** + * Request terrain macro material data. + */ + class TerrainMacroMaterialRequests + : public AZ::ComponentBus + { + public: + //////////////////////////////////////////////////////////////////////// + // EBusTraits + using MutexType = AZStd::recursive_mutex; + //////////////////////////////////////////////////////////////////////// + + virtual ~TerrainMacroMaterialRequests() = default; + + // Get the terrain macro material and the region that it covers. + virtual void GetTerrainMacroMaterialData(AZ::Data::Instance& macroMaterial, AZ::Aabb& macroMaterialRegion) = 0; + }; + + using TerrainMacroMaterialRequestBus = AZ::EBus; + + /** + * Notifications for when the terrain macro material data changes. + */ + class TerrainMacroMaterialNotifications : public AZ::EBusTraits + { + public: + ////////////////////////////////////////////////////////////////////////// + // EBusTraits overrides + static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple; + static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; + ////////////////////////////////////////////////////////////////////////// + + virtual void OnTerrainMacroMaterialCreated( + [[maybe_unused]] AZ::EntityId macroMaterialEntity, + [[maybe_unused]] AZ::Data::Instance macroMaterial, + [[maybe_unused]] const AZ::Aabb& macroMaterialRegion) + { + } + + virtual void OnTerrainMacroMaterialChanged( + [[maybe_unused]] AZ::EntityId macroMaterialEntity, + [[maybe_unused]] AZ::Data::Instance macroMaterial) + { + } + + virtual void OnTerrainMacroMaterialRegionChanged( + [[maybe_unused]] AZ::EntityId macroMaterialEntity, + [[maybe_unused]] const AZ::Aabb& oldRegion, + [[maybe_unused]] const AZ::Aabb& newRegion) + { + } + + virtual void OnTerrainMacroMaterialDestroyed([[maybe_unused]] AZ::EntityId macroMaterialEntity) + { + } + }; + using TerrainMacroMaterialNotificationBus = AZ::EBus; + +} diff --git a/Gems/Terrain/Code/terrain_editor_shared_files.cmake b/Gems/Terrain/Code/terrain_editor_shared_files.cmake index efb68eca31..2db46dc264 100644 --- a/Gems/Terrain/Code/terrain_editor_shared_files.cmake +++ b/Gems/Terrain/Code/terrain_editor_shared_files.cmake @@ -25,4 +25,6 @@ set(FILES Source/EditorTerrainModule.h Source/TerrainModule.cpp Source/TerrainModule.h + Source/TerrainRenderer/EditorComponents/EditorTerrainMacroMaterialComponent.cpp + Source/TerrainRenderer/EditorComponents/EditorTerrainMacroMaterialComponent.h ) diff --git a/Gems/Terrain/Code/terrain_files.cmake b/Gems/Terrain/Code/terrain_files.cmake index 6d190b7d5e..5739ed6079 100644 --- a/Gems/Terrain/Code/terrain_files.cmake +++ b/Gems/Terrain/Code/terrain_files.cmake @@ -24,8 +24,11 @@ set(FILES Source/Components/TerrainWorldDebuggerComponent.h Source/Components/TerrainWorldRendererComponent.cpp Source/Components/TerrainWorldRendererComponent.h + Source/TerrainRenderer/Components/TerrainMacroMaterialComponent.cpp + Source/TerrainRenderer/Components/TerrainMacroMaterialComponent.h Source/TerrainRenderer/TerrainFeatureProcessor.cpp Source/TerrainRenderer/TerrainFeatureProcessor.h + Source/TerrainRenderer/TerrainMacroMaterialBus.h Source/TerrainSystem/TerrainSystem.cpp Source/TerrainSystem/TerrainSystem.h Source/TerrainSystem/TerrainSystemBus.h diff --git a/Gems/TextureAtlas/Code/Source/Editor/AtlasBuilderWorker.cpp b/Gems/TextureAtlas/Code/Source/Editor/AtlasBuilderWorker.cpp index abcdce99b9..f6e6a1254c 100644 --- a/Gems/TextureAtlas/Code/Source/Editor/AtlasBuilderWorker.cpp +++ b/Gems/TextureAtlas/Code/Source/Editor/AtlasBuilderWorker.cpp @@ -757,9 +757,7 @@ namespace TextureAtlasBuilder if (input.m_presetName.empty()) { - // Default to the TextureAtlas preset which is currently set to use compression for all platforms except for iOS. - // Currently the only fully supported compression for iOS is PVRTC which requires the texture to be square and a power of 2. - // Due to this limitation, we default to using no compression for iOS until ASTC is fully supported + // Default to the TextureAtlas preset which is currently set to use compression const AZStd::string defaultPresetName = "UserInterface_Compressed"; input.m_presetName = defaultPresetName; } diff --git a/Registry/setregbuilder.assetprocessor.setreg b/Registry/setregbuilder.assetprocessor.setreg index 25b51470bf..00e6e2f7f8 100644 --- a/Registry/setregbuilder.assetprocessor.setreg +++ b/Registry/setregbuilder.assetprocessor.setreg @@ -20,7 +20,8 @@ "Excludes": [ "/Amazon/AzCore/Runtime", - "/Amazon/AzCore/Bootstrap/project_path" + "/Amazon/AzCore/Bootstrap/project_path", + "/O3DE/Runtime", ] } } diff --git a/cmake/3rdParty/Platform/Android/BuiltInPackages_android.cmake b/cmake/3rdParty/Platform/Android/BuiltInPackages_android.cmake index 4d17e4b20b..c1a770d1ff 100644 --- a/cmake/3rdParty/Platform/Android/BuiltInPackages_android.cmake +++ b/cmake/3rdParty/Platform/Android/BuiltInPackages_android.cmake @@ -18,7 +18,7 @@ ly_associate_package(PACKAGE_NAME lux_core-2.2-rev5-multiplatform TARGETS lux # platform-specific: ly_associate_package(PACKAGE_NAME tiff-4.2.0.15-rev2-android TARGETS tiff PACKAGE_HASH 252b99e5886ec59fdccf38603c1399dd3fc02d878641aba35a7f8d2504065a06) ly_associate_package(PACKAGE_NAME freetype-2.10.4.16-android TARGETS freetype PACKAGE_HASH df9e4d559ea0f03b0666b48c79813b1cd4d9624429148a249865de9f5c2c11cd) -ly_associate_package(PACKAGE_NAME AWSNativeSDK-1.7.167-rev6-android TARGETS AWSNativeSDK PACKAGE_HASH 1624ba9aaf03d001ed0ffc57d2f945ff82590e75a7ea868de35043cf673e82fb) +ly_associate_package(PACKAGE_NAME AWSNativeSDK-1.9.50-rev1-android TARGETS AWSNativeSDK PACKAGE_HASH 33771499f9080cbaab613459927e52911e68f94fa356397885e85005efbd1490) ly_associate_package(PACKAGE_NAME Lua-5.3.5-rev5-android TARGETS Lua PACKAGE_HASH 1f638e94a17a87fe9e588ea456d5893876094b4db191234380e4c4eb9e06c300) ly_associate_package(PACKAGE_NAME PhysX-4.1.2.29882248-rev3-android TARGETS PhysX PACKAGE_HASH b8cb6aa46b2a21671f6cb1f6a78713a3ba88824d0447560ff5ce6c01014b9f43) ly_associate_package(PACKAGE_NAME mikkelsen-1.0.0.4-android TARGETS mikkelsen PACKAGE_HASH 075e8e4940884971063b5a9963014e2e517246fa269c07c7dc55b8cf2cd99705) diff --git a/cmake/3rdParty/Platform/Linux/BuiltInPackages_linux.cmake b/cmake/3rdParty/Platform/Linux/BuiltInPackages_linux.cmake index fb02a80088..8e4a3ef828 100644 --- a/cmake/3rdParty/Platform/Linux/BuiltInPackages_linux.cmake +++ b/cmake/3rdParty/Platform/Linux/BuiltInPackages_linux.cmake @@ -20,7 +20,6 @@ ly_associate_package(PACKAGE_NAME SQLite-3.32.2-rev3-multiplatform ly_associate_package(PACKAGE_NAME glad-2.0.0-beta-rev2-multiplatform TARGETS glad PACKAGE_HASH ff97ee9664e97d0854b52a3734c2289329d9f2b4cd69478df6d0ca1f1c9392ee) ly_associate_package(PACKAGE_NAME lux_core-2.2-rev5-multiplatform TARGETS lux_core PACKAGE_HASH c8c13cf7bc351643e1abd294d0841b24dee60e51647dff13db7aec396ad1e0b5) ly_associate_package(PACKAGE_NAME xxhash-0.7.4-rev1-multiplatform TARGETS xxhash PACKAGE_HASH e81f3e6c4065975833996dd1fcffe46c3cf0f9e3a4207ec5f4a1b564ba75861e) -ly_associate_package(PACKAGE_NAME PVRTexTool-4.24.0-rev4-multiplatform TARGETS PVRTexTool PACKAGE_HASH d0d6da61c7557de0d2c71fc35ba56c3be49555b703f0e853d4c58225537acf1e) # platform-specific: ly_associate_package(PACKAGE_NAME AWSGameLiftServerSDK-3.4.1-rev1-linux TARGETS AWSGameLiftServerSDK PACKAGE_HASH a8149a95bd100384af6ade97e2b21a56173740d921e6c3da8188cd51554d39af) @@ -29,7 +28,6 @@ ly_associate_package(PACKAGE_NAME freetype-2.10.4.16-linux ly_associate_package(PACKAGE_NAME AWSNativeSDK-1.7.167-rev6-linux TARGETS AWSNativeSDK PACKAGE_HASH 490291e4c8057975c3ab86feb971b8a38871c58bac5e5d86abdd1aeb7141eec4) ly_associate_package(PACKAGE_NAME Lua-5.3.5-rev5-linux TARGETS Lua PACKAGE_HASH 1adc812abe3dd0dbb2ca9756f81d8f0e0ba45779ac85bf1d8455b25c531a38b0) ly_associate_package(PACKAGE_NAME PhysX-4.1.2.29882248-rev3-linux TARGETS PhysX PACKAGE_HASH a110249cbef4f266b0002c4ee9a71f59f373040cefbe6b82f1e1510c811edde6) -ly_associate_package(PACKAGE_NAME etc2comp-9cd0f9cae0-rev1-linux TARGETS etc2comp PACKAGE_HASH 9283aa5db5bb7fb90a0ddb7a9f3895317c8ebe8044943124bbb3673a41407430) ly_associate_package(PACKAGE_NAME mcpp-2.7.2_az.2-rev1-linux TARGETS mcpp PACKAGE_HASH df7a998d0bc3fedf44b5bdebaf69ddad6033355b71a590e8642445ec77bc6c41) ly_associate_package(PACKAGE_NAME mikkelsen-1.0.0.4-linux TARGETS mikkelsen PACKAGE_HASH 5973b1e71a64633588eecdb5b5c06ca0081f7be97230f6ef64365cbda315b9c8) ly_associate_package(PACKAGE_NAME googletest-1.8.1-rev4-linux TARGETS googletest PACKAGE_HASH 7b7ad330f369450c316a4c4592d17fbb4c14c731c95bd8f37757203e8c2bbc1b) diff --git a/cmake/3rdParty/Platform/Mac/BuiltInPackages_mac.cmake b/cmake/3rdParty/Platform/Mac/BuiltInPackages_mac.cmake index e80956530a..631c42784a 100644 --- a/cmake/3rdParty/Platform/Mac/BuiltInPackages_mac.cmake +++ b/cmake/3rdParty/Platform/Mac/BuiltInPackages_mac.cmake @@ -21,7 +21,6 @@ ly_associate_package(PACKAGE_NAME azslc-1.7.23-rev1-multiplatform ly_associate_package(PACKAGE_NAME glad-2.0.0-beta-rev2-multiplatform TARGETS glad PACKAGE_HASH ff97ee9664e97d0854b52a3734c2289329d9f2b4cd69478df6d0ca1f1c9392ee) ly_associate_package(PACKAGE_NAME lux_core-2.2-rev5-multiplatform TARGETS lux_core PACKAGE_HASH c8c13cf7bc351643e1abd294d0841b24dee60e51647dff13db7aec396ad1e0b5) ly_associate_package(PACKAGE_NAME xxhash-0.7.4-rev1-multiplatform TARGETS xxhash PACKAGE_HASH e81f3e6c4065975833996dd1fcffe46c3cf0f9e3a4207ec5f4a1b564ba75861e) -ly_associate_package(PACKAGE_NAME PVRTexTool-4.24.0-rev4-multiplatform TARGETS PVRTexTool PACKAGE_HASH d0d6da61c7557de0d2c71fc35ba56c3be49555b703f0e853d4c58225537acf1e) # platform-specific: ly_associate_package(PACKAGE_NAME DirectXShaderCompilerDxc-1.6.2104-o3de-rev3-mac TARGETS DirectXShaderCompilerDxc PACKAGE_HASH 3f77367dbb0342136ec4ebbd44bc1fedf7198089a0f83c5631248530769b2be6) @@ -31,7 +30,6 @@ ly_associate_package(PACKAGE_NAME freetype-2.10.4.16-mac ly_associate_package(PACKAGE_NAME AWSNativeSDK-1.7.167-rev5-mac TARGETS AWSNativeSDK PACKAGE_HASH ffb890bd9cf23afb429b9214ad9bac1bf04696f07a0ebb93c42058c482ab2f01) ly_associate_package(PACKAGE_NAME Lua-5.3.5-rev6-mac TARGETS Lua PACKAGE_HASH b9079fd35634774c9269028447562c6b712dbc83b9c64975c095fd423ff04c08) ly_associate_package(PACKAGE_NAME PhysX-4.1.2.29882248-rev3-mac TARGETS PhysX PACKAGE_HASH 5e092a11d5c0a50c4dd99bb681a04b566a4f6f29aa08443d9bffc8dc12c27c8e) -ly_associate_package(PACKAGE_NAME etc2comp-9cd0f9cae0-rev1-mac TARGETS etc2comp PACKAGE_HASH 1966ab101c89db7ecf30984917e0a48c0d02ee0e4d65b798743842b9469c0818) ly_associate_package(PACKAGE_NAME mcpp-2.7.2_az.2-rev1-mac TARGETS mcpp PACKAGE_HASH be9558905c9c49179ef3d7d84f0a5472415acdf7fe2d76eb060d9431723ddf2e) ly_associate_package(PACKAGE_NAME mikkelsen-1.0.0.4-mac TARGETS mikkelsen PACKAGE_HASH 83af99ca8bee123684ad254263add556f0cf49486c0b3e32e6d303535714e505) ly_associate_package(PACKAGE_NAME googletest-1.8.1-rev4-mac TARGETS googletest PACKAGE_HASH cbf020d5ef976c5db8b6e894c6c63151ade85ed98e7c502729dd20172acae5a8) diff --git a/cmake/3rdParty/Platform/Windows/BuiltInPackages_windows.cmake b/cmake/3rdParty/Platform/Windows/BuiltInPackages_windows.cmake index 7e112c405e..428e5e9526 100644 --- a/cmake/3rdParty/Platform/Windows/BuiltInPackages_windows.cmake +++ b/cmake/3rdParty/Platform/Windows/BuiltInPackages_windows.cmake @@ -21,7 +21,6 @@ ly_associate_package(PACKAGE_NAME azslc-1.7.23-rev1-multiplatform ly_associate_package(PACKAGE_NAME glad-2.0.0-beta-rev2-multiplatform TARGETS glad PACKAGE_HASH ff97ee9664e97d0854b52a3734c2289329d9f2b4cd69478df6d0ca1f1c9392ee) ly_associate_package(PACKAGE_NAME lux_core-2.2-rev5-multiplatform TARGETS lux_core PACKAGE_HASH c8c13cf7bc351643e1abd294d0841b24dee60e51647dff13db7aec396ad1e0b5) ly_associate_package(PACKAGE_NAME xxhash-0.7.4-rev1-multiplatform TARGETS xxhash PACKAGE_HASH e81f3e6c4065975833996dd1fcffe46c3cf0f9e3a4207ec5f4a1b564ba75861e) -ly_associate_package(PACKAGE_NAME PVRTexTool-4.24.0-rev4-multiplatform TARGETS PVRTexTool PACKAGE_HASH d0d6da61c7557de0d2c71fc35ba56c3be49555b703f0e853d4c58225537acf1e) # platform-specific: ly_associate_package(PACKAGE_NAME AWSGameLiftServerSDK-3.4.1-rev1-windows TARGETS AWSGameLiftServerSDK PACKAGE_HASH a0586b006e4def65cc25f388de17dc475e417dc1e6f9d96749777c88aa8271b0) @@ -32,7 +31,6 @@ ly_associate_package(PACKAGE_NAME freetype-2.10.4.16-windows ly_associate_package(PACKAGE_NAME AWSNativeSDK-1.7.167-rev4-windows TARGETS AWSNativeSDK PACKAGE_HASH a900e80f7259e43aed5c847afee2599ada37f29db70505481397675bcbb6c76c) ly_associate_package(PACKAGE_NAME Lua-5.3.5-rev5-windows TARGETS Lua PACKAGE_HASH 136faccf1f73891e3fa3b95f908523187792e56f5b92c63c6a6d7e72d1158d40) ly_associate_package(PACKAGE_NAME PhysX-4.1.2.29882248-rev3-windows TARGETS PhysX PACKAGE_HASH 0c5ffbd9fa588e5cf7643721a7cfe74d0fe448bf82252d39b3a96d06dfca2298) -ly_associate_package(PACKAGE_NAME etc2comp-9cd0f9cae0-rev1-windows TARGETS etc2comp PACKAGE_HASH fc9ae937b2ec0d42d5e7d0e9e8c80e5e4d257673fb33bc9b7d6db76002117123) ly_associate_package(PACKAGE_NAME mcpp-2.7.2_az.2-rev1-windows TARGETS mcpp PACKAGE_HASH 794789aba639bfe2f4e8fcb4424d679933dd6290e523084aa0a4e287ac44acb2) ly_associate_package(PACKAGE_NAME mikkelsen-1.0.0.4-windows TARGETS mikkelsen PACKAGE_HASH 872c4d245a1c86139aa929f2b465b63ea4ea55b04ced50309135dd4597457a4e) ly_associate_package(PACKAGE_NAME googletest-1.8.1-rev4-windows TARGETS googletest PACKAGE_HASH 7e8f03ae8a01563124e3daa06386f25a2b311c10bb95bff05cae6c41eff83837) diff --git a/cmake/3rdParty/Platform/iOS/BuiltInPackages_ios.cmake b/cmake/3rdParty/Platform/iOS/BuiltInPackages_ios.cmake index c9de80c809..abfba29e5a 100644 --- a/cmake/3rdParty/Platform/iOS/BuiltInPackages_ios.cmake +++ b/cmake/3rdParty/Platform/iOS/BuiltInPackages_ios.cmake @@ -19,7 +19,7 @@ ly_associate_package(PACKAGE_NAME lux_core-2.2-rev5-multiplatform TARGETS lux # platform-specific: ly_associate_package(PACKAGE_NAME tiff-4.2.0.15-rev2-ios TARGETS tiff PACKAGE_HASH d864beb0c955a55f28c2a993843afb2ecf6e01519ddfc857cedf34fc5db68d49) ly_associate_package(PACKAGE_NAME freetype-2.10.4.16-ios TARGETS freetype PACKAGE_HASH 3ac3c35e056ae4baec2e40caa023d76a7a3320895ef172b6655e9261b0dc2e29) -ly_associate_package(PACKAGE_NAME AWSNativeSDK-1.7.167-rev3-ios TARGETS AWSNativeSDK PACKAGE_HASH 1246219a213ccfff76b526011febf521586d44dbc1753e474f8fb5fd861654a4) +ly_associate_package(PACKAGE_NAME AWSNativeSDK-1.7.167-rev4-ios TARGETS AWSNativeSDK PACKAGE_HASH d10e7496ca705577032821011beaf9f2507689f23817bfa0ed4d2a2758afcd02) ly_associate_package(PACKAGE_NAME Lua-5.3.5-rev5-ios TARGETS Lua PACKAGE_HASH c2d3c4e67046c293049292317a7d60fdb8f23effeea7136aefaef667163e5ffe) ly_associate_package(PACKAGE_NAME PhysX-4.1.2.29882248-rev3-ios TARGETS PhysX PACKAGE_HASH b1bbc1fc068d2c6e1eb18eecd4e8b776adc516833e8da3dcb1970cef2a8f0cbd) ly_associate_package(PACKAGE_NAME mikkelsen-1.0.0.4-ios TARGETS mikkelsen PACKAGE_HASH 976aaa3ccd8582346132a10af253822ccc5d5bcc9ea5ba44d27848f65ee88a8a) diff --git a/cmake/Platform/Windows/Packaging/Bootstrapper.wxs b/cmake/Platform/Windows/Packaging/Bootstrapper.wxs index 079c20e212..f3af199bc2 100644 --- a/cmake/Platform/Windows/Packaging/Bootstrapper.wxs +++ b/cmake/Platform/Windows/Packaging/Bootstrapper.wxs @@ -20,7 +20,7 @@ + Value="[InstallFolder]\bin\Windows\profile\Default\o3de.exe"/> diff --git a/cmake/Platform/Windows/Packaging/Shortcuts.wxs b/cmake/Platform/Windows/Packaging/Shortcuts.wxs index fefd15294a..fbfa174d06 100644 --- a/cmake/Platform/Windows/Packaging/Shortcuts.wxs +++ b/cmake/Platform/Windows/Packaging/Shortcuts.wxs @@ -18,7 +18,9 @@ - + + + @@ -52,18 +54,18 @@ diff --git a/scripts/build/Jenkins/Jenkinsfile b/scripts/build/Jenkins/Jenkinsfile index 5fe19ddf46..ffca69a14f 100644 --- a/scripts/build/Jenkins/Jenkinsfile +++ b/scripts/build/Jenkins/Jenkinsfile @@ -793,27 +793,32 @@ catch(Exception e) { } finally { try { - if(env.SNS_TOPIC) { - snsPublish( - topicArn: env.SNS_TOPIC, - subject:'Build Result', - message:"${currentBuild.currentResult}:${BUILD_URL}:${env.RECREATE_VOLUME}:${env.CLEAN_OUTPUT_DIRECTORY}:${env.CLEAN_ASSETS}" - ) - } node('controller') { if("${currentBuild.currentResult}" == "SUCCESS") { + buildFailure = "" emailBody = "${BUILD_URL}\nSuccess!" } else { buildFailure = tm('${BUILD_FAILURE_ANALYZER}') emailBody = "${BUILD_URL}\n${buildFailure}!" - if(env.SNS_TOPIC_BUILD_FAILURE) { - message_json = ["build_url":env.BUILD_URL, "repository_name":env.REPOSITORY_NAME, "branch_name":env.BRANCH_NAME, "build_failure":buildFailure] - snsPublish( - topicArn: env.SNS_TOPIC_BUILD_FAILURE, - subject:'Build Failure', - message:JsonOutput.toJson(message_json) - ) - } + + } + if(env.POST_AR_BUILD_SNS_TOPIC) { + message_json = [ + "build_url": env.BUILD_URL, + "build_number": env.BUILD_NUMBER, + "repository_name": env.REPOSITORY_NAME, + "branch_name": env.BRANCH_NAME, + "build_result": "${currentBuild.currentResult}", + "build_failure": buildFailure, + "recreate_volume": env.RECREATE_VOLUME, + "clean_output_directory": env.CLEAN_OUTPUT_DIRECTORY, + "clean_assets": env.CLEAN_ASSETS + ] + snsPublish( + topicArn: env.POST_AR_BUILD_SNS_TOPIC, + subject:'Build Result', + message:JsonOutput.toJson(message_json) + ) } emailext ( body: "${emailBody}", diff --git a/scripts/build/Platform/Windows/build_config.json b/scripts/build/Platform/Windows/build_config.json index c607c3d821..7c39661273 100644 --- a/scripts/build/Platform/Windows/build_config.json +++ b/scripts/build/Platform/Windows/build_config.json @@ -31,7 +31,6 @@ ], "steps": [ "profile_vs2019", - "test_impact_analysis_profile_vs2019", "asset_profile_vs2019", "test_cpu_profile_vs2019" ] diff --git a/scripts/build/Platform/Windows/deploy_cdk_applications.cmd b/scripts/build/Platform/Windows/deploy_cdk_applications.cmd index 2845707923..54a2485360 100644 --- a/scripts/build/Platform/Windows/deploy_cdk_applications.cmd +++ b/scripts/build/Platform/Windows/deploy_cdk_applications.cmd @@ -57,7 +57,7 @@ IF ERRORLEVEL 1 ( exit /b 1 ) -CALL :DeployCDKApplication AWSCore --all +CALL :DeployCDKApplication AWSCore --all "-c disable_access_log=true" IF ERRORLEVEL 1 ( exit /b 1 ) diff --git a/scripts/build/bootstrap/incremental_build_util.py b/scripts/build/bootstrap/incremental_build_util.py index 101e31b5db..5c77559085 100644 --- a/scripts/build/bootstrap/incremental_build_util.py +++ b/scripts/build/bootstrap/incremental_build_util.py @@ -18,6 +18,8 @@ from contextlib import contextmanager import threading import _thread +from botocore.config import Config + DEFAULT_REGION = 'us-west-2' DEFAULT_DISK_SIZE = 300 DEFAULT_DISK_TYPE = 'gp2' @@ -173,10 +175,27 @@ def get_region_name(): def get_ec2_client(region): - client = boto3.client('ec2', region_name=region) + client_config = Config( + region_name=region, + retries={ + 'mode': 'standard' + } + ) + client = boto3.client('ec2', config=client_config) return client +def get_ec2_resource(region): + resource_config = Config( + region_name=region, + retries={ + 'mode': 'standard' + } + ) + resource = boto3.resource('ec2', config=resource_config) + return resource + + def get_ec2_instance_id(): try: instance_id = urllib.request.urlopen('http://169.254.169.254/latest/meta-data/instance-id').read() @@ -395,14 +414,11 @@ def detach_volume_from_ec2_instance(volume, ec2_instance_id, force, timeout_dura def mount_ebs(snapshot_hint, repository_name, project, pipeline, branch, platform, build_type, disk_size, disk_type): - session = boto3.session.Session() - region = session.region_name - if region is None: - region = DEFAULT_REGION + region = get_region_name() ec2_client = get_ec2_client(region) ec2_instance_id = get_ec2_instance_id() ec2_availability_zone = get_availability_zone() - ec2_resource = boto3.resource('ec2', region_name=region) + ec2_resource = get_ec2_resource(region) ec2_instance = ec2_resource.Instance(ec2_instance_id) for volume in ec2_instance.volumes.all(): @@ -469,7 +485,7 @@ def mount_ebs(snapshot_hint, repository_name, project, pipeline, branch, platfor def unmount_ebs(): region = get_region_name() ec2_instance_id = get_ec2_instance_id() - ec2_resource = boto3.resource('ec2', region_name=region) + ec2_resource = get_ec2_resource(region) ec2_instance = ec2_resource.Instance(ec2_instance_id) if os.path.isfile('envinject.properties'): diff --git a/scripts/o3de/o3de/engine_template.py b/scripts/o3de/o3de/engine_template.py index 802641bd3a..cba7539aaa 100755 --- a/scripts/o3de/o3de/engine_template.py +++ b/scripts/o3de/o3de/engine_template.py @@ -59,6 +59,14 @@ binary_file_ext = { '.motionset' } +cpp_file_ext = { + '.cpp', + '.h', + '.hpp', + '.hxx', + '.inl' +} + expect_license_info_ext = { '.cpp', '.h', @@ -402,7 +410,7 @@ def create_template(source_path: pathlib.Path, # if no template path, error if not template_path: logger.info(f'Template path empty. Using source name {source_name}') - template_path = source_name + template_path = pathlib.Path(source_name) if not template_path.is_absolute(): default_templates_folder = manifest.get_registered(default_folder='templates') template_path = default_templates_folder / template_path @@ -518,21 +526,52 @@ def create_template(source_path: pathlib.Path, replacements.append((source_name.upper(), '${NameUpper}')) replacements.append((source_name, '${Name}')) replacements.append((sanitized_source_name, '${SanitizedCppName}')) + sanitized_name_index = len(replacements) - 1 + + def _is_cpp_file(file_path: pathlib.Path) -> bool: + """ + Internal helper method to check if a file is a C++ file based + on its extension, so we can determine if we need to prefer + the ${SanitizedCppName} + :param file_path: The input file path + :return: bool: Whether or not the input file path has a C++ extension + """ + name, ext = os.path.splitext(file_path) + + return ext.lower() in cpp_file_ext - def _transform_into_template(s_data: object) -> (bool, str): + def _transform_into_template(s_data: object, + prefer_sanitized_name: bool = False) -> (bool, str): """ Internal function to transform any data into templated data :param s_data: the input data, this could be file data or file name data + :param prefer_sanitized_name: Optionally swap the sanitized name with the normal name + This can be necessary when creating the template, the source + name and sanitized source name might be the same, but C++ + files will need to prefer the sanitized version, or else + there might be compile errors (e.g. '-' characters in the name) :return: bool: whether or not the returned data MAY need to be transformed to instantiate it t_data: potentially transformed data 0 for success or non 0 failure code """ + def swap_sanitized_name_and_normal(): + replacements[sanitized_name_index-1], replacements[sanitized_name_index] = \ + replacements[sanitized_name_index], replacements[sanitized_name_index-1] + # copy the src data to the transformed data, then operate only on transformed data t_data = str(s_data) + # If we need to prefer the sanitized name, then swap it for the normal + if prefer_sanitized_name: + swap_sanitized_name_and_normal() + # run all the replacements for replacement in replacements: t_data = t_data.replace(replacement[0], replacement[1]) + # Once we are done running the replacements, reset the list if we had modified it + if prefer_sanitized_name: + swap_sanitized_name_and_normal() + if not keep_license_text: t_data = _replace_license_text(t_data) @@ -704,7 +743,7 @@ def create_template(source_path: pathlib.Path, # open the file and attempt to transform it with open(entry_abs, 'r') as s: source_data = s.read() - templated, source_data = _transform_into_template(source_data) + templated, source_data = _transform_into_template(source_data, _is_cpp_file(entry_abs)) # if the file type is a file that we expect to fins license header and we don't find any # warn that the we didn't find the license info, this makes it easy to make sure we didn't @@ -840,7 +879,7 @@ def create_template(source_path: pathlib.Path, # open the file and attempt to transform it with open(entry_abs, 'r') as s: source_data = s.read() - templated, source_data = _transform_into_template(source_data) + templated, source_data = _transform_into_template(source_data, _is_cpp_file(entry_abs)) # if the file type is a file that we expect to fins license header and we don't find any # warn that the we didn't find the license info, this makes it easy to make sure we didn't diff --git a/scripts/o3de/o3de/register.py b/scripts/o3de/o3de/register.py index 7db09368ec..c3d201f23c 100644 --- a/scripts/o3de/o3de/register.py +++ b/scripts/o3de/o3de/register.py @@ -487,14 +487,14 @@ def register_repo(json_data: dict, if remove: logger.warn(f'Removing repo uri {repo_uri}.') return 0 - repo_sha256 = hashlib.sha256(url.encode()) cache_file = manifest.get_o3de_cache_folder() / str(repo_sha256.hexdigest() + '.json') - result = utils.download_file(url, cache_file) + result = utils.download_file(parsed_uri, cache_file) if result == 0: - json_data['repos'].insert(0, repo_uri.as_posix()) + json_data['repos'].insert(0, repo_uri) + repo_set = set() result = repo.process_add_o3de_repo(cache_file, repo_set) return result @@ -621,6 +621,7 @@ def register(engine_path: pathlib.Path = None, return 1 result = result or register_gem_path(json_data, gem_path, remove, external_subdir_engine_path, external_subdir_project_path) + if isinstance(external_subdir_path, pathlib.PurePath): if not external_subdir_path: logger.error(f'External Subdirectory path is None.') diff --git a/scripts/o3de/o3de/repo.py b/scripts/o3de/o3de/repo.py index 8492f391e5..52abfc3386 100644 --- a/scripts/o3de/o3de/repo.py +++ b/scripts/o3de/o3de/repo.py @@ -12,6 +12,7 @@ import pathlib import shutil import urllib.parse import urllib.request +import hashlib from o3de import manifest, utils, validation @@ -24,7 +25,6 @@ def process_add_o3de_repo(file_name: str or pathlib.Path, file_name = pathlib.Path(file_name).resolve() if not validation.valid_o3de_repo_json(file_name): return 1 - cache_folder = manifest.get_o3de_cache_folder() with file_name.open('r') as f: @@ -34,11 +34,30 @@ def process_add_o3de_repo(file_name: str or pathlib.Path, logger.error(f'{file_name} failed to load: {str(e)}') return 1 - for o3de_object_uris, manifest_json in [(repo_data['engines'], 'engine.json'), - (repo_data['projects'], 'project.json'), - (repo_data['gems'], 'gem.json'), - (repo_data['template'], 'template.json'), - (repo_data['restricted'], 'restricted.json')]: + # A repo may not contain all types of object. + manifest_download_list = [] + try: + manifest_download_list.append((repo_data['engines'], 'engine.json')) + except KeyError: + pass + try: + manifest_download_list.append((repo_data['projects'], 'project.json')) + except KeyError: + pass + try: + manifest_download_list.append((repo_data['gems'], 'gem.json')) + except KeyError: + pass + try: + manifest_download_list.append((repo_data['templates'], 'template.json')) + except KeyError: + pass + try: + manifest_download_list.append((repo_data['restricted'], 'restricted.json')) + except KeyError: + pass + + for o3de_object_uris, manifest_json in manifest_download_list: for o3de_object_uri in o3de_object_uris: manifest_json_uri = f'{o3de_object_uri}/{manifest_json}' manifest_json_sha256 = hashlib.sha256(manifest_json_uri.encode()) @@ -49,7 +68,27 @@ def process_add_o3de_repo(file_name: str or pathlib.Path, if download_file_result != 0: return download_file_result - repo_set |= repo_data['repos'] + # Having a repo is also optional + repo_list = [] + try: + repo_list.add(repo_data['repos']) + except KeyError: + pass + + for repo in repo_list: + if repo not in repo_set: + repo_set.add(repo) + for o3de_object_uri in o3de_object_uris: + parsed_uri = urllib.parse.urlparse(f'{repo}/repo.json') + manifest_json_sha256 = hashlib.sha256(parsed_uri.geturl().encode()) + cache_file = cache_folder / str(manifest_json_sha256.hexdigest() + '.json') + if cache_file.is_file(): + cache_file.unlink() + download_file_result = utils.download_file(parsed_uri, cache_file) + if download_file_result != 0: + return download_file_result + + return process_add_o3de_repo(parsed_uri.geturl(), repo_set) return 0 @@ -70,11 +109,10 @@ def refresh_repos() -> int: if repo_uri not in repo_set: repo_set.add(repo_uri) - repo_uri = f'{repo_uri}/repo.json' - repo_sha256 = hashlib.sha256(repo_uri.encode()) + parsed_uri = urllib.parse.urlparse(f'{repo_uri}/repo.json') + repo_sha256 = hashlib.sha256(parsed_uri.geturl().encode()) cache_file = cache_folder / str(repo_sha256.hexdigest() + '.json') if not cache_file.is_file(): - parsed_uri = urllib.parse.urlparse(repo_uri) download_file_result = utils.download_file(parsed_uri, cache_file) if download_file_result != 0: return download_file_result diff --git a/scripts/o3de/o3de/utils.py b/scripts/o3de/o3de/utils.py index 18f80584cc..9f9c747390 100755 --- a/scripts/o3de/o3de/utils.py +++ b/scripts/o3de/o3de/utils.py @@ -13,6 +13,10 @@ import uuid import pathlib import shutil import urllib.request +import logging + +logger = logging.getLogger() +logging.basicConfig() def validate_identifier(identifier: str) -> bool: """ @@ -97,11 +101,11 @@ def download_file(parsed_uri, download_path: pathlib.Path) -> int: if download_path.is_file(): logger.warn(f'File already downloaded to {download_path}.') elif parsed_uri.scheme in ['http', 'https', 'ftp', 'ftps']: - with urllib.request.urlopen(url) as s: + with urllib.request.urlopen(parsed_uri.geturl()) as s: with download_path.open('wb') as f: shutil.copyfileobj(s, f) else: - origin_file = pathlib.Path(url).resolve() + origin_file = pathlib.Path(parsed_uri.geturl()).resolve() if not origin_file.is_file(): return 1 shutil.copy(origin_file, download_path) diff --git a/scripts/o3de/tests/unit_test_engine_template.py b/scripts/o3de/tests/unit_test_engine_template.py index 16ac24d74c..ed1c8258d3 100755 --- a/scripts/o3de/tests/unit_test_engine_template.py +++ b/scripts/o3de/tests/unit_test_engine_template.py @@ -31,17 +31,17 @@ TEST_TEMPLATED_CONTENT_WITHOUT_LICENSE = """ #include #include -namespace ${Name} +namespace ${SanitizedCppName} { - class ${Name}Requests + class ${SanitizedCppName}Requests { public: - AZ_RTTI(${Name}Requests, "{${Random_Uuid}}"); - virtual ~${Name}Requests() = default; + AZ_RTTI(${SanitizedCppName}Requests, "{${Random_Uuid}}"); + virtual ~${SanitizedCppName}Requests() = default; // Put your public methods here }; - class ${Name}BusTraits + class ${SanitizedCppName}BusTraits : public AZ::EBusTraits { public: @@ -52,31 +52,31 @@ namespace ${Name} ////////////////////////////////////////////////////////////////////////// }; - using ${Name}RequestBus = AZ::EBus<${Name}Requests, ${Name}BusTraits>; - using ${Name}Interface = AZ::Interface<${Name}Requests>; + using ${SanitizedCppName}RequestBus = AZ::EBus<${SanitizedCppName}Requests, ${SanitizedCppName}BusTraits>; + using ${SanitizedCppName}Interface = AZ::Interface<${SanitizedCppName}Requests>; -} // namespace ${Name} +} // namespace ${SanitizedCppName} """ TEST_TEMPLATED_CONTENT_WITH_LICENSE = CPP_LICENSE_TEXT + TEST_TEMPLATED_CONTENT_WITHOUT_LICENSE TEST_CONCRETE_TESTTEMPLATE_CONTENT_WITHOUT_LICENSE = string.Template( - TEST_TEMPLATED_CONTENT_WITHOUT_LICENSE).safe_substitute({'Name': "TestTemplate"}) + TEST_TEMPLATED_CONTENT_WITHOUT_LICENSE).safe_substitute({'SanitizedCppName': "TestTemplate"}) TEST_CONCRETE_TESTTEMPLATE_CONTENT_WITH_LICENSE = string.Template( - TEST_TEMPLATED_CONTENT_WITH_LICENSE).safe_substitute({'Name': "TestTemplate"}) + TEST_TEMPLATED_CONTENT_WITH_LICENSE).safe_substitute({'SanitizedCppName': "TestTemplate"}) TEST_CONCRETE_TESTPROJECT_TEMPLATE_CONTENT_WITHOUT_LICENSE = string.Template( - TEST_TEMPLATED_CONTENT_WITHOUT_LICENSE).safe_substitute({'Name': "TestProject"}) + TEST_TEMPLATED_CONTENT_WITHOUT_LICENSE).safe_substitute({'SanitizedCppName': "TestProject"}) TEST_CONCRETE_TESTPROJECT_TEMPLATE_CONTENT_WITH_LICENSE = string.Template( - TEST_TEMPLATED_CONTENT_WITH_LICENSE).safe_substitute({'Name': "TestProject"}) + TEST_TEMPLATED_CONTENT_WITH_LICENSE).safe_substitute({'SanitizedCppName': "TestProject"}) TEST_CONCRETE_TESTGEM_TEMPLATE_CONTENT_WITHOUT_LICENSE = string.Template( - TEST_TEMPLATED_CONTENT_WITHOUT_LICENSE).safe_substitute({'Name': "TestGem"}) + TEST_TEMPLATED_CONTENT_WITHOUT_LICENSE).safe_substitute({'SanitizedCppName': "TestGem"}) TEST_CONCRETE_TESTGEM_TEMPLATE_CONTENT_WITH_LICENSE = string.Template( - TEST_TEMPLATED_CONTENT_WITH_LICENSE).safe_substitute({'Name': "TestGem"}) + TEST_TEMPLATED_CONTENT_WITH_LICENSE).safe_substitute({'SanitizedCppName': "TestGem"}) TEST_TEMPLATE_JSON_CONTENTS = """\ {