diff --git a/AutomatedTesting/Gem/PythonTests/AWS/Windows/aws_metrics/aws_metrics_automation_test.py b/AutomatedTesting/Gem/PythonTests/AWS/Windows/aws_metrics/aws_metrics_automation_test.py index 34b2217916..6f3113d771 100644 --- a/AutomatedTesting/Gem/PythonTests/AWS/Windows/aws_metrics/aws_metrics_automation_test.py +++ b/AutomatedTesting/Gem/PythonTests/AWS/Windows/aws_metrics/aws_metrics_automation_test.py @@ -14,6 +14,7 @@ from datetime import datetime import ly_test_tools.log.log_monitor from AWS.common import constants +from AWS.common.resource_mappings import AWS_RESOURCE_MAPPINGS_ACCOUNT_ID_KEY from .aws_metrics_custom_thread import AWSMetricsThread # fixture imports @@ -200,6 +201,59 @@ class TestAWSMetricsWindows(object): for thread in operational_threads: thread.join() + @pytest.mark.parametrize('level', ['AWS/Metrics']) + def test_realtime_and_batch_analytics_no_global_accountid(self, + level: str, + launcher: pytest.fixture, + asset_processor: pytest.fixture, + workspace: pytest.fixture, + aws_utils: pytest.fixture, + resource_mappings: pytest.fixture, + aws_metrics_utils: pytest.fixture): + """ + Verify that the metrics events are sent to CloudWatch and S3 for analytics. + """ + # Remove top-level account ID from resource mappings + resource_mappings.clear_select_keys([AWS_RESOURCE_MAPPINGS_ACCOUNT_ID_KEY]) + # Start Kinesis analytics application on a separate thread to avoid blocking the test. + kinesis_analytics_application_thread = AWSMetricsThread(target=update_kinesis_analytics_application_status, + args=(aws_metrics_utils, resource_mappings, True)) + kinesis_analytics_application_thread.start() + + log_monitor = setup(launcher, asset_processor) + + # Kinesis analytics application needs to be in the running state before we start the game launcher. + kinesis_analytics_application_thread.join() + launcher.args = ['+LoadLevel', level] + launcher.args.extend(['-rhi=null']) + start_time = datetime.utcnow() + with launcher.start(launch_ap=False): + monitor_metrics_submission(log_monitor) + + # Verify that real-time analytics metrics are delivered to CloudWatch. + aws_metrics_utils.verify_cloud_watch_delivery( + AWS_METRICS_FEATURE_NAME, + 'TotalLogins', + [], + start_time) + logger.info('Real-time metrics are sent to CloudWatch.') + + # Run time-consuming operations on separate threads to avoid blocking the test. + operational_threads = list() + operational_threads.append( + AWSMetricsThread(target=query_metrics_from_s3, + args=(aws_metrics_utils, resource_mappings))) + operational_threads.append( + AWSMetricsThread(target=verify_operational_metrics, + args=(aws_metrics_utils, resource_mappings, start_time))) + operational_threads.append( + AWSMetricsThread(target=update_kinesis_analytics_application_status, + args=(aws_metrics_utils, resource_mappings, False))) + for thread in operational_threads: + thread.start() + for thread in operational_threads: + thread.join() + @pytest.mark.parametrize('level', ['AWS/Metrics']) def test_unauthorized_user_request_rejected(self, level: str, diff --git a/AutomatedTesting/Gem/PythonTests/AWS/Windows/core/test_aws_resource_interaction.py b/AutomatedTesting/Gem/PythonTests/AWS/Windows/core/test_aws_resource_interaction.py index 949186ad50..59c517fd1c 100644 --- a/AutomatedTesting/Gem/PythonTests/AWS/Windows/core/test_aws_resource_interaction.py +++ b/AutomatedTesting/Gem/PythonTests/AWS/Windows/core/test_aws_resource_interaction.py @@ -18,6 +18,7 @@ import ly_test_tools.environment.process_utils as process_utils import ly_test_tools.o3de.asset_processor_utils as asset_processor_utils from AWS.common import constants +from AWS.common.resource_mappings import AWS_RESOURCE_MAPPINGS_ACCOUNT_ID_KEY # fixture imports from assetpipeline.ap_fixtures.asset_processor_fixture import asset_processor @@ -141,3 +142,51 @@ class TestAWSCoreAWSResourceInteraction(object): 'The expected file wasn\'t successfully downloaded.' # clean up the file directories. shutil.rmtree(s3_download_dir) + + @pytest.mark.parametrize('expected_lines', [ + ['(Script) - [S3] Head object request is done', + '(Script) - [S3] Head object success: Object example.txt is found.', + '(Script) - [S3] Get object success: Object example.txt is downloaded.', + '(Script) - [Lambda] Completed Invoke', + '(Script) - [Lambda] Invoke success: {"statusCode": 200, "body": {}}', + '(Script) - [DynamoDB] Results finished']]) + @pytest.mark.parametrize('unexpected_lines', [ + ['(Script) - [S3] Head object error: No response body.', + '(Script) - [S3] Get object error: Request validation failed, output file directory doesn\'t exist.', + '(Script) - Request validation failed, output file miss full path.', + '(Script) - ']]) + def test_scripting_behavior_no_global_accountid(self, + level: str, + launcher: pytest.fixture, + workspace: pytest.fixture, + asset_processor: pytest.fixture, + resource_mappings: pytest.fixture, + aws_utils: pytest.fixture, + expected_lines: typing.List[str], + unexpected_lines: typing.List[str]): + """ + Setup: Updates resource mapping file using existing CloudFormation stacks. + Tests: Interact with AWS S3, DynamoDB and Lambda services. + Verification: Script canvas nodes can communicate with AWS services successfully. + """ + + resource_mappings.clear_select_keys([AWS_RESOURCE_MAPPINGS_ACCOUNT_ID_KEY]) + log_monitor, s3_download_dir = setup(launcher, asset_processor) + write_test_data_to_dynamodb_table(resource_mappings, aws_utils) + + launcher.args = ['+LoadLevel', level] + launcher.args.extend(['-rhi=null']) + + with launcher.start(launch_ap=False): + result = log_monitor.monitor_log_for_lines( + expected_lines=expected_lines, + unexpected_lines=unexpected_lines, + halt_on_unexpected=True + ) + + assert result, "Expected lines weren't found." + + assert os.path.exists(os.path.join(s3_download_dir, 'output.txt')), \ + 'The expected file wasn\'t successfully downloaded.' + # clean up the file directories. + shutil.rmtree(s3_download_dir) diff --git a/AutomatedTesting/Gem/PythonTests/AWS/common/resource_mappings.py b/AutomatedTesting/Gem/PythonTests/AWS/common/resource_mappings.py index 5f01ecdbf8..988d5bf1fc 100644 --- a/AutomatedTesting/Gem/PythonTests/AWS/common/resource_mappings.py +++ b/AutomatedTesting/Gem/PythonTests/AWS/common/resource_mappings.py @@ -102,3 +102,17 @@ class ResourceMappings: def get_resource_name_id(self, resource_key: str): return self._resource_mappings[AWS_RESOURCE_MAPPINGS_KEY][resource_key]['Name/ID'] + + def clear_select_keys(self, resource_keys=None) -> None: + """ + Clears values from select resource mapping keys. + :param resource_keys: list of keys to clear out + """ + with open(self._resource_mapping_file_path) as file_content: + resource_mappings = json.load(file_content) + + for key in resource_keys: + resource_mappings[key] = '' + + with open(self._resource_mapping_file_path, 'w') as file_content: + json.dump(resource_mappings, file_content, indent=4) \ No newline at end of file diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/ap_fixtures/bundler_batch_setup_fixture.py b/AutomatedTesting/Gem/PythonTests/assetpipeline/ap_fixtures/bundler_batch_setup_fixture.py index 35d7982ded..9281d5947e 100755 --- a/AutomatedTesting/Gem/PythonTests/assetpipeline/ap_fixtures/bundler_batch_setup_fixture.py +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/ap_fixtures/bundler_batch_setup_fixture.py @@ -158,7 +158,7 @@ def bundler_batch_setup_fixture(request, workspace, asset_processor, timeout) -> else: cmd.append(f"--{key}") if append_defaults: - cmd.append(f"--project-path={os.path.join(workspace.paths.engine_root(), workspace.project)}") + cmd.append(f"--project-path={workspace.paths.project()}") return cmd # ****** diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_bundler_batch_tests.py b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_bundler_batch_tests.py index 7f85e5e317..f5e5642573 100755 --- a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_bundler_batch_tests.py +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_bundler_batch_tests.py @@ -88,214 +88,6 @@ class TestsAssetBundlerBatch_WindowsAndMac(object): bundler_batch_helper.call_bundles(help="") bundler_batch_helper.call_bundleSeed(help="") - @pytest.mark.BAT - @pytest.mark.assetpipeline - @pytest.mark.test_case_id("C16877175") - @pytest.mark.skip("'animations/animationeditorfiles/sample1.animgraph' missing, needs investigation") - def test_WindowsAndMac_CreateAssetList_DependenciesCorrect(self, workspace, bundler_batch_helper): - r""" - Tests that an asset list created maps dependencies correctly. - testdependencieslevel\level.pak and lists of known dependencies are used for validation - - Test Steps: - 1. Create an asset list from the level.pak - 2. Create Lists of expected assets in the level.pak - 3. Add lists of expected assets to a single list - 4. Compare list of expected assets to actual assets - """ - helper = bundler_batch_helper - - # Create the asset list file - helper.call_assetLists( - addSeed=r"levels\testdependencieslevel\level.pak", - assetListFile=helper['asset_info_file_request'] - ) - - assert os.path.isfile(helper["asset_info_file_result"]) - - # Lists of known relative locations of assets - default_level_assets = [ - "engineassets/texturemsg/defaultnouvs.dds", - "engineassets/texturemsg/defaultnouvs.dds.1", - "engineassets/texturemsg/defaultnouvs.dds.2", - "engineassets/texturemsg/defaultnouvs.dds.3", - "engineassets/texturemsg/defaultnouvs.dds.4", - "engineassets/texturemsg/defaultnouvs.dds.5", - "engineassets/texturemsg/defaultnouvs.dds.6", - "engineassets/texturemsg/defaultnouvs.dds.7", - "engineassets/texturemsg/defaultnouvs_ddn.dds", - "engineassets/texturemsg/defaultnouvs_ddn.dds.1", - "engineassets/texturemsg/defaultnouvs_ddn.dds.2", - "engineassets/texturemsg/defaultnouvs_ddn.dds.3", - "engineassets/texturemsg/defaultnouvs_ddn.dds.4", - "engineassets/texturemsg/defaultnouvs_ddn.dds.5", - "engineassets/texturemsg/defaultnouvs_spec.dds", - "engineassets/texturemsg/defaultnouvs_spec.dds.1", - "engineassets/texturemsg/defaultnouvs_spec.dds.2", - "engineassets/texturemsg/defaultnouvs_spec.dds.3", - "engineassets/texturemsg/defaultnouvs_spec.dds.4", - "engineassets/texturemsg/defaultnouvs_spec.dds.5", - "engineassets/textures/defaults/16_grey.dds", - "engineassets/textures/cubemap/default_level_cubemap.dds", - "engineassets/textures/cubemap/default_level_cubemap.dds.1", - "engineassets/textures/cubemap/default_level_cubemap.dds.2", - "engineassets/textures/cubemap/default_level_cubemap.dds.3", - "engineassets/textures/cubemap/default_level_cubemap.dds.4", - "engineassets/textures/cubemap/default_level_cubemap_diff.dds", - "engineassets/materials/water/ocean_default.mtl", - "engineassets/textures/defaults/spot_default.dds", - "engineassets/textures/defaults/spot_default.dds.1", - "engineassets/textures/defaults/spot_default.dds.2", - "engineassets/textures/defaults/spot_default.dds.3", - "engineassets/textures/defaults/spot_default.dds.4", - "engineassets/textures/defaults/spot_default.dds.5", - "materials/material_terrain_default.mtl", - "textures/skys/night/half_moon.dds", - "textures/skys/night/half_moon.dds.1", - "textures/skys/night/half_moon.dds.2", - "textures/skys/night/half_moon.dds.3", - "textures/skys/night/half_moon.dds.4", - "textures/skys/night/half_moon.dds.5", - "textures/skys/night/half_moon.dds.6", - "engineassets/materials/sky/sky.mtl", - "levels/testdependencieslevel/level.pak", - "levels/testdependencieslevel/terrain/cover.ctc", - "levels/testdependencieslevel/terraintexture.pak", - ] - - sequence_material_cube_assets = [ - "textures/test_texture_sequence/test_texture_sequence000.dds", - "textures/test_texture_sequence/test_texture_sequence001.dds", - "textures/test_texture_sequence/test_texture_sequence002.dds", - "textures/test_texture_sequence/test_texture_sequence003.dds", - "textures/test_texture_sequence/test_texture_sequence004.dds", - "textures/test_texture_sequence/test_texture_sequence005.dds", - "objects/_primitives/_box_1x1.cgf", - "materials/test_texture_sequence.mtl", - "objects/_primitives/_box_1x1.mtl", - "textures/_primitives/middle_gray_checker.dds", - "textures/_primitives/middle_gray_checker.dds.1", - "textures/_primitives/middle_gray_checker.dds.2", - "textures/_primitives/middle_gray_checker.dds.3", - "textures/_primitives/middle_gray_checker.dds.4", - "textures/_primitives/middle_gray_checker.dds.5", - "textures/_primitives/middle_gray_checker_ddn.dds", - "textures/_primitives/middle_gray_checker_ddn.dds.1", - "textures/_primitives/middle_gray_checker_ddn.dds.2", - "textures/_primitives/middle_gray_checker_ddn.dds.3", - "textures/_primitives/middle_gray_checker_ddn.dds.4", - "textures/_primitives/middle_gray_checker_ddn.dds.5", - "textures/_primitives/middle_gray_checker_spec.dds", - "textures/_primitives/middle_gray_checker_spec.dds.1", - "textures/_primitives/middle_gray_checker_spec.dds.2", - "textures/_primitives/middle_gray_checker_spec.dds.3", - "textures/_primitives/middle_gray_checker_spec.dds.4", - "textures/_primitives/middle_gray_checker_spec.dds.5", - ] - - character_with_simplified_material_assets = [ - "objects/characters/jack/jack.actor", - "objects/characters/jack/jack.mtl", - "objects/characters/jack/textures/jack_diff.dds", - "objects/characters/jack/textures/jack_diff.dds.1", - "objects/characters/jack/textures/jack_diff.dds.2", - "objects/characters/jack/textures/jack_diff.dds.3", - "objects/characters/jack/textures/jack_diff.dds.4", - "objects/characters/jack/textures/jack_diff.dds.5", - "objects/characters/jack/textures/jack_diff.dds.6", - "objects/characters/jack/textures/jack_diff.dds.7", - "objects/characters/jack/textures/jack_spec.dds", - "objects/characters/jack/textures/jack_spec.dds.1", - "objects/characters/jack/textures/jack_spec.dds.2", - "objects/characters/jack/textures/jack_spec.dds.3", - "objects/characters/jack/textures/jack_spec.dds.4", - "objects/characters/jack/textures/jack_spec.dds.5", - "objects/characters/jack/textures/jack_spec.dds.6", - "objects/characters/jack/textures/jack_spec.dds.7", - "objects/default/editorprimitive.mtl", - "engineassets/textures/grey.dds", - "animations/animationeditorfiles/sample0.animgraph", - "animations/motions/jack_death_fall_back_zup.motion", - "animations/animationeditorfiles/sample1.animgraph", - "animations/animationeditorfiles/sample0.motionset", - "animations/motions/rin_jump.motion", - "animations/animationeditorfiles/sample1.motionset", - "animations/motions/rin_idle.motion", - "animations/motions/jack_idle_aim_zup.motion", - ] - - spawner_assets = [ - "slices/sphere.dynamicslice", - "objects/default/primitive_sphere.cgf", - "test1.luac", - "test2.luac", - ] - - ui_canvas_assets = [ - "fonts/vera.ttf", - "fonts/vera.font", - "scriptcanvas/mainmenu.scriptcanvas_compiled", - "fonts/vera.fontfamily", - "ui/canvas/start.uicanvas", - "fonts/vera-italic.font", - "ui/textureatlas/sample.texatlasidx", - "fonts/vera-bold-italic.ttf", - "fonts/vera-bold.font", - "ui/textures/prefab/button_normal.dds", - "ui/textures/prefab/button_normal.sprite", - "fonts/vera-italic.ttf", - "ui/textureatlas/sample.dds", - "fonts/vera-bold-italic.font", - "fonts/vera-bold.ttf", - "ui/textures/prefab/button_disabled.dds", - "ui/textures/prefab/button_disabled.sprite", - ] - - wwise_and_atl_assets = [ - "libs/gameaudio/wwise/levels/testdependencieslevel/test_dependencies_level.xml", - "sounds/wwise/test_bank3.bnk", - "sounds/wwise/test_bank4.bnk", - "sounds/wwise/test_bank5.bnk", - "sounds/wwise/test_bank1.bnk", - "sounds/wwise/init.bnk", - "sounds/wwise/499820003.wem", - "sounds/wwise/196049145.wem", - ] - - particle_library_assets = [ - "libs/particles/milestone2particles.xml", - "textures/milestone2/particles/fx_launchermuzzlering_01.dds", - "textures/milestone2/particles/fx_launchermuzzlering_01.dds.1", - "textures/milestone2/particles/fx_launchermuzzlering_01.dds.2", - "textures/milestone2/particles/fx_launchermuzzlering_01.dds.3", - "textures/milestone2/particles/fx_launchermuzzlering_01.dds.4", - "textures/milestone2/particles/fx_launchermuzzlering_01.dds.5", - "textures/milestone2/particles/fx_sparkstreak_01.dds", - "textures/milestone2/particles/fx_launchermuzzlefront_01.dds", - "textures/milestone2/particles/fx_launchermuzzlefront_01.dds.1", - "textures/milestone2/particles/fx_launchermuzzlefront_01.dds.2", - "textures/milestone2/particles/fx_launchermuzzlefront_01.dds.3", - "textures/milestone2/particles/fx_launchermuzzlefront_01.dds.4", - "textures/milestone2/particles/fx_launchermuzzlefront_01.dds.5", - ] - - lens_flares_library_assets = ["libs/flares/flares.xml", "textures/lights/flare01.dds"] - - expected_assets_list = default_level_assets - expected_assets_list.extend(sequence_material_cube_assets) - expected_assets_list.extend(character_with_simplified_material_assets) - expected_assets_list.extend(spawner_assets) - expected_assets_list.extend(ui_canvas_assets) - expected_assets_list.extend(wwise_and_atl_assets) - expected_assets_list.extend(particle_library_assets) - expected_assets_list.extend(lens_flares_library_assets) # All expected assets - - # Get actual calculated dependencies from the asset list created - actual_assets_list = [] - for rel_path in helper.get_asset_relative_paths(helper["asset_info_file_result"]): - actual_assets_list.append(rel_path) - - assert sorted(actual_assets_list) == sorted(expected_assets_list) @pytest.mark.BAT @pytest.mark.assetpipeline @@ -310,9 +102,9 @@ class TestsAssetBundlerBatch_WindowsAndMac(object): 3. Read and store contents of asset list into memory 4. Attempt to create a new asset list in without using --allowOverwrites 5. Verify that Asset Bundler returns false - 6. Verify that file contents of the orignally created asset list did not change from what was stored in memory + 6. Verify that file contents of the originally created asset list did not change from what was stored in memory 7. Attempt to create a new asset list without debug while allowing overwrites - 8. Verify that file contents of the orignally created asset list changed from what was stored in memory + 8. Verify that file contents of the originally created asset list changed from what was stored in memory """ helper = bundler_batch_helper seed_list = os.path.join(workspace.paths.engine_root(), "Assets", "Engine", "SeedAssetList.seed") # Engine seed list @@ -919,7 +711,7 @@ class TestsAssetBundlerBatch_WindowsAndMac(object): # Extra arguments for pattern comparison cmd.extend([f"--filePatternType={pattern_type}", f"--filePattern={pattern}"]) if workspace.project: - cmd.append(f'--project-path={project_name}') + cmd.append(f'--project-path={workspace.paths.project()}') return cmd # End generate_compare_command() @@ -960,7 +752,7 @@ class TestsAssetBundlerBatch_WindowsAndMac(object): output_mac_asset_list = helper.platform_file_name(last_output_arg, platform) # Build execution command - cmd = generate_compare_command(platform_arg, workspace.project) + cmd = generate_compare_command(platform_arg, workspace.paths.project()) # Execute command subprocess.check_call(cmd) @@ -995,7 +787,7 @@ class TestsAssetBundlerBatch_WindowsAndMac(object): f"--comparisonRulesFile={rule_file}", f"--comparisonType={args[1]}", r"--addComparison", - f"--project-path={workspace.project}", + f"--project-path={workspace.paths.project()}", ] if args[1] == "4": # If pattern comparison, append a few extra arguments @@ -1117,7 +909,7 @@ class TestsAssetBundlerBatch_WindowsAndMac(object): "--addDefaultSeedListFiles", "--platform=pc", "--print", - f"--project-path={workspace.project}" + f"--project-path={workspace.paths.project()}" ], universal_newlines=True, ) @@ -1189,7 +981,7 @@ class TestsAssetBundlerBatch_WindowsAndMac(object): # Make sure file gets deleted on teardown request.addfinalizer(lambda: fs.delete([bundle_result_path], True, False)) - bundles_folder = os.path.join(workspace.paths.engine_root(), workspace.project, "Bundles") + bundles_folder = os.path.join(workspace.paths.project(), "Bundles") level_pak = r"levels\testdependencieslevel\level.pak" bundle_request_path = os.path.join(bundles_folder, "bundle.pak") bundle_result_path = os.path.join(bundles_folder, @@ -1243,23 +1035,64 @@ class TestsAssetBundlerBatch_WindowsAndMac(object): 2. Verify file was created 3. Verify that only the expected assets are present in the created asset list """ - expected_assets = [ + expected_assets = sorted([ "ui/canvases/lyshineexamples/animation/multiplesequences.uicanvas", - "ui/textures/prefab/button_normal.sprite" - ] + "ui/textures/prefab/button_disabled.tif.streamingimage", + "ui/textures/prefab/tooltip_sliced.tif.streamingimage", + "ui/textures/prefab/button_normal.tif.streamingimage" + ]) + # Printing these lists out can save a step in debugging if this test fails on Jenkins. + logger.info(f"expected_assets: {expected_assets}") + + skip_assets = sorted([ + "ui/scripts/lyshineexamples/animation/multiplesequences.luac", + "ui/scripts/lyshineexamples/unloadthiscanvasbutton.luac", + "fonts/vera.fontfamily", + "fonts/vera-italic.font", + "fonts/vera.font", + "fonts/vera-bold.font", + "fonts/vera-bold-italic.font", + "fonts/vera-italic.ttf", + "fonts/vera.ttf", + "fonts/vera-bold.ttf", + "fonts/vera-bold-italic.ttf" + ]) + logger.info(f"skip_assets: {skip_assets}") + + expected_and_skip_assets = sorted(expected_assets + skip_assets) + # Printing both together to make it quick to compare the results in the logs for a test failure on Jenkins + logger.info(f"expected_and_skip_assets: {expected_and_skip_assets}") + + # First, generate an asset info file without skipping, to get a list that can be used as a baseline to verify + # the files were actually skipped, and not just missing. + bundler_batch_helper.call_assetLists( + assetListFile=bundler_batch_helper['asset_info_file_request'], + addSeed="ui/canvases/lyshineexamples/animation/multiplesequences.uicanvas" + ) + assert os.path.isfile(bundler_batch_helper["asset_info_file_result"]) + assets_in_no_skip_list = [] + for rel_path in bundler_batch_helper.get_asset_relative_paths(bundler_batch_helper["asset_info_file_result"]): + assets_in_no_skip_list.append(rel_path) + assets_in_no_skip_list = sorted(assets_in_no_skip_list) + logger.info(f"assets_in_no_skip_list: {assets_in_no_skip_list}") + assert assets_in_no_skip_list == expected_and_skip_assets + + # Now generate an asset info file using the skip command, and verify the skip files are not in the list. bundler_batch_helper.call_assetLists( assetListFile=bundler_batch_helper['asset_info_file_request'], addSeed="ui/canvases/lyshineexamples/animation/multiplesequences.uicanvas", - skip="ui/textures/prefab/button_disabled.sprite,ui/scripts/lyshineexamples/animation/multiplesequences.luac," - "ui/textures/prefab/tooltip_sliced.sprite,ui/scripts/lyshineexamples/unloadthiscanvasbutton.luac,fonts/vera.fontfamily,fonts/vera-italic.font," - "fonts/vera.font,fonts/vera-bold.font,fonts/vera-bold-italic.font,fonts/vera-italic.ttf,fonts/vera.ttf,fonts/vera-bold.ttf,fonts/vera-bold-italic.ttf" + allowOverwrites="", + skip=','.join(skip_assets) ) + assert os.path.isfile(bundler_batch_helper["asset_info_file_result"]) assets_in_list = [] for rel_path in bundler_batch_helper.get_asset_relative_paths(bundler_batch_helper["asset_info_file_result"]): assets_in_list.append(rel_path) + assets_in_list = sorted(assets_in_list) + logger.info(f"assets_in_list: {assets_in_list}") + assert assets_in_list == expected_assets - assert sorted(assets_in_list) == sorted(expected_assets) @pytest.mark.BAT @pytest.mark.assetpipeline diff --git a/Code/Editor/AzAssetBrowser/AzAssetBrowserRequestHandler.cpp b/Code/Editor/AzAssetBrowser/AzAssetBrowserRequestHandler.cpp index 1c2be16a3d..6fc10e379a 100644 --- a/Code/Editor/AzAssetBrowser/AzAssetBrowserRequestHandler.cpp +++ b/Code/Editor/AzAssetBrowser/AzAssetBrowserRequestHandler.cpp @@ -199,41 +199,6 @@ namespace AzAssetBrowserRequestHandlerPrivate } } } - - // Helper utility - determines if the thing being dragged is a FBX from the scene import pipeline - // This is important to differentiate. - // when someone drags a MTL file directly into the viewport, even from a FBX, we want to spawn it as a decal - // but when someone drags a FBX that contains MTL files, we want only to spawn the meshes. - // so we have to specifically differentiate here between the mimeData type that contains the source as the root - // (dragging the fbx file itself) - // and one which contains the actual product at its root. - - bool IsDragOfFBX(const QMimeData* mimeData) - { - AZStd::vector entries; - if (!AssetBrowserEntry::FromMimeData(mimeData, entries)) - { - // if mimedata does not even contain entries, no point in proceeding. - return false; - } - - for (auto entry : entries) - { - if (entry->GetEntryType() != AssetBrowserEntry::AssetEntryType::Source) - { - continue; - } - // this is a source file. Is it the filetype we're looking for? - if (SourceAssetBrowserEntry* source = azrtti_cast(entry)) - { - if (AzFramework::StringFunc::Equal(source->GetExtension().c_str(), ".fbx", false)) - { - return true; - } - } - } - return false; - } } AzAssetBrowserRequestHandler::AzAssetBrowserRequestHandler() diff --git a/Code/Editor/CryEdit.cpp b/Code/Editor/CryEdit.cpp index fd38d4f52f..ad04e355f5 100644 --- a/Code/Editor/CryEdit.cpp +++ b/Code/Editor/CryEdit.cpp @@ -370,10 +370,8 @@ void CCryEditApp::RegisterActionHandlers() ON_COMMAND(ID_EDIT_FETCH, OnEditFetch) ON_COMMAND(ID_FILE_EXPORTTOGAMENOSURFACETEXTURE, OnFileExportToGameNoSurfaceTexture) ON_COMMAND(ID_VIEW_SWITCHTOGAME, OnViewSwitchToGame) - MainWindow::instance()->GetActionManager()->RegisterActionHandler(ID_VIEW_SWITCHTOGAME_FULLSCREEN, [this]() { - ed_previewGameInFullscreen_once = true; - OnViewSwitchToGame(); - }); + ON_COMMAND(ID_VIEW_SWITCHTOGAME_VIEWPORT, OnViewSwitchToGame) + ON_COMMAND(ID_VIEW_SWITCHTOGAME_FULLSCREEN, OnViewSwitchToGameFullScreen) ON_COMMAND(ID_MOVE_OBJECT, OnMoveObject) ON_COMMAND(ID_RENAME_OBJ, OnRenameObj) ON_COMMAND(ID_UNDO, OnUndo) @@ -2573,6 +2571,12 @@ void CCryEditApp::OnViewSwitchToGame() GetIEditor()->SetInGameMode(inGame); } +void CCryEditApp::OnViewSwitchToGameFullScreen() +{ + ed_previewGameInFullscreen_once = true; + OnViewSwitchToGame(); +} + ////////////////////////////////////////////////////////////////////////// void CCryEditApp::OnExportSelectedObjects() { @@ -4184,6 +4188,8 @@ extern "C" int AZ_DLL_EXPORT CryEditMain(int argc, char* argv[]) "\nThis could be because of incorrectly configured components, or missing required gems." "\nSee other errors for more details."); + AzToolsFramework::EditorEventsBus::Broadcast(&AzToolsFramework::EditorEvents::NotifyEditorInitialized); + if (didCryEditStart) { app->EnableOnIdle(); diff --git a/Code/Editor/CryEdit.h b/Code/Editor/CryEdit.h index 53ee8f1905..8c514170ae 100644 --- a/Code/Editor/CryEdit.h +++ b/Code/Editor/CryEdit.h @@ -212,6 +212,7 @@ public: void OnEditFetch(); void OnFileExportToGameNoSurfaceTexture(); void OnViewSwitchToGame(); + void OnViewSwitchToGameFullScreen(); void OnViewDeploy(); void DeleteSelectedEntities(bool includeDescendants); void OnMoveObject(); diff --git a/Code/Editor/Include/IEditorMaterialManager.h b/Code/Editor/Include/IEditorMaterialManager.h index d76ec32829..6f71c5ddd1 100644 --- a/Code/Editor/Include/IEditorMaterialManager.h +++ b/Code/Editor/Include/IEditorMaterialManager.h @@ -9,10 +9,6 @@ #define CRYINCLUDE_EDITOR_MATERIAL_IEDITORMATERIALMANAGER_H #pragma once -#define MATERIAL_FILE_EXT ".mtl" -#define DCC_MATERIAL_FILE_EXT ".dccmtl" -#define MATERIALS_PATH "materials/" - #include #include diff --git a/Code/Editor/MainWindow.cpp b/Code/Editor/MainWindow.cpp index 528b4eba6b..14ba902b15 100644 --- a/Code/Editor/MainWindow.cpp +++ b/Code/Editor/MainWindow.cpp @@ -939,27 +939,27 @@ void MainWindow::InitActions() .Connect(&QAction::triggered, this, &MainWindow::OnRefreshAudioSystem); // Game actions - am->AddAction(ID_VIEW_SWITCHTOGAME, tr("Play &Game")) + am->AddAction(ID_VIEW_SWITCHTOGAME, tr("Play Game")) .SetIcon(QIcon(":/stylesheet/img/UI20/toolbar/Play.svg")) + .SetToolTip(tr("Play Game")) + .SetStatusTip(tr("Activate the game input mode")) + .SetCheckable(true) + .RegisterUpdateCallback(cryEdit, &CCryEditApp::OnUpdatePlayGame); + am->AddAction(ID_VIEW_SWITCHTOGAME_VIEWPORT, tr("Play Game")) .SetShortcut(tr("Ctrl+G")) .SetToolTip(tr("Play Game (Ctrl+G)")) .SetStatusTip(tr("Activate the game input mode")) - .SetApplyHoverEffect() - .SetCheckable(true) .RegisterUpdateCallback(cryEdit, &CCryEditApp::OnUpdatePlayGame); - am->AddAction(ID_VIEW_SWITCHTOGAME_FULLSCREEN, tr("Play &Game (Maximized)")) + am->AddAction(ID_VIEW_SWITCHTOGAME_FULLSCREEN, tr("Play Game (Maximized)")) .SetShortcut(tr("Ctrl+Shift+G")) .SetStatusTip(tr("Activate the game input mode (maximized)")) - .SetIcon(Style::icon("Play")) - .SetApplyHoverEffect() - .SetCheckable(true); + .RegisterUpdateCallback(cryEdit, &CCryEditApp::OnUpdatePlayGame); am->AddAction(ID_TOOLBAR_WIDGET_PLAYCONSOLE_LABEL, tr("Play Controls")) .SetText(tr("Play Controls")); am->AddAction(ID_SWITCH_PHYSICS, tr("Simulate")) .SetIcon(QIcon(":/stylesheet/img/UI20/toolbar/Simulate_Physics.svg")) .SetShortcut(tr("Ctrl+P")) .SetToolTip(tr("Simulate (Ctrl+P)")) - .SetCheckable(true) .SetStatusTip(tr("Enable processing of Physics and AI.")) .SetApplyHoverEffect() .SetCheckable(true) @@ -1266,7 +1266,9 @@ void MainWindow::OnGameModeChanged(bool inGameMode) // block signals on the switch to game actions before setting the checked state, as // setting the checked state triggers the action, which will re-enter this function // and result in an infinite loop - AZStd::vector actions = { m_actionManager->GetAction(ID_VIEW_SWITCHTOGAME), m_actionManager->GetAction(ID_VIEW_SWITCHTOGAME_FULLSCREEN) }; + AZStd::vector actions = { m_actionManager->GetAction(ID_VIEW_SWITCHTOGAME_VIEWPORT), + m_actionManager->GetAction(ID_VIEW_SWITCHTOGAME_FULLSCREEN), + m_actionManager->GetAction(ID_VIEW_SWITCHTOGAME)}; for (auto action : actions) { action->blockSignals(true); diff --git a/Code/Editor/Plugins/ComponentEntityEditorPlugin/UI/AssetCatalogModel.cpp b/Code/Editor/Plugins/ComponentEntityEditorPlugin/UI/AssetCatalogModel.cpp index df8d6db4a8..60fda239dc 100644 --- a/Code/Editor/Plugins/ComponentEntityEditorPlugin/UI/AssetCatalogModel.cpp +++ b/Code/Editor/Plugins/ComponentEntityEditorPlugin/UI/AssetCatalogModel.cpp @@ -18,7 +18,6 @@ #include #include -#include #include #include @@ -136,14 +135,6 @@ AssetCatalogModel::AssetCatalogModel(QObject* parent) } } - // Special cases for SimpleAssets. If these get full-fledged AssetData types, these cases can be removed. - QString textureExtensions = LmbrCentral::TextureAsset::GetFileFilter(); - m_extensionToAssetType.insert(AZStd::make_pair(textureExtensions.replace("*", "").replace(" ", "").toStdString().c_str(), AZStd::vector { AZ::AzTypeInfo::Uuid() })); - QString materialExtensions = LmbrCentral::MaterialAsset::GetFileFilter(); - m_extensionToAssetType.insert(AZStd::make_pair(materialExtensions.replace("*", "").replace(" ", "").toStdString().c_str(), AZStd::vector { AZ::AzTypeInfo::Uuid() })); - QString dccMaterialExtensions = LmbrCentral::DccMaterialAsset::GetFileFilter(); - m_extensionToAssetType.insert(AZStd::make_pair(dccMaterialExtensions.replace("*", "").replace(" ", "").toStdString().c_str(), AZStd::vector { AZ::AzTypeInfo::Uuid() })); - AZ::SerializeContext* serializeContext = nullptr; EBUS_EVENT_RESULT(serializeContext, AZ::ComponentApplicationBus, GetSerializeContext); AZ_Assert(serializeContext, "Failed to acquire application serialize context."); diff --git a/Code/Editor/Resource.h b/Code/Editor/Resource.h index b3640fac70..ba3cd39fe7 100644 --- a/Code/Editor/Resource.h +++ b/Code/Editor/Resource.h @@ -104,6 +104,7 @@ #define ID_FILE_EXPORTTOGAMENOSURFACETEXTURE 33473 #define ID_VIEW_SWITCHTOGAME 33477 #define ID_VIEW_SWITCHTOGAME_FULLSCREEN 33478 +#define ID_VIEW_SWITCHTOGAME_VIEWPORT 33479 #define ID_MOVE_OBJECT 33481 #define ID_RENAME_OBJ 33483 #define ID_FETCH 33496 diff --git a/Code/Editor/ToolbarManager.cpp b/Code/Editor/ToolbarManager.cpp index 00b7992ef0..6b610d23ce 100644 --- a/Code/Editor/ToolbarManager.cpp +++ b/Code/Editor/ToolbarManager.cpp @@ -590,6 +590,16 @@ AmazonToolbar ToolbarManager::GetObjectToolbar() const return t; } +QMenu* ToolbarManager::CreatePlayButtonMenu() const +{ + QMenu* playButtonMenu = new QMenu("Play Game"); + + playButtonMenu->addAction(m_actionManager->GetAction(ID_VIEW_SWITCHTOGAME_VIEWPORT)); + playButtonMenu->addAction(m_actionManager->GetAction(ID_VIEW_SWITCHTOGAME_FULLSCREEN)); + + return playButtonMenu; +} + AmazonToolbar ToolbarManager::GetPlayConsoleToolbar() const { AmazonToolbar t = AmazonToolbar("PlayConsole", QObject::tr("Play Controls")); @@ -598,8 +608,17 @@ AmazonToolbar ToolbarManager::GetPlayConsoleToolbar() const t.AddAction(ID_TOOLBAR_WIDGET_SPACER_RIGHT, ORIGINAL_TOOLBAR_VERSION); t.AddAction(ID_TOOLBAR_SEPARATOR, ORIGINAL_TOOLBAR_VERSION); t.AddAction(ID_TOOLBAR_WIDGET_PLAYCONSOLE_LABEL, ORIGINAL_TOOLBAR_VERSION); - t.AddAction(ID_VIEW_SWITCHTOGAME, TOOLBARS_WITH_PLAY_GAME); - t.AddAction(ID_VIEW_SWITCHTOGAME_FULLSCREEN, TOOLBARS_WITH_PLAY_GAME); + + QAction* playAction = m_actionManager->GetAction(ID_VIEW_SWITCHTOGAME); + QToolButton* playButton = new QToolButton(t.Toolbar()); + + QMenu* menu = CreatePlayButtonMenu(); + menu->setParent(t.Toolbar()); + playAction->setMenu(menu); + + playButton->setDefaultAction(playAction); + t.AddWidget(playButton, ID_VIEW_SWITCHTOGAME, ORIGINAL_TOOLBAR_VERSION); + t.AddAction(ID_TOOLBAR_SEPARATOR, ORIGINAL_TOOLBAR_VERSION); t.AddAction(ID_SWITCH_PHYSICS, TOOLBARS_WITH_PLAY_GAME); return t; @@ -728,7 +747,14 @@ void AmazonToolbar::SetActionsOnInternalToolbar(ActionManager* actionManager) { if (actionManager->HasAction(actionId)) { - m_toolbar->addAction(actionManager->GetAction(actionId)); + if (actionData.widget != nullptr) + { + m_toolbar->addWidget(actionData.widget); + } + else + { + m_toolbar->addAction(actionManager->GetAction(actionId)); + } } } } @@ -1367,7 +1393,12 @@ void AmazonToolbar::InstantiateToolbar(QMainWindow* mainWindow, ToolbarManager* void AmazonToolbar::AddAction(int actionId, int toolbarVersionAdded) { - m_actions.push_back({ actionId, toolbarVersionAdded }); + AddWidget(nullptr, actionId, toolbarVersionAdded); +} + +void AmazonToolbar::AddWidget(QWidget* widget, int actionId, int toolbarVersionAdded) +{ + m_actions.push_back({ actionId, toolbarVersionAdded, widget }); } void AmazonToolbar::Clear() diff --git a/Code/Editor/ToolbarManager.h b/Code/Editor/ToolbarManager.h index be537533b6..ae6b0c7296 100644 --- a/Code/Editor/ToolbarManager.h +++ b/Code/Editor/ToolbarManager.h @@ -87,6 +87,7 @@ public: const QString& GetTranslatedName() const { return m_translatedName; } void AddAction(int actionId, int toolbarVersionAdded = 0); + void AddWidget(QWidget* widget, int actionId, int toolbarVersionAdded = 0); QToolBar* Toolbar() const { return m_toolbar; } @@ -117,6 +118,7 @@ private: { int actionId; int toolbarVersionAdded; + QWidget* widget; bool operator ==(const AmazonToolbar::ActionData& other) const { @@ -133,7 +135,9 @@ private: class AmazonToolBarExpanderWatcher; class ToolbarManager + : public QObject { + Q_OBJECT public: explicit ToolbarManager(ActionManager* actionManager, MainWindow* mainWindow); ~ToolbarManager(); @@ -178,6 +182,8 @@ private: void UpdateAllowedAreas(QToolBar* toolbar); bool IsDirty(const AmazonToolbar& toolbar) const; + QMenu* CreatePlayButtonMenu() const; + const AmazonToolbar* FindDefaultToolbar(const QString& toolbarName) const; AmazonToolbar* FindToolbar(const QString& toolbarName); diff --git a/Code/Framework/AzCore/AzCore/Jobs/JobManagerComponent.cpp b/Code/Framework/AzCore/AzCore/Jobs/JobManagerComponent.cpp index 883fa15991..acf24e8d42 100644 --- a/Code/Framework/AzCore/AzCore/Jobs/JobManagerComponent.cpp +++ b/Code/Framework/AzCore/AzCore/Jobs/JobManagerComponent.cpp @@ -57,11 +57,12 @@ namespace AZ int numberOfWorkerThreads = m_numberOfWorkerThreads; if (numberOfWorkerThreads <= 0) // spawn default number of threads { + #if (AZ_TRAIT_THREAD_NUM_JOB_MANAGER_WORKER_THREADS) + numberOfWorkerThreads = AZ_TRAIT_THREAD_NUM_JOB_MANAGER_WORKER_THREADS; + #else 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) + #endif // (AZ_TRAIT_THREAD_NUM_JOB_MANAGER_WORKER_THREADS) } threadDesc.m_cpuId = AFFINITY_MASK_USERTHREADS; diff --git a/Code/Framework/AzCore/AzCore/Name/NameDictionary.cpp b/Code/Framework/AzCore/AzCore/Name/NameDictionary.cpp index 6cba54a17f..1b39eb81bd 100644 --- a/Code/Framework/AzCore/AzCore/Name/NameDictionary.cpp +++ b/Code/Framework/AzCore/AzCore/Name/NameDictionary.cpp @@ -32,7 +32,12 @@ namespace AZ if (!s_instance) { - s_instance = AZ::Environment::CreateVariable(NameDictionaryInstanceName); + // Because the NameDictionary allocates memory using the AZ::Allocator and it is created + // in the executable memory space, it's ownership cannot be transferred to other module memory spaces + // Otherwise this could cause the the NameDictionary to be destroyed in static de-init + // after the AZ::Allocators have been destroyed + // Therefore we supply the isTransferOwnership value of false using CreateVariableEx + s_instance = AZ::Environment::CreateVariableEx(NameDictionaryInstanceName, true, false); } } diff --git a/Code/Framework/AzCore/AzCore/Serialization/Json/JsonSystemComponent.cpp b/Code/Framework/AzCore/AzCore/Serialization/Json/JsonSystemComponent.cpp index da02e70181..0ba44e4e61 100644 --- a/Code/Framework/AzCore/AzCore/Serialization/Json/JsonSystemComponent.cpp +++ b/Code/Framework/AzCore/AzCore/Serialization/Json/JsonSystemComponent.cpp @@ -104,6 +104,8 @@ namespace AZ ->HandlesType(); jsonContext->Serializer() ->HandlesType(); + jsonContext->Serializer() + ->HandlesType(); MathReflect(jsonContext); } diff --git a/Code/Framework/AzCore/AzCore/Serialization/Json/UnsupportedTypesSerializer.cpp b/Code/Framework/AzCore/AzCore/Serialization/Json/UnsupportedTypesSerializer.cpp index 2392ff435f..ad1839f974 100644 --- a/Code/Framework/AzCore/AzCore/Serialization/Json/UnsupportedTypesSerializer.cpp +++ b/Code/Framework/AzCore/AzCore/Serialization/Json/UnsupportedTypesSerializer.cpp @@ -15,6 +15,7 @@ namespace AZ AZ_CLASS_ALLOCATOR_IMPL(JsonAnySerializer, SystemAllocator, 0); AZ_CLASS_ALLOCATOR_IMPL(JsonVariantSerializer, SystemAllocator, 0); AZ_CLASS_ALLOCATOR_IMPL(JsonOptionalSerializer, SystemAllocator, 0); + AZ_CLASS_ALLOCATOR_IMPL(JsonBitsetSerializer, SystemAllocator, 0); JsonSerializationResult::Result JsonUnsupportedTypesSerializer::Load(void*, const Uuid&, const rapidjson::Value&, JsonDeserializerContext& context) @@ -49,4 +50,10 @@ namespace AZ return "The Json Serialization doesn't support AZStd::optional by design. No JSON format has yet been found that wasn't deemed too " "complex or overly verbose."; } + + AZStd::string_view JsonBitsetSerializer::GetMessage() const + { + return "The Json Serialization doesn't support AZStd::bitset by design. No JSON format has yet been found that is content creator " + "friendly i.e., easy to comprehend the intent."; + } } // namespace AZ diff --git a/Code/Framework/AzCore/AzCore/Serialization/Json/UnsupportedTypesSerializer.h b/Code/Framework/AzCore/AzCore/Serialization/Json/UnsupportedTypesSerializer.h index d913289d3d..fdcac4c761 100644 --- a/Code/Framework/AzCore/AzCore/Serialization/Json/UnsupportedTypesSerializer.h +++ b/Code/Framework/AzCore/AzCore/Serialization/Json/UnsupportedTypesSerializer.h @@ -65,4 +65,14 @@ namespace AZ protected: AZStd::string_view GetMessage() const override; }; + + class JsonBitsetSerializer : public JsonUnsupportedTypesSerializer + { + public: + AZ_RTTI(JsonBitsetSerializer, "{10CE969D-D69E-4B3F-8593-069736F8F705}", JsonUnsupportedTypesSerializer); + AZ_CLASS_ALLOCATOR_DECL; + + protected: + AZStd::string_view GetMessage() const override; + }; } // namespace AZ diff --git a/Code/Framework/AzCore/AzCore/Task/TaskGraphSystemComponent.cpp b/Code/Framework/AzCore/AzCore/Task/TaskGraphSystemComponent.cpp index 56b56e96a1..1cacef7ff3 100644 --- a/Code/Framework/AzCore/AzCore/Task/TaskGraphSystemComponent.cpp +++ b/Code/Framework/AzCore/AzCore/Task/TaskGraphSystemComponent.cpp @@ -30,8 +30,13 @@ namespace AZ if (Interface::Get() == nullptr) { + #if (AZ_TRAIT_THREAD_NUM_TASK_GRAPH_WORKER_THREADS) + const uint32_t numberOfWorkerThreads = AZ_TRAIT_THREAD_NUM_TASK_GRAPH_WORKER_THREADS; + #else + const uint32_t numberOfWorkerThreads = Threading::CalcNumWorkerThreads(cl_taskGraphThreadsConcurrencyRatio, cl_taskGraphThreadsMinNumber, cl_taskGraphThreadsNumReserved); + #endif // (AZ_TRAIT_THREAD_NUM_TASK_GRAPH_WORKER_THREADS) 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)); + m_taskExecutor = aznew TaskExecutor(numberOfWorkerThreads); TaskExecutor::SetInstance(m_taskExecutor); } } diff --git a/Code/Framework/AzCore/Platform/Android/AzCore/AzCore_Traits_Android.h b/Code/Framework/AzCore/Platform/Android/AzCore/AzCore_Traits_Android.h index 495c8d5f2c..e05e000a4e 100644 --- a/Code/Framework/AzCore/Platform/Android/AzCore/AzCore_Traits_Android.h +++ b/Code/Framework/AzCore/Platform/Android/AzCore/AzCore_Traits_Android.h @@ -75,7 +75,6 @@ #define AZ_TRAIT_HEAPSCHEMA_COMPILE_MALLINFO 0 #define AZ_TRAIT_IS_ABS_PATH_IF_COLON_FOUND_ANYWHERE 0 #define AZ_TRAIT_JSON_CLANG_IGNORE_UNKNOWN_WARNING 0 -#define AZ_TRAIT_MAX_JOB_MANAGER_WORKER_THREADS 0 #define AZ_TRAIT_PERF_MEMORYBENCHMARK_IS_AVAILABLE 0 #define AZ_TRAIT_PUMP_SYSTEM_EVENTS_WHILE_LOADING 0 #define AZ_TRAIT_PUMP_SYSTEM_EVENTS_WHILE_LOADING_INTERVAL_MS 0 diff --git a/Code/Framework/AzCore/Platform/Linux/AzCore/AzCore_Traits_Linux.h b/Code/Framework/AzCore/Platform/Linux/AzCore/AzCore_Traits_Linux.h index 6ba369e86d..2c0d554078 100644 --- a/Code/Framework/AzCore/Platform/Linux/AzCore/AzCore_Traits_Linux.h +++ b/Code/Framework/AzCore/Platform/Linux/AzCore/AzCore_Traits_Linux.h @@ -75,7 +75,6 @@ #define AZ_TRAIT_HEAPSCHEMA_COMPILE_MALLINFO 1 #define AZ_TRAIT_IS_ABS_PATH_IF_COLON_FOUND_ANYWHERE 0 #define AZ_TRAIT_JSON_CLANG_IGNORE_UNKNOWN_WARNING 0 -#define AZ_TRAIT_MAX_JOB_MANAGER_WORKER_THREADS 0 #define AZ_TRAIT_PERF_MEMORYBENCHMARK_IS_AVAILABLE 0 #define AZ_TRAIT_PUMP_SYSTEM_EVENTS_WHILE_LOADING 0 #define AZ_TRAIT_PUMP_SYSTEM_EVENTS_WHILE_LOADING_INTERVAL_MS 0 diff --git a/Code/Framework/AzCore/Platform/Mac/AzCore/AzCore_Traits_Mac.h b/Code/Framework/AzCore/Platform/Mac/AzCore/AzCore_Traits_Mac.h index a41b5c6baa..816a28b728 100644 --- a/Code/Framework/AzCore/Platform/Mac/AzCore/AzCore_Traits_Mac.h +++ b/Code/Framework/AzCore/Platform/Mac/AzCore/AzCore_Traits_Mac.h @@ -75,7 +75,6 @@ #define AZ_TRAIT_HEAPSCHEMA_COMPILE_MALLINFO 1 #define AZ_TRAIT_IS_ABS_PATH_IF_COLON_FOUND_ANYWHERE 0 #define AZ_TRAIT_JSON_CLANG_IGNORE_UNKNOWN_WARNING 0 -#define AZ_TRAIT_MAX_JOB_MANAGER_WORKER_THREADS 0 #define AZ_TRAIT_PERF_MEMORYBENCHMARK_IS_AVAILABLE 0 #define AZ_TRAIT_PUMP_SYSTEM_EVENTS_WHILE_LOADING 0 #define AZ_TRAIT_PUMP_SYSTEM_EVENTS_WHILE_LOADING_INTERVAL_MS 0 diff --git a/Code/Framework/AzCore/Platform/Windows/AzCore/AzCore_Traits_Windows.h b/Code/Framework/AzCore/Platform/Windows/AzCore/AzCore_Traits_Windows.h index 2f9fcefdbd..83cff9b54f 100644 --- a/Code/Framework/AzCore/Platform/Windows/AzCore/AzCore_Traits_Windows.h +++ b/Code/Framework/AzCore/Platform/Windows/AzCore/AzCore_Traits_Windows.h @@ -75,7 +75,6 @@ #define AZ_TRAIT_HEAPSCHEMA_COMPILE_MALLINFO 1 #define AZ_TRAIT_IS_ABS_PATH_IF_COLON_FOUND_ANYWHERE 0 #define AZ_TRAIT_JSON_CLANG_IGNORE_UNKNOWN_WARNING 1 -#define AZ_TRAIT_MAX_JOB_MANAGER_WORKER_THREADS 0 #define AZ_TRAIT_PERF_MEMORYBENCHMARK_IS_AVAILABLE 1 #define AZ_TRAIT_PUMP_SYSTEM_EVENTS_WHILE_LOADING 0 #define AZ_TRAIT_PUMP_SYSTEM_EVENTS_WHILE_LOADING_INTERVAL_MS 0 diff --git a/Code/Framework/AzCore/Platform/iOS/AzCore/AzCore_Traits_iOS.h b/Code/Framework/AzCore/Platform/iOS/AzCore/AzCore_Traits_iOS.h index d53f4b057e..b0832c54f6 100644 --- a/Code/Framework/AzCore/Platform/iOS/AzCore/AzCore_Traits_iOS.h +++ b/Code/Framework/AzCore/Platform/iOS/AzCore/AzCore_Traits_iOS.h @@ -76,7 +76,6 @@ #define AZ_TRAIT_HEAPSCHEMA_COMPILE_MALLINFO 1 #define AZ_TRAIT_IS_ABS_PATH_IF_COLON_FOUND_ANYWHERE 0 #define AZ_TRAIT_JSON_CLANG_IGNORE_UNKNOWN_WARNING 0 -#define AZ_TRAIT_MAX_JOB_MANAGER_WORKER_THREADS 0 #define AZ_TRAIT_PERF_MEMORYBENCHMARK_IS_AVAILABLE 0 #define AZ_TRAIT_PUMP_SYSTEM_EVENTS_WHILE_LOADING 0 #define AZ_TRAIT_PUMP_SYSTEM_EVENTS_WHILE_LOADING_INTERVAL_MS 0 diff --git a/Code/Framework/AzFramework/AzFramework/Archive/Archive.cpp b/Code/Framework/AzFramework/AzFramework/Archive/Archive.cpp index c1c5775958..0fede85aa7 100644 --- a/Code/Framework/AzFramework/AzFramework/Archive/Archive.cpp +++ b/Code/Framework/AzFramework/AzFramework/Archive/Archive.cpp @@ -1241,6 +1241,10 @@ namespace AZ::IO m_arrZips.insert(revItZip.base(), desc); + // This lock is for m_arrZips. + // Unlock it now because the modification is complete, and events responding to this signal + // will attempt to lock the same mutex, causing the application to lock up. + lock.unlock(); m_levelOpenEvent.Signal(levelDirs); } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/API/ToolsApplicationAPI.h b/Code/Framework/AzToolsFramework/AzToolsFramework/API/ToolsApplicationAPI.h index fd4196a296..282898c37d 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/API/ToolsApplicationAPI.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/API/ToolsApplicationAPI.h @@ -927,6 +927,9 @@ namespace AzToolsFramework /// Notify that the MainWindow has been fully initialized virtual void NotifyMainWindowInitialized(QMainWindow* /*mainWindow*/) {} + /// Notify that the Editor has been fully initialized + virtual void NotifyEditorInitialized() {} + /// Signal that an asset should be highlighted / selected virtual void SelectAsset(const QString& /* assetPath */) {} }; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Application/ToolsApplication.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Application/ToolsApplication.cpp index f203251407..dcdaaf5f7d 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Application/ToolsApplication.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Application/ToolsApplication.cpp @@ -215,12 +215,17 @@ namespace AzToolsFramework , public AZ::BehaviorEBusHandler { AZ_EBUS_BEHAVIOR_BINDER(EditorEventsBusHandler, "{352F80BB-469A-40B6-B322-FE57AB51E4DA}", AZ::SystemAllocator, - NotifyRegisterViews); + NotifyRegisterViews, NotifyEditorInitialized); void NotifyRegisterViews() override { Call(FN_NotifyRegisterViews); } + + void NotifyEditorInitialized() override + { + Call(FN_NotifyEditorInitialized); + } }; } // Internal @@ -445,6 +450,7 @@ namespace AzToolsFramework ->Attribute(AZ::Script::Attributes::Module, "editor") ->Handler() ->Event("NotifyRegisterViews", &EditorEvents::NotifyRegisterViews) + ->Event("NotifyEditorInitialized", &EditorEvents::NotifyEditorInitialized) ; behaviorContext->EBus("ViewPaneCallbackBus") diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/AssetBrowserComponent.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/AssetBrowserComponent.cpp index 7da6f794d6..172bd3a29c 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/AssetBrowserComponent.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/AssetBrowserComponent.cpp @@ -234,11 +234,6 @@ namespace AzToolsFramework return SourceFileDetails("Icons/AssetBrowser/Lua_16.svg"); } - if (AzFramework::StringFunc::Equal(extension.c_str(), ".mtl")) - { - return SourceFileDetails("Icons/AssetBrowser/Material_16.svg"); - } - if (AzFramework::StringFunc::Equal(extension.c_str(), AzToolsFramework::SliceUtilities::GetSliceFileExtension().c_str())) { return SourceFileDetails("Icons/AssetBrowser/Slice_16.svg"); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/AssetPicker/AssetPickerDialog.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/AssetPicker/AssetPickerDialog.cpp index 4ebeb03a71..eae8ec2a1e 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/AssetPicker/AssetPickerDialog.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/AssetBrowser/AssetPicker/AssetPickerDialog.cpp @@ -31,7 +31,10 @@ AZ_POP_DISABLE_WARNING AZ_CVAR( bool, ed_hideAssetPickerPathColumn, true, nullptr, AZ::ConsoleFunctorFlags::Null, "Hide AssetPicker path column for a clearer view."); -AZ_CVAR_EXTERNED(bool, ed_useNewAssetBrowserTableView); + +AZ_CVAR( + bool, ed_useNewAssetPickerView, false, nullptr, AZ::ConsoleFunctorFlags::Null, + "Uses the new Asset Picker View."); namespace AzToolsFramework { @@ -106,7 +109,7 @@ namespace AzToolsFramework m_persistentState = AZ::UserSettings::CreateFind(AZ::Crc32(("AssetBrowserTreeView_Dialog_" + name).toUtf8().data()), AZ::UserSettings::CT_GLOBAL); m_ui->m_assetBrowserTableViewWidget->setVisible(false); - if (ed_useNewAssetBrowserTableView) + if (ed_useNewAssetPickerView) { m_ui->m_assetBrowserTreeViewWidget->setVisible(false); m_ui->m_assetBrowserTableViewWidget->setVisible(true); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Component/EditorComponentAPIComponent.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Component/EditorComponentAPIComponent.cpp index 2cca3804ea..20a15643c6 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Component/EditorComponentAPIComponent.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Component/EditorComponentAPIComponent.cpp @@ -597,11 +597,13 @@ namespace AzToolsFramework pte.SetVisibleEnforcement(true); } + ScopedUndoBatch undo("Modify Entity Property"); PropertyOutcome result = pte.SetProperty(propertyPath, value); if (result.IsSuccess()) { PropertyEditorEntityChangeNotificationBus::Event(componentInstance.GetEntityId(), &PropertyEditorEntityChangeNotifications::OnEntityComponentPropertyChanged, componentInstance.GetComponentId()); } + undo.MarkEntityDirty(componentInstance.GetEntityId()); return result; } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabViewportFocusPathHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabViewportFocusPathHandler.cpp index 920e99665d..c1fc9138d1 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabViewportFocusPathHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabViewportFocusPathHandler.cpp @@ -64,6 +64,9 @@ namespace AzToolsFramework::Prefab ); m_backButton->setToolTip("Up one level (-)"); + + // Currently hide this button until we can correctly disable/enable it based on context. + m_backButton->hide(); } void PrefabViewportFocusPathHandler::OnPrefabFocusChanged() diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/EntityPropertyEditor.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/EntityPropertyEditor.cpp index 29bc106eed..9ecbdf5ffc 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/EntityPropertyEditor.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/EntityPropertyEditor.cpp @@ -4705,13 +4705,6 @@ namespace AzToolsFramework { if (mimeData->hasFormat(AssetBrowser::AssetBrowserEntry::GetMimeType())) { - // extra special case: MTLs from FBX drags are ignored. are we dragging a FBX file? - bool isDraggingFBXFile = false; - AssetBrowser::AssetBrowserEntry::ForEachEntryInMimeData(mimeData, [&](const AssetBrowser::SourceAssetBrowserEntry* source) - { - isDraggingFBXFile = isDraggingFBXFile || AzFramework::StringFunc::Equal(source->GetExtension().c_str(), ".fbx", false); - }); - // the usual case - we only allow asset browser drops of assets that have actually been associated with a kind of component. AssetBrowser::AssetBrowserEntry::ForEachEntryInMimeData(mimeData, [&](const AssetBrowser::ProductAssetBrowserEntry* product) { @@ -4723,17 +4716,7 @@ namespace AzToolsFramework if (canCreateComponent && !componentTypeId.IsNull()) { - // we have a component type that handles this asset. - // but we disallow it if its a MTL file from a FBX and the FBX itself is being dragged. Its still allowed - // to drag the actual MTL. - EBusFindAssetTypeByName materialAssetTypeResult("Material"); - AZ::AssetTypeInfoBus::BroadcastResult(materialAssetTypeResult, &AZ::AssetTypeInfo::GetAssetType); - AZ::Data::AssetType materialAssetType = materialAssetTypeResult.GetAssetType(); - - if ((!isDraggingFBXFile) || (product->GetAssetType() != materialAssetType)) - { - callbackFunction(product); - } + callbackFunction(product); } }); } diff --git a/Code/Tools/AssetProcessor/AssetBuilderSDK/AssetBuilderSDK/AssetBuilderSDK.cpp b/Code/Tools/AssetProcessor/AssetBuilderSDK/AssetBuilderSDK/AssetBuilderSDK.cpp index a02eef8b75..f9b5d6aef7 100644 --- a/Code/Tools/AssetProcessor/AssetBuilderSDK/AssetBuilderSDK/AssetBuilderSDK.cpp +++ b/Code/Tools/AssetProcessor/AssetBuilderSDK/AssetBuilderSDK/AssetBuilderSDK.cpp @@ -690,7 +690,6 @@ namespace AssetBuilderSDK static const char* textureExtensions = ".dds"; static const char* staticMeshExtensions = ".cgf"; static const char* skinnedMeshExtensions = ".skin"; - static const char* materialExtensions = ".mtl"; // MIPS static const int c_MaxMipsCount = 11; // 11 is for 8k textures non-compressed. When not compressed it is using one file per mip. @@ -805,11 +804,6 @@ namespace AssetBuilderSDK return textureAssetType; } - if (AzFramework::StringFunc::Find(materialExtensions, extension.c_str()) != AZStd::string::npos) - { - return materialAssetType; - } - if (AzFramework::StringFunc::Find(staticMeshExtensions, extension.c_str()) != AZStd::string::npos) { return meshAssetType; diff --git a/Code/Tools/ProjectManager/Platform/Linux/ProjectBuilderWorker_linux.cpp b/Code/Tools/ProjectManager/Platform/Linux/ProjectBuilderWorker_linux.cpp index 2987fc4dc2..fdeaef93bb 100644 --- a/Code/Tools/ProjectManager/Platform/Linux/ProjectBuilderWorker_linux.cpp +++ b/Code/Tools/ProjectManager/Platform/Linux/ProjectBuilderWorker_linux.cpp @@ -23,25 +23,11 @@ namespace O3DE::ProjectManager QString cmakeGenerator = (whichNinjaResult.IsSuccess()) ? "Ninja Multi-Config" : "Unix Makefiles"; bool compileProfileOnBuild = (whichNinjaResult.IsSuccess()); - // On Linux the default compiler is gcc. For O3DE, it is clang, so we need to specify the version of clang that is detected - // in order to get the compiler option. - auto compilerOptionResult = ProjectUtils::FindSupportedCompilerForPlatform(); - if (!compilerOptionResult.IsSuccess()) - { - return AZ::Failure(compilerOptionResult.GetError()); - } - auto clangCompilers = compilerOptionResult.GetValue().split('|'); - AZ_Assert(clangCompilers.length()==2, "Invalid clang compiler pair specification"); - - QString clangCompilerOption = clangCompilers[0]; - QString clangPPCompilerOption = clangCompilers[1]; QString targetBuildPath = QDir(m_projectInfo.m_path).filePath(ProjectBuildPathPostfix); QStringList generateProjectArgs = QStringList{ProjectCMakeCommand, "-B", ProjectBuildPathPostfix, "-S", ".", QString("-G%1").arg(cmakeGenerator), - QString("-DCMAKE_C_COMPILER=").append(clangCompilerOption), - QString("-DCMAKE_CXX_COMPILER=").append(clangPPCompilerOption), QString("-DLY_3RDPARTY_PATH=").append(thirdPartyPath)}; if (!compileProfileOnBuild) { diff --git a/Code/Tools/ProjectManager/Platform/Linux/ProjectManager_Traits_Linux.h b/Code/Tools/ProjectManager/Platform/Linux/ProjectManager_Traits_Linux.h index 7c0543361f..d7edcfcf12 100644 --- a/Code/Tools/ProjectManager/Platform/Linux/ProjectManager_Traits_Linux.h +++ b/Code/Tools/ProjectManager/Platform/Linux/ProjectManager_Traits_Linux.h @@ -9,3 +9,4 @@ #pragma once #define AZ_TRAIT_PROJECT_MANAGER_CUSTOM_TITLEBAR false +#define AZ_TRAIT_PROJECT_MANAGER_CREATE_DESKTOP_SHORTCUT false diff --git a/Code/Tools/ProjectManager/Platform/Linux/ProjectUtils_linux.cpp b/Code/Tools/ProjectManager/Platform/Linux/ProjectUtils_linux.cpp index e901d807b4..a7bd2dae08 100644 --- a/Code/Tools/ProjectManager/Platform/Linux/ProjectUtils_linux.cpp +++ b/Code/Tools/ProjectManager/Platform/Linux/ProjectUtils_linux.cpp @@ -17,13 +17,11 @@ namespace O3DE::ProjectManager namespace ProjectUtils { // The list of clang C/C++ compiler command lines to validate on the host Linux system - const QStringList SupportedClangCommands = {"clang-12|clang++-12"}; + const QStringList SupportedClangVersions = {"13", "12", "11", "10", "9", "8", "7", "6.0"}; AZ::Outcome GetCommandLineProcessEnvironment() { QProcessEnvironment currentEnvironment(QProcessEnvironment::systemEnvironment()); - currentEnvironment.insert("CC", "clang-12"); - currentEnvironment.insert("CXX", "clang++-12"); return AZ::Success(currentEnvironment); } @@ -39,16 +37,13 @@ namespace O3DE::ProjectManager } // Look for the first compatible version of clang. The list below will contain the known clang compilers that have been tested for O3DE. - for (const QString& supportClangCommand : SupportedClangCommands) + for (const QString& supportClangVersion : SupportedClangVersions) { - auto clangCompilers = supportClangCommand.split('|'); - AZ_Assert(clangCompilers.length()==2, "Invalid clang compiler pair specification"); - - auto whichClangResult = ProjectUtils::ExecuteCommandResult("which", QStringList{clangCompilers[0]}, QProcessEnvironment::systemEnvironment()); - auto whichClangPPResult = ProjectUtils::ExecuteCommandResult("which", QStringList{clangCompilers[1]}, QProcessEnvironment::systemEnvironment()); + auto whichClangResult = ProjectUtils::ExecuteCommandResult("which", QStringList{QString("clang-%1").arg(supportClangVersion)}, QProcessEnvironment::systemEnvironment()); + auto whichClangPPResult = ProjectUtils::ExecuteCommandResult("which", QStringList{QString("clang++-%1").arg(supportClangVersion)}, QProcessEnvironment::systemEnvironment()); if (whichClangResult.IsSuccess() && whichClangPPResult.IsSuccess()) { - return AZ::Success(supportClangCommand); + return AZ::Success(QString("clang-%1").arg(supportClangVersion)); } } return AZ::Failure(QObject::tr("Clang not found.

" @@ -101,5 +96,10 @@ namespace O3DE::ProjectManager { return AZ::Utils::GetExecutableDirectory(); } + + AZ::Outcome CreateDesktopShortcut([[maybe_unused]] const QString& filename, [[maybe_unused]] const QString& targetPath, [[maybe_unused]] const QStringList& arguments) + { + return AZ::Failure(QObject::tr("Creating desktop shortcuts functionality not implemented for this platform yet.")); + } } // namespace ProjectUtils } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Platform/Mac/ProjectManager_Traits_Mac.h b/Code/Tools/ProjectManager/Platform/Mac/ProjectManager_Traits_Mac.h index 7c0543361f..d7edcfcf12 100644 --- a/Code/Tools/ProjectManager/Platform/Mac/ProjectManager_Traits_Mac.h +++ b/Code/Tools/ProjectManager/Platform/Mac/ProjectManager_Traits_Mac.h @@ -9,3 +9,4 @@ #pragma once #define AZ_TRAIT_PROJECT_MANAGER_CUSTOM_TITLEBAR false +#define AZ_TRAIT_PROJECT_MANAGER_CREATE_DESKTOP_SHORTCUT false diff --git a/Code/Tools/ProjectManager/Platform/Mac/ProjectUtils_mac.cpp b/Code/Tools/ProjectManager/Platform/Mac/ProjectUtils_mac.cpp index b768200398..62011bf04b 100644 --- a/Code/Tools/ProjectManager/Platform/Mac/ProjectUtils_mac.cpp +++ b/Code/Tools/ProjectManager/Platform/Mac/ProjectUtils_mac.cpp @@ -137,5 +137,10 @@ namespace O3DE::ProjectManager return editorPath; } + + AZ::Outcome CreateDesktopShortcut([[maybe_unused]] const QString& filename, [[maybe_unused]] const QString& targetPath, [[maybe_unused]] const QStringList& arguments) + { + return AZ::Failure(QObject::tr("Creating desktop shortcuts functionality not implemented for this platform yet.")); + } } // namespace ProjectUtils } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Platform/Windows/ProjectManager_Traits_Windows.h b/Code/Tools/ProjectManager/Platform/Windows/ProjectManager_Traits_Windows.h index e6422b5a77..9e4d29b58f 100644 --- a/Code/Tools/ProjectManager/Platform/Windows/ProjectManager_Traits_Windows.h +++ b/Code/Tools/ProjectManager/Platform/Windows/ProjectManager_Traits_Windows.h @@ -9,3 +9,4 @@ #pragma once #define AZ_TRAIT_PROJECT_MANAGER_CUSTOM_TITLEBAR true +#define AZ_TRAIT_PROJECT_MANAGER_CREATE_DESKTOP_SHORTCUT true diff --git a/Code/Tools/ProjectManager/Platform/Windows/ProjectUtils_windows.cpp b/Code/Tools/ProjectManager/Platform/Windows/ProjectUtils_windows.cpp index 871f8e9567..d08da1d5e1 100644 --- a/Code/Tools/ProjectManager/Platform/Windows/ProjectUtils_windows.cpp +++ b/Code/Tools/ProjectManager/Platform/Windows/ProjectUtils_windows.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include @@ -146,5 +147,26 @@ namespace O3DE::ProjectManager { return AZ::Utils::GetExecutableDirectory(); } + + AZ::Outcome CreateDesktopShortcut(const QString& filename, const QString& targetPath, const QStringList& arguments) + { + const QString cmd{"powershell.exe"}; + const QString desktopPath = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); + const QString shortcutPath = QString("%1/%2.lnk").arg(desktopPath).arg(filename); + const QString arg = QString("$s=(New-Object -COM WScript.Shell).CreateShortcut('%1');$s.TargetPath='%2';$s.Arguments='%3';$s.Save();") + .arg(shortcutPath) + .arg(targetPath) + .arg(arguments.join(' ')); + auto createShortcutResult = ExecuteCommandResult(cmd, QStringList{"-Command", arg}, QProcessEnvironment::systemEnvironment()); + if (!createShortcutResult.IsSuccess()) + { + return AZ::Failure(QObject::tr("Failed to create desktop shortcut %1

" + "Please verify you have permission to create files at the specified location.

%2") + .arg(shortcutPath) + .arg(createShortcutResult.GetError())); + } + + return AZ::Success(QObject::tr("Desktop shortcut created at
%2").arg(desktopPath).arg(shortcutPath)); + } } // namespace ProjectUtils } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Resources/ProjectManager.qrc b/Code/Tools/ProjectManager/Resources/ProjectManager.qrc index 8dd7e4c9b5..2260ae62b9 100644 --- a/Code/Tools/ProjectManager/Resources/ProjectManager.qrc +++ b/Code/Tools/ProjectManager/Resources/ProjectManager.qrc @@ -41,5 +41,6 @@ Download.svg in_progress.gif gem.svg + checkmark.svg diff --git a/Code/Tools/ProjectManager/Resources/ProjectManager.qss b/Code/Tools/ProjectManager/Resources/ProjectManager.qss index d3ec066be7..426f409581 100644 --- a/Code/Tools/ProjectManager/Resources/ProjectManager.qss +++ b/Code/Tools/ProjectManager/Resources/ProjectManager.qss @@ -563,6 +563,52 @@ QProgressBar::chunk { margin-top:5px; } +#gemCatalogUpdateGemButton, +#gemCatalogUninstallGemButton +{ + qproperty-flat: true; + min-height:24px; + max-height:24px; + border-radius: 3px; + text-align:center; + font-size:12px; + font-weight:600; +} + +#gemCatalogUpdateGemButton { + background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 #888888, stop: 1.0 #555555); +} +#gemCatalogUpdateGemButton:hover { + background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 #999999, stop: 1.0 #666666); +} +#gemCatalogUpdateGemButton:pressed { + background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 #555555, stop: 1.0 #777777); +} + +#footer > #gemCatalogUninstallGemButton, +#gemCatalogUninstallGemButton { + background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 #E32C27, stop: 1.0 #951D21); +} +#footer > #gemCatalogUninstallGemButton:hover, +#gemCatalogUninstallGemButton:hover { + background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 #FD3129, stop: 1.0 #AF2221); +} +#footer > #gemCatalogUninstallGemButton:pressed, +#gemCatalogUninstallGemButton:pressed { + background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 #951D1F, stop: 1.0 #C92724); +} + +#gemCatalogDialogSubTitle { + font-size:14px; + font-weight:600; +} + /************** Filter Tag widget **************/ #FilterTagWidgetTextLabel { diff --git a/Code/Tools/ProjectManager/Resources/checkmark.svg b/Code/Tools/ProjectManager/Resources/checkmark.svg new file mode 100644 index 0000000000..d612b35370 --- /dev/null +++ b/Code/Tools/ProjectManager/Resources/checkmark.svg @@ -0,0 +1,12 @@ + + + Icons / Hub / Download Copy 5 + + + + + + + + + diff --git a/Code/Tools/ProjectManager/Source/DownloadController.cpp b/Code/Tools/ProjectManager/Source/DownloadController.cpp index 6326b2fc11..06e51b5116 100644 --- a/Code/Tools/ProjectManager/Source/DownloadController.cpp +++ b/Code/Tools/ProjectManager/Source/DownloadController.cpp @@ -18,7 +18,6 @@ namespace O3DE::ProjectManager { DownloadController::DownloadController(QWidget* parent) : QObject() - , m_lastProgress(0) , m_parent(parent) { m_worker = new DownloadWorker(); @@ -69,10 +68,9 @@ namespace O3DE::ProjectManager } } - void DownloadController::UpdateUIProgress(int progress) + void DownloadController::UpdateUIProgress(int bytesDownloaded, int totalBytes) { - m_lastProgress = progress; - emit GemDownloadProgress(m_gemNames.front(), progress); + emit GemDownloadProgress(m_gemNames.front(), bytesDownloaded, totalBytes); } void DownloadController::HandleResults(const QString& result) @@ -88,6 +86,7 @@ namespace O3DE::ProjectManager QString gemName = m_gemNames.front(); m_gemNames.erase(m_gemNames.begin()); emit Done(gemName, succeeded); + emit GemDownloadRemoved(gemName); if (!m_gemNames.empty()) { diff --git a/Code/Tools/ProjectManager/Source/DownloadController.h b/Code/Tools/ProjectManager/Source/DownloadController.h index 0bf0ae473c..211d9b48bc 100644 --- a/Code/Tools/ProjectManager/Source/DownloadController.h +++ b/Code/Tools/ProjectManager/Source/DownloadController.h @@ -53,7 +53,7 @@ namespace O3DE::ProjectManager } } public slots: - void UpdateUIProgress(int progress); + void UpdateUIProgress(int bytesDownloaded, int totalBytes); void HandleResults(const QString& result); signals: @@ -61,14 +61,12 @@ namespace O3DE::ProjectManager void Done(const QString& gemName, bool success = true); void GemDownloadAdded(const QString& gemName); void GemDownloadRemoved(const QString& gemName); - void GemDownloadProgress(const QString& gemName, int percentage); + void GemDownloadProgress(const QString& gemName, int bytesDownloaded, int totalBytes); private: DownloadWorker* m_worker; QThread m_workerThread; QWidget* m_parent; AZStd::vector m_gemNames; - - int m_lastProgress; }; } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/DownloadWorker.cpp b/Code/Tools/ProjectManager/Source/DownloadWorker.cpp index 9bda1b34cc..560bfe05de 100644 --- a/Code/Tools/ProjectManager/Source/DownloadWorker.cpp +++ b/Code/Tools/ProjectManager/Source/DownloadWorker.cpp @@ -20,12 +20,13 @@ namespace O3DE::ProjectManager void DownloadWorker::StartDownload() { - auto gemDownloadProgress = [=](int downloadProgress) + auto gemDownloadProgress = [=](int bytesDownloaded, int totalBytes) { - m_downloadProgress = downloadProgress; - emit UpdateProgress(downloadProgress); + emit UpdateProgress(bytesDownloaded, totalBytes); }; - AZ::Outcome gemInfoResult = PythonBindingsInterface::Get()->DownloadGem(m_gemName, gemDownloadProgress); + AZ::Outcome gemInfoResult = + PythonBindingsInterface::Get()->DownloadGem(m_gemName, gemDownloadProgress, /*force*/true); + if (gemInfoResult.IsSuccess()) { emit Done(""); diff --git a/Code/Tools/ProjectManager/Source/DownloadWorker.h b/Code/Tools/ProjectManager/Source/DownloadWorker.h index 316a730a78..4084080ff7 100644 --- a/Code/Tools/ProjectManager/Source/DownloadWorker.h +++ b/Code/Tools/ProjectManager/Source/DownloadWorker.h @@ -31,12 +31,11 @@ namespace O3DE::ProjectManager void SetGemToDownload(const QString& gemName, bool downloadNow = true); signals: - void UpdateProgress(int progress); + void UpdateProgress(int bytesDownloaded, int totalBytes); void Done(QString result = ""); private: QString m_gemName; - int m_downloadProgress; }; } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.cpp index bd0a6e9bc3..e676ba73d3 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.cpp @@ -15,6 +15,8 @@ #include #include #include +#include +#include namespace O3DE::ProjectManager { @@ -224,7 +226,6 @@ namespace O3DE::ProjectManager connect(m_downloadController, &DownloadController::GemDownloadAdded, this, &CartOverlayWidget::GemDownloadAdded); connect(m_downloadController, &DownloadController::GemDownloadRemoved, this, &CartOverlayWidget::GemDownloadRemoved); connect(m_downloadController, &DownloadController::GemDownloadProgress, this, &CartOverlayWidget::GemDownloadProgress); - connect(m_downloadController, &DownloadController::Done, this, &CartOverlayWidget::GemDownloadComplete); } void CartOverlayWidget::GemDownloadAdded(const QString& gemName) @@ -288,29 +289,41 @@ namespace O3DE::ProjectManager } } - void CartOverlayWidget::GemDownloadProgress(const QString& gemName, int percentage) + void CartOverlayWidget::GemDownloadProgress(const QString& gemName, int bytesDownloaded, int totalBytes) { QWidget* gemToUpdate = m_downloadingListWidget->findChild(gemName); if (gemToUpdate) { QLabel* progressLabel = gemToUpdate->findChild("DownloadProgressLabel"); - if (progressLabel) - { - progressLabel->setText(QString("%1%").arg(percentage)); - } QProgressBar* progressBar = gemToUpdate->findChild("DownloadProgressBar"); - if (progressBar) + + // totalBytes can be 0 if the server does not return a content-length for the object + if (totalBytes != 0) { - progressBar->setValue(percentage); + int downloadPercentage = static_cast((bytesDownloaded / static_cast(totalBytes)) * 100); + if (progressLabel) + { + progressLabel->setText(QString("%1%").arg(downloadPercentage)); + } + if (progressBar) + { + progressBar->setValue(downloadPercentage); + } + } + else + { + if (progressLabel) + { + progressLabel->setText(QLocale::system().formattedDataSize(bytesDownloaded)); + } + if (progressBar) + { + progressBar->setRange(0, 0); + } } } } - void CartOverlayWidget::GemDownloadComplete(const QString& gemName, bool /*success*/) - { - GemDownloadRemoved(gemName); // update the list to remove the gem that has finished - } - QVector CartOverlayWidget::GetTagsFromModelIndices(const QVector& gems) const { QVector tags; @@ -389,7 +402,7 @@ namespace O3DE::ProjectManager { const QVector toBeAdded = m_gemModel->GatherGemsToBeAdded(/*includeDependencies=*/true); const QVector toBeRemoved = m_gemModel->GatherGemsToBeRemoved(/*includeDependencies=*/true); - if (toBeAdded.isEmpty() && toBeRemoved.isEmpty()) + if (toBeAdded.isEmpty() && toBeRemoved.isEmpty() && m_downloadController->IsDownloadQueueEmpty()) { return; } @@ -430,6 +443,7 @@ namespace O3DE::ProjectManager GemCatalogHeaderWidget::GemCatalogHeaderWidget(GemModel* gemModel, GemSortFilterProxyModel* filterProxyModel, DownloadController* downloadController, QWidget* parent) : QFrame(parent) + , m_downloadController(downloadController) { QHBoxLayout* hLayout = new QHBoxLayout(); hLayout->setAlignment(Qt::AlignLeft); @@ -456,8 +470,25 @@ namespace O3DE::ProjectManager hLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding)); hLayout->addSpacerItem(new QSpacerItem(75, 0, QSizePolicy::Fixed)); - CartButton* cartButton = new CartButton(gemModel, downloadController); - hLayout->addWidget(cartButton); + // spinner + m_downloadSpinnerMovie = new QMovie(":/in_progress.gif"); + m_downloadSpinner = new QLabel(this); + m_downloadSpinner->setScaledContents(true); + m_downloadSpinner->setMaximumSize(16, 16); + m_downloadSpinner->setMovie(m_downloadSpinnerMovie); + hLayout->addWidget(m_downloadSpinner); + hLayout->addSpacing(8); + + // downloading label + m_downloadLabel = new QLabel(tr("Downloading")); + hLayout->addWidget(m_downloadLabel); + m_downloadSpinner->hide(); + m_downloadLabel->hide(); + + hLayout->addSpacing(16); + + m_cartButton = new CartButton(gemModel, downloadController); + hLayout->addWidget(m_cartButton); hLayout->addSpacing(16); // Separating line @@ -469,6 +500,7 @@ namespace O3DE::ProjectManager hLayout->addSpacing(16); QMenu* gemMenu = new QMenu(this); + gemMenu->addAction( tr("Refresh"), [this]() { emit RefreshGems(); }); gemMenu->addAction( tr("Show Gem Repos"), [this]() { emit OpenGemsRepo(); }); gemMenu->addSeparator(); gemMenu->addAction( tr("Add Existing Gem"), [this]() { emit AddGem(); }); @@ -479,6 +511,27 @@ namespace O3DE::ProjectManager gemMenuButton->setIcon(QIcon(":/menu.svg")); gemMenuButton->setIconSize(QSize(36, 24)); hLayout->addWidget(gemMenuButton); + + connect(m_downloadController, &DownloadController::GemDownloadAdded, this, &GemCatalogHeaderWidget::GemDownloadAdded); + connect(m_downloadController, &DownloadController::GemDownloadRemoved, this, &GemCatalogHeaderWidget::GemDownloadRemoved); + } + + void GemCatalogHeaderWidget::GemDownloadAdded(const QString& /*gemName*/) + { + m_downloadSpinner->show(); + m_downloadLabel->show(); + m_downloadSpinnerMovie->start(); + m_cartButton->ShowOverlay(); + } + + void GemCatalogHeaderWidget::GemDownloadRemoved(const QString& /*gemName*/) + { + if (m_downloadController->IsDownloadQueueEmpty()) + { + m_downloadSpinner->hide(); + m_downloadLabel->hide(); + m_downloadSpinnerMovie->stop(); + } } void GemCatalogHeaderWidget::ReinitForProject() diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.h index f3242d6db7..5174fde57d 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.h @@ -24,6 +24,7 @@ QT_FORWARD_DECLARE_CLASS(QVBoxLayout) QT_FORWARD_DECLARE_CLASS(QHBoxLayout) QT_FORWARD_DECLARE_CLASS(QHideEvent) QT_FORWARD_DECLARE_CLASS(QMoveEvent) +QT_FORWARD_DECLARE_CLASS(QMovie) namespace O3DE::ProjectManager { @@ -39,8 +40,7 @@ namespace O3DE::ProjectManager public slots: void GemDownloadAdded(const QString& gemName); void GemDownloadRemoved(const QString& gemName); - void GemDownloadProgress(const QString& gemName, int percentage); - void GemDownloadComplete(const QString& gemName, bool success); + void GemDownloadProgress(const QString& gemName, int bytesDownloaded, int totalBytes); private: QVector GetTagsFromModelIndices(const QVector& gems) const; @@ -96,12 +96,22 @@ namespace O3DE::ProjectManager void ReinitForProject(); + public slots: + void GemDownloadAdded(const QString& gemName); + void GemDownloadRemoved(const QString& gemName); + signals: void AddGem(); void OpenGemsRepo(); + void RefreshGems(); private: AzQtComponents::SearchLineEdit* m_filterLineEdit = nullptr; inline constexpr static int s_height = 60; + DownloadController* m_downloadController = nullptr; + QLabel* m_downloadSpinner = nullptr; + QLabel* m_downloadLabel = nullptr; + QMovie* m_downloadSpinnerMovie = nullptr; + CartButton* m_cartButton = nullptr; }; } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp index 732f4813a2..0dacbbb906 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp @@ -12,7 +12,11 @@ #include #include #include +#include +#include #include +#include + #include #include #include @@ -47,6 +51,7 @@ namespace O3DE::ProjectManager vLayout->addWidget(m_headerWidget); connect(m_gemModel, &GemModel::gemStatusChanged, this, &GemCatalogScreen::OnGemStatusChanged); + connect(m_headerWidget, &GemCatalogHeaderWidget::RefreshGems, this, &GemCatalogScreen::Refresh); connect(m_headerWidget, &GemCatalogHeaderWidget::OpenGemsRepo, this, &GemCatalogScreen::HandleOpenGemRepo); connect(m_headerWidget, &GemCatalogHeaderWidget::AddGem, this, &GemCatalogScreen::OnAddGemClicked); connect(m_downloadController, &DownloadController::Done, this, &GemCatalogScreen::OnGemDownloadResult); @@ -60,6 +65,8 @@ namespace O3DE::ProjectManager m_gemInspector->setFixedWidth(240); connect(m_gemInspector, &GemInspector::TagClicked, [=](const Tag& tag) { SelectGem(tag.id); }); + connect(m_gemInspector, &GemInspector::UpdateGem, this, &GemCatalogScreen::UpdateGem); + connect(m_gemInspector, &GemInspector::UninstallGem, this, &GemCatalogScreen::UninstallGem); QWidget* filterWidget = new QWidget(this); filterWidget->setFixedWidth(240); @@ -99,7 +106,7 @@ namespace O3DE::ProjectManager FillModel(projectPath); - m_proxyModel->ResetFilters(); + m_proxyModel->ResetFilters(false); m_proxyModel->sort(/*column=*/0); if (m_filterWidget) @@ -118,9 +125,10 @@ namespace O3DE::ProjectManager // Select the first entry after everything got correctly sized QTimer::singleShot(200, [=]{ - QModelIndex firstModelIndex = m_gemListView->model()->index(0,0); - m_gemListView->selectionModel()->select(firstModelIndex, QItemSelectionModel::ClearAndSelect); - }); + QModelIndex firstModelIndex = m_gemModel->index(0, 0); + QModelIndex proxyIndex = m_proxyModel->mapFromSource(firstModelIndex); + m_proxyModel->GetSelectionModel()->setCurrentIndex(proxyIndex, QItemSelectionModel::ClearAndSelect); + }); } void GemCatalogScreen::OnAddGemClicked() @@ -202,7 +210,7 @@ namespace O3DE::ProjectManager const bool gemFound = gemInfoHash.contains(gemName); if (!gemFound && !m_gemModel->IsAdded(index) && !m_gemModel->IsAddedDependency(index)) { - m_gemModel->removeRow(i); + m_gemModel->RemoveGem(index); } else { @@ -228,8 +236,11 @@ namespace O3DE::ProjectManager m_proxyModel->sort(/*column=*/0); // temporary, until we can refresh filter counts - m_proxyModel->ResetFilters(); + m_proxyModel->ResetFilters(false); m_filterWidget->ResetAllFilters(); + + // Reselect the same selection to proc UI updates + m_proxyModel->GetSelectionModel()->setCurrentIndex(m_proxyModel->GetSelectionModel()->currentIndex(), QItemSelectionModel::Select); } void GemCatalogScreen::OnGemStatusChanged(const QString& gemName, uint32_t numChangedDependencies) @@ -253,24 +264,24 @@ namespace O3DE::ProjectManager notification = GemModel::GetDisplayName(modelIndex); if (numChangedDependencies > 0) { - notification += " " + tr("and") + " "; + notification += tr(" and "); } if (added && GemModel::GetDownloadStatus(modelIndex) == GemInfo::DownloadStatus::NotDownloaded) { m_downloadController->AddGemDownload(GemModel::GetName(modelIndex)); - GemModel::SetDownloadStatus(*m_proxyModel, m_proxyModel->mapFromSource(modelIndex), GemInfo::DownloadStatus::Downloading); + GemModel::SetDownloadStatus(*m_gemModel, modelIndex, GemInfo::DownloadStatus::Downloading); } } - if (numChangedDependencies == 1 ) + if (numChangedDependencies == 1) { - notification += "1 Gem " + tr("dependency"); + notification += tr("1 Gem dependency"); } else if (numChangedDependencies > 1) { - notification += QString("%1 Gem ").arg(numChangedDependencies) + tr("dependencies"); + notification += tr("%1 Gem %2").arg(QString(numChangedDependencies), tr("dependencies")); } - notification += " " + (added ? tr("activated") : tr("deactivated")); + notification += (added ? tr(" activated") : tr(" deactivated")); AzQtComponents::ToastConfiguration toastConfiguration(AzQtComponents::ToastType::Custom, notification, ""); toastConfiguration.m_customIconImage = ":/gem.svg"; @@ -290,10 +301,102 @@ namespace O3DE::ProjectManager } QModelIndex proxyIndex = m_proxyModel->mapFromSource(modelIndex); - m_proxyModel->GetSelectionModel()->select(proxyIndex, QItemSelectionModel::ClearAndSelect); + m_proxyModel->GetSelectionModel()->setCurrentIndex(proxyIndex, QItemSelectionModel::ClearAndSelect); m_gemListView->scrollTo(proxyIndex); } + void GemCatalogScreen::UpdateGem(const QModelIndex& modelIndex) + { + const QString selectedGemName = m_gemModel->GetName(modelIndex); + const QString selectedGemLastUpdate = m_gemModel->GetLastUpdated(modelIndex); + const QString selectedDisplayGemName = m_gemModel->GetDisplayName(modelIndex); + const QString selectedGemRepoUri = m_gemModel->GetRepoUri(modelIndex); + + // Refresh gem repo + if (!selectedGemRepoUri.isEmpty()) + { + AZ::Outcome refreshResult = PythonBindingsInterface::Get()->RefreshGemRepo(selectedGemRepoUri); + if (refreshResult.IsSuccess()) + { + Refresh(); + } + else + { + QMessageBox::critical( + this, tr("Operation failed"), + tr("Failed to refresh gem repository %1
Error:
%2").arg(selectedGemRepoUri, refreshResult.GetError().c_str())); + } + } + // If repo uri isn't specified warn user that repo might not be refreshed + else + { + int result = QMessageBox::warning( + this, tr("Gem Repository Unspecified"), + tr("The repo for %1 is unspecfied. Repository cannot be automatically refreshed. " + "Please ensure this gem's repo is refreshed before attempting to update.") + .arg(selectedDisplayGemName), + QMessageBox::Cancel, QMessageBox::Ok); + + // Allow user to cancel update to manually refresh repo + if (result != QMessageBox::Ok) + { + return; + } + } + + // Check if there is an update avaliable now that repo is refreshed + bool updateAvaliable = PythonBindingsInterface::Get()->IsGemUpdateAvaliable(selectedGemName, selectedGemLastUpdate); + + GemUpdateDialog* confirmUpdateDialog = new GemUpdateDialog(selectedGemName, updateAvaliable, this); + if (confirmUpdateDialog->exec() == QDialog::Accepted) + { + m_downloadController->AddGemDownload(selectedGemName); + } + } + + void GemCatalogScreen::UninstallGem(const QModelIndex& modelIndex) + { + const QString selectedDisplayGemName = m_gemModel->GetDisplayName(modelIndex); + + GemUninstallDialog* confirmUninstallDialog = new GemUninstallDialog(selectedDisplayGemName, this); + if (confirmUninstallDialog->exec() == QDialog::Accepted) + { + const QString selectedGemPath = m_gemModel->GetPath(modelIndex); + + // Remove gem from gems to be added + GemModel::SetIsAdded(*m_gemModel, modelIndex, false); + + // Unregister the gem + auto unregisterResult = PythonBindingsInterface::Get()->UnregisterGem(selectedGemPath); + if (!unregisterResult) + { + QMessageBox::critical(this, tr("Failed to unregister gem"), unregisterResult.GetError().c_str()); + } + else + { + const QString selectedGemName = m_gemModel->GetName(modelIndex); + + // Remove gem from model + m_gemModel->RemoveGem(modelIndex); + + // Delete uninstalled gem directory + if (!ProjectUtils::DeleteProjectFiles(selectedGemPath, /*force*/true)) + { + QMessageBox::critical( + this, tr("Failed to remove gem directory"), tr("Could not delete gem directory at:
%1").arg(selectedGemPath)); + } + + // Show undownloaded remote gem again + Refresh(); + + // Select remote gem + QModelIndex remoteGemIndex = m_gemModel->FindIndexByNameString(selectedGemName); + QModelIndex proxyIndex = m_proxyModel->mapFromSource(remoteGemIndex); + m_proxyModel->GetSelectionModel()->setCurrentIndex(proxyIndex, QItemSelectionModel::ClearAndSelect); + } + } + } + void GemCatalogScreen::hideEvent(QHideEvent* event) { ScreenWidget::hideEvent(event); @@ -472,7 +575,8 @@ namespace O3DE::ProjectManager if (succeeded) { // refresh the information for downloaded gems - const AZ::Outcome, AZStd::string>& allGemInfosResult = PythonBindingsInterface::Get()->GetAllGemInfos(m_projectPath); + const AZ::Outcome, AZStd::string>& allGemInfosResult = + PythonBindingsInterface::Get()->GetAllGemInfos(m_projectPath); if (allGemInfosResult.IsSuccess()) { // we should find the gem name now in all gem infos @@ -480,19 +584,45 @@ namespace O3DE::ProjectManager { if (gemInfo.m_name == gemName) { - QModelIndex index = m_gemModel->FindIndexByNameString(gemName); - if (index.isValid()) + QModelIndex oldIndex = m_gemModel->FindIndexByNameString(gemName); + if (oldIndex.isValid()) { - m_gemModel->setData(index, GemInfo::Downloaded, GemModel::RoleDownloadStatus); - m_gemModel->setData(index, gemInfo.m_path, GemModel::RolePath); - m_gemModel->setData(index, gemInfo.m_path, GemModel::RoleDirectoryLink); + // Check if old gem is selected + bool oldGemSelected = false; + if (m_gemModel->GetSelectionModel()->currentIndex() == oldIndex) + { + oldGemSelected = true; + } + + // Remove old remote gem + m_gemModel->RemoveGem(oldIndex); + + // Add new downloaded version of gem + QModelIndex newIndex = m_gemModel->AddGem(gemInfo); + GemModel::SetDownloadStatus(*m_gemModel, newIndex, GemInfo::DownloadSuccessful); + GemModel::SetIsAdded(*m_gemModel, newIndex, true); + + // Select new version of gem if it was previously selected + if (oldGemSelected) + { + QModelIndex proxyIndex = m_proxyModel->mapFromSource(newIndex); + m_proxyModel->GetSelectionModel()->setCurrentIndex(proxyIndex, QItemSelectionModel::ClearAndSelect); + } } - return; + break; } } } } + else + { + QModelIndex index = m_gemModel->FindIndexByNameString(gemName); + if (index.isValid()) + { + GemModel::SetDownloadStatus(*m_gemModel, index, GemInfo::DownloadFailed); + } + } } ProjectManagerScreen GemCatalogScreen::GetScreenEnum() diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h index da6d2efa7b..c5fbb057ef 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h @@ -51,6 +51,8 @@ namespace O3DE::ProjectManager void SelectGem(const QString& gemName); void OnGemDownloadResult(const QString& gemName, bool succeeded = true); void Refresh(); + void UpdateGem(const QModelIndex& modelIndex); + void UninstallGem(const QModelIndex& modelIndex); protected: void hideEvent(QHideEvent* event) override; @@ -77,6 +79,6 @@ namespace O3DE::ProjectManager DownloadController* m_downloadController = nullptr; bool m_notificationsEnabled = true; QSet m_gemsToRegisterWithProject; - QString m_projectPath = nullptr; + QString m_projectPath; }; } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h index 12cce5a4ea..5c1bc90c6e 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h @@ -57,7 +57,9 @@ namespace O3DE::ProjectManager UnknownDownloadStatus = -1, NotDownloaded, Downloading, - Downloaded, + DownloadSuccessful, + DownloadFailed, + Downloaded }; static QString GetDownloadStatusString(DownloadStatus status); @@ -85,6 +87,7 @@ namespace O3DE::ProjectManager QString m_licenseLink; QString m_directoryLink; QString m_documentationLink; + QString m_repoUri; QString m_version = "Unknown Version"; QString m_lastUpdatedDate = "Unknown Date"; int m_binarySizeInKB = 0; diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.cpp index 1bcfc6ce9d..26844b43e6 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.cpp @@ -14,6 +14,7 @@ #include #include #include +#include namespace O3DE::ProjectManager { @@ -70,6 +71,8 @@ namespace O3DE::ProjectManager void GemInspector::Update(const QModelIndex& modelIndex) { + m_curModelIndex = modelIndex; + if (!modelIndex.isValid()) { m_mainWidget->hide(); @@ -123,6 +126,20 @@ namespace O3DE::ProjectManager const int binarySize = m_model->GetBinarySizeInKB(modelIndex); m_binarySizeLabel->setText(tr("Binary Size: %1").arg(binarySize ? tr("%1 KB").arg(binarySize) : tr("Unknown"))); + // Update and Uninstall buttons + if (m_model->GetGemOrigin(modelIndex) == GemInfo::Remote && + (m_model->GetDownloadStatus(modelIndex) == GemInfo::Downloaded || + m_model->GetDownloadStatus(modelIndex) == GemInfo::DownloadSuccessful)) + { + m_updateGemButton->show(); + m_uninstallGemButton->show(); + } + else + { + m_updateGemButton->hide(); + m_uninstallGemButton->hide(); + } + m_mainWidget->adjustSize(); m_mainWidget->show(); } @@ -223,7 +240,7 @@ namespace O3DE::ProjectManager // Depending gems m_dependingGems = new GemsSubWidget(); - connect(m_dependingGems, &GemsSubWidget::TagClicked, this, [=](const Tag& tag){ emit TagClicked(tag); }); + connect(m_dependingGems, &GemsSubWidget::TagClicked, this, [this](const Tag& tag){ emit TagClicked(tag); }); m_mainLayout->addWidget(m_dependingGems); m_mainLayout->addSpacing(20); @@ -234,5 +251,20 @@ namespace O3DE::ProjectManager m_versionLabel = CreateStyledLabel(m_mainLayout, s_baseFontSize, s_textColor); m_lastUpdatedLabel = CreateStyledLabel(m_mainLayout, s_baseFontSize, s_textColor); m_binarySizeLabel = CreateStyledLabel(m_mainLayout, s_baseFontSize, s_textColor); + + m_mainLayout->addSpacing(20); + + // Update and Uninstall buttons + m_updateGemButton = new QPushButton(tr("Update Gem")); + m_updateGemButton->setObjectName("gemCatalogUpdateGemButton"); + m_mainLayout->addWidget(m_updateGemButton); + connect(m_updateGemButton, &QPushButton::clicked, this , [this]{ emit UpdateGem(m_curModelIndex); }); + + m_mainLayout->addSpacing(10); + + m_uninstallGemButton = new QPushButton(tr("Uninstall Gem")); + m_uninstallGemButton->setObjectName("gemCatalogUninstallGemButton"); + m_mainLayout->addWidget(m_uninstallGemButton); + connect(m_uninstallGemButton, &QPushButton::clicked, this , [this]{ emit UninstallGem(m_curModelIndex); }); } } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.h index 9a6ad84dea..6a5de17fcd 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.h @@ -16,11 +16,12 @@ #include #include -#include #endif QT_FORWARD_DECLARE_CLASS(QVBoxLayout) QT_FORWARD_DECLARE_CLASS(QLabel) +QT_FORWARD_DECLARE_CLASS(QSpacerItem) +QT_FORWARD_DECLARE_CLASS(QPushButton) namespace O3DE::ProjectManager { @@ -45,6 +46,8 @@ namespace O3DE::ProjectManager signals: void TagClicked(const Tag& tag); + void UpdateGem(const QModelIndex& modelIndex); + void UninstallGem(const QModelIndex& modelIndex); private slots: void OnSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected); @@ -55,6 +58,7 @@ namespace O3DE::ProjectManager GemModel* m_model = nullptr; QWidget* m_mainWidget = nullptr; QVBoxLayout* m_mainLayout = nullptr; + QModelIndex m_curModelIndex; // General info (top) section QLabel* m_nameLabel = nullptr; @@ -77,5 +81,8 @@ namespace O3DE::ProjectManager QLabel* m_versionLabel = nullptr; QLabel* m_lastUpdatedLabel = nullptr; QLabel* m_binarySizeLabel = nullptr; + + QPushButton* m_updateGemButton = nullptr; + QPushButton* m_uninstallGemButton = nullptr; }; } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.cpp index e15c4b3b39..dd94e42fc4 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.cpp @@ -37,6 +37,8 @@ namespace O3DE::ProjectManager SetStatusIcon(m_notDownloadedPixmap, ":/Download.svg"); SetStatusIcon(m_unknownStatusPixmap, ":/X.svg"); + SetStatusIcon(m_downloadSuccessfulPixmap, ":/checkmark.svg"); + SetStatusIcon(m_downloadFailedPixmap, ":/Warning.svg"); m_downloadingMovie = new QMovie(":/in_progress.gif"); } @@ -480,6 +482,14 @@ namespace O3DE::ProjectManager currentFrame = currentFrame.scaled(s_statusIconSize, s_statusIconSize); statusPixmap = ¤tFrame; } + else if (downloadStatus == GemInfo::DownloadStatus::DownloadSuccessful) + { + statusPixmap = &m_downloadSuccessfulPixmap; + } + else if (downloadStatus == GemInfo::DownloadStatus::DownloadFailed) + { + statusPixmap = &m_downloadFailedPixmap; + } else if (downloadStatus == GemInfo::DownloadStatus::NotDownloaded) { statusPixmap = &m_notDownloadedPixmap; diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.h index c013be0d9e..107de6de15 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.h @@ -97,6 +97,8 @@ namespace O3DE::ProjectManager QPixmap m_unknownStatusPixmap; QPixmap m_notDownloadedPixmap; + QPixmap m_downloadSuccessfulPixmap; + QPixmap m_downloadFailedPixmap; QMovie* m_downloadingMovie = nullptr; }; } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.cpp index 88c54de0b3..95fdc8e1e2 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.cpp @@ -26,14 +26,14 @@ namespace O3DE::ProjectManager return m_selectionModel; } - void GemModel::AddGem(const GemInfo& gemInfo) + QModelIndex GemModel::AddGem(const GemInfo& gemInfo) { if (FindIndexByNameString(gemInfo.m_name).isValid()) { // do not add gems with duplicate names // this can happen by mistake or when a gem repo has a gem with the same name as a local gem AZ_TracePrintf("GemModel", "Ignoring duplicate gem: %s", gemInfo.m_name.toUtf8().constData()); - return; + return QModelIndex(); } QStandardItem* item = new QStandardItem(); @@ -61,11 +61,28 @@ namespace O3DE::ProjectManager item->setData(gemInfo.m_downloadStatus, RoleDownloadStatus); item->setData(gemInfo.m_licenseText, RoleLicenseText); item->setData(gemInfo.m_licenseLink, RoleLicenseLink); + item->setData(gemInfo.m_repoUri, RoleRepoUri); appendRow(item); const QModelIndex modelIndex = index(rowCount()-1, 0); m_nameToIndexMap[gemInfo.m_name] = modelIndex; + + return modelIndex; + } + + void GemModel::RemoveGem(const QModelIndex& modelIndex) + { + removeRow(modelIndex.row()); + } + + void GemModel::RemoveGem(const QString& gemName) + { + auto nameFind = m_nameToIndexMap.find(gemName); + if (nameFind != m_nameToIndexMap.end()) + { + removeRow(nameFind->row()); + } } void GemModel::Clear() @@ -255,6 +272,11 @@ namespace O3DE::ProjectManager return modelIndex.data(RoleLicenseLink).toString(); } + QString GemModel::GetRepoUri(const QModelIndex& modelIndex) + { + return modelIndex.data(RoleRepoUri).toString(); + } + GemModel* GemModel::GetSourceModel(QAbstractItemModel* model) { GemSortFilterProxyModel* proxyModel = qobject_cast(model); @@ -369,11 +391,30 @@ namespace O3DE::ProjectManager void GemModel::OnRowsAboutToBeRemoved(const QModelIndex& parent, int first, int last) { + bool selectedRowRemoved = false; for (int i = first; i <= last; ++i) { QModelIndex modelIndex = index(i, 0, parent); const QString& gemName = GetName(modelIndex); m_nameToIndexMap.remove(gemName); + + if (GetSelectionModel()->isRowSelected(i)) + { + selectedRowRemoved = true; + } + } + + // Select a valid row if currently selected row was removed + if (selectedRowRemoved) + { + for (const QModelIndex& index : m_nameToIndexMap) + { + if (index.isValid()) + { + GetSelectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect); + break; + } + } } } diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h index e25a1c7703..bb89d46861 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h @@ -51,10 +51,13 @@ namespace O3DE::ProjectManager RoleRequirement, RoleDownloadStatus, RoleLicenseText, - RoleLicenseLink + RoleLicenseLink, + RoleRepoUri }; - void AddGem(const GemInfo& gemInfo); + QModelIndex AddGem(const GemInfo& gemInfo); + void RemoveGem(const QModelIndex& modelIndex); + void RemoveGem(const QString& gemName); void Clear(); void UpdateGemDependencies(); @@ -80,6 +83,7 @@ namespace O3DE::ProjectManager static QString GetRequirement(const QModelIndex& modelIndex); static QString GetLicenseText(const QModelIndex& modelIndex); static QString GetLicenseLink(const QModelIndex& modelIndex); + static QString GetRepoUri(const QModelIndex& modelIndex); static GemModel* GetSourceModel(QAbstractItemModel* model); static const GemModel* GetSourceModel(const QAbstractItemModel* model); diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemSortFilterProxyModel.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemSortFilterProxyModel.cpp index 32d0e2fee9..a32492cf1e 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemSortFilterProxyModel.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemSortFilterProxyModel.cpp @@ -204,9 +204,12 @@ namespace O3DE::ProjectManager emit OnInvalidated(); } - void GemSortFilterProxyModel::ResetFilters() + void GemSortFilterProxyModel::ResetFilters(bool clearSearchString) { - m_searchString.clear(); + if (clearSearchString) + { + m_searchString.clear(); + } m_gemSelectedFilter = GemSelected::NoFilter; m_gemActiveFilter = GemActive::NoFilter; m_gemOriginFilter = {}; diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemSortFilterProxyModel.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemSortFilterProxyModel.h index ab739e62f9..0c58d66ccf 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemSortFilterProxyModel.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemSortFilterProxyModel.h @@ -70,7 +70,7 @@ namespace O3DE::ProjectManager void SetFeatures(const QSet& features) { m_featureFilter = features; InvalidateFilter(); } void InvalidateFilter(); - void ResetFilters(); + void ResetFilters(bool clearSearchString = true); signals: void OnInvalidated(); diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemUninstallDialog.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemUninstallDialog.cpp new file mode 100644 index 0000000000..1408e29b6d --- /dev/null +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemUninstallDialog.cpp @@ -0,0 +1,60 @@ +/* + * 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 O3DE::ProjectManager +{ + GemUninstallDialog::GemUninstallDialog(const QString& gemName, QWidget* parent) + : QDialog(parent) + { + setWindowTitle(tr("Uninstall Remote Gem")); + setObjectName("GemUninstallDialog"); + setAttribute(Qt::WA_DeleteOnClose); + setModal(true); + + QVBoxLayout* layout = new QVBoxLayout(); + layout->setMargin(30); + layout->setAlignment(Qt::AlignTop); + setLayout(layout); + + // Body + QLabel* subTitleLabel = new QLabel(tr("Are you sure you want to uninstall %1?").arg(gemName)); + subTitleLabel->setObjectName("gemCatalogDialogSubTitle"); + layout->addWidget(subTitleLabel); + + layout->addSpacing(10); + + QLabel* bodyLabel = new QLabel(tr("The Gem and its related files will be uninstalled. This does not affect the Gem's repository. " + "You can reinstall this Gem from the Catalog, but its contents may be subject to change.")); + bodyLabel->setWordWrap(true); + bodyLabel->setFixedSize(QSize(440, 80)); + layout->addWidget(bodyLabel); + + layout->addSpacing(40); + + // Buttons + QDialogButtonBox* dialogButtons = new QDialogButtonBox(); + dialogButtons->setObjectName("footer"); + layout->addWidget(dialogButtons); + + QPushButton* cancelButton = dialogButtons->addButton(tr("Cancel"), QDialogButtonBox::RejectRole); + cancelButton->setProperty("secondary", true); + QPushButton* uninstallButton = dialogButtons->addButton(tr("Uninstall Gem"), QDialogButtonBox::ApplyRole); + uninstallButton->setObjectName("gemCatalogUninstallGemButton"); + + connect(cancelButton, &QPushButton::clicked, this, &QDialog::reject); + connect(uninstallButton, &QPushButton::clicked, this, &QDialog::accept); + } +} // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemUninstallDialog.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemUninstallDialog.h new file mode 100644 index 0000000000..9e3f4c3f3b --- /dev/null +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemUninstallDialog.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 + +#if !defined(Q_MOC_RUN) +#include +#endif + +namespace O3DE::ProjectManager +{ + class GemUninstallDialog + : public QDialog + { + Q_OBJECT // AUTOMOC + public: + explicit GemUninstallDialog(const QString& gemName, QWidget *parent = nullptr); + ~GemUninstallDialog() = default; + }; +} // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemUpdateDialog.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemUpdateDialog.cpp new file mode 100644 index 0000000000..82d205aab5 --- /dev/null +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemUpdateDialog.cpp @@ -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 + * + */ + +#include + +#include +#include +#include +#include +#include + +namespace O3DE::ProjectManager +{ + GemUpdateDialog::GemUpdateDialog(const QString& gemName, bool updateAvaliable, QWidget* parent) + : QDialog(parent) + { + setWindowTitle(tr("Update Remote Gem")); + setObjectName("GemUpdateDialog"); + setAttribute(Qt::WA_DeleteOnClose); + setModal(true); + + QVBoxLayout* layout = new QVBoxLayout(); + layout->setMargin(30); + layout->setAlignment(Qt::AlignTop); + setLayout(layout); + + // Body + QLabel* subTitleLabel = new QLabel(tr("%1 to the latest version of %2?").arg( + updateAvaliable ? tr("Update") : tr("Force update"), gemName)); + subTitleLabel->setObjectName("gemCatalogDialogSubTitle"); + layout->addWidget(subTitleLabel); + + layout->addSpacing(10); + + QLabel* bodyLabel = new QLabel(tr("%1The latest version of this Gem may not be compatible with your engine. " + "Updating this Gem will remove any local changes made to this Gem, " + "and may remove old features that are in use.").arg( + updateAvaliable ? "" : tr("No update detected for Gem. " + "This will force a re-download of the gem. "))); + bodyLabel->setWordWrap(true); + bodyLabel->setFixedSize(QSize(440, 80)); + layout->addWidget(bodyLabel); + + layout->addSpacing(40); + + // Buttons + QDialogButtonBox* dialogButtons = new QDialogButtonBox(); + dialogButtons->setObjectName("footer"); + layout->addWidget(dialogButtons); + + QPushButton* cancelButton = dialogButtons->addButton(tr("Cancel"), QDialogButtonBox::RejectRole); + cancelButton->setProperty("secondary", true); + QPushButton* updateButton = + dialogButtons->addButton(tr("%1Update Gem").arg(updateAvaliable ? "" : tr("Force ")), QDialogButtonBox::ApplyRole); + + connect(cancelButton, &QPushButton::clicked, this, &QDialog::reject); + connect(updateButton, &QPushButton::clicked, this, &QDialog::accept); + } +} // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemUpdateDialog.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemUpdateDialog.h new file mode 100644 index 0000000000..cf34abfb3d --- /dev/null +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemUpdateDialog.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 + +#if !defined(Q_MOC_RUN) +#include +#endif + +namespace O3DE::ProjectManager +{ + class GemUpdateDialog + : public QDialog + { + Q_OBJECT // AUTOMOC + public : + explicit GemUpdateDialog(const QString& gemName, bool updateAvaliable = true, QWidget* parent = nullptr); + ~GemUpdateDialog() = default; + }; +} // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.cpp b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.cpp index 794635a3e3..91432c2346 100644 --- a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.cpp +++ b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.cpp @@ -75,7 +75,7 @@ namespace O3DE::ProjectManager // Select the first entry after everything got correctly sized QTimer::singleShot(200, [=]{ QModelIndex firstModelIndex = m_gemRepoListView->model()->index(0,0); - m_gemRepoListView->selectionModel()->select(firstModelIndex, QItemSelectionModel::ClearAndSelect); + m_gemRepoListView->selectionModel()->setCurrentIndex(firstModelIndex, QItemSelectionModel::ClearAndSelect); }); } diff --git a/Code/Tools/ProjectManager/Source/ProjectButtonWidget.cpp b/Code/Tools/ProjectManager/Source/ProjectButtonWidget.cpp index c425c344e1..98916cccf6 100644 --- a/Code/Tools/ProjectManager/Source/ProjectButtonWidget.cpp +++ b/Code/Tools/ProjectManager/Source/ProjectButtonWidget.cpp @@ -8,7 +8,11 @@ #include #include +#include +#include #include +#include +#include #include #include @@ -23,6 +27,7 @@ #include #include #include +#include namespace O3DE::ProjectManager { @@ -205,6 +210,29 @@ namespace O3DE::ProjectManager { AzQtComponents::ShowFileOnDesktop(m_projectInfo.m_path); }); + +#if AZ_TRAIT_PROJECT_MANAGER_CREATE_DESKTOP_SHORTCUT + menu->addAction(tr("Create Editor desktop shortcut..."), this, [this]() + { + AZ::IO::FixedMaxPath executableDirectory = ProjectUtils::GetEditorDirectory(); + AZStd::string executableFilename = "Editor"; + AZ::IO::FixedMaxPath editorExecutablePath = executableDirectory / (executableFilename + AZ_TRAIT_OS_EXECUTABLE_EXTENSION); + + const QString shortcutName = QString("%1 Editor").arg(m_projectInfo.m_displayName); + const QString arg = QString("--regset=\"/Amazon/AzCore/Bootstrap/project_path=%1\"").arg(m_projectInfo.m_path); + + auto result = ProjectUtils::CreateDesktopShortcut(shortcutName, editorExecutablePath.c_str(), { arg }); + if(result.IsSuccess()) + { + QMessageBox::information(this, tr("Desktop Shortcut Created"), result.GetValue()); + } + else + { + QMessageBox::critical(this, tr("Failed to create shortcut"), result.GetError()); + } + }); +#endif // AZ_TRAIT_PROJECT_MANAGER_CREATE_DESKTOP_SHORTCUT + menu->addSeparator(); menu->addAction(tr("Duplicate"), this, [this]() { emit CopyProject(m_projectInfo); }); menu->addSeparator(); diff --git a/Code/Tools/ProjectManager/Source/ProjectUtils.cpp b/Code/Tools/ProjectManager/Source/ProjectUtils.cpp index bb2bcd070e..4a0e1c153c 100644 --- a/Code/Tools/ProjectManager/Source/ProjectUtils.cpp +++ b/Code/Tools/ProjectManager/Source/ProjectUtils.cpp @@ -628,11 +628,11 @@ namespace O3DE::ProjectManager return AZ::Failure(QObject::tr("Process for command '%1' timed out at %2 seconds").arg(cmd).arg(commandTimeoutSeconds)); } int resultCode = execProcess.exitCode(); + QString resultOutput = execProcess.readAllStandardOutput(); if (resultCode != 0) { - return AZ::Failure(QObject::tr("Process for command '%1' failed (result code %2").arg(cmd).arg(resultCode)); + return AZ::Failure(QObject::tr("Process for command '%1' failed (result code %2) %3").arg(cmd).arg(resultCode).arg(resultOutput)); } - QString resultOutput = execProcess.readAllStandardOutput(); return AZ::Success(resultOutput); } diff --git a/Code/Tools/ProjectManager/Source/ProjectUtils.h b/Code/Tools/ProjectManager/Source/ProjectUtils.h index 890d50d2de..ee605b5117 100644 --- a/Code/Tools/ProjectManager/Source/ProjectUtils.h +++ b/Code/Tools/ProjectManager/Source/ProjectUtils.h @@ -68,6 +68,15 @@ namespace O3DE::ProjectManager AZ::Outcome GetProjectBuildPath(const QString& projectPath); AZ::Outcome OpenCMakeGUI(const QString& projectPath); AZ::Outcome RunGetPythonScript(const QString& enginePath); + + /** + * Create a desktop shortcut. + * @param filename the name of the desktop shorcut file + * @param target the path to the target to run + * @param arguments the argument list to provide to the target + * @return AZ::Outcome with the command result on success + */ + AZ::Outcome CreateDesktopShortcut(const QString& filename, const QString& targetPath, const QStringList& arguments); AZ::IO::FixedMaxPath GetEditorDirectory(); diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.cpp b/Code/Tools/ProjectManager/Source/PythonBindings.cpp index 021066e1c7..d1dedaaec4 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.cpp +++ b/Code/Tools/ProjectManager/Source/PythonBindings.cpp @@ -515,7 +515,11 @@ namespace O3DE::ProjectManager auto pyProjectPath = QString_To_Py_Path(projectPath); for (auto path : m_manifest.attr("get_all_gems")(pyProjectPath)) { - gems.push_back(GemInfoFromPath(path, pyProjectPath)); + GemInfo gemInfo = GemInfoFromPath(path, pyProjectPath); + // Mark as downloaded because this gem was registered with an existing directory + gemInfo.m_downloadStatus = GemInfo::DownloadStatus::Downloaded; + + gems.push_back(AZStd::move(gemInfo)); } }); if (!result.IsSuccess()) @@ -560,7 +564,7 @@ namespace O3DE::ProjectManager return AZ::Success(AZStd::move(gemNames)); } - AZ::Outcome PythonBindings::RegisterGem(const QString& gemPath, const QString& projectPath) + AZ::Outcome PythonBindings::GemRegistration(const QString& gemPath, const QString& projectPath, bool remove) { bool registrationResult = false; auto result = ExecuteWithLockErrorHandling( @@ -582,7 +586,8 @@ namespace O3DE::ProjectManager pybind11::none(), // default_restricted_folder pybind11::none(), // default_third_party_folder pybind11::none(), // external_subdir_engine_path - externalProjectPath // external_subdir_project_path + externalProjectPath, // external_subdir_project_path + remove // remove ); // Returns an exit code so boolify it then invert result @@ -595,12 +600,23 @@ namespace O3DE::ProjectManager } else if (!registrationResult) { - return AZ::Failure(AZStd::string::format("Failed to register gem path %s", gemPath.toUtf8().constData())); + return AZ::Failure(AZStd::string::format( + "Failed to %s gem path %s", remove ? "unregister" : "register", gemPath.toUtf8().constData())); } return AZ::Success(); } + AZ::Outcome PythonBindings::RegisterGem(const QString& gemPath, const QString& projectPath) + { + return GemRegistration(gemPath, projectPath); + } + + AZ::Outcome PythonBindings::UnregisterGem(const QString& gemPath, const QString& projectPath) + { + return GemRegistration(gemPath, projectPath, /*remove*/true); + } + bool PythonBindings::AddProject(const QString& path) { bool registrationResult = false; @@ -715,6 +731,7 @@ namespace O3DE::ProjectManager gemInfo.m_documentationLink = Py_To_String_Optional(data, "documentation_url", ""); gemInfo.m_licenseText = Py_To_String_Optional(data, "license", "Unspecified License"); gemInfo.m_licenseLink = Py_To_String_Optional(data, "license_url", ""); + gemInfo.m_repoUri = Py_To_String_Optional(data, "repo_uri", ""); if (gemInfo.m_creator.contains("Open 3D Engine")) { @@ -728,6 +745,11 @@ namespace O3DE::ProjectManager { gemInfo.m_gemOrigin = GemInfo::GemOrigin::Remote; } + // If no origin was provided this cannot be remote and would be specified if O3DE so it should be local + else + { + gemInfo.m_gemOrigin = GemInfo::GemOrigin::Local; + } // As long Base Open3DEngine gems are installed before first startup non-remote gems will be downloaded if (gemInfo.m_gemOrigin != GemInfo::GemOrigin::Remote) @@ -1166,49 +1188,6 @@ namespace O3DE::ProjectManager return AZ::Success(AZStd::move(gemRepos)); } - AZ::Outcome PythonBindings::DownloadGem(const QString& gemName, std::function gemProgressCallback) - { - // This process is currently limited to download a single gem at a time. - bool downloadSucceeded = false; - - m_requestCancelDownload = false; - auto result = ExecuteWithLockErrorHandling( - [&] - { - auto downloadResult = m_download.attr("download_gem")( - QString_To_Py_String(gemName), // gem name - pybind11::none(), // destination path - false, // skip auto register - false, // force - pybind11::cpp_function( - [this, gemProgressCallback](int progress) - { - gemProgressCallback(progress); - - return m_requestCancelDownload; - }) // Callback for download progress and cancelling - ); - downloadSucceeded = (downloadResult.cast() == 0); - }); - - - if (!result.IsSuccess()) - { - return result; - } - else if (!downloadSucceeded) - { - return AZ::Failure("Failed to download gem."); - } - - return AZ::Success(); - } - - void PythonBindings::CancelDownload() - { - m_requestCancelDownload = true; - } - AZ::Outcome, AZStd::string> PythonBindings::GetAllGemRepoGemsInfos() { QVector gemInfos; @@ -1235,4 +1214,64 @@ namespace O3DE::ProjectManager return AZ::Success(AZStd::move(gemInfos)); } + + AZ::Outcome PythonBindings::DownloadGem( + const QString& gemName, std::function gemProgressCallback, bool force) + { + // This process is currently limited to download a single gem at a time. + bool downloadSucceeded = false; + + m_requestCancelDownload = false; + auto result = ExecuteWithLockErrorHandling( + [&] + { + auto downloadResult = m_download.attr("download_gem")( + QString_To_Py_String(gemName), // gem name + pybind11::none(), // destination path + false, // skip auto register + force, // force overwrite + pybind11::cpp_function( + [this, gemProgressCallback](int bytesDownloaded, int totalBytes) + { + gemProgressCallback(bytesDownloaded, totalBytes); + + return m_requestCancelDownload; + }) // Callback for download progress and cancelling + ); + downloadSucceeded = (downloadResult.cast() == 0); + }); + + + if (!result.IsSuccess()) + { + return result; + } + else if (!downloadSucceeded) + { + return AZ::Failure("Failed to download gem."); + } + + return AZ::Success(); + } + + void PythonBindings::CancelDownload() + { + m_requestCancelDownload = true; + } + + bool PythonBindings::IsGemUpdateAvaliable(const QString& gemName, const QString& lastUpdated) + { + bool updateAvaliableResult = false; + bool result = ExecuteWithLock( + [&] + { + auto pyGemName = QString_To_Py_String(gemName); + auto pyLastUpdated = QString_To_Py_String(lastUpdated); + auto pythonUpdateAvaliableResult = m_download.attr("is_o3de_gem_update_available")(pyGemName, pyLastUpdated); + + updateAvaliableResult = pythonUpdateAvaliableResult.cast(); + }); + + return result && updateAvaliableResult; + } } diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.h b/Code/Tools/ProjectManager/Source/PythonBindings.h index 4375d56d02..ecc6f65dc3 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.h +++ b/Code/Tools/ProjectManager/Source/PythonBindings.h @@ -43,6 +43,7 @@ namespace O3DE::ProjectManager AZ::Outcome, AZStd::string> GetAllGemInfos(const QString& projectPath) override; AZ::Outcome, AZStd::string> GetEnabledGemNames(const QString& projectPath) override; AZ::Outcome RegisterGem(const QString& gemPath, const QString& projectPath = {}) override; + AZ::Outcome UnregisterGem(const QString& gemPath, const QString& projectPath = {}) override; // Project AZ::Outcome CreateProject(const QString& projectTemplatePath, const ProjectInfo& projectInfo) override; @@ -64,9 +65,10 @@ namespace O3DE::ProjectManager bool AddGemRepo(const QString& repoUri) override; bool RemoveGemRepo(const QString& repoUri) override; AZ::Outcome, AZStd::string> GetAllGemRepoInfos() override; - AZ::Outcome DownloadGem(const QString& gemName, std::function gemProgressCallback) override; - void CancelDownload() override; AZ::Outcome, AZStd::string> GetAllGemRepoGemsInfos() override; + AZ::Outcome DownloadGem(const QString& gemName, std::function gemProgressCallback, bool force = false) override; + void CancelDownload() override; + bool IsGemUpdateAvaliable(const QString& gemName, const QString& lastUpdated) override; private: AZ_DISABLE_COPY_MOVE(PythonBindings); @@ -77,6 +79,7 @@ namespace O3DE::ProjectManager GemRepoInfo GetGemRepoInfo(pybind11::handle repoUri); ProjectInfo ProjectInfoFromPath(pybind11::handle path); ProjectTemplateInfo ProjectTemplateInfoFromPath(pybind11::handle path, pybind11::handle pyProjectPath); + AZ::Outcome GemRegistration(const QString& gemPath, const QString& projectPath, bool remove = false); bool RegisterThisEngine(); bool StopPython(); diff --git a/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h b/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h index 1134804f1f..65337869fd 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h +++ b/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h @@ -94,11 +94,19 @@ namespace O3DE::ProjectManager /** * Registers the gem to the specified project, or to the o3de_manifest.json if no project path is given * @param gemPath the path to the gem - * @param projectPath the path to the project. If empty, will register the external path in o3de_manifest.json + * @param projectPath the path to the project. If empty, will register the external path in o3de_manifest.json * @return An outcome with the success flag as well as an error message in case of a failure. */ virtual AZ::Outcome RegisterGem(const QString& gemPath, const QString& projectPath = {}) = 0; + /** + * Unregisters the gem from the specified project, or from the o3de_manifest.json if no project path is given + * @param gemPath the path to the gem + * @param projectPath the path to the project. If empty, will unregister the external path in o3de_manifest.json + * @return An outcome with the success flag as well as an error message in case of a failure. + */ + virtual AZ::Outcome UnregisterGem(const QString& gemPath, const QString& projectPath = {}) = 0; + // Projects @@ -209,24 +217,34 @@ namespace O3DE::ProjectManager */ virtual AZ::Outcome, AZStd::string> GetAllGemRepoInfos() = 0; - /** - * Downloads and registers a Gem. - * @param gemName the name of the Gem to download - * @param gemProgressCallback a callback function that is called with an int percentage download value - * @return an outcome with a string error message on failure. - */ - virtual AZ::Outcome DownloadGem(const QString& gemName, std::function gemProgressCallback) = 0; - - /** - * Cancels the current download. - */ - virtual void CancelDownload() = 0; - /** * Gathers all gem infos for all gems registered from repos. * @return A list of gem infos. */ virtual AZ::Outcome, AZStd::string> GetAllGemRepoGemsInfos() = 0; + + /** + * Downloads and registers a Gem. + * @param gemName the name of the Gem to download. + * @param gemProgressCallback a callback function that is called with an int percentage download value. + * @param force should we forcibly overwrite the old version of the gem. + * @return an outcome with a string error message on failure. + */ + virtual AZ::Outcome DownloadGem( + const QString& gemName, std::function gemProgressCallback, bool force = false) = 0; + + /** + * Cancels the current download. + */ + virtual void CancelDownload() = 0; + + /** + * Checks if there is an update avaliable for a gem on a repo. + * @param gemName the name of the gem to check. + * @param lastUpdated last time the gem was update. + * @return true if update is avaliable, false if not. + */ + virtual bool IsGemUpdateAvaliable(const QString& gemName, const QString& lastUpdated) = 0; }; using PythonBindingsInterface = AZ::Interface; diff --git a/Code/Tools/ProjectManager/project_manager_files.cmake b/Code/Tools/ProjectManager/project_manager_files.cmake index e2e35717f6..fcfae2f336 100644 --- a/Code/Tools/ProjectManager/project_manager_files.cmake +++ b/Code/Tools/ProjectManager/project_manager_files.cmake @@ -96,6 +96,10 @@ set(FILES Source/GemCatalog/GemListHeaderWidget.cpp Source/GemCatalog/GemModel.h Source/GemCatalog/GemModel.cpp + Source/GemCatalog/GemUninstallDialog.h + Source/GemCatalog/GemUninstallDialog.cpp + Source/GemCatalog/GemUpdateDialog.h + Source/GemCatalog/GemUpdateDialog.cpp Source/GemCatalog/GemDependenciesDialog.h Source/GemCatalog/GemDependenciesDialog.cpp Source/GemCatalog/GemRequirementDialog.h diff --git a/Gems/AWSCore/Code/Include/Private/ResourceMapping/AWSResourceMappingConstants.h b/Gems/AWSCore/Code/Include/Private/ResourceMapping/AWSResourceMappingConstants.h index 97eb5b6492..a3d18c1ea1 100644 --- a/Gems/AWSCore/Code/Include/Private/ResourceMapping/AWSResourceMappingConstants.h +++ b/Gems/AWSCore/Code/Include/Private/ResourceMapping/AWSResourceMappingConstants.h @@ -68,7 +68,7 @@ namespace AWSCore }, "AccountIdString": { "type": "string", - "pattern": "^[0-9]{12}$|EMPTY" + "pattern": "^[0-9]{12}$|EMPTY|^$" }, "NonEmptyString": { "type": "string", diff --git a/Gems/AWSCore/Code/Tests/ResourceMapping/AWSResourceMappingManagerTest.cpp b/Gems/AWSCore/Code/Tests/ResourceMapping/AWSResourceMappingManagerTest.cpp index fb03dea4c0..a90518006f 100644 --- a/Gems/AWSCore/Code/Tests/ResourceMapping/AWSResourceMappingManagerTest.cpp +++ b/Gems/AWSCore/Code/Tests/ResourceMapping/AWSResourceMappingManagerTest.cpp @@ -59,6 +59,34 @@ R"({ "Version": "1.0.0" })"; +static constexpr const char TEST_VALID_EMPTY_ACCOUNTID_RESOURCE_MAPPING_CONFIG_FILE[] = + R"({ + "AWSResourceMappings": { + "TestLambda": { + "Type": "AWS::Lambda::Function", + "Name/ID": "MyTestLambda", + "Region": "us-east-1", + "AccountId": "012345678912" + }, + "TestS3Bucket": { + "Type": "AWS::S3::Bucket", + "Name/ID": "MyTestS3Bucket" + }, + "TestService.RESTApiId": { + "Type": "AWS::ApiGateway::RestApi", + "Name/ID": "1234567890" + }, + "TestService.RESTApiStage": { + "Type": "AWS::ApiGateway::Stage", + "Name/ID": "prod", + "Region": "us-east-1" + } + }, + "AccountId": "", + "Region": "us-west-2", + "Version": "1.0.0" +})"; + static constexpr const char TEST_INVALID_RESOURCE_MAPPING_CONFIG_FILE[] = R"({ "AWSResourceMappings": {}, @@ -237,6 +265,21 @@ TEST_F(AWSResourceMappingManagerTest, ActivateManager_ParseValidConfigFile_Confi EXPECT_TRUE(actualEbusCalls == testThreadNumber); } +TEST_F(AWSResourceMappingManagerTest, ActivateManager_ParseValidConfigFile_GlobalAccountIdEmpty) +{ + CreateTestConfigFile(TEST_VALID_EMPTY_ACCOUNTID_RESOURCE_MAPPING_CONFIG_FILE); + m_resourceMappingManager->ActivateManager(); + + AZStd::string actualAccountId; + AZStd::string actualRegion; + AWSResourceMappingRequestBus::BroadcastResult(actualAccountId, &AWSResourceMappingRequests::GetDefaultAccountId); + AWSResourceMappingRequestBus::BroadcastResult(actualRegion, &AWSResourceMappingRequests::GetDefaultRegion); + EXPECT_EQ(m_reloadConfigurationCounter, 0); + EXPECT_TRUE(actualAccountId.empty()); + EXPECT_FALSE(actualRegion.empty()); + EXPECT_TRUE(m_resourceMappingManager->GetStatus() == AWSResourceMappingManager::Status::Ready); +} + TEST_F(AWSResourceMappingManagerTest, DeactivateManager_AfterActivatingWithValidConfigFile_ConfigDataGetCleanedUp) { CreateTestConfigFile(TEST_VALID_RESOURCE_MAPPING_CONFIG_FILE); diff --git a/Gems/AWSCore/Code/Tools/ResourceMappingTool/controller/view_edit_controller.py b/Gems/AWSCore/Code/Tools/ResourceMappingTool/controller/view_edit_controller.py index bdfe7fa11e..39f7b5ccf1 100755 --- a/Gems/AWSCore/Code/Tools/ResourceMappingTool/controller/view_edit_controller.py +++ b/Gems/AWSCore/Code/Tools/ResourceMappingTool/controller/view_edit_controller.py @@ -69,17 +69,13 @@ class ViewEditController(QObject): json_dict: Dict[str, any] = \ json_utils.convert_resources_to_json_dict(self._proxy_model.get_resources(), self._config_file_json_source) - configuration: Configuration = self._configuration_manager.configuration - if json_dict.get(json_utils.RESOURCE_MAPPING_ACCOUNTID_JSON_KEY_NAME) == \ - json_utils.RESOURCE_MAPPING_ACCOUNTID_TEMPLATE_VALUE: - json_dict[json_utils.RESOURCE_MAPPING_ACCOUNTID_JSON_KEY_NAME] = configuration.account_id - if json_dict == self._config_file_json_source: # skip because no difference found against existing json file return True # try to write in memory json content into json file try: + configuration: Configuration = self._configuration_manager.configuration config_file_full_path: str = file_utils.join_path(configuration.config_directory, config_file_name) json_utils.write_into_json_file(config_file_full_path, json_dict) self._config_file_json_source = json_dict diff --git a/Gems/AWSCore/Code/Tools/ResourceMappingTool/tests/unit/controller/test_view_edit_controller.py b/Gems/AWSCore/Code/Tools/ResourceMappingTool/tests/unit/controller/test_view_edit_controller.py index c73bddcd9c..41d4490c44 100755 --- a/Gems/AWSCore/Code/Tools/ResourceMappingTool/tests/unit/controller/test_view_edit_controller.py +++ b/Gems/AWSCore/Code/Tools/ResourceMappingTool/tests/unit/controller/test_view_edit_controller.py @@ -420,8 +420,30 @@ class TestViewEditController(TestCase): self._mocked_view_edit_page.config_file_combobox.currentText.return_value = \ TestViewEditController._expected_config_file_name expected_json_dict: Dict[str, any] = { - "dummyKey": "dummyValue", - self._expected_account_id_attribute_name: self._expected_account_id_template_vale} + "dummyKey": "dummyValue" + } + mock_json_utils.validate_resources_according_to_json_schema.return_value = [] + mock_json_utils.convert_resources_to_json_dict.return_value = expected_json_dict + mock_file_utils.join_path.return_value = TestViewEditController._expected_config_file_full_path + mocked_call_args: call = self._mocked_view_edit_page.save_changes_button.clicked.connect.call_args[0] + + mocked_call_args[0]() # triggering save_changes_button connected function + mock_json_utils.convert_resources_to_json_dict.assert_called_once() + mock_json_utils.write_into_json_file.assert_called_once_with( + TestViewEditController._expected_config_file_full_path, expected_json_dict) + self._mocked_proxy_model.override_all_resources_status.assert_called_once_with( + ResourceMappingAttributesStatus(ResourceMappingAttributesStatus.SUCCESS_STATUS_VALUE, + [ResourceMappingAttributesStatus.SUCCESS_STATUS_VALUE])) + + @patch("controller.view_edit_controller.file_utils") + @patch("controller.view_edit_controller.json_utils") + def test_page_save_changes_button_json_file_saved_and_template_account_id_unchanged( + self, mock_json_utils: MagicMock, mock_file_utils: MagicMock) -> None: + self._mocked_view_edit_page.config_file_combobox.currentText.return_value = \ + TestViewEditController._expected_config_file_name + expected_json_dict: Dict[str, any] = { + self._expected_account_id_attribute_name: self._expected_account_id_template_vale + } mock_json_utils.RESOURCE_MAPPING_ACCOUNTID_JSON_KEY_NAME = self._expected_account_id_attribute_name mock_json_utils.RESOURCE_MAPPING_ACCOUNTID_TEMPLATE_VALUE = self._expected_account_id_template_vale mock_json_utils.validate_resources_according_to_json_schema.return_value = [] @@ -430,7 +452,31 @@ class TestViewEditController(TestCase): mocked_call_args: call = self._mocked_view_edit_page.save_changes_button.clicked.connect.call_args[0] mocked_call_args[0]() # triggering save_changes_button connected function - assert expected_json_dict["AccountId"] == self._mocked_configuration_manager.configuration.account_id + assert expected_json_dict[mock_json_utils.RESOURCE_MAPPING_ACCOUNTID_JSON_KEY_NAME] == self._expected_account_id_template_vale + mock_json_utils.convert_resources_to_json_dict.assert_called_once() + mock_json_utils.write_into_json_file.assert_called_once_with( + TestViewEditController._expected_config_file_full_path, expected_json_dict) + self._mocked_proxy_model.override_all_resources_status.assert_called_once_with( + ResourceMappingAttributesStatus(ResourceMappingAttributesStatus.SUCCESS_STATUS_VALUE, + [ResourceMappingAttributesStatus.SUCCESS_STATUS_VALUE])) + + @patch("controller.view_edit_controller.file_utils") + @patch("controller.view_edit_controller.json_utils") + def test_page_save_changes_button_json_file_saved_and_empty_account_id_unchanged( + self, mock_json_utils: MagicMock, mock_file_utils: MagicMock) -> None: + self._mocked_view_edit_page.config_file_combobox.currentText.return_value = \ + TestViewEditController._expected_config_file_name + expected_json_dict: Dict[str, any] = { + self._expected_account_id_attribute_name: '' + } + mock_json_utils.RESOURCE_MAPPING_ACCOUNTID_JSON_KEY_NAME = self._expected_account_id_attribute_name + mock_json_utils.validate_resources_according_to_json_schema.return_value = [] + mock_json_utils.convert_resources_to_json_dict.return_value = expected_json_dict + mock_file_utils.join_path.return_value = TestViewEditController._expected_config_file_full_path + mocked_call_args: call = self._mocked_view_edit_page.save_changes_button.clicked.connect.call_args[0] + + mocked_call_args[0]() # triggering save_changes_button connected function + assert expected_json_dict[mock_json_utils.RESOURCE_MAPPING_ACCOUNTID_JSON_KEY_NAME] == '' mock_json_utils.convert_resources_to_json_dict.assert_called_once() mock_json_utils.write_into_json_file.assert_called_once_with( TestViewEditController._expected_config_file_full_path, expected_json_dict) diff --git a/Gems/AWSCore/Code/Tools/ResourceMappingTool/tests/unit/utils/test_json_utils.py b/Gems/AWSCore/Code/Tools/ResourceMappingTool/tests/unit/utils/test_json_utils.py index 7eb92ff222..9fe99461ba 100755 --- a/Gems/AWSCore/Code/Tools/ResourceMappingTool/tests/unit/utils/test_json_utils.py +++ b/Gems/AWSCore/Code/Tools/ResourceMappingTool/tests/unit/utils/test_json_utils.py @@ -103,6 +103,11 @@ class TestJsonUtils(TestCase): invalid_json_dict.pop(json_utils.RESOURCE_MAPPING_ACCOUNTID_JSON_KEY_NAME) self.assertRaises(KeyError, json_utils.validate_json_dict_according_to_json_schema, invalid_json_dict) + def test_validate_json_dict_according_to_json_schema_raise_error_when_json_dict_has_empty_accountid(self) -> None: + valid_json_dict: Dict[str, any] = copy.deepcopy(TestJsonUtils._expected_json_dict) + valid_json_dict[json_utils.RESOURCE_MAPPING_ACCOUNTID_JSON_KEY_NAME] = '' + json_utils.validate_json_dict_according_to_json_schema(valid_json_dict) + def test_validate_json_dict_according_to_json_schema_pass_when_json_dict_has_template_accountid(self) -> None: valid_json_dict: Dict[str, any] = copy.deepcopy(TestJsonUtils._expected_json_dict) valid_json_dict[json_utils.RESOURCE_MAPPING_ACCOUNTID_JSON_KEY_NAME] = \ diff --git a/Gems/AWSCore/Code/Tools/ResourceMappingTool/utils/json_utils.py b/Gems/AWSCore/Code/Tools/ResourceMappingTool/utils/json_utils.py index 5b9377ada3..7926cca970 100755 --- a/Gems/AWSCore/Code/Tools/ResourceMappingTool/utils/json_utils.py +++ b/Gems/AWSCore/Code/Tools/ResourceMappingTool/utils/json_utils.py @@ -28,7 +28,7 @@ _RESOURCE_MAPPING_JSON_FORMAT_VERSION: str = "1.0.0" RESOURCE_MAPPING_ACCOUNTID_JSON_KEY_NAME: str = "AccountId" RESOURCE_MAPPING_ACCOUNTID_TEMPLATE_VALUE: str = "EMPTY" -_RESOURCE_MAPPING_ACCOUNTID_PATTERN: str = f"^[0-9]{{12}}|{RESOURCE_MAPPING_ACCOUNTID_TEMPLATE_VALUE}$" +_RESOURCE_MAPPING_ACCOUNTID_PATTERN: str = f"^[0-9]{{12}}$|{RESOURCE_MAPPING_ACCOUNTID_TEMPLATE_VALUE}|^$" _RESOURCE_MAPPING_REGION_PATTERN: str = "^[a-z]{2}-[a-z]{4,9}-[0-9]{1}$" _RESOURCE_MAPPING_VERSION_PATTERN: str = "^[0-9]{1}.[0-9]{1}.[0-9]{1}$" diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslShaderBuilderSystemComponent.cpp b/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslShaderBuilderSystemComponent.cpp index e41c04a0be..99b7c814d1 100644 --- a/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslShaderBuilderSystemComponent.cpp +++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslShaderBuilderSystemComponent.cpp @@ -82,7 +82,7 @@ namespace AZ // Register Shader Asset Builder AssetBuilderSDK::AssetBuilderDesc shaderAssetBuilderDescriptor; shaderAssetBuilderDescriptor.m_name = "Shader Asset Builder"; - shaderAssetBuilderDescriptor.m_version = 107; // Required .azsl extension in .shader file references + shaderAssetBuilderDescriptor.m_version = 108; // The Build Time Stamp of ShaderAsset And ShaderVariantAsset Should Be Based On GetTimeUTCMilliSecond() // .shader file changes trigger rebuilds shaderAssetBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern( AZStd::string::format("*.%s", RPI::ShaderSourceData::Extension), AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard)); shaderAssetBuilderDescriptor.m_busId = azrtti_typeid(); @@ -108,7 +108,7 @@ namespace AZ shaderVariantAssetBuilderDescriptor.m_name = "Shader Variant Asset Builder"; // Both "Shader Variant Asset Builder" and "Shader Asset Builder" produce ShaderVariantAsset products. If you update // ShaderVariantAsset you will need to update BOTH version numbers, not just "Shader Variant Asset Builder". - shaderVariantAssetBuilderDescriptor.m_version = 26; // [AZSL] Changing inlineConstant to rootConstant keyword work. + shaderVariantAssetBuilderDescriptor.m_version = 27; // The Build Time Stamp of ShaderAsset And ShaderVariantAsset Should Be Based On GetTimeUTCMilliSecond(). shaderVariantAssetBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern(AZStd::string::format("*.%s", RPI::ShaderVariantListSourceData::Extension), AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard)); shaderVariantAssetBuilderDescriptor.m_busId = azrtti_typeid(); shaderVariantAssetBuilderDescriptor.m_createJobFunction = AZStd::bind(&ShaderVariantAssetBuilder::CreateJobs, &m_shaderVariantAssetBuilder, AZStd::placeholders::_1, AZStd::placeholders::_2); diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderAssetBuilder.cpp b/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderAssetBuilder.cpp index 89e202a4bc..e431b74282 100644 --- a/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderAssetBuilder.cpp +++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderAssetBuilder.cpp @@ -162,7 +162,7 @@ namespace AZ // has the same value, because later the ShaderVariantTreeAsset job will fetch this value from the local ShaderAsset // which could cross platforms (i.e. building an android ShaderVariantTreeAsset on PC would fetch the tiemstamp from // the PC's ShaderAsset). - AZStd::sys_time_t shaderAssetBuildTimestamp = AZStd::GetTimeNowMicroSecond(); + AZ::u64 shaderAssetBuildTimestamp = AZStd::GetTimeUTCMilliSecond(); // Need to get the name of the azsl file from the .shader source asset, to be able to declare a dependency to SRG Layout Job. // and the macro options to preprocess. @@ -229,8 +229,8 @@ namespace AZ } // for all request.m_enabledPlatforms AZ_TracePrintf( - ShaderAssetBuilderName, "CreateJobs for %s took %llu microseconds", shaderAssetSourceFileFullPath.c_str(), - AZStd::GetTimeNowMicroSecond() - shaderAssetBuildTimestamp); + ShaderAssetBuilderName, "CreateJobs for %s took %llu milliseconds", shaderAssetSourceFileFullPath.c_str(), + AZStd::GetTimeUTCMilliSecond() - shaderAssetBuildTimestamp); response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success; } @@ -355,8 +355,8 @@ namespace AZ return; } - // Get the time stamp string as sys_time_t, and also convert back to string to make sure it was converted correctly. - AZStd::sys_time_t shaderAssetBuildTimestamp = 0; + // Get the time stamp string as u64, and also convert back to string to make sure it was converted correctly. + AZ::u64 shaderAssetBuildTimestamp = 0; auto shaderAssetBuildTimestampIterator = request.m_jobDescription.m_jobParameters.find(ShaderAssetBuildTimestampParam); if (shaderAssetBuildTimestampIterator != request.m_jobDescription.m_jobParameters.end()) { diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderVariantAssetBuilder.cpp b/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderVariantAssetBuilder.cpp index bb40baca7d..5eaa0d9ddb 100644 --- a/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderVariantAssetBuilder.cpp +++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderVariantAssetBuilder.cpp @@ -765,7 +765,7 @@ namespace AZ return; } - const AZStd::sys_time_t shaderVariantAssetBuildTimestamp = AZStd::GetTimeNowMicroSecond(); + const AZ::u64 shaderVariantAssetBuildTimestamp = AZStd::GetTimeUTCMilliSecond(); auto supervariantList = ShaderBuilderUtility::GetSupervariantListFromShaderSourceData(shaderSourceDescriptor); diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderVariantAssetBuilder.h b/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderVariantAssetBuilder.h index 2eaf1d9d8b..b0457656af 100644 --- a/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderVariantAssetBuilder.h +++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderVariantAssetBuilder.h @@ -38,7 +38,7 @@ namespace AZ const AZStd::string& m_tempDirPath; //! Used to synchronize versions of the ShaderAsset and ShaderVariantAsset, //! especially during hot-reload. A (ShaderVariantAsset.timestamp) >= (ShaderAsset.timestamp). - const AZStd::sys_time_t m_assetBuildTimestamp; + const AZ::u64 m_assetBuildTimestamp; const RPI::ShaderSourceData& m_shaderSourceDataDescriptor; const RPI::ShaderOptionGroupLayout& m_shaderOptionGroupLayout; const MapOfStringToStageType& m_shaderEntryPoints; diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Shadow/DirectionalLightShadow.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Shadow/DirectionalLightShadow.azsli index a7122aaf3a..59817af701 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Shadow/DirectionalLightShadow.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/Shadow/DirectionalLightShadow.azsli @@ -166,7 +166,7 @@ float DirectionalLightShadow::GetThickness(uint lightIndex, float3 shadowCoords[ bool2 DirectionalLightShadow::IsShadowed(float3 shadowCoord, uint indexOfCascade) { static const float PixelMargin = 1.5; // avoiding artifact between cascade levels. - static const float DepthMargin = 0.01; // avoiding artifact when near depth bounds. + static const float DepthMargin = 1e-8; // avoiding artifact when near depth bounds. // size is the shadowap's width and height. const uint size = ViewSrg::m_directionalLightShadows[m_lightIndex].m_shadowmapSize; @@ -210,8 +210,8 @@ float DirectionalLightShadow::GetVisibilityFromLightNoFilter() float DirectionalLightShadow::GetVisibilityFromLightPcf() { - static const float DepthMargin = 0.01; // avoiding artifact when near depth bounds. static const float PixelMargin = 1.5; // avoiding artifact between cascade levels. + static const float DepthMargin = 1e-8; // avoiding artifact when near depth bounds. const uint size = ViewSrg::m_directionalLightShadows[m_lightIndex].m_shadowmapSize; const uint cascadeCount = ViewSrg::m_directionalLightShadows[m_lightIndex].m_cascadeCount; diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridblenddistance.azshader b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridblenddistance.azshader index 31fae5d98b..a9b5567f6b 100644 Binary files a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridblenddistance.azshader and b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridblenddistance.azshader differ diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridblenddistance_dx12_0.azshadervariant b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridblenddistance_dx12_0.azshadervariant index 19e9fdfc8d..6d4b6d0fca 100644 Binary files a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridblenddistance_dx12_0.azshadervariant and b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridblenddistance_dx12_0.azshadervariant differ diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridblenddistance_null_0.azshadervariant b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridblenddistance_null_0.azshadervariant index 43c4a615cf..5d8800a6fe 100644 Binary files a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridblenddistance_null_0.azshadervariant and b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridblenddistance_null_0.azshadervariant differ diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridblenddistance_vulkan_0.azshadervariant b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridblenddistance_vulkan_0.azshadervariant index 75f070a03e..930f7898c4 100644 Binary files a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridblenddistance_vulkan_0.azshadervariant and b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridblenddistance_vulkan_0.azshadervariant differ diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridblendirradiance.azshader b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridblendirradiance.azshader index 0025388bc1..268020b431 100644 Binary files a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridblendirradiance.azshader and b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridblendirradiance.azshader differ diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridblendirradiance_dx12_0.azshadervariant b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridblendirradiance_dx12_0.azshadervariant index 34a9b3659f..d95ec6c1b9 100644 Binary files a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridblendirradiance_dx12_0.azshadervariant and b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridblendirradiance_dx12_0.azshadervariant differ diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridblendirradiance_null_0.azshadervariant b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridblendirradiance_null_0.azshadervariant index 4a2b0e9944..3253ddba37 100644 Binary files a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridblendirradiance_null_0.azshadervariant and b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridblendirradiance_null_0.azshadervariant differ diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridblendirradiance_vulkan_0.azshadervariant b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridblendirradiance_vulkan_0.azshadervariant index c053a7db19..c38b94c4f1 100644 Binary files a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridblendirradiance_vulkan_0.azshadervariant and b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridblendirradiance_vulkan_0.azshadervariant differ diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridborderupdatecolumn.azshader b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridborderupdatecolumn.azshader index c507b12563..5c46d368ce 100644 Binary files a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridborderupdatecolumn.azshader and b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridborderupdatecolumn.azshader differ diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridborderupdatecolumn_dx12_0.azshadervariant b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridborderupdatecolumn_dx12_0.azshadervariant index f60c713597..eb6938f8c9 100644 Binary files a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridborderupdatecolumn_dx12_0.azshadervariant and b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridborderupdatecolumn_dx12_0.azshadervariant differ diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridborderupdatecolumn_null_0.azshadervariant b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridborderupdatecolumn_null_0.azshadervariant index 3e810bcfb5..8c83c1e3ff 100644 Binary files a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridborderupdatecolumn_null_0.azshadervariant and b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridborderupdatecolumn_null_0.azshadervariant differ diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridborderupdatecolumn_vulkan_0.azshadervariant b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridborderupdatecolumn_vulkan_0.azshadervariant index 5918f277b5..2fbb9ffffe 100644 Binary files a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridborderupdatecolumn_vulkan_0.azshadervariant and b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridborderupdatecolumn_vulkan_0.azshadervariant differ diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridborderupdaterow.azshader b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridborderupdaterow.azshader index 120eb70e54..96053f4091 100644 Binary files a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridborderupdaterow.azshader and b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridborderupdaterow.azshader differ diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridborderupdaterow_dx12_0.azshadervariant b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridborderupdaterow_dx12_0.azshadervariant index d38d779696..4bdbdddf33 100644 Binary files a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridborderupdaterow_dx12_0.azshadervariant and b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridborderupdaterow_dx12_0.azshadervariant differ diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridborderupdaterow_null_0.azshadervariant b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridborderupdaterow_null_0.azshadervariant index 6d7a604701..77668a2450 100644 Binary files a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridborderupdaterow_null_0.azshadervariant and b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridborderupdaterow_null_0.azshadervariant differ diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridborderupdaterow_vulkan_0.azshadervariant b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridborderupdaterow_vulkan_0.azshadervariant index dee941cfae..fb3dd771ca 100644 Binary files a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridborderupdaterow_vulkan_0.azshadervariant and b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridborderupdaterow_vulkan_0.azshadervariant differ diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridclassification.azshader b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridclassification.azshader index e2e0fa90f5..eda8d53376 100644 Binary files a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridclassification.azshader and b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridclassification.azshader differ diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridclassification_dx12_0.azshadervariant b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridclassification_dx12_0.azshadervariant index 7a92fc2de5..2df610df77 100644 Binary files a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridclassification_dx12_0.azshadervariant and b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridclassification_dx12_0.azshadervariant differ diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridclassification_null_0.azshadervariant b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridclassification_null_0.azshadervariant index 43408b26f3..f2458e692b 100644 Binary files a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridclassification_null_0.azshadervariant and b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridclassification_null_0.azshadervariant differ diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridclassification_vulkan_0.azshadervariant b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridclassification_vulkan_0.azshadervariant index 877085446d..0e08e84f74 100644 Binary files a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridclassification_vulkan_0.azshadervariant and b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridclassification_vulkan_0.azshadervariant differ diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracing.azshader b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracing.azshader index 2c403c77f8..1cea4860a1 100644 Binary files a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracing.azshader and b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracing.azshader differ diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracing_dx12_0.azshadervariant b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracing_dx12_0.azshadervariant index 4bcc47ee43..5605a47e9c 100644 Binary files a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracing_dx12_0.azshadervariant and b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracing_dx12_0.azshadervariant differ diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracing_null_0.azshadervariant b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracing_null_0.azshadervariant index f173416210..15f3477cb5 100644 Binary files a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracing_null_0.azshadervariant and b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracing_null_0.azshadervariant differ diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracing_vulkan_0.azshadervariant b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracing_vulkan_0.azshadervariant index 0eb04a25b8..c2bcd4ab07 100644 Binary files a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracing_vulkan_0.azshadervariant and b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracing_vulkan_0.azshadervariant differ diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingclosesthit.azshader b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingclosesthit.azshader index 2847d0035a..9958708a64 100644 Binary files a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingclosesthit.azshader and b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingclosesthit.azshader differ diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingclosesthit_dx12_0.azshadervariant b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingclosesthit_dx12_0.azshadervariant index 93cfb47819..fbd1d90251 100644 Binary files a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingclosesthit_dx12_0.azshadervariant and b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingclosesthit_dx12_0.azshadervariant differ diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingclosesthit_null_0.azshadervariant b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingclosesthit_null_0.azshadervariant index 4a16e24211..483be0eceb 100644 Binary files a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingclosesthit_null_0.azshadervariant and b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingclosesthit_null_0.azshadervariant differ diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingclosesthit_vulkan_0.azshadervariant b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingclosesthit_vulkan_0.azshadervariant index df52c9c8d2..530eef2f10 100644 Binary files a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingclosesthit_vulkan_0.azshadervariant and b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingclosesthit_vulkan_0.azshadervariant differ diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingmiss.azshader b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingmiss.azshader index d95fd5b3b2..eddac4e2bd 100644 Binary files a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingmiss.azshader and b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingmiss.azshader differ diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingmiss_dx12_0.azshadervariant b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingmiss_dx12_0.azshadervariant index c853de4d14..ad06bbd104 100644 Binary files a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingmiss_dx12_0.azshadervariant and b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingmiss_dx12_0.azshadervariant differ diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingmiss_null_0.azshadervariant b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingmiss_null_0.azshadervariant index 40e18c215c..527b42569a 100644 Binary files a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingmiss_null_0.azshadervariant and b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingmiss_null_0.azshadervariant differ diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingmiss_vulkan_0.azshadervariant b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingmiss_vulkan_0.azshadervariant index 34761ccf98..426d955938 100644 Binary files a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingmiss_vulkan_0.azshadervariant and b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingmiss_vulkan_0.azshadervariant differ diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridrelocation.azshader b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridrelocation.azshader index c19020dc84..08ed61ab03 100644 Binary files a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridrelocation.azshader and b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridrelocation.azshader differ diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridrelocation_dx12_0.azshadervariant b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridrelocation_dx12_0.azshadervariant index a7d44b5541..d1012a896f 100644 Binary files a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridrelocation_dx12_0.azshadervariant and b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridrelocation_dx12_0.azshadervariant differ diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridrelocation_null_0.azshadervariant b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridrelocation_null_0.azshadervariant index 82e0065216..cb730be1df 100644 Binary files a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridrelocation_null_0.azshadervariant and b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridrelocation_null_0.azshadervariant differ diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridrelocation_vulkan_0.azshadervariant b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridrelocation_vulkan_0.azshadervariant index 20b81aee6c..b58af4c473 100644 Binary files a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridrelocation_vulkan_0.azshadervariant and b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridrelocation_vulkan_0.azshadervariant differ diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridrender.azshader b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridrender.azshader index aec2786540..56009d56ab 100644 Binary files a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridrender.azshader and b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridrender.azshader differ diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridrender_dx12_0.azshadervariant b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridrender_dx12_0.azshadervariant index de851f187e..e9893c96da 100644 Binary files a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridrender_dx12_0.azshadervariant and b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridrender_dx12_0.azshadervariant differ diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridrender_null_0.azshadervariant b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridrender_null_0.azshadervariant index 5d8207dfdb..d195b08c34 100644 Binary files a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridrender_null_0.azshadervariant and b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridrender_null_0.azshadervariant differ diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridrender_vulkan_0.azshadervariant b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridrender_vulkan_0.azshadervariant index c819e57c4c..47789d9b9b 100644 Binary files a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridrender_vulkan_0.azshadervariant and b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridrender_vulkan_0.azshadervariant differ diff --git a/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.cpp index 410c80dbdc..ce5d3cc363 100644 --- a/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.cpp @@ -1248,6 +1248,32 @@ namespace AZ property.m_shadowmapViewNeedsUpdate = true; } + float DirectionalLightFeatureProcessor::GetShadowmapSizeFromCameraView(const LightHandle handle, const RPI::View* cameraView) const + { + const DirectionalLightShadowData& shadowData = m_shadowData.at(cameraView).GetData(handle.GetIndex()); + return static_cast(shadowData.m_shadowmapSize); + } + + void DirectionalLightFeatureProcessor::SnapAabbToPixelIncrements(const float invShadowmapSize, Vector3& orthoMin, Vector3& orthoMax) + { + // This function stops the cascaded shadowmap from shimmering as the camera moves. + // See CascadedShadowsManager.cpp in the Microsoft CascadedShadowMaps11 sample for details. + + const Vector3 normalizeByBufferSize = Vector3(invShadowmapSize, invShadowmapSize, invShadowmapSize); + + const Vector3 worldUnitsPerTexel = (orthoMax - orthoMin) * normalizeByBufferSize; + + // We snap the camera to 1 pixel increments so that moving the camera does not cause the shadows to jitter. + // This is a matter of dividing by the world space size of a texel + orthoMin /= worldUnitsPerTexel; + orthoMin = orthoMin.GetFloor(); + orthoMin *= worldUnitsPerTexel; + + orthoMax /= worldUnitsPerTexel; + orthoMax = orthoMax.GetFloor(); + orthoMax *= worldUnitsPerTexel; + } + void DirectionalLightFeatureProcessor::UpdateShadowmapViews(LightHandle handle) { ShadowProperty& property = m_shadowProperties.GetData(handle.GetIndex()); @@ -1259,18 +1285,26 @@ namespace AZ for (auto& segmentIt : property.m_segments) { + const float invShadowmapSize = 1.0f / GetShadowmapSizeFromCameraView(handle, segmentIt.first); + for (uint16_t cascadeIndex = 0; cascadeIndex < segmentIt.second.size(); ++cascadeIndex) { - const Aabb viewAabb = CalculateShadowViewAabb( - handle, segmentIt.first, cascadeIndex, lightTransform); + const Aabb viewAabb = CalculateShadowViewAabb(handle, segmentIt.first, cascadeIndex, lightTransform); if (viewAabb.IsValid() && viewAabb.IsFinite()) { + const float cascadeNear = viewAabb.GetMin().GetY(); + const float cascadeFar = viewAabb.GetMax().GetY(); + + Vector3 snappedAabbMin = viewAabb.GetMin(); + Vector3 snappedAabbMax = viewAabb.GetMax(); + + SnapAabbToPixelIncrements(invShadowmapSize, snappedAabbMin, snappedAabbMax); + Matrix4x4 viewToClipMatrix = Matrix4x4::CreateIdentity(); - MakeOrthographicMatrixRH(viewToClipMatrix, - viewAabb.GetMin().GetElement(0), viewAabb.GetMax().GetElement(0), - viewAabb.GetMin().GetElement(2), viewAabb.GetMax().GetElement(2), - viewAabb.GetMin().GetElement(1), viewAabb.GetMax().GetElement(1)); + MakeOrthographicMatrixRH( + viewToClipMatrix, snappedAabbMin.GetElement(0), snappedAabbMax.GetElement(0), snappedAabbMin.GetElement(2), + snappedAabbMax.GetElement(2), cascadeNear, cascadeFar); CascadeSegment& segment = segmentIt.second[cascadeIndex]; segment.m_aabb = viewAabb; diff --git a/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.h b/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.h index 8d7a9d76e4..77f2db38d6 100644 --- a/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.h +++ b/Gems/Atom/Feature/Common/Code/Source/CoreLights/DirectionalLightFeatureProcessor.h @@ -341,6 +341,9 @@ namespace AZ //! This draws bounding boxes of cascades. void DrawCascadeBoundingBoxes(LightHandle handle); + float GetShadowmapSizeFromCameraView(const LightHandle handle, const RPI::View* cameraView) const; + void SnapAabbToPixelIncrements(const float invShadowmapSize, Vector3& orthoMin, Vector3& orthoMax); + IndexedDataVector m_shadowProperties; // [GFX TODO][ATOM-2012] shadow for multiple directional lights LightHandle m_shadowingLightHandle; diff --git a/Gems/Atom/Feature/Common/Code/Source/FrameCaptureSystemComponent.cpp b/Gems/Atom/Feature/Common/Code/Source/FrameCaptureSystemComponent.cpp index 8b016410aa..5fc4fed420 100644 --- a/Gems/Atom/Feature/Common/Code/Source/FrameCaptureSystemComponent.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/FrameCaptureSystemComponent.cpp @@ -142,7 +142,12 @@ namespace AZ Utils::PngFile image = Utils::PngFile::Create(readbackResult.m_imageDescriptor.m_size, format, *buffer); Utils::PngFile::SaveSettings saveSettings; - saveSettings.m_compressionLevel = r_pngCompressionLevel; + + if (auto console = AZ::Interface::Get(); console != nullptr) + { + console->GetCvarValue("r_pngCompressionLevel", saveSettings.m_compressionLevel); + } + // We should probably strip alpha to save space, especially for automated test screenshots. Alpha is left in to maintain // prior behavior, changing this is out of scope for the current task. Note, it would have bit of a cascade effect where // AtomSampleViewer's ScriptReporter assumes an RGBA image. diff --git a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshOutputStreamManager.cpp b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshOutputStreamManager.cpp index 8bf518e277..87fb46a7c6 100644 --- a/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshOutputStreamManager.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/SkinnedMesh/SkinnedMeshOutputStreamManager.cpp @@ -98,7 +98,12 @@ namespace AZ } m_needsInit = false; - const AZ::u64 sizeInMb = r_skinnedMeshInstanceMemoryPoolSize; + AZ::u64 sizeInMb{}; + if (auto console = AZ::Interface::Get(); console != nullptr) + { + console->GetCvarValue("r_skinnedMeshInstanceMemoryPoolSize", sizeInMb); + } + m_sizeInBytes = sizeInMb * (1024u * 1024u); CalculateAlignment(); diff --git a/Gems/Atom/RHI/DX12/Code/Source/RHI/AsyncUploadQueue.cpp b/Gems/Atom/RHI/DX12/Code/Source/RHI/AsyncUploadQueue.cpp index 5f85d3ad03..fc80cc50d6 100644 --- a/Gems/Atom/RHI/DX12/Code/Source/RHI/AsyncUploadQueue.cpp +++ b/Gems/Atom/RHI/DX12/Code/Source/RHI/AsyncUploadQueue.cpp @@ -33,12 +33,20 @@ namespace AZ ID3D12DeviceX* dx12Device = device.GetDevice(); m_copyQueue = CommandQueue::Create(); - + + // The async upload queue should always use the primary copy queue, + // but because this change is being made in the stabilization branch + // we will put it behind a define out of an abundance of caution, and + // change it to always do this once the change gets back to development. + #if defined(AZ_DX12_USE_PRIMARY_COPY_QUEUE_FOR_ASYNC_UPLOAD_QUEUE) + m_copyQueue = &device.GetCommandQueueContext().GetCommandQueue(RHI::HardwareQueueClass::Copy); + #else // Make a secondary Copy queue, the primary queue is owned by the CommandQueueContext CommandQueueDescriptor commandQueueDesc; commandQueueDesc.m_hardwareQueueClass = RHI::HardwareQueueClass::Copy; commandQueueDesc.m_hardwareQueueSubclass = HardwareQueueSubclass::Secondary; m_copyQueue->Init(device, commandQueueDesc); + #endif // defined(AZ_DX12_ASYNC_UPLOAD_QUEUE_USE_PRIMARY_COPY_QUEUE) m_uploadFence.Init(dx12Device, RHI::FenceState::Signaled); for (size_t i = 0; i < descriptor.m_frameCount; ++i) diff --git a/Gems/Atom/RPI/Assets/Textures/Defaults/DefaultFallback.png b/Gems/Atom/RPI/Assets/Textures/Defaults/DefaultFallback.png new file mode 100644 index 0000000000..1352d14edf --- /dev/null +++ b/Gems/Atom/RPI/Assets/Textures/Defaults/DefaultFallback.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fb91c050a829ff03b972202cf8c90034e4f252d972332224791d135c07d9d528 +size 796 diff --git a/Gems/Atom/RPI/Assets/Textures/Defaults/Missing.png b/Gems/Atom/RPI/Assets/Textures/Defaults/Missing.png new file mode 100644 index 0000000000..3e9bc68ea5 --- /dev/null +++ b/Gems/Atom/RPI/Assets/Textures/Defaults/Missing.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:28c3cfd8958813b4b539738bfff589731da0aeec5b3376558f377da2ebe973ff +size 5455 diff --git a/Gems/Atom/RPI/Assets/Textures/Defaults/Processing.png b/Gems/Atom/RPI/Assets/Textures/Defaults/Processing.png new file mode 100644 index 0000000000..914499d6e7 --- /dev/null +++ b/Gems/Atom/RPI/Assets/Textures/Defaults/Processing.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8a0935be7347d695ed1716d030b5bae68153a88239b0ff15f67f900ac90be442 +size 5038 diff --git a/Gems/Atom/RPI/Assets/Textures/Defaults/ProcessingFailed.png b/Gems/Atom/RPI/Assets/Textures/Defaults/ProcessingFailed.png new file mode 100644 index 0000000000..558b16b96e --- /dev/null +++ b/Gems/Atom/RPI/Assets/Textures/Defaults/ProcessingFailed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bb243cd6d6414b4e95eab919fa94193b57825902b8d09f40ce3f334d829e74e2 +size 6286 diff --git a/Gems/Atom/RPI/Assets/seedList.seed b/Gems/Atom/RPI/Assets/seedList.seed index 300092e6c3..622638d698 100644 --- a/Gems/Atom/RPI/Assets/seedList.seed +++ b/Gems/Atom/RPI/Assets/seedList.seed @@ -24,6 +24,22 @@ + + + + + + + + + + + + + + + + diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Shader/ShaderVariantAssetCreator.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Shader/ShaderVariantAssetCreator.h index fd069f21e0..488bfb09f8 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Shader/ShaderVariantAssetCreator.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Shader/ShaderVariantAssetCreator.h @@ -40,7 +40,7 @@ namespace AZ //! Set the timestamp value when the ProcessJob() started. //! This is needed to synchronize between the ShaderAsset and ShaderVariantAsset when hot-reloading shaders. //! The idea is that this timestamp must be greater or equal than the ShaderAsset. - void SetBuildTimestamp(AZStd::sys_time_t buildTimestamp); + void SetBuildTimestamp(AZ::u64 buildTimestamp); //! Assigns a shaderStageFunction, which contains the byte code, to the slot dictated by the shader stage. void SetShaderFunction(RHI::ShaderStage shaderStage, RHI::Ptr shaderStageFunction); diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Image/StreamingImageAssetHandler.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Image/StreamingImageAssetHandler.h index 3f4e15744a..b3b62cbc31 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Image/StreamingImageAssetHandler.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Image/StreamingImageAssetHandler.h @@ -25,6 +25,9 @@ namespace AZ const Data::Asset& asset, AZStd::shared_ptr stream, const Data::AssetFilterCB& assetLoadFilterCB) override; + + // Return a default fallback image if an asset is missing + Data::AssetId AssetMissingInCatalog(const Data::Asset& /*asset*/) override; }; } } diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderAsset.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderAsset.h index 711ebb61a7..70451bd055 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderAsset.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderAsset.h @@ -294,7 +294,7 @@ namespace AZ Name m_drawListName; //! Use to synchronize versions of the ShaderAsset and ShaderVariantTreeAsset, especially during hot-reload. - AZStd::sys_time_t m_buildTimestamp = 0; + AZ::u64 m_buildTimestamp = 0; /////////////////////////////////////////////////////////////////// diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderVariantAsset.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderVariantAsset.h index 66bbd7b188..5146ae370a 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderVariantAsset.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Shader/ShaderVariantAsset.h @@ -61,7 +61,7 @@ namespace AZ //! Return the timestamp when this asset was built, and it must be >= than the timestamp of the main ShaderAsset. //! This is used to synchronize versions of the ShaderAsset and ShaderVariantAsset, especially during hot-reload. - AZStd::sys_time_t GetBuildTimestamp() const; + AZ::u64 GetBuildTimestamp() const; bool IsRootVariant() const { return m_stableId == RPI::RootShaderVariantStableId; } @@ -80,7 +80,7 @@ namespace AZ AZStd::array, RHI::ShaderStageCount> m_functionsByStage; //! Used to synchronize versions of the ShaderAsset and ShaderVariantAsset, especially during hot-reload. - AZStd::sys_time_t m_buildTimestamp = 0; + AZ::u64 m_buildTimestamp = 0; }; class ShaderVariantAssetHandler final diff --git a/Gems/Atom/RPI/Code/Source/RPI.Edit/Shader/ShaderVariantAssetCreator.cpp b/Gems/Atom/RPI/Code/Source/RPI.Edit/Shader/ShaderVariantAssetCreator.cpp index e0e83cfce2..5b32edc0bf 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Edit/Shader/ShaderVariantAssetCreator.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Edit/Shader/ShaderVariantAssetCreator.cpp @@ -92,7 +92,7 @@ namespace AZ ///////////////////////////////////////////////////////////////////// // Methods for all shader variant types - void ShaderVariantAssetCreator::SetBuildTimestamp(AZStd::sys_time_t buildTimestamp) + void ShaderVariantAssetCreator::SetBuildTimestamp(AZ::u64 buildTimestamp) { if (ValidateIsReady()) { diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/Shader.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/Shader.cpp index 337eaa5455..b1ae460af6 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/Shader.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Shader/Shader.cpp @@ -202,17 +202,17 @@ namespace AZ return; } AZ_Assert(m_asset->m_buildTimestamp == m_reloadedRootShaderVariantAsset->GetBuildTimestamp(), - "shaderAsset timeStamp=%lld, but Root ShaderVariantAsset timeStamp=%lld", + "shaderAsset '%s' timeStamp=%lld, but Root ShaderVariantAsset timeStamp=%lld", m_asset.GetHint().c_str(), m_asset->m_buildTimestamp, m_reloadedRootShaderVariantAsset->GetBuildTimestamp()); m_asset->UpdateRootShaderVariantAsset(m_supervariantIndex, m_reloadedRootShaderVariantAsset); m_reloadedRootShaderVariantAsset = {}; // Clear the temporary reference. if (ShaderReloadDebugTracker::IsEnabled()) { - auto makeTimeString = [](AZStd::sys_time_t timestamp, AZStd::sys_time_t now) + auto makeTimeString = [](AZ::u64 timestamp, AZ::u64 now) { - AZStd::sys_time_t elapsedMicroseconds = now - timestamp; - double elapsedSeconds = aznumeric_cast(elapsedMicroseconds / 1'000'000); + AZ::u64 elapsedMillis = now - timestamp; + double elapsedSeconds = aznumeric_cast(elapsedMillis / 1'000); AZStd::string timeString = AZStd::string::format("%lld (%f seconds ago)", timestamp, elapsedSeconds); return timeString; }; diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Image/StreamingImageAssetHandler.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Image/StreamingImageAssetHandler.cpp index fa39820329..fd04635da3 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Image/StreamingImageAssetHandler.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Image/StreamingImageAssetHandler.cpp @@ -7,6 +7,8 @@ */ #include +#include +#include namespace AZ { @@ -40,5 +42,56 @@ namespace AZ return loadResult; } - } -} + + Data::AssetId StreamingImageAssetHandler::AssetMissingInCatalog(const Data::Asset& asset) + { + // Find out if the asset is missing completely, or just still processing + // and escalate the asset to the top of the list + AzFramework::AssetSystem::AssetStatus missingAssetStatus; + AzFramework::AssetSystemRequestBus::BroadcastResult( + missingAssetStatus, &AzFramework::AssetSystem::AssetSystemRequests::GetAssetStatusById, asset.GetId().m_guid); + + // Determine which fallback image to use + const char* relativePath = "textures/defaults/defaultfallback.png.streamingimage"; + + bool useDebugFallbackImages = true; + if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr) + { + settingsRegistry->GetObject(useDebugFallbackImages, "/O3DE/Atom/RPI/UseDebugFallbackImages"); + } + + if (useDebugFallbackImages) + { + switch (missingAssetStatus) + { + case AzFramework::AssetSystem::AssetStatus::AssetStatus_Queued: + case AzFramework::AssetSystem::AssetStatus::AssetStatus_Compiling: + relativePath = "textures/defaults/processing.png.streamingimage"; + break; + case AzFramework::AssetSystem::AssetStatus::AssetStatus_Failed: + relativePath = "textures/defaults/processingfailed.png.streamingimage"; + break; + case AzFramework::AssetSystem::AssetStatus::AssetStatus_Missing: + case AzFramework::AssetSystem::AssetStatus::AssetStatus_Unknown: + case AzFramework::AssetSystem::AssetStatus::AssetStatus_Compiled: + relativePath = "textures/defaults/missing.png.streamingimage"; + break; + } + } + + // Make sure the fallback image has been processed + AzFramework::AssetSystem::AssetStatus status = AzFramework::AssetSystem::AssetStatus_Unknown; + AzFramework::AssetSystemRequestBus::BroadcastResult( + status, &AzFramework::AssetSystemRequestBus::Events::CompileAssetSync, relativePath); + + // Return the asset id of the fallback image + Data::AssetId assetId{}; + bool autoRegisterIfNotFound = false; + Data::AssetCatalogRequestBus::BroadcastResult( + assetId, &Data::AssetCatalogRequestBus::Events::GetAssetIdByPath, relativePath, + azrtti_typeid(), autoRegisterIfNotFound); + + return assetId; + } + } // namespace RPI +} // namespace AZ diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderVariantAsset.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderVariantAsset.cpp index 442fc6a79f..ec1a65a6d5 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderVariantAsset.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderVariantAsset.cpp @@ -60,7 +60,7 @@ namespace AZ } } - AZStd::sys_time_t ShaderVariantAsset::GetBuildTimestamp() const + AZ::u64 ShaderVariantAsset::GetBuildTimestamp() const { return m_buildTimestamp; } diff --git a/Gems/Atom/RPI/Registry/atom_rpi.release.setreg b/Gems/Atom/RPI/Registry/atom_rpi.release.setreg new file mode 100644 index 0000000000..72fb4f01e8 --- /dev/null +++ b/Gems/Atom/RPI/Registry/atom_rpi.release.setreg @@ -0,0 +1,9 @@ +{ + "O3DE": { + "Atom": { + "RPI": { + "UseDebugFallbackImages": false + } + } + } +} diff --git a/Gems/Atom/RPI/Registry/atom_rpi.setreg b/Gems/Atom/RPI/Registry/atom_rpi.setreg index 16ce8b3bd7..fa3c13a81e 100644 --- a/Gems/Atom/RPI/Registry/atom_rpi.setreg +++ b/Gems/Atom/RPI/Registry/atom_rpi.setreg @@ -17,7 +17,8 @@ "DynamicDrawSystemDescriptor": { "DynamicBufferPoolSize": 50331648 // 3 * 16 * 1024 * 1024 (for 3 frames) } - } + }, + "UseDebugFallbackImages": true } } } diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Window/AtomToolsMainWindow.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Window/AtomToolsMainWindow.cpp index aeca51230a..f07fd9c536 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Window/AtomToolsMainWindow.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Window/AtomToolsMainWindow.cpp @@ -50,8 +50,9 @@ namespace AtomToolsFramework void AtomToolsMainWindow::ActivateWindow() { - activateWindow(); + show(); raise(); + activateWindow(); } bool AtomToolsMainWindow::AddDockWidget(const AZStd::string& name, QWidget* widget, uint32_t area, uint32_t orientation) diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ImageBasedLights/ImageBasedLightComponentController.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ImageBasedLights/ImageBasedLightComponentController.cpp index 6ea7580fc6..73cfd53c21 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ImageBasedLights/ImageBasedLightComponentController.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ImageBasedLights/ImageBasedLightComponentController.cpp @@ -163,8 +163,6 @@ namespace AZ return true; } } - // If this asset didn't load or isn't a cubemap, release it. - configAsset.Release(); return false; } diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SkyBox/HDRiSkyboxComponentController.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SkyBox/HDRiSkyboxComponentController.cpp index 171c5e417f..7bf6237c1a 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SkyBox/HDRiSkyboxComponentController.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SkyBox/HDRiSkyboxComponentController.cpp @@ -196,8 +196,6 @@ namespace AZ } else { - // If this asset didn't load or isn't a cubemap, release it. - m_configuration.m_cubemapAsset.Release(); m_featureProcessorInterface->SetCubemap(nullptr); } } diff --git a/Gems/AtomTressFX/Code/Builders/HairBuilderComponent.cpp b/Gems/AtomTressFX/Code/Builders/HairBuilderComponent.cpp index f80261f4b5..7f99e4c55f 100644 --- a/Gems/AtomTressFX/Code/Builders/HairBuilderComponent.cpp +++ b/Gems/AtomTressFX/Code/Builders/HairBuilderComponent.cpp @@ -46,14 +46,6 @@ namespace AZ { m_hairAssetBuilder.RegisterBuilder(); m_hairAssetHandler.Register(); - - // Add asset types and extensions to AssetCatalog. - auto assetCatalog = AZ::Data::AssetCatalogRequestBus::FindFirstHandler(); - if (assetCatalog) - { - assetCatalog->EnableCatalogForAsset(azrtti_typeid()); - assetCatalog->AddExtension(AMD::TFXCombinedFileExtension); - } } void HairBuilderComponent::Deactivate() diff --git a/Gems/LmbrCentral/Code/Source/Builders/MaterialBuilder/MaterialBuilderComponent.cpp b/Gems/LmbrCentral/Code/Source/Builders/MaterialBuilder/MaterialBuilderComponent.cpp deleted file mode 100644 index 48973b2779..0000000000 --- a/Gems/LmbrCentral/Code/Source/Builders/MaterialBuilder/MaterialBuilderComponent.cpp +++ /dev/null @@ -1,612 +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 "MaterialBuilderComponent.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace MaterialBuilder -{ - [[maybe_unused]] const char s_materialBuilder[] = "MaterialBuilder"; - - namespace Internal - { - const char g_nodeNameMaterial[] = "Material"; - const char g_nodeNameSubmaterial[] = "SubMaterials"; - const char g_nodeNameTexture[] = "Texture"; - const char g_nodeNameTextures[] = "Textures"; - const char g_attributeFileName[] = "File"; - - const int g_numSourceImageFormats = 9; - const char* g_sourceImageFormats[g_numSourceImageFormats] = { ".tif", ".tiff", ".bmp", ".gif", ".jpg", ".jpeg", ".tga", ".png", ".dds" }; - bool IsSupportedImageExtension(const AZStd::string& extension) - { - for (const char* format : g_sourceImageFormats) - { - if (extension == format) - { - return true; - } - } - return false; - } - - // Cleans up legacy pathing from older materials - const char* CleanLegacyPathingFromTexturePath(const char* texturePath) - { - // Copied from MaterialHelpers::SetTexturesFromXml, line 459 - // legacy. Some textures used to be referenced using "engine\\" or "engine/" - this is no longer valid - if ( - (strlen(texturePath) > 7) && - (azstrnicmp(texturePath, "engine", 6) == 0) && - ((texturePath[6] == '\\') || (texturePath[6] == '/')) - ) - { - texturePath = texturePath + 7; - } - - // legacy: Files were saved into a mtl with many leading forward or back slashes, we eat them all here. We want it to start with a relative path. - const char* actualFileName = texturePath; - while ((actualFileName[0]) && ((actualFileName[0] == '\\') || (actualFileName[0] == '/'))) - { - ++actualFileName; - } - return actualFileName; - } - - // Parses the material XML for all texture paths - AZ::Outcome GetTexturePathsFromMaterial(AZ::rapidxml::xml_node* materialNode, AZStd::vector& paths) - { - AZ::Outcome resultOutcome = AZ::Failure(AZStd::string("")); - AZStd::string success_with_warning_message; - - // check if this material has a set of textures defined, and if so, grab all the paths from the textures - AZ::rapidxml::xml_node* texturesNode = materialNode->first_node(g_nodeNameTextures); - if (texturesNode) - { - AZ::rapidxml::xml_node* textureNode = texturesNode->first_node(g_nodeNameTexture); - // it is possible for an empty node to exist for things like collision materials, so check - // to make sure that there is at least one child node before starting to iterate. - if (textureNode) - { - do - { - AZ::rapidxml::xml_attribute* fileAttribute = textureNode->first_attribute(g_attributeFileName); - if (!fileAttribute) - { - success_with_warning_message = "Texture node exists but does not have a file attribute defined"; - } - else - { - const char* rawTexturePath = fileAttribute->value(); - // do an initial clean-up of the path taken from the file, similar to MaterialHelpers::SetTexturesFromXml - AZStd::string texturePath = CleanLegacyPathingFromTexturePath(rawTexturePath); - paths.emplace_back(AZStd::move(texturePath)); - } - - textureNode = textureNode->next_sibling(g_nodeNameTexture); - } while (textureNode); - } - } - - // check to see if this material has sub materials defined. If so, recurse into this function for each sub material - AZ::rapidxml::xml_node* subMaterialsNode = materialNode->first_node(g_nodeNameSubmaterial); - if (subMaterialsNode) - { - AZ::rapidxml::xml_node* subMaterialNode = subMaterialsNode->first_node(g_nodeNameMaterial); - if (subMaterialNode == nullptr) - { - // this is a malformed material as there is no material node child in the SubMaterials node, so error out - return AZ::Failure(AZStd::string("SubMaterials node exists but does not have any child Material nodes.")); - } - - do - { - // grab the texture paths from the submaterial, or error out if necessary - AZ::Outcome subMaterialTexturePathsResult = GetTexturePathsFromMaterial(subMaterialNode, paths); - if (!subMaterialTexturePathsResult.IsSuccess()) - { - return subMaterialTexturePathsResult; - } - else if (!subMaterialTexturePathsResult.GetValue().empty()) - { - success_with_warning_message = subMaterialTexturePathsResult.GetValue(); - } - - subMaterialNode = subMaterialNode->next_sibling(g_nodeNameMaterial); - } while (subMaterialNode); - } - - if (texturesNode == nullptr && subMaterialsNode == nullptr) - { - return AZ::Failure(AZStd::string("Failed to find a Textures node or SubMaterials node in this material. At least one of these must exist to be able to gather texture dependencies.")); - } - - if (!success_with_warning_message.empty()) - { - return AZ::Success(success_with_warning_message); - } - return AZ::Success(AZStd::string()); - } - - // find a sequence of digits with a string starting from lastDigitIndex, and try to parse that sequence to and int - // and store it in outAnimIndex. - bool ParseFilePathForCompleteNumber(const AZStd::string& filePath, int& lastDigitIndex, int& outAnimIndex) - { - int firstAnimIndexDigit = lastDigitIndex; - while (isdigit(static_cast(filePath[lastDigitIndex]))) - { - ++lastDigitIndex; - } - if (!AzFramework::StringFunc::LooksLikeInt(filePath.substr(firstAnimIndexDigit, lastDigitIndex - firstAnimIndexDigit).c_str(), &outAnimIndex)) - { - return false; - } - return true; - } - - // Parse the texture path for a texture animation to determine the actual names of the textures to resolve that - // make up the entire sequence. - AZ::Outcome GetAllTexturesInTextureSequence(const AZStd::string& path, AZStd::vector& texturesInSequence) - { - // Taken from CShaderMan::mfReadTexSequence - // All comments next to variable declarations in this function are the original variable names in - // CShaderMan::mfReadTexSequence, to help keep track of how these variables relate to the original function - AZStd::string prefix; - AZStd::string postfix; - - AZStd::string filePath = path; // name - AZStd::string extension; // ext - AzFramework::StringFunc::Path::GetExtension(filePath.c_str(), extension); - AzFramework::StringFunc::Path::StripExtension(filePath); - - // unsure if it is actually possible to enter here or the original version with '$' as the indicator - // for texture sequences, but they check for both just in case, so this will match the behavior. - char separator = '#'; // chSep - int firstSeparatorIndex = static_cast(filePath.find(separator)); - if (firstSeparatorIndex == AZStd::string::npos) - { - firstSeparatorIndex = static_cast(filePath.find('$')); - if (firstSeparatorIndex == AZStd::string::npos) - { - return AZ::Failure(AZStd::string("Failed to find separator '#' or '$' in texture path.")); - } - separator = '$'; - } - - // we don't actually care about getting the speed of the animation, so just remove everything from the - // end of the string starting with the last open parenthesis - size_t speedStartIndex = filePath.find_last_of('('); - if (speedStartIndex != AZStd::string::npos) - { - AzFramework::StringFunc::LKeep(filePath, speedStartIndex); - AzFramework::StringFunc::Append(filePath, '\0'); - } - - // try to find where the digits start after the separator (there can be any number of separators - // between the texture name prefix and where the digit range starts) - int firstAnimIndexDigit = -1; // m - int numSeparators = 0; // j - for (int stringIndex = firstSeparatorIndex; stringIndex < filePath.length(); ++stringIndex) - { - if (filePath[stringIndex] == separator) - { - ++numSeparators; - if (firstSeparatorIndex == -1) - { - firstSeparatorIndex = stringIndex; - } - } - else if (firstSeparatorIndex > 0 && firstAnimIndexDigit < 0) - { - firstAnimIndexDigit = stringIndex; - break; - } - } - if (numSeparators == 0) - { - return AZ::Failure(AZStd::string("Failed to find separator '#' or '$' in texture path.")); - } - - // store off everything before the separator - prefix = AZStd::move(filePath.substr(0, firstSeparatorIndex)); - - int startAnimIndex = 0; // startn - int endAnimIndex = 0; // endn - // we only found the separator, but no indexes, so just assume its 0 - 999 - if (firstAnimIndexDigit < 0) - { - startAnimIndex = 0; - endAnimIndex = 999; - } - else - { - // find the length of the first index, then parse that to an int - int lastDigitIndex = firstAnimIndexDigit; - if (!ParseFilePathForCompleteNumber(filePath, lastDigitIndex, startAnimIndex)) - { - return AZ::Failure(AZStd::string("Failed to determine first index of the sequence after the separators in texture path.")); - } - - // reset to the start of the next index - ++lastDigitIndex; - - // find the length of the end index, then parse that to an int - if (!ParseFilePathForCompleteNumber(filePath, lastDigitIndex, endAnimIndex)) - { - return AZ::Failure(AZStd::string("Failed to determine last index of the sequence after the first index of the sequence in texture path.")); - } - - // save off the rest of the string - postfix = AZStd::move(filePath.substr(lastDigitIndex)); - } - - int numTextures = endAnimIndex - startAnimIndex + 1; - const char* textureNameFormat = "%s%.*d%s%s"; // prefix, num separators (number of digits), sequence index, postfix, extension) - for (int sequenceIndex = 0; sequenceIndex < numTextures; ++sequenceIndex) - { - texturesInSequence.emplace_back(AZStd::move(AZStd::string::format(textureNameFormat, prefix.c_str(), numSeparators, startAnimIndex + sequenceIndex, postfix.c_str(), extension.c_str()))); - } - - return AZ::Success(); - } - - // Determine which product path to use based on the path stored in the texture, and make it relative to - // the cache. - bool ResolveMaterialTexturePath(const AZStd::string& path, AZStd::string& outPath) - { - AZStd::string aliasedPath = path; - - //if its a source image format try to load the dds - AZStd::string extension; - bool hasExtension = AzFramework::StringFunc::Path::GetExtension(path.c_str(), extension); - - // Replace all supported extensions with DDS if it has an extension. If the extension exists but is not supported, fail out. - if (hasExtension && IsSupportedImageExtension(extension)) - { - AzFramework::StringFunc::Path::ReplaceExtension(aliasedPath, ".dds"); - } - else if (hasExtension) - { - AZ_Warning(s_materialBuilder, false, "Failed to resolve texture path %s as the path is not to a supported texture format. Please make sure that textures in materials are formats supported by Open 3D Engine.", aliasedPath.c_str()); - return false; - } - - AZStd::to_lower(aliasedPath.begin(), aliasedPath.end()); - AzFramework::StringFunc::Path::Normalize(aliasedPath); - - AZStd::string currentFolderSpecifier = AZStd::string::format(".%c", AZ_CORRECT_FILESYSTEM_SEPARATOR); - if (AzFramework::StringFunc::StartsWith(aliasedPath, currentFolderSpecifier)) - { - AzFramework::StringFunc::Strip(aliasedPath, currentFolderSpecifier.c_str(), false, true); - } - - AZStd::string resolvedPath; - char fullPathBuffer[AZ_MAX_PATH_LEN] = {}; - // if there is an alias already at the front of the path, resolve it, and try to make it relative to the - // cache (@products@). If it can't, then error out. - // This case handles the possibility of aliases existing in texture paths in materials that is still supported - // by the legacy loading code, however it is not currently used, so the else path is always taken. - if (aliasedPath[0] == '@') - { - if (!AZ::IO::FileIOBase::GetDirectInstance()->ResolvePath(aliasedPath.c_str(), fullPathBuffer, AZ_MAX_PATH_LEN)) - { - AZ_Warning(s_materialBuilder, false, "Failed to resolve the alias in texture path %s. Please make sure all aliases are registered with the engine.", aliasedPath.c_str()); - return false; - } - resolvedPath = fullPathBuffer; - AzFramework::StringFunc::Path::Normalize(resolvedPath); - if (!AzFramework::StringFunc::Replace(resolvedPath, AZ::IO::FileIOBase::GetDirectInstance()->GetAlias("@products@"), "")) - { - AZ_Warning(s_materialBuilder, false, "Failed to resolve aliased texture path %s to be relative to the asset cache. Please make sure this alias resolves to a path within the asset cache.", aliasedPath.c_str()); - return false; - } - } - else - { - resolvedPath = AZStd::move(aliasedPath); - } - - // AP deferred path resolution requires UNIX separators and no leading separators, so clean up and convert here - if (AzFramework::StringFunc::StartsWith(resolvedPath, AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING)) - { - AzFramework::StringFunc::Strip(resolvedPath, AZ_CORRECT_FILESYSTEM_SEPARATOR, false, true); - } - AzFramework::StringFunc::Replace(resolvedPath, AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING, "/"); - - outPath = AZStd::move(resolvedPath); - return true; - } - - } - - BuilderPluginComponent::BuilderPluginComponent() - { - } - - BuilderPluginComponent::~BuilderPluginComponent() - { - } - - void BuilderPluginComponent::Init() - { - } - - void BuilderPluginComponent::Activate() - { - // Register material builder - AssetBuilderSDK::AssetBuilderDesc builderDescriptor; - builderDescriptor.m_name = "MaterialBuilderWorker"; - builderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern("*.mtl", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard)); - builderDescriptor.m_busId = MaterialBuilderWorker::GetUUID(); - builderDescriptor.m_version = 5; - builderDescriptor.m_createJobFunction = AZStd::bind(&MaterialBuilderWorker::CreateJobs, &m_materialBuilder, AZStd::placeholders::_1, AZStd::placeholders::_2); - builderDescriptor.m_processJobFunction = AZStd::bind(&MaterialBuilderWorker::ProcessJob, &m_materialBuilder, AZStd::placeholders::_1, AZStd::placeholders::_2); - - // (optimization) this builder does not emit source dependencies: - builderDescriptor.m_flags |= AssetBuilderSDK::AssetBuilderDesc::BF_EmitsNoDependencies; - - m_materialBuilder.BusConnect(builderDescriptor.m_busId); - - EBUS_EVENT(AssetBuilderSDK::AssetBuilderBus, RegisterBuilderInformation, builderDescriptor); - } - - void BuilderPluginComponent::Deactivate() - { - m_materialBuilder.BusDisconnect(); - } - - void BuilderPluginComponent::Reflect([[maybe_unused]] AZ::ReflectContext* context) - { - if (AZ::SerializeContext* serializeContext = azrtti_cast(context)) - { - serializeContext->Class() - ->Version(1) - ->Attribute(AZ::Edit::Attributes::SystemComponentTags, AZStd::vector({ AssetBuilderSDK::ComponentTags::AssetBuilder })); - } - } - - MaterialBuilderWorker::MaterialBuilderWorker() - { - } - MaterialBuilderWorker::~MaterialBuilderWorker() - { - } - - void MaterialBuilderWorker::ShutDown() - { - // This will be called on a different thread than the process job thread - m_isShuttingDown = true; - } - - // This happens early on in the file scanning pass. - // This function should always create the same jobs and not do any checking whether the job is up to date. - void MaterialBuilderWorker::CreateJobs(const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response) - { - if (m_isShuttingDown) - { - response.m_result = AssetBuilderSDK::CreateJobsResultCode::ShuttingDown; - return; - } - - for (const AssetBuilderSDK::PlatformInfo& info : request.m_enabledPlatforms) - { - AssetBuilderSDK::JobDescriptor descriptor; - descriptor.m_jobKey = "Material Builder Job"; - descriptor.SetPlatformIdentifier(info.m_identifier.c_str()); - descriptor.m_priority = 8; // meshes are more important (at 10) but mats are still pretty important. - response.m_createJobOutputs.push_back(descriptor); - } - - response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success; - } - - // The request will contain the CreateJobResponse you constructed earlier, including any keys and - // values you placed into the hash table - void MaterialBuilderWorker::ProcessJob(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response) - { - AZ_TracePrintf(AssetBuilderSDK::InfoWindow, "Starting Job.\n"); - AZStd::string fileName; - AzFramework::StringFunc::Path::GetFullFileName(request.m_fullPath.c_str(), fileName); - AZStd::string destPath; - - // Do all work inside the tempDirPath. - AzFramework::StringFunc::Path::ConstructFull(request.m_tempDirPath.c_str(), fileName.c_str(), destPath, true); - - AZ::IO::LocalFileIO fileIO; - if (!m_isShuttingDown && fileIO.Copy(request.m_fullPath.c_str(), destPath.c_str()) == AZ::IO::ResultCode::Success) - { - // Push assets back into the response's product list - // Assets you created in your temp path can be specified using paths relative to the temp path - // since that is assumed where you're writing stuff. - AZStd::string relPath = destPath; - AssetBuilderSDK::ProductPathDependencySet dependencyPaths; - response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Success; - AssetBuilderSDK::JobProduct jobProduct(fileName); - - bool dependencyResult = GatherProductDependencies(request.m_fullPath, dependencyPaths); - if (dependencyResult) - { - jobProduct.m_pathDependencies = AZStd::move(dependencyPaths); - jobProduct.m_dependenciesHandled = true; // We've output the dependencies immediately above so it's OK to tell the AP we've handled dependencies - } - else - { - AZ_Error(s_materialBuilder, false, "Dependency gathering for %s failed.", request.m_fullPath.c_str()); - } - response.m_outputProducts.push_back(jobProduct); - } - else - { - if (m_isShuttingDown) - { - AZ_TracePrintf(AssetBuilderSDK::ErrorWindow, "Cancelled job %s because shutdown was requested.\n", request.m_fullPath.c_str()); - response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Cancelled; - } - else - { - AZ_TracePrintf(AssetBuilderSDK::ErrorWindow, "Error during processing job %s.\n", request.m_fullPath.c_str()); - response.m_resultCode = AssetBuilderSDK::ProcessJobResult_Failed; - } - } - } - - bool MaterialBuilderWorker::GetResolvedTexturePathsFromMaterial(const AZStd::string& path, AZStd::vector& resolvedPaths) - { - if (!AZ::IO::SystemFile::Exists(path.c_str())) - { - AZ_Error(s_materialBuilder, false, "Failed to find material at path %s. Please make sure this material exists on disk.", path.c_str()); - return false; - } - - uint64_t fileSize = AZ::IO::SystemFile::Length(path.c_str()); - if (fileSize == 0) - { - AZ_Error(s_materialBuilder, false, "Material at path %s is an empty file. Please make sure this material was properly saved to disk.", path.c_str()); - return false; - } - - AZStd::vector buffer(fileSize + 1); - buffer[fileSize] = 0; - if (!AZ::IO::SystemFile::Read(path.c_str(), buffer.data())) - { - AZ_Error(s_materialBuilder, false, "Failed to read material at path %s. Please make sure the file is not open or being edited by another program.", path.c_str()); - return false; - } - - AZ::rapidxml::xml_document* xmlDoc = azcreate(AZ::rapidxml::xml_document, (), AZ::SystemAllocator, "Mtl builder temp XML Reader"); - if (!xmlDoc->parse(buffer.data())) - { - azdestroy(xmlDoc, AZ::SystemAllocator, AZ::rapidxml::xml_document); - AZ_Error(s_materialBuilder, false, "Failed to parse material at path %s into XML. Please make sure that the material was properly saved to disk.", path.c_str()); - return false; - } - - // if the first node in this file isn't a material, this must not actually be a material so it can't have deps - AZ::rapidxml::xml_node* rootNode = xmlDoc->first_node(Internal::g_nodeNameMaterial); - if (!rootNode) - { - azdestroy(xmlDoc, AZ::SystemAllocator, AZ::rapidxml::xml_document); - AZ_Error(s_materialBuilder, false, "Failed to find root material node for material at path %s. Please make sure that the material was properly saved to disk.", path.c_str()); - return false; - } - - AZStd::vector texturePaths; - // gather all textures in the material file - AZ::Outcome texturePathsResult = Internal::GetTexturePathsFromMaterial(rootNode, texturePaths); - if (!texturePathsResult.IsSuccess()) - { - azdestroy(xmlDoc, AZ::SystemAllocator, AZ::rapidxml::xml_document); - AZ_Error(s_materialBuilder, false, "Failed to gather dependencies for %s as the material file is malformed. %s", path.c_str(), texturePathsResult.GetError().c_str()); - return false; - } - else if (!texturePathsResult.GetValue().empty()) - { - AZ_Warning(s_materialBuilder, false, "Some nodes in material %s could not be read as the material is malformed. %s. Some dependencies might not be reported correctly. Please make sure that the material was properly saved to disk.", path.c_str(), texturePathsResult.GetValue().c_str()); - } - azdestroy(xmlDoc, AZ::SystemAllocator, AZ::rapidxml::xml_document); - - // fail this if there are absolute paths. - for (const AZStd::string& texPath : texturePaths) - { - if (AZ::IO::PathView(texPath).IsAbsolute()) - { - AZ_Warning(s_materialBuilder, false, "Skipping resolving of texture path %s in material %s as the texture path is an absolute path. Please update the texture path to be relative to the asset cache.", texPath.c_str(), path.c_str()); - texturePaths.erase(AZStd::find(texturePaths.begin(), texturePaths.end(), texPath)); - } - } - - // for each path in the array, split any texture animation entry up into the individual files and add each to the list. - for (const AZStd::string& texPath : texturePaths) - { - if (texPath.find('#') != AZStd::string::npos) - { - AZStd::vector actualTexturePaths; - AZ::Outcome parseTextureSequenceResult = Internal::GetAllTexturesInTextureSequence(texPath, actualTexturePaths); - if (parseTextureSequenceResult.IsSuccess()) - { - texturePaths.erase(AZStd::find(texturePaths.begin(), texturePaths.end(), texPath)); - texturePaths.insert(texturePaths.end(), actualTexturePaths.begin(), actualTexturePaths.end()); - } - else - { - texturePaths.erase(AZStd::find(texturePaths.begin(), texturePaths.end(), texPath)); - AZ_Warning(s_materialBuilder, false, "Failed to parse texture sequence %s when trying to gather dependencies for %s. %s Please make sure the texture sequence path is formatted correctly. Registering dependencies for the texture sequence will be skipped.", texPath.c_str(), path.c_str(), parseTextureSequenceResult.GetError().c_str()); - } - } - } - - // for each texture in the file - for (const AZStd::string& texPath : texturePaths) - { - // if the texture path starts with a '$' then it is a special runtime defined texture, so it it doesn't have - // an actual asset on disk to depend on. If the texture path doesn't have an extension, then it is a texture - // that is determined at runtime (such as 'nearest_cubemap'), so also ignore those, as other things pull in - // those dependencies. - if (AzFramework::StringFunc::StartsWith(texPath, "$") || !AzFramework::StringFunc::Path::HasExtension(texPath.c_str())) - { - continue; - } - - // resolve the path in the file. - AZStd::string resolvedPath; - if (!Internal::ResolveMaterialTexturePath(texPath, resolvedPath)) - { - AZ_Warning(s_materialBuilder, false, "Failed to resolve texture path %s to a product path when gathering dependencies for %s. Registering dependencies on this texture path will be skipped.", texPath.c_str(), path.c_str()); - continue; - } - - resolvedPaths.emplace_back(AZStd::move(resolvedPath)); - } - - return true; - } - - bool MaterialBuilderWorker::PopulateProductDependencyList(AZStd::vector& resolvedPaths, AssetBuilderSDK::ProductPathDependencySet& dependencies) - { - for (const AZStd::string& texturePath : resolvedPaths) - { - if (texturePath.empty()) - { - AZ_Warning(s_materialBuilder, false, "Resolved path is empty.\n"); - return false; - } - - dependencies.emplace(texturePath, AssetBuilderSDK::ProductPathDependencyType::ProductFile); - } - return true; - } - - bool MaterialBuilderWorker::GatherProductDependencies(const AZStd::string& path, AssetBuilderSDK::ProductPathDependencySet& dependencies) - { - AZStd::vector resolvedTexturePaths; - if (!GetResolvedTexturePathsFromMaterial(path, resolvedTexturePaths)) - { - return false; - } - - if (!PopulateProductDependencyList(resolvedTexturePaths, dependencies)) - { - AZ_Warning(s_materialBuilder, false, "Failed to populate dependency list for material %s with possible variants for textures.", path.c_str()); - } - - return true; - } - - AZ::Uuid MaterialBuilderWorker::GetUUID() - { - return AZ::Uuid::CreateString("{258D34AC-12F8-4196-B535-3206D8E7287B}"); - } -} diff --git a/Gems/LmbrCentral/Code/Source/Builders/MaterialBuilder/MaterialBuilderComponent.h b/Gems/LmbrCentral/Code/Source/Builders/MaterialBuilder/MaterialBuilderComponent.h deleted file mode 100644 index a7813cf0bd..0000000000 --- a/Gems/LmbrCentral/Code/Source/Builders/MaterialBuilder/MaterialBuilderComponent.h +++ /dev/null @@ -1,65 +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 MaterialBuilder -{ - //! Material builder is responsible for building material files - class MaterialBuilderWorker - : public AssetBuilderSDK::AssetBuilderCommandBus::Handler - { - public: - MaterialBuilderWorker(); - ~MaterialBuilderWorker(); - - //! Asset Builder Callback Functions - void CreateJobs(const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response); - void ProcessJob(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response); - - //!AssetBuilderSDK::AssetBuilderCommandBus interface - void ShutDown() override; - - //! Returns the UUID for this builder - static AZ::Uuid GetUUID(); - - bool GetResolvedTexturePathsFromMaterial(const AZStd::string& path, AZStd::vector& resolvedPaths); - bool PopulateProductDependencyList(AZStd::vector& resolvedPaths, AssetBuilderSDK::ProductPathDependencySet& dependencies); - - private: - bool GatherProductDependencies(const AZStd::string& path, AssetBuilderSDK::ProductPathDependencySet& dependencies); - - bool m_isShuttingDown = false; - }; - - class BuilderPluginComponent - : public AZ::Component - { - public: - AZ_COMPONENT(BuilderPluginComponent, "{4D1A4B0C-54CE-4397-B8AE-ADD08898C2CD}") - static void Reflect(AZ::ReflectContext* context); - - BuilderPluginComponent(); - - ////////////////////////////////////////////////////////////////////////// - // AZ::Component - virtual void Init(); // create objects, allocate memory and initialize yourself without reaching out to the outside world - virtual void Activate(); // reach out to the outside world and connect up to what you need to, register things, etc. - virtual void Deactivate(); // unregister things, disconnect from the outside world - ////////////////////////////////////////////////////////////////////////// - - virtual ~BuilderPluginComponent(); // free memory an uninitialize yourself. - - private: - MaterialBuilderWorker m_materialBuilder; - }; -} diff --git a/Gems/LmbrCentral/Code/Source/LmbrCentral.cpp b/Gems/LmbrCentral/Code/Source/LmbrCentral.cpp index 027d19fdef..ad90c16f43 100644 --- a/Gems/LmbrCentral/Code/Source/LmbrCentral.cpp +++ b/Gems/LmbrCentral/Code/Source/LmbrCentral.cpp @@ -38,9 +38,6 @@ #include "Geometry/GeometrySystemComponent.h" #include -// Unhandled asset types -// Material -#include "Unhandled/Material/MaterialAssetTypeInfo.h" // Other #include "Unhandled/Other/AudioAssetTypeInfo.h" #include "Unhandled/Other/CharacterPhysicsAssetTypeInfo.h" @@ -353,8 +350,6 @@ namespace LmbrCentral // Add asset types and extensions to AssetCatalog. Uses "AssetCatalogService". if (auto assetCatalog = AZ::Data::AssetCatalogRequestBus::FindFirstHandler(); assetCatalog) { - assetCatalog->EnableCatalogForAsset(AZ::AzTypeInfo::Uuid()); - assetCatalog->EnableCatalogForAsset(AZ::AzTypeInfo::Uuid()); assetCatalog->EnableCatalogForAsset(AZ::AzTypeInfo::Uuid()); assetCatalog->EnableCatalogForAsset(AZ::AzTypeInfo::Uuid()); @@ -364,24 +359,12 @@ namespace LmbrCentral assetCatalog->AddExtension("dds"); assetCatalog->AddExtension("caf"); assetCatalog->AddExtension("xml"); - assetCatalog->AddExtension("mtl"); - assetCatalog->AddExtension("dccmtl"); assetCatalog->AddExtension("sprite"); assetCatalog->AddExtension("cax"); } AZ::Data::AssetManagerNotificationBus::Handler::BusConnect(); - - // Register unhandled asset type info - // Material - auto materialAssetTypeInfo = aznew MaterialAssetTypeInfo(); - materialAssetTypeInfo->Register(); - m_unhandledAssetInfo.emplace_back(materialAssetTypeInfo); - // DCC Material - auto dccMaterialAssetTypeInfo = aznew DccMaterialAssetTypeInfo(); - dccMaterialAssetTypeInfo->Register(); - m_unhandledAssetInfo.emplace_back(dccMaterialAssetTypeInfo); // Other auto audioAssetTypeInfo = aznew AudioAssetTypeInfo(); audioAssetTypeInfo->Register(); diff --git a/Gems/LmbrCentral/Code/Source/LmbrCentralEditor.cpp b/Gems/LmbrCentral/Code/Source/LmbrCentralEditor.cpp index 511bf98582..61b8c8f73d 100644 --- a/Gems/LmbrCentral/Code/Source/LmbrCentralEditor.cpp +++ b/Gems/LmbrCentral/Code/Source/LmbrCentralEditor.cpp @@ -43,7 +43,6 @@ #include #include #include -#include #include #include #include "Builders/CopyDependencyBuilder/CopyDependencyBuilderComponent.h" @@ -84,7 +83,6 @@ namespace LmbrCentral CopyDependencyBuilder::CopyDependencyBuilderComponent::CreateDescriptor(), DependencyBuilder::DependencyBuilderComponent::CreateDescriptor(), LevelBuilder::LevelBuilderComponent::CreateDescriptor(), - MaterialBuilder::BuilderPluginComponent::CreateDescriptor(), SliceBuilder::BuilderPluginComponent::CreateDescriptor(), TranslationBuilder::BuilderPluginComponent::CreateDescriptor(), LuaBuilder::BuilderPluginComponent::CreateDescriptor(), diff --git a/Gems/LmbrCentral/Code/Source/Unhandled/Material/MaterialAssetTypeInfo.cpp b/Gems/LmbrCentral/Code/Source/Unhandled/Material/MaterialAssetTypeInfo.cpp deleted file mode 100644 index 24bc43740d..0000000000 --- a/Gems/LmbrCentral/Code/Source/Unhandled/Material/MaterialAssetTypeInfo.cpp +++ /dev/null @@ -1,88 +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 "MaterialAssetTypeInfo.h" - -#include - -namespace LmbrCentral -{ - // MaterialAssetTypeInfo - - MaterialAssetTypeInfo::~MaterialAssetTypeInfo() - { - Unregister(); - } - - void MaterialAssetTypeInfo::Register() - { - AZ::AssetTypeInfoBus::Handler::BusConnect(AZ::AzTypeInfo::Uuid()); - } - - void MaterialAssetTypeInfo::Unregister() - { - AZ::AssetTypeInfoBus::Handler::BusDisconnect(AZ::AzTypeInfo::Uuid()); - } - - AZ::Data::AssetType MaterialAssetTypeInfo::GetAssetType() const - { - return AZ::AzTypeInfo::Uuid(); - } - - const char* MaterialAssetTypeInfo::GetAssetTypeDisplayName() const - { - return "Material"; - } - - const char* MaterialAssetTypeInfo::GetGroup() const - { - return "Material"; - } - - const char* MaterialAssetTypeInfo::GetBrowserIcon() const - { - return "Icons/Components/Decal.svg"; - } - - // DccMaterialAssetTypeInfo - - DccMaterialAssetTypeInfo::~DccMaterialAssetTypeInfo() - { - Unregister(); - } - - void DccMaterialAssetTypeInfo::Register() - { - AZ::AssetTypeInfoBus::Handler::BusConnect(AZ::AzTypeInfo::Uuid()); - } - - void DccMaterialAssetTypeInfo::Unregister() - { - AZ::AssetTypeInfoBus::Handler::BusDisconnect(AZ::AzTypeInfo::Uuid()); - } - - AZ::Data::AssetType DccMaterialAssetTypeInfo::GetAssetType() const - { - return AZ::AzTypeInfo::Uuid(); - } - - const char* DccMaterialAssetTypeInfo::GetAssetTypeDisplayName() const - { - return "DccMaterial"; - } - - const char* DccMaterialAssetTypeInfo::GetGroup() const - { - return "DccMaterial"; - } - - const char* DccMaterialAssetTypeInfo::GetBrowserIcon() const - { - return "Icons/Components/Decal.svg"; - } -} // namespace LmbrCentral diff --git a/Gems/LmbrCentral/Code/Source/Unhandled/Material/MaterialAssetTypeInfo.h b/Gems/LmbrCentral/Code/Source/Unhandled/Material/MaterialAssetTypeInfo.h deleted file mode 100644 index 2eafa31b41..0000000000 --- a/Gems/LmbrCentral/Code/Source/Unhandled/Material/MaterialAssetTypeInfo.h +++ /dev/null @@ -1,55 +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 LmbrCentral -{ - class MaterialAssetTypeInfo - : public AZ::AssetTypeInfoBus::Handler - { - public: - - AZ_CLASS_ALLOCATOR(MaterialAssetTypeInfo, AZ::SystemAllocator, 0); - - ~MaterialAssetTypeInfo() override; - - ////////////////////////////////////////////////////////////////////////////////////////////// - // AZ::AssetTypeInfoBus::Handler - AZ::Data::AssetType GetAssetType() const override; - const char* GetAssetTypeDisplayName() const override; - const char* GetGroup() const override; - const char* GetBrowserIcon() const override; - ////////////////////////////////////////////////////////////////////////////////////////////// - - void Register(); - void Unregister(); - }; - - class DccMaterialAssetTypeInfo - : public AZ::AssetTypeInfoBus::Handler - { - public: - - AZ_CLASS_ALLOCATOR(DccMaterialAssetTypeInfo, AZ::SystemAllocator, 0); - - ~DccMaterialAssetTypeInfo() override; - - ////////////////////////////////////////////////////////////////////////////////////////////// - // AZ::AssetTypeInfoBus::Handler - AZ::Data::AssetType GetAssetType() const override; - const char* GetAssetTypeDisplayName() const override; - const char* GetGroup() const override; - const char* GetBrowserIcon() const override; - ////////////////////////////////////////////////////////////////////////////////////////////// - - void Register(); - void Unregister(); - }; -} // namespace LmbrCentral diff --git a/Gems/LmbrCentral/Code/Tests/Builders/MaterialBuilderTests.cpp b/Gems/LmbrCentral/Code/Tests/Builders/MaterialBuilderTests.cpp deleted file mode 100644 index 3786ef7565..0000000000 --- a/Gems/LmbrCentral/Code/Tests/Builders/MaterialBuilderTests.cpp +++ /dev/null @@ -1,261 +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 - -namespace UnitTest -{ - using namespace MaterialBuilder; - using namespace AZ; - - class MaterialBuilderTests - : public UnitTest::AllocatorsTestFixture - , public UnitTest::TraceBusRedirector - { - protected: - void SetUp() override - { - UnitTest::AllocatorsTestFixture::SetUp(); - - m_app.reset(aznew AzToolsFramework::ToolsApplication); - m_app->Start(AZ::ComponentApplication::Descriptor()); - // Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is - // shared across the whole engine, if multiple tests are run in parallel, the saving could cause a crash - // in the unit tests. - AZ::UserSettingsComponentRequestBus::Broadcast(&AZ::UserSettingsComponentRequests::DisableSaveOnFinalize); - AZ::Debug::TraceMessageBus::Handler::BusConnect(); - - const AZStd::string engineRoot = AZ::Test::GetEngineRootPath(); - AZ::IO::FileIOBase::GetInstance()->SetAlias("@engroot@", engineRoot.c_str()); - - AZ::IO::Path assetRoot(AZ::Utils::GetProjectPath()); - assetRoot /= "Cache"; - AZ::IO::FileIOBase::GetInstance()->SetAlias("@products@", assetRoot.c_str()); - } - - void TearDown() override - { - AZ::Debug::TraceMessageBus::Handler::BusDisconnect(); - m_app->Stop(); - m_app.reset(); - - UnitTest::AllocatorsTestFixture::TearDown(); - } - - AZStd::string GetTestFileAliasedPath(AZStd::string_view fileName) - { - constexpr char testFileFolder[] = "@engroot@/Gems/LmbrCentral/Code/Tests/Materials/"; - return AZStd::string::format("%s%.*s", testFileFolder, aznumeric_cast(fileName.size()), fileName.data()); - } - - AZStd::string GetTestFileFullPath(AZStd::string_view fileName) - { - AZStd::string aliasedPath = GetTestFileAliasedPath(fileName); - char resolvedPath[AZ_MAX_PATH_LEN]; - AZ::IO::FileIOBase::GetInstance()->ResolvePath(aliasedPath.c_str(), resolvedPath, AZ_MAX_PATH_LEN); - return AZStd::string(resolvedPath); - } - - void TestFailureCase(AZStd::string_view fileName, [[maybe_unused]] int expectedErrorCount) - { - MaterialBuilderWorker worker; - AZStd::vector resolvedPaths; - - AZStd::string absoluteMatPath = GetTestFileFullPath(fileName); - - AZ_TEST_START_ASSERTTEST; - ASSERT_FALSE(worker.GetResolvedTexturePathsFromMaterial(absoluteMatPath, resolvedPaths)); - AZ_TEST_STOP_ASSERTTEST(expectedErrorCount * 2); // The assert tests double count AZ errors, so just multiply expected count by 2 - ASSERT_EQ(resolvedPaths.size(), 0); - } - - void TestSuccessCase(AZStd::string_view fileName, AZStd::vector& expectedTextures) - { - MaterialBuilderWorker worker; - AZStd::vector resolvedPaths; - size_t texturesInMaterialFile = expectedTextures.size(); - - AZStd::string absoluteMatPath = GetTestFileFullPath(fileName); - ASSERT_TRUE(worker.GetResolvedTexturePathsFromMaterial(absoluteMatPath, resolvedPaths)); - ASSERT_EQ(resolvedPaths.size(), texturesInMaterialFile); - if (texturesInMaterialFile > 0) - { - ASSERT_THAT(resolvedPaths, testing::ElementsAreArray(expectedTextures)); - - AssetBuilderSDK::ProductPathDependencySet dependencies; - ASSERT_TRUE(worker.PopulateProductDependencyList(resolvedPaths, dependencies)); - ASSERT_EQ(dependencies.size(), texturesInMaterialFile); - } - } - - void TestSuccessCase(AZStd::string_view fileName, const char* expectedTexture) - { - AZStd::vector expectedTextures; - expectedTextures.push_back(expectedTexture); - TestSuccessCase(fileName, expectedTextures); - } - - void TestSuccessCaseNoDependencies(AZStd::string_view fileName) - { - AZStd::vector expectedTextures; - TestSuccessCase(fileName, expectedTextures); - } - - AZStd::unique_ptr m_app; - }; - - TEST_F(MaterialBuilderTests, MaterialBuilder_EmptyFile_ExpectFailure) - { - // Should fail in MaterialBuilderWorker::GetResolvedTexturePathsFromMaterial, when checking for the size of the file. - TestFailureCase("test_mat1.mtl", 1); - } - - TEST_F(MaterialBuilderTests, MaterialBuilder_MalformedMaterial_NoChildren_ExpectFailure) - { - // Should fail in MaterialBuilderWorker::GetResolvedTexturePathsFromMaterial after calling - // Internal::GetTexturePathsFromMaterial, which should return an AZ::Failure when both a Textures node and a - // SubMaterials node are not found. No other AZ_Errors should be generated. - TestFailureCase("test_mat2.mtl", 1); - } - - TEST_F(MaterialBuilderTests, MaterialBuilder_MalformedMaterial_EmptyTexturesNode_NoDependencies) - { - TestSuccessCaseNoDependencies("test_mat3.mtl"); - } - - TEST_F(MaterialBuilderTests, MaterialBuilder_MalformedMaterial_EmptySubMaterialNode_ExpectFailure) - { - // Should fail in MaterialBuilderWorker::GetResolvedTexturePathsFromMaterial after calling - // Internal::GetTexturePathsFromMaterial, which should return an AZ::Failure when a SubMaterials node is present, - // but has no children Material node. No other AZ_Errors should be generated. - TestFailureCase("test_mat4.mtl", 1); - } - - TEST_F(MaterialBuilderTests, MaterialBuilder_MalformedMaterial_EmptyTextureNode_NoDependencies) - { - TestSuccessCaseNoDependencies("test_mat5.mtl"); - } - - TEST_F(MaterialBuilderTests, MaterialBuilder_MalformedMaterial_EmptyMaterialInSubMaterial_ExpectFailure) - { - // Should fail in MaterialBuilderWorker::GetResolvedTexturePathsFromMaterial after calling - // Internal::GetTexturePathsFromMaterial, which should return an AZ::Failure when a SubMaterials node is present, - // but a child Material node has no child Textures node and no child SubMaterials node. No other AZ_Errors should - // be generated. - TestFailureCase("test_mat6.mtl", 1); - } - - TEST_F(MaterialBuilderTests, MaterialBuilder_MalformedMaterial_EmptyTextureNodeInSubMaterial_NoDependencies) - { - TestSuccessCaseNoDependencies("test_mat7.mtl"); - } - -#if AZ_TRAIT_OS_USE_WINDOWS_FILE_PATHS // The following test file 'test_mat8.mtl' has a windows-specific absolute path, so this test is only valid on windows - TEST_F(MaterialBuilderTests, MaterialBuilder_TextureAbsolutePath_NoDependencies) - { - TestSuccessCaseNoDependencies("test_mat8.mtl"); - } -#endif - - TEST_F(MaterialBuilderTests, MaterialBuilder_TextureRuntimeAlias_NoDependencies) - { - TestSuccessCaseNoDependencies("test_mat9.mtl"); - } - - TEST_F(MaterialBuilderTests, MaterialBuilder_TextureRuntimeTexture_NoDependencies) - { - TestSuccessCaseNoDependencies("test_mat10.mtl"); - } - - TEST_F(MaterialBuilderTests, MaterialBuilder_SingleMaterialSingleTexture_ValidSourceFormat) - { - // texture referenced is textures/natural/terrain/am_floor_tile_ddn.png - const char* expectedPath = "textures/natural/terrain/am_floor_tile_ddn.dds"; - TestSuccessCase("test_mat11.mtl", expectedPath); - } - - TEST_F(MaterialBuilderTests, MaterialBuilder_SingleMaterialSingleTexture_ValidProductFormat) - { - // texture referenced is textures/natural/terrain/am_floor_tile_ddn.dds - const char* expectedPath = "textures/natural/terrain/am_floor_tile_ddn.dds"; - TestSuccessCase("test_mat12.mtl", expectedPath); - } - - TEST_F(MaterialBuilderTests, MaterialBuilder_SingleMaterialSingleTexture_InvalidSourceFormat_NoDependenices) - { - // texture referenced is textures/natural/terrain/am_floor_tile_ddn.txt - TestSuccessCaseNoDependencies("test_mat13.mtl"); - } - - TEST_F(MaterialBuilderTests, MaterialBuilder_TextureAnimSequence) - { - AZStd::vector expectedPaths = { - "path/to/my/textures/test_anim_sequence_01_texture000.dds", - "path/to/my/textures/test_anim_sequence_01_texture001.dds", - "path/to/my/textures/test_anim_sequence_01_texture002.dds", - "path/to/my/textures/test_anim_sequence_01_texture003.dds", - "path/to/my/textures/test_anim_sequence_01_texture004.dds", - "path/to/my/textures/test_anim_sequence_01_texture005.dds" - }; - TestSuccessCase("test_mat14.mtl", expectedPaths); - } - - TEST_F(MaterialBuilderTests, MaterialBuilder_SingleMaterialMultipleTexture) - { - AZStd::vector expectedPaths = { - "engineassets/textures/hex.dds", - "engineassets/textures/hex_ddn.dds" - }; - TestSuccessCase("test_mat15.mtl", expectedPaths); - } - - TEST_F(MaterialBuilderTests, MaterialBuilder_MalformedMaterial_MultipleTextures_OneEmptyTexture) - { - TestSuccessCase("test_mat16.mtl", "engineassets/textures/hex_ddn.dds"); - } - - TEST_F(MaterialBuilderTests, MaterialBuilder_SingleMaterialMultipleTexture_ResolveLeadingSeparatorsAndAliases) - { - AZStd::vector expectedPaths = { - "engineassets/textures/hex.dds", // resolved from "/engineassets/textures/hex.dds" - "engineassets/textures/hex_ddn.dds", // resolved from "./engineassets/textures/hex_ddn.dds" - "engineassets/textures/hex_spec.dds" // resolved from "@products@/engineassets/textures/hex_spec.dds" - }; - TestSuccessCase("test_mat17.mtl", expectedPaths); - } - - TEST_F(MaterialBuilderTests, MaterialBuilder_SubMaterialSingleTexture) - { - AZStd::vector expectedPaths = { - "engineassets/textures/scratch.dds", - "engineassets/textures/perlinnoise2d.dds" - }; - TestSuccessCase("test_mat18.mtl", expectedPaths); - } - - TEST_F(MaterialBuilderTests, MaterialBuilder_SubMaterialMultipleTexture) - { - AZStd::vector expectedPaths = { - "engineassets/textures/scratch.dds", - "engineassets/textures/scratch_ddn.dds", - "engineassets/textures/perlinnoise2d.dds", - "engineassets/textures/perlinnoisenormal_ddn.dds" - }; - TestSuccessCase("test_mat19.mtl", expectedPaths); - } -} diff --git a/Gems/LmbrCentral/Code/lmbrcentral_editor_files.cmake b/Gems/LmbrCentral/Code/lmbrcentral_editor_files.cmake index 5c77888922..aea2f493c7 100644 --- a/Gems/LmbrCentral/Code/lmbrcentral_editor_files.cmake +++ b/Gems/LmbrCentral/Code/lmbrcentral_editor_files.cmake @@ -116,8 +116,6 @@ set(FILES Source/Builders/LevelBuilder/LevelBuilderComponent.h Source/Builders/LevelBuilder/LevelBuilderWorker.cpp Source/Builders/LevelBuilder/LevelBuilderWorker.h - Source/Builders/MaterialBuilder/MaterialBuilderComponent.cpp - Source/Builders/MaterialBuilder/MaterialBuilderComponent.h Source/Builders/SliceBuilder/SliceBuilderComponent.cpp Source/Builders/SliceBuilder/SliceBuilderComponent.h Source/Builders/SliceBuilder/SliceBuilderWorker.cpp diff --git a/Gems/LmbrCentral/Code/lmbrcentral_editor_tests_files.cmake b/Gems/LmbrCentral/Code/lmbrcentral_editor_tests_files.cmake index 0f0cf484d1..afba79e566 100644 --- a/Gems/LmbrCentral/Code/lmbrcentral_editor_tests_files.cmake +++ b/Gems/LmbrCentral/Code/lmbrcentral_editor_tests_files.cmake @@ -21,7 +21,6 @@ set(FILES Tests/Builders/CopyDependencyBuilderTest.cpp Tests/Builders/SliceBuilderTests.cpp Tests/Builders/LevelBuilderTest.cpp - Tests/Builders/MaterialBuilderTests.cpp Tests/Builders/LuaBuilderTests.cpp Tests/Builders/SeedBuilderTests.cpp Source/LmbrCentral.cpp diff --git a/Gems/LmbrCentral/Code/lmbrcentral_files.cmake b/Gems/LmbrCentral/Code/lmbrcentral_files.cmake index 18412e2a38..20a366b5f7 100644 --- a/Gems/LmbrCentral/Code/lmbrcentral_files.cmake +++ b/Gems/LmbrCentral/Code/lmbrcentral_files.cmake @@ -145,8 +145,6 @@ set(FILES Source/Shape/ShapeComponentConverters.inl Source/Shape/ShapeGeometryUtil.h Source/Shape/ShapeGeometryUtil.cpp - Source/Unhandled/Material/MaterialAssetTypeInfo.cpp - Source/Unhandled/Material/MaterialAssetTypeInfo.h Source/Unhandled/Other/AudioAssetTypeInfo.cpp Source/Unhandled/Other/AudioAssetTypeInfo.h Source/Unhandled/Other/CharacterPhysicsAssetTypeInfo.cpp diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/AutoGen/AutoComponent_Source.jinja b/Gems/Multiplayer/Code/Include/Multiplayer/AutoGen/AutoComponent_Source.jinja index e9bc21875b..d0b77159f8 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/AutoGen/AutoComponent_Source.jinja +++ b/Gems/Multiplayer/Code/Include/Multiplayer/AutoGen/AutoComponent_Source.jinja @@ -308,13 +308,21 @@ void {{ ClassName }}::Set{{ UpperFirst(Property.attrib['Name']) }}(const {{ Prop {% endmacro %} {# +#} +{% macro PrintRpcParameters(printPrefix, paramDefines) %} +{% if paramDefines|count > 0 %} +{{ printPrefix }}{{ ', '.join(paramDefines) }} +{% endif %} +{% endmacro %} +{# + #} {% macro DefineRpcInvocation(Component, ClassName, Property, InvokeFrom, HandleOn) %} {% set paramNames = [] %} {% set paramTypes = [] %} {% set paramDefines = [] %} {{ AutoComponentMacros.ParseRpcParams(Property, paramNames, paramTypes, paramDefines) }} -void {{ ClassName }}::{{ UpperFirst(Property.attrib['Name']) }}({{ ', '.join(paramDefines) }}) +void {{ ClassName }}::{{ UpperFirst(Property.attrib['Name']) }}({{ PrintRpcParameters('', paramDefines) }}) { constexpr Multiplayer::RpcIndex rpcId = static_cast({{ UpperFirst(Component.attrib['Name']) }}Internal::RemoteProcedure::{{ UpperFirst(Property.attrib['Name']) }}); {% if Property.attrib['IsReliable']|booleanTrue %} @@ -358,7 +366,7 @@ void {{ ClassName }}::{{ UpperFirst(Property.attrib['Name']) }}({{ ', '.join(par {% set paramTypes = [] %} {% set paramDefines = [] %} {{ AutoComponentMacros.ParseRpcParams(Property, paramNames, paramTypes, paramDefines) }} - ->Method("{{ UpperFirst(Property.attrib['Name']) }}", []({{ ClassName }}* self, {{ ', '.join(paramDefines) }}) { + ->Method("{{ UpperFirst(Property.attrib['Name']) }}", []({{ ClassName }}* self{{ PrintRpcParameters(', ', paramDefines) }}) { {% if (InvokeFrom == 'Server') %} self->{{ UpperFirst(Property.attrib['Name']) }}({{ ', '.join(paramNames) }}); {% elif (InvokeFrom == 'Authority') or (InvokeFrom == 'Autonomous') %} @@ -372,7 +380,7 @@ void {{ ClassName }}::{{ UpperFirst(Property.attrib['Name']) }}({{ ', '.join(par } {% endif %} }) - ->Method("{{ UpperFirst(Property.attrib['Name']) }}ByEntityId", [](AZ::EntityId id, {{ ', '.join(paramDefines) }}) { + ->Method("{{ UpperFirst(Property.attrib['Name']) }}ByEntityId", [](AZ::EntityId id{{ PrintRpcParameters(', ', paramDefines) }}) { AZ::Entity* entity = AZ::Interface::Get()->FindEntity(id); if (!entity) @@ -497,9 +505,9 @@ case {{ UpperFirst(Component.attrib['Name']) }}Internal::RemoteProcedure::{{ Upp if (m_controller) { AZ_Assert(GetNetBindComponent()->GetNetEntityRole() == Multiplayer::NetEntityRole::Authority, "Entity proxy does not have authority"); - m_controller->Handle{{ UpperFirst(Property.attrib['Name']) }}(invokingConnection, {{ ', '.join(rpcParamList) }}); + m_controller->Handle{{ UpperFirst(Property.attrib['Name']) }}(invokingConnection{{ PrintRpcParameters(', ', rpcParamList) }}); {% if (Property.attrib['GenerateEventBindings']|booleanTrue == true) %} - m_controller->Get{{ UpperFirst(Property.attrib['Name']) }}Event().Signal({{ ', '.join(rpcParamList) }}); + m_controller->Get{{ UpperFirst(Property.attrib['Name']) }}Event().Signal({{ PrintRpcParameters('', rpcParamList) }}); {% endif %} } else // Note that this rpc is marked reliable, trigger the appropriate rpc event so it can be forwarded @@ -513,15 +521,15 @@ case {{ UpperFirst(Component.attrib['Name']) }}Internal::RemoteProcedure::{{ Upp if (m_controller) { AZ_Assert(GetNetBindComponent()->GetNetEntityRole() == Multiplayer::NetEntityRole::Autonomous, "Entity proxy does not have autonomy"); - m_controller->Handle{{ UpperFirst(Property.attrib['Name']) }}(invokingConnection, {{ ', '.join(rpcParamList) }}); + m_controller->Handle{{ UpperFirst(Property.attrib['Name']) }}(invokingConnection{{ PrintRpcParameters(', ', rpcParamList) }}); {% if Property.attrib['GenerateEventBindings']|booleanTrue == true %} - m_controller->Get{{ UpperFirst(Property.attrib['Name']) }}Event().Signal({{ ', '.join(rpcParamList) }}); + m_controller->Get{{ UpperFirst(Property.attrib['Name']) }}Event().Signal({{ PrintRpcParameters('', rpcParamList) }}); {% endif %} } {% elif HandleOn == 'Client' %} - Handle{{ UpperFirst(Property.attrib['Name']) }}(invokingConnection, {{ ', '.join(rpcParamList) }}); + Handle{{ UpperFirst(Property.attrib['Name']) }}(invokingConnection{{ PrintRpcParameters(', ', rpcParamList) }}); {% if Property.attrib['GenerateEventBindings']|booleanTrue == true %} - m_{{ UpperFirst(Property.attrib['Name']) }}Event.Signal({{ ', '.join(rpcParamList) }}); + m_{{ UpperFirst(Property.attrib['Name']) }}Event.Signal({{ PrintRpcParameters('', rpcParamList) }}); {% endif %} {% endif %} } diff --git a/Gems/PhysX/Code/Editor/Source/ComponentModes/Joints/JointsComponentMode.cpp b/Gems/PhysX/Code/Editor/Source/ComponentModes/Joints/JointsComponentMode.cpp index ce95e27db3..08e31a89f0 100644 --- a/Gems/PhysX/Code/Editor/Source/ComponentModes/Joints/JointsComponentMode.cpp +++ b/Gems/PhysX/Code/Editor/Source/ComponentModes/Joints/JointsComponentMode.cpp @@ -307,7 +307,16 @@ namespace PhysX AZStd::vector JointsComponentMode::PopulateViewportUiImpl() { - return AZStd::vector(m_modeSelectionClusterIds.begin(), m_modeSelectionClusterIds.end()); + AZStd::vector ids; + ids.reserve(m_modeSelectionClusterIds.size()); + for (auto clusterid : m_modeSelectionClusterIds) + { + if (clusterid != AzToolsFramework::ViewportUi::InvalidClusterId) + { + ids.emplace_back(clusterid); + } + } + return ids; } void JointsComponentMode::SetCurrentMode(JointsComponentModeCommon::SubComponentModes::ModeType newMode, ButtonData& buttonData) @@ -353,31 +362,64 @@ namespace PhysX void JointsComponentMode::SetupSubModes(const AZ::EntityComponentIdPair& entityComponentIdPair) { - //create the 3 cluster groups - for (auto& clusterId : m_modeSelectionClusterIds) - { - AzToolsFramework::ViewportUi::ViewportUiRequestBus::EventResult( - clusterId, AzToolsFramework::ViewportUi::DefaultViewportId, - &AzToolsFramework::ViewportUi::ViewportUiRequestBus::Events::CreateCluster, - AzToolsFramework::ViewportUi::Alignment::TopLeft); - } - //retrieve the enabled sub components from the entity AZStd::vector subModesState; EditorJointRequestBus::EventResult(subModesState, entityComponentIdPair, &EditorJointRequests::GetSubComponentModesState); + //group 1 is always available so create it + AzToolsFramework::ViewportUi::ViewportUiRequestBus::EventResult( + m_modeSelectionClusterIds[static_cast(ClusterGroups::Group1)], AzToolsFramework::ViewportUi::DefaultViewportId, + &AzToolsFramework::ViewportUi::ViewportUiRequestBus::Events::CreateCluster, AzToolsFramework::ViewportUi::Alignment::TopLeft); + + //check if groups 2 and/or 3 need to be created + for (auto [modeType, _] : subModesState) + { + const AzToolsFramework::ViewportUi::ClusterId group2Id = GetClusterId(ClusterGroups::Group2); + const AzToolsFramework::ViewportUi::ClusterId group3Id = GetClusterId(ClusterGroups::Group3); + switch (modeType) + { + case JointsComponentModeCommon::SubComponentModes::ModeType::Damping: + case JointsComponentModeCommon::SubComponentModes::ModeType::Stiffness: + case JointsComponentModeCommon::SubComponentModes::ModeType::TwistLimits: + case JointsComponentModeCommon::SubComponentModes::ModeType::SwingLimits: + { + if (group2Id == AzToolsFramework::ViewportUi::InvalidClusterId) + { + AzToolsFramework::ViewportUi::ViewportUiRequestBus::EventResult( + m_modeSelectionClusterIds[static_cast(ClusterGroups::Group2)], + AzToolsFramework::ViewportUi::DefaultViewportId, + &AzToolsFramework::ViewportUi::ViewportUiRequestBus::Events::CreateCluster, + AzToolsFramework::ViewportUi::Alignment::TopLeft); + } + } + break; + case JointsComponentModeCommon::SubComponentModes::ModeType::MaxForce: + case JointsComponentModeCommon::SubComponentModes::ModeType::MaxTorque: + { + if (group3Id == AzToolsFramework::ViewportUi::InvalidClusterId) + { + AzToolsFramework::ViewportUi::ViewportUiRequestBus::EventResult( + m_modeSelectionClusterIds[static_cast(ClusterGroups::Group3)], + AzToolsFramework::ViewportUi::DefaultViewportId, + &AzToolsFramework::ViewportUi::ViewportUiRequestBus::Events::CreateCluster, + AzToolsFramework::ViewportUi::Alignment::TopLeft); + } + } + break; + default: + AZ_Error("Joints", false, "Joints component mode cluster UI setup found unknown sub mode."); + break; + } + //if both are created - break; + if (group2Id != AzToolsFramework::ViewportUi::InvalidClusterId && group3Id != AzToolsFramework::ViewportUi::InvalidClusterId) + { + break; + } + } + const AzToolsFramework::ViewportUi::ClusterId group1ClusterId = GetClusterId(ClusterGroups::Group1); const AzToolsFramework::ViewportUi::ClusterId group2ClusterId = GetClusterId(ClusterGroups::Group2); - //hide cluster 2, if something is added to it. it will make is visible - AzToolsFramework::ViewportUi::ViewportUiRequestBus::Event( - AzToolsFramework::ViewportUi::DefaultViewportId, &AzToolsFramework::ViewportUi::ViewportUiRequestBus::Events::SetClusterVisible, - group2ClusterId, false); - const AzToolsFramework::ViewportUi::ClusterId group3ClusterId = GetClusterId(ClusterGroups::Group3); - // hide cluster 3, if something is added to it. it will make is visible - AzToolsFramework::ViewportUi::ViewportUiRequestBus::Event( - AzToolsFramework::ViewportUi::DefaultViewportId, &AzToolsFramework::ViewportUi::ViewportUiRequestBus::Events::SetClusterVisible, - group3ClusterId, false); //translation and rotation are enabled for all joints in group 1 m_subModes[JointsComponentModeCommon::SubComponentModes::ModeType::Translation] = @@ -408,10 +450,6 @@ namespace PhysX Internal::RegisterClusterButton(group3ClusterId, "joints/MaxForce", SubModeData::MaxForceToolTip); m_buttonData[JointsComponentModeCommon::SubComponentModes::ModeType::MaxForce] = ButtonData{ group3ClusterId, buttonId }; - - AzToolsFramework::ViewportUi::ViewportUiRequestBus::Event( - AzToolsFramework::ViewportUi::DefaultViewportId, - &AzToolsFramework::ViewportUi::ViewportUiRequestBus::Events::SetClusterVisible, group3ClusterId, true); } break; case JointsComponentModeCommon::SubComponentModes::ModeType::MaxTorque: @@ -424,10 +462,6 @@ namespace PhysX Internal::RegisterClusterButton(group3ClusterId, "joints/MaxTorque", SubModeData::MaxTorqueToolTip); m_buttonData[JointsComponentModeCommon::SubComponentModes::ModeType::MaxTorque] = ButtonData{ group3ClusterId, buttonId }; - - AzToolsFramework::ViewportUi::ViewportUiRequestBus::Event( - AzToolsFramework::ViewportUi::DefaultViewportId, - &AzToolsFramework::ViewportUi::ViewportUiRequestBus::Events::SetClusterVisible, group3ClusterId, true); } break; case JointsComponentModeCommon::SubComponentModes::ModeType::Damping: @@ -439,10 +473,6 @@ namespace PhysX const AzToolsFramework::ViewportUi::ButtonId buttonId = Internal::RegisterClusterButton(group2ClusterId, "joints/Damping", SubModeData::DampingToolTip); m_buttonData[JointsComponentModeCommon::SubComponentModes::ModeType::Damping] = ButtonData{ group2ClusterId, buttonId }; - - AzToolsFramework::ViewportUi::ViewportUiRequestBus::Event( - AzToolsFramework::ViewportUi::DefaultViewportId, - &AzToolsFramework::ViewportUi::ViewportUiRequestBus::Events::SetClusterVisible, group2ClusterId, true); } break; case JointsComponentModeCommon::SubComponentModes::ModeType::Stiffness: @@ -455,10 +485,6 @@ namespace PhysX Internal::RegisterClusterButton(group2ClusterId, "joints/Stiffness", SubModeData::StiffnessToolTip); m_buttonData[JointsComponentModeCommon::SubComponentModes::ModeType::Stiffness] = ButtonData{ group2ClusterId, buttonId }; - - AzToolsFramework::ViewportUi::ViewportUiRequestBus::Event( - AzToolsFramework::ViewportUi::DefaultViewportId, - &AzToolsFramework::ViewportUi::ViewportUiRequestBus::Events::SetClusterVisible, group2ClusterId, true); } break; case JointsComponentModeCommon::SubComponentModes::ModeType::TwistLimits: @@ -473,10 +499,6 @@ namespace PhysX Internal::RegisterClusterButton(group2ClusterId, "joints/TwistLimits", SubModeData::TwistLimitsToolTip); m_buttonData[JointsComponentModeCommon::SubComponentModes::ModeType::TwistLimits] = ButtonData{ group2ClusterId, buttonId }; - - AzToolsFramework::ViewportUi::ViewportUiRequestBus::Event( - AzToolsFramework::ViewportUi::DefaultViewportId, - &AzToolsFramework::ViewportUi::ViewportUiRequestBus::Events::SetClusterVisible, group2ClusterId, true); } break; case JointsComponentModeCommon::SubComponentModes::ModeType::SwingLimits: @@ -489,10 +511,6 @@ namespace PhysX Internal::RegisterClusterButton(group2ClusterId, "joints/SwingLimits", SubModeData::SwingLimitsToolTip); m_buttonData[JointsComponentModeCommon::SubComponentModes::ModeType::SwingLimits] = ButtonData{ group2ClusterId, buttonId }; - - AzToolsFramework::ViewportUi::ViewportUiRequestBus::Event( - AzToolsFramework::ViewportUi::DefaultViewportId, - &AzToolsFramework::ViewportUi::ViewportUiRequestBus::Events::SetClusterVisible, group2ClusterId, true); } break; case JointsComponentModeCommon::SubComponentModes::ModeType::SnapPosition: @@ -517,6 +535,9 @@ namespace PhysX ButtonData{ group1ClusterId, buttonId }; } break; + default: + AZ_Error("Joints", false, "Joints component mode cluster button setup found unknown sub mode."); + break; } } @@ -560,10 +581,13 @@ namespace PhysX for (int i = 0; i < static_cast(ClusterGroups::GroupCount); i++) { - AzToolsFramework::ViewportUi::ViewportUiRequestBus::Event( - AzToolsFramework::ViewportUi::DefaultViewportId, - &AzToolsFramework::ViewportUi::ViewportUiRequestBus::Events::RegisterClusterEventHandler, m_modeSelectionClusterIds[i], - m_modeSelectionHandlers[i]); + if (m_modeSelectionClusterIds[i] != AzToolsFramework::ViewportUi::InvalidClusterId) + { + AzToolsFramework::ViewportUi::ViewportUiRequestBus::Event( + AzToolsFramework::ViewportUi::DefaultViewportId, + &AzToolsFramework::ViewportUi::ViewportUiRequestBus::Events::RegisterClusterEventHandler, + m_modeSelectionClusterIds[i], m_modeSelectionHandlers[i]); + } } // set the translate as enabled by default. @@ -588,10 +612,14 @@ namespace PhysX { for (auto clusterid : m_modeSelectionClusterIds) { - AzToolsFramework::ViewportUi::ViewportUiRequestBus::Event( - AzToolsFramework::ViewportUi::DefaultViewportId, &AzToolsFramework::ViewportUi::ViewportUiRequestBus::Events::RemoveCluster, - clusterid); + if (clusterid != AzToolsFramework::ViewportUi::InvalidClusterId) + { + AzToolsFramework::ViewportUi::ViewportUiRequestBus::Event( + AzToolsFramework::ViewportUi::DefaultViewportId, + &AzToolsFramework::ViewportUi::ViewportUiRequestBus::Events::RemoveCluster, clusterid); + } } + m_modeSelectionClusterIds.assign(static_cast(ClusterGroups::GroupCount), AzToolsFramework::ViewportUi::InvalidClusterId); } AzToolsFramework::ViewportUi::ClusterId JointsComponentMode::GetClusterId(ClusterGroups group) diff --git a/Gems/ScriptCanvas/Code/Editor/View/Widgets/CanvasWidget.cpp b/Gems/ScriptCanvas/Code/Editor/View/Widgets/CanvasWidget.cpp index e448f3ea40..dfa39b0047 100644 --- a/Gems/ScriptCanvas/Code/Editor/View/Widgets/CanvasWidget.cpp +++ b/Gems/ScriptCanvas/Code/Editor/View/Widgets/CanvasWidget.cpp @@ -84,9 +84,10 @@ namespace ScriptCanvasEditor { m_assetId = assetId; - EditorGraphRequests* editorGraphRequests = EditorGraphRequestBus::FindFirstHandler(m_scriptCanvasId); - - editorGraphRequests->SetAssetId(m_assetId); + if (EditorGraphRequests* editorGraphRequests = EditorGraphRequestBus::FindFirstHandler(m_scriptCanvasId)) + { + editorGraphRequests->SetAssetId(m_assetId); + } } const GraphCanvas::ViewId& CanvasWidget::GetViewId() const diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Asset/RuntimeAsset.cpp b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Asset/RuntimeAsset.cpp index 0dcd8ebbbd..d55fcf7838 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Asset/RuntimeAsset.cpp +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Asset/RuntimeAsset.cpp @@ -59,6 +59,7 @@ namespace ScriptCanvas m_script = AZStd::move(other.m_script); m_requiredAssets = AZStd::move(other.m_requiredAssets); m_requiredScriptEvents = AZStd::move(other.m_requiredScriptEvents); + m_areStaticsInitialized = AZStd::move(other.m_areStaticsInitialized); } return *this; diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Asset/RuntimeAsset.h b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Asset/RuntimeAsset.h index 24c83b20a1..8c5da5ac07 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Asset/RuntimeAsset.h +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Asset/RuntimeAsset.h @@ -76,6 +76,9 @@ namespace ScriptCanvas AZStd::vector m_activationInputStorage; Execution::ActivationInputRange m_activationInputRange; + // used to initialize statics only once, and not necessarily on the loading thread + bool m_areStaticsInitialized = false; + bool RequiresStaticInitialization() const; bool RequiresDependencyConstructionParameters() const; diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Asset/RuntimeAssetHandler.cpp b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Asset/RuntimeAssetHandler.cpp index df7b07d37b..05b6f4397b 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Asset/RuntimeAssetHandler.cpp +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Asset/RuntimeAssetHandler.cpp @@ -94,7 +94,6 @@ namespace ScriptCanvas RuntimeAsset* runtimeAsset = asset.GetAs(); AZ_Assert(runtimeAsset, "RuntimeAssetHandler::InitAsset This should be a Script Canvas runtime asset, as this is the only type this handler processes!"); Execution::Context::InitializeActivationData(runtimeAsset->GetData()); - Execution::InitializeInterpretedStatics(runtimeAsset->GetData()); } } @@ -157,4 +156,5 @@ namespace ScriptCanvas } } } + } diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Execution/ExecutionState.h b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Execution/ExecutionState.h index 8c2f0a67c4..89ddf178f3 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Execution/ExecutionState.h +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Execution/ExecutionState.h @@ -19,6 +19,10 @@ #include #include +#if !defined(_RELEASE) +#define SCRIPT_CANVAS_RUNTIME_ASSET_CHECK +#endif + namespace AZ { class ReflectContext; diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Execution/Interpreted/ExecutionInterpretedAPI.cpp b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Execution/Interpreted/ExecutionInterpretedAPI.cpp index 4d7c8a2cda..bba56847ce 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Execution/Interpreted/ExecutionInterpretedAPI.cpp +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Execution/Interpreted/ExecutionInterpretedAPI.cpp @@ -506,6 +506,8 @@ namespace ScriptCanvas #if defined(AZ_PROFILE_BUILD) || defined(AZ_DEBUG_BUILD) Execution::InitializeFromLuaStackFunctions(const_cast(runtimeData.m_debugMap)); #endif + AZ_WarningOnce("ScriptCanvas", !runtimeData.m_areStaticsInitialized, "ScriptCanvas runtime data already initalized"); + if (runtimeData.RequiresStaticInitialization()) { AZ::ScriptLoadResult result{}; diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Execution/Interpreted/ExecutionStateInterpreted.cpp b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Execution/Interpreted/ExecutionStateInterpreted.cpp index 9224d4f9a4..a92c13ac63 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Execution/Interpreted/ExecutionStateInterpreted.cpp +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Execution/Interpreted/ExecutionStateInterpreted.cpp @@ -6,14 +6,13 @@ * */ -#include "ExecutionStateInterpreted.h" - #include #include #include - -#include "Execution/Interpreted/ExecutionStateInterpretedUtility.h" -#include "Execution/RuntimeComponent.h" +#include +#include +#include +#include namespace ExecutionStateInterpretedCpp { @@ -33,7 +32,29 @@ namespace ScriptCanvas ExecutionStateInterpreted::ExecutionStateInterpreted(const ExecutionStateConfig& config) : ExecutionState(config) , m_interpretedAsset(config.runtimeData.m_script) - {} + { + RuntimeAsset* runtimeAsset = config.asset.Get(); + +#if defined(SCRIPT_CANVAS_RUNTIME_ASSET_CHECK) + if (!runtimeAsset) + { + AZ_Error("ScriptCanvas", false + , "ExecutionStateInterpreted created with ExecutionStateConfig that contained bad runtime asset data. %s" + , config.asset.GetId().ToString().data()); + return; + } +#else + AZ_Assert(false + , "ExecutionStateInterpreted created with ExecutionStateConfig that contained bad runtime asset data. %s" + , config.asset.GetId().ToString().data()); +#endif + + if (!runtimeAsset->GetData().m_areStaticsInitialized) + { + runtimeAsset->GetData().m_areStaticsInitialized = true; + Execution::InitializeInterpretedStatics(runtimeAsset->GetData()); + } + } void ExecutionStateInterpreted::ClearLuaRegistryIndex() { diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Execution/RuntimeComponent.cpp b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Execution/RuntimeComponent.cpp index 5fe5c32a48..930b84abe4 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Execution/RuntimeComponent.cpp +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Execution/RuntimeComponent.cpp @@ -19,9 +19,8 @@ #include #include -#if !defined(_RELEASE) -#define SCRIPT_CANVAS_RUNTIME_ASSET_CHECK -#endif +#include +#include AZ_DECLARE_BUDGET(ScriptCanvas); @@ -112,11 +111,13 @@ namespace ScriptCanvas #if defined(SCRIPT_CANVAS_RUNTIME_ASSET_CHECK) if (!m_runtimeOverrides.m_runtimeAsset.Get()) { - AZ_Error("ScriptCanvas", false, "RuntimeComponent::m_runtimeAsset AssetId: %s was valid, but the data was not pre-loaded, so this script will not run", m_runtimeOverrides.m_runtimeAsset.GetId().ToString().data()); + AZ_Error("ScriptCanvas", false, "RuntimeComponent::m_runtimeAsset AssetId: %s was valid, but the data was not pre-loaded, so this script will not run" + , m_runtimeOverrides.m_runtimeAsset.GetId().ToString().data()); return; } #else - AZ_Assert(m_runtimeOverrides.m_runtimeAsset.Get(), "RuntimeComponent::m_runtimeAsset AssetId: %s was valid, but the data was not pre-loaded, so this script will not run", m_runtimeOverrides.m_runtimeAsset.GetId().ToString().data()); + AZ_Assert(m_runtimeOverrides.m_runtimeAsset.Get(), "RuntimeComponent::m_runtimeAsset AssetId: %s was valid, but the data was not pre-loaded, so this script will not run" + , m_runtimeOverrides.m_runtimeAsset.GetId().ToString().data()); #endif AZ_PROFILE_SCOPE(ScriptCanvas, "RuntimeComponent::InitializeExecution (%s)", m_runtimeOverrides.m_runtimeAsset.GetId().ToString().c_str()); @@ -126,11 +127,13 @@ namespace ScriptCanvas #if defined(SCRIPT_CANVAS_RUNTIME_ASSET_CHECK) if (!m_executionState) { - AZ_Error("ScriptCanvas", false, "RuntimeComponent::m_runtimeAsset AssetId: %s failed to create an execution state, possibly due to missing dependent asset, script will not run", m_runtimeOverrides.m_runtimeAsset.GetId().ToString().data()); + AZ_Error("ScriptCanvas", false, "RuntimeComponent::m_runtimeAsset AssetId: %s failed to create an execution state, possibly due to missing dependent asset, script will not run" + , m_runtimeOverrides.m_runtimeAsset.GetId().ToString().data()); return; } #else - AZ_Assert(m_executionState, "RuntimeComponent::m_runtimeAsset AssetId: %s failed to create an execution state, possibly due to missing dependent asset, script will not run", m_runtimeOverrides.m_runtimeAsset.GetId().ToString().data()); + AZ_Assert(m_executionState, "RuntimeComponent::m_runtimeAsset AssetId: %s failed to create an execution state, possibly due to missing dependent asset, script will not run" + , m_runtimeOverrides.m_runtimeAsset.GetId().ToString().data()); #endif AZ::EntityBus::Handler::BusConnect(GetEntityId()); @@ -179,4 +182,3 @@ namespace ScriptCanvas } } -#undef SCRIPT_CANVAS_RUNTIME_ASSET_CHECK diff --git a/Gems/StartingPointCamera/Code/Source/CameraLookAtBehaviors/SlideAlongAxisBasedOnAngle.cpp b/Gems/StartingPointCamera/Code/Source/CameraLookAtBehaviors/SlideAlongAxisBasedOnAngle.cpp index 5031f149b8..1d8d6adf6c 100644 --- a/Gems/StartingPointCamera/Code/Source/CameraLookAtBehaviors/SlideAlongAxisBasedOnAngle.cpp +++ b/Gems/StartingPointCamera/Code/Source/CameraLookAtBehaviors/SlideAlongAxisBasedOnAngle.cpp @@ -9,8 +9,8 @@ #include "SlideAlongAxisBasedOnAngle.h" #include "StartingPointCamera/StartingPointCameraUtilities.h" #include -#include #include +#include namespace Camera { @@ -32,31 +32,43 @@ namespace Camera AZ::EditContext* editContext = serializeContext->GetEditContext(); if (editContext) { - editContext->Class("SlideAlongAxisBasedOnAngle", "Slide 0..SlideDistance along Axis based on Angle Type. Maps from 90..-90 degrees") - ->ClassElement(AZ::Edit::ClassElements::EditorData, "") - ->DataElement(AZ::Edit::UIHandlers::ComboBox, &SlideAlongAxisBasedOnAngle::m_axisToSlideAlong, "Axis to slide along", "The Axis to slide along") - ->EnumAttribute(RelativeAxisType::ForwardBackward, "Forwards and Backwards") - ->EnumAttribute(RelativeAxisType::LeftRight, "Right and Left") - ->EnumAttribute(RelativeAxisType::UpDown, "Up and Down") - ->DataElement(AZ::Edit::UIHandlers::ComboBox, &SlideAlongAxisBasedOnAngle::m_angleTypeToChangeFor, "Angle Type", "The angle type to base the slide off of") - ->EnumAttribute(EulerAngleType::Pitch, "Pitch") - ->EnumAttribute(EulerAngleType::Roll, "Roll") - ->EnumAttribute(EulerAngleType::Yaw, "Yaw") - ->DataElement(0, &SlideAlongAxisBasedOnAngle::m_maximumPositiveSlideDistance, "Max Positive Slide Distance", "The maximum distance to slide in the positive") - ->Attribute(AZ::Edit::Attributes::Suffix, "m") - ->DataElement(0, &SlideAlongAxisBasedOnAngle::m_maximumNegativeSlideDistance, "Max Negative Slide Distance", "The maximum distance to slide in the negative") - ->Attribute(AZ::Edit::Attributes::Suffix, "m") - ->ClassElement(AZ::Edit::ClassElements::Group, "Vector Components To Ignore") - ->Attribute(AZ::Edit::Attributes::AutoExpand, true) - ->DataElement(0, &SlideAlongAxisBasedOnAngle::m_ignoreX, "X", "When active, the X Component will be ignored.") - ->DataElement(0, &SlideAlongAxisBasedOnAngle::m_ignoreY, "Y", "When active, the Y Component will be ignored.") - ->DataElement(0, &SlideAlongAxisBasedOnAngle::m_ignoreZ, "Z", "When active, the Z Component will be ignored.") + editContext->Class("SlideAlongAxisBasedOnAngle", + "Slide 0..SlideDistance along Axis based on Angle Type. Maps from 90..-90 degrees") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->DataElement(AZ::Edit::UIHandlers::ComboBox, &SlideAlongAxisBasedOnAngle::m_axisToSlideAlong, "Axis to slide along", + "The Axis to slide along") + ->EnumAttribute(RelativeAxisType::ForwardBackward, "Forwards and Backwards") + ->EnumAttribute(RelativeAxisType::LeftRight, "Right and Left") + ->EnumAttribute(RelativeAxisType::UpDown, "Up and Down") + ->DataElement(AZ::Edit::UIHandlers::ComboBox, &SlideAlongAxisBasedOnAngle::m_angleTypeToChangeFor, "Angle Type", + "The angle type to base the slide off of") + ->EnumAttribute(EulerAngleType::Pitch, "Pitch") + ->EnumAttribute(EulerAngleType::Roll, "Roll") + ->EnumAttribute(EulerAngleType::Yaw, "Yaw") + ->DataElement(0, &SlideAlongAxisBasedOnAngle::m_maximumPositiveSlideDistance, "Max Positive Slide Distance", + "The maximum distance to slide in the positive") + ->Attribute(AZ::Edit::Attributes::Suffix, "m") + ->DataElement(0, &SlideAlongAxisBasedOnAngle::m_maximumNegativeSlideDistance, "Max Negative Slide Distance", + "The maximum distance to slide in the negative") + ->Attribute(AZ::Edit::Attributes::Suffix, "m") + ->ClassElement(AZ::Edit::ClassElements::Group, "Vector Components To Ignore") + ->Attribute(AZ::Edit::Attributes::AutoExpand, true) + ->DataElement(0, &SlideAlongAxisBasedOnAngle::m_ignoreX, "X", "When active, the X Component will be ignored.") + ->Attribute(AZ::Edit::Attributes::ReadOnly, &SlideAlongAxisBasedOnAngle::YAndZIgnored) + ->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::AttributesAndValues) + ->DataElement(0, &SlideAlongAxisBasedOnAngle::m_ignoreY, "Y", "When active, the Y Component will be ignored.") + ->Attribute(AZ::Edit::Attributes::ReadOnly, &SlideAlongAxisBasedOnAngle::XAndZIgnored) + ->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::AttributesAndValues) + ->DataElement(0, &SlideAlongAxisBasedOnAngle::m_ignoreZ, "Z", "When active, the Z Component will be ignored.") + ->Attribute(AZ::Edit::Attributes::ReadOnly, &SlideAlongAxisBasedOnAngle::XAndYIgnored) + ->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::AttributesAndValues) ; } } } - void SlideAlongAxisBasedOnAngle::AdjustLookAtTarget([[maybe_unused]] float deltaTime, [[maybe_unused]] const AZ::Transform& targetTransform, AZ::Transform& outLookAtTargetTransform) + void SlideAlongAxisBasedOnAngle::AdjustLookAtTarget( + [[maybe_unused]] float deltaTime, [[maybe_unused]] const AZ::Transform& targetTransform, AZ::Transform& outLookAtTargetTransform) { float angle = GetEulerAngleFromTransform(outLookAtTargetTransform, m_angleTypeToChangeFor); float currentPositionOnRange = -angle / AZ::Constants::HalfPi; @@ -67,4 +79,20 @@ namespace Camera outLookAtTargetTransform.SetTranslation(outLookAtTargetTransform.GetTranslation() + basis * currentPositionOnRange * slideScale); } -} + + bool SlideAlongAxisBasedOnAngle::XAndYIgnored() const + { + return m_ignoreX && m_ignoreY; + } + + bool SlideAlongAxisBasedOnAngle::XAndZIgnored() const + { + return m_ignoreX && m_ignoreZ; + } + + bool SlideAlongAxisBasedOnAngle::YAndZIgnored() const + { + return m_ignoreY && m_ignoreZ; + } + +} // namespace Camera diff --git a/Gems/StartingPointCamera/Code/Source/CameraLookAtBehaviors/SlideAlongAxisBasedOnAngle.h b/Gems/StartingPointCamera/Code/Source/CameraLookAtBehaviors/SlideAlongAxisBasedOnAngle.h index 2c756d1ac7..e517b6ffbd 100644 --- a/Gems/StartingPointCamera/Code/Source/CameraLookAtBehaviors/SlideAlongAxisBasedOnAngle.h +++ b/Gems/StartingPointCamera/Code/Source/CameraLookAtBehaviors/SlideAlongAxisBasedOnAngle.h @@ -6,11 +6,11 @@ * */ #pragma once -#include -#include -#include #include "StartingPointCamera/StartingPointCameraConstants.h" +#include #include +#include +#include namespace Camera { @@ -38,6 +38,10 @@ namespace Camera void Activate(AZ::EntityId) override {} void Deactivate() override {} + bool XAndYIgnored() const; + bool XAndZIgnored() const; + bool YAndZIgnored() const; + private: ////////////////////////////////////////////////////////////////////////// // Reflected data diff --git a/Gems/Terrain/Code/CMakeLists.txt b/Gems/Terrain/Code/CMakeLists.txt index 4b2f32e172..d532284350 100644 --- a/Gems/Terrain/Code/CMakeLists.txt +++ b/Gems/Terrain/Code/CMakeLists.txt @@ -26,8 +26,6 @@ ly_add_target( Gem::GradientSignal Gem::SurfaceData Gem::LmbrCentral - - ) ly_add_target( @@ -49,14 +47,14 @@ ly_add_target( ) # the above module is for use in all client/server types -ly_create_alias(NAME Terrain.Servers NAMESPACE Gem TARGETS Gem::Terrain) -ly_create_alias(NAME Terrain.Clients NAMESPACE Gem TARGETS Gem::Terrain) +ly_create_alias(NAME Terrain.Servers NAMESPACE Gem TARGETS Gem::Terrain Gem::SurfaceData.Servers Gem::GradientSignal.Servers) +ly_create_alias(NAME Terrain.Clients NAMESPACE Gem TARGETS Gem::Terrain Gem::SurfaceData.Clients Gem::GradientSignal.Clients) # If we are on a host platform, we want to add the host tools targets like the Terrain.Editor target which # will also depend on Terrain.Static if(PAL_TRAIT_BUILD_HOST_TOOLS) ly_add_target( - NAME Terrain.Editor MODULE + NAME Terrain.Editor GEM_MODULE NAMESPACE Gem AUTOMOC FILES_CMAKE @@ -78,8 +76,8 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS) ) # the above module is for use in dev tool situations - ly_create_alias(NAME Terrain.Builders NAMESPACE Gem TARGETS Gem::Terrain.Editor) - ly_create_alias(NAME Terrain.Tools NAMESPACE Gem TARGETS Gem::Terrain.Editor) + ly_create_alias(NAME Terrain.Builders NAMESPACE Gem TARGETS Gem::Terrain.Editor Gem::SurfaceData.Builders Gem::GradientSignal.Builders) + ly_create_alias(NAME Terrain.Tools NAMESPACE Gem TARGETS Gem::Terrain.Editor Gem::SurfaceData.Tools Gem::GradientSignal.Tools) endif() ################################################################################ diff --git a/Gems/Vegetation/Code/Source/InstanceSystemComponent.cpp b/Gems/Vegetation/Code/Source/InstanceSystemComponent.cpp index dd38daa633..85ba4bda61 100644 --- a/Gems/Vegetation/Code/Source/InstanceSystemComponent.cpp +++ b/Gems/Vegetation/Code/Source/InstanceSystemComponent.cpp @@ -15,9 +15,6 @@ #include #include -#include -#include - #include #include #include diff --git a/cmake/Platform/Common/Install_common.cmake b/cmake/Platform/Common/Install_common.cmake index 5e9f6ad0ce..7904bbe611 100644 --- a/cmake/Platform/Common/Install_common.cmake +++ b/cmake/Platform/Common/Install_common.cmake @@ -202,18 +202,16 @@ function(ly_setup_target OUTPUT_CONFIGURED_TARGET ALIAS_TARGET_NAME absolute_tar endforeach() list(JOIN INCLUDE_DIRECTORIES_PLACEHOLDER "\n" INCLUDE_DIRECTORIES_PLACEHOLDER) - string(REPEAT " " 8 PLACEHOLDER_INDENT) - get_target_property(RUNTIME_DEPENDENCIES_PLACEHOLDER ${TARGET_NAME} MANUALLY_ADDED_DEPENDENCIES) - if(RUNTIME_DEPENDENCIES_PLACEHOLDER) # not found properties return the name of the variable with a "-NOTFOUND" at the end, here we set it to empty if not found - set(RUNTIME_DEPENDENCIES_PLACEHOLDER "${PLACEHOLDER_INDENT}${RUNTIME_DEPENDENCIES_PLACEHOLDER}") - list(JOIN RUNTIME_DEPENDENCIES_PLACEHOLDER "\n${PLACEHOLDER_INDENT}" RUNTIME_DEPENDENCIES_PLACEHOLDER) - else() - unset(RUNTIME_DEPENDENCIES_PLACEHOLDER) - endif() - string(REPEAT " " 12 PLACEHOLDER_INDENT) get_property(interface_build_dependencies_props TARGET ${TARGET_NAME} PROPERTY LY_DELAYED_LINK) unset(INTERFACE_BUILD_DEPENDENCIES_PLACEHOLDER) + # We can have private build dependencies that contains direct or indirect runtime dependencies. + # Since imported targets cannot contain build dependencies, we need another way to propagate the runtime dependencies. + # We dont want to put such dependencies in the interface because a user can mistakenly use a symbol that is not available + # when using the engine from source (and that the author of the target didn't want to set public). + # To overcome this, we will actually expose the private build dependencies as runtime dependencies. Our runtime dependency + # algorithm will walk recursively also through static libraries and will only copy binaries to the output. + unset(RUNTIME_DEPENDENCIES_PLACEHOLDER) if(interface_build_dependencies_props) cmake_parse_arguments(build_deps "" "" "PRIVATE;PUBLIC;INTERFACE" ${interface_build_dependencies_props}) # Interface and public dependencies should always be exposed @@ -226,6 +224,8 @@ function(ly_setup_target OUTPUT_CONFIGURED_TARGET ALIAS_TARGET_NAME absolute_tar if("${target_type}" STREQUAL "STATIC_LIBRARY") set(build_deps_target "${build_deps_target};${build_deps_PRIVATE}") endif() + # But we will also pass the private dependencies as runtime dependencies (note the comment above) + set(RUNTIME_DEPENDENCIES_PLACEHOLDER ${build_deps_PRIVATE}) foreach(build_dependency IN LISTS build_deps_target) # Skip wrapping produced when targets are not created in the same directory if(build_dependency) @@ -235,6 +235,18 @@ function(ly_setup_target OUTPUT_CONFIGURED_TARGET ALIAS_TARGET_NAME absolute_tar endif() list(JOIN INTERFACE_BUILD_DEPENDENCIES_PLACEHOLDER "\n" INTERFACE_BUILD_DEPENDENCIES_PLACEHOLDER) + string(REPEAT " " 8 PLACEHOLDER_INDENT) + get_target_property(manually_added_dependencies ${TARGET_NAME} MANUALLY_ADDED_DEPENDENCIES) + if(manually_added_dependencies) # not found properties return the name of the variable with a "-NOTFOUND" at the end, here we set it to empty if not found + list(APPEND RUNTIME_DEPENDENCIES_PLACEHOLDER ${manually_added_dependencies}) + endif() + if(RUNTIME_DEPENDENCIES_PLACEHOLDER) + set(RUNTIME_DEPENDENCIES_PLACEHOLDER "${PLACEHOLDER_INDENT}${RUNTIME_DEPENDENCIES_PLACEHOLDER}") + list(JOIN RUNTIME_DEPENDENCIES_PLACEHOLDER "\n${PLACEHOLDER_INDENT}" RUNTIME_DEPENDENCIES_PLACEHOLDER) + else() + unset(RUNTIME_DEPENDENCIES_PLACEHOLDER) + endif() + string(REPEAT " " 8 PLACEHOLDER_INDENT) # If a target has an LY_PROJECT_NAME property, forward that property to new target get_target_property(target_project_association ${TARGET_NAME} LY_PROJECT_NAME) diff --git a/scripts/o3de/o3de/download.py b/scripts/o3de/o3de/download.py index 9e576d64b5..9b7c8cc782 100644 --- a/scripts/o3de/o3de/download.py +++ b/scripts/o3de/o3de/download.py @@ -105,7 +105,7 @@ def download_o3de_object(object_name: str, default_folder_name: str, dest_path: origin_uri = downloadable_object_data['originuri'] parsed_uri = urllib.parse.urlparse(origin_uri) - download_zip_result = utils.download_zip_file(parsed_uri, download_zip_path, download_progress_callback) + download_zip_result = utils.download_zip_file(parsed_uri, download_zip_path, force_overwrite, download_progress_callback) if download_zip_result != 0: return download_zip_result diff --git a/scripts/o3de/o3de/repo.py b/scripts/o3de/o3de/repo.py index a6b1505761..c8fac38605 100644 --- a/scripts/o3de/o3de/repo.py +++ b/scripts/o3de/o3de/repo.py @@ -74,11 +74,11 @@ def process_add_o3de_repo(file_name: str or pathlib.Path, manifest_json_uri = f'{o3de_object_uri}/{manifest_json}' manifest_json_sha256 = hashlib.sha256(manifest_json_uri.encode()) cache_file = cache_folder / str(manifest_json_sha256.hexdigest() + '.json') - if not cache_file.is_file(): - parsed_uri = urllib.parse.urlparse(manifest_json_uri) - download_file_result = utils.download_file(parsed_uri, cache_file) - if download_file_result != 0: - return download_file_result + + parsed_uri = urllib.parse.urlparse(manifest_json_uri) + download_file_result = utils.download_file(parsed_uri, cache_file, True) + if download_file_result != 0: + return download_file_result # Having a repo is also optional repo_list = [] @@ -96,7 +96,7 @@ def process_add_o3de_repo(file_name: str or pathlib.Path, 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) + download_file_result = utils.download_file(parsed_uri, cache_file, True) if download_file_result != 0: return download_file_result @@ -165,7 +165,7 @@ def refresh_repo(repo_uri: str, repo_sha256 = hashlib.sha256(parsed_uri.geturl().encode()) cache_file = cache_folder / str(repo_sha256.hexdigest() + '.json') - download_file_result = utils.download_file(parsed_uri, cache_file) + download_file_result = utils.download_file(parsed_uri, cache_file, True) if download_file_result != 0: return download_file_result @@ -178,12 +178,7 @@ def refresh_repo(repo_uri: str, def refresh_repos() -> int: json_data = manifest.load_o3de_manifest() - - # clear the cache cache_folder = manifest.get_o3de_cache_folder() - shutil.rmtree(cache_folder) - cache_folder = manifest.get_o3de_cache_folder() # will recreate it - result = 0 # set will stop circular references diff --git a/scripts/o3de/o3de/utils.py b/scripts/o3de/o3de/utils.py old mode 100755 new mode 100644 index 9872c0779b..88f84ae75e --- a/scripts/o3de/o3de/utils.py +++ b/scripts/o3de/o3de/utils.py @@ -117,7 +117,7 @@ def backup_folder(folder: str or pathlib.Path) -> None: if backup_folder_name.is_dir(): renamed = True -def download_file(parsed_uri, download_path: pathlib.Path, force_overwrite, download_progress_callback = None) -> int: +def download_file(parsed_uri, download_path: pathlib.Path, force_overwrite: bool = False, download_progress_callback = None) -> int: """ :param parsed_uri: uniform resource identifier to zip file to download :param download_path: location path on disk to download file @@ -140,9 +140,9 @@ def download_file(parsed_uri, download_path: pathlib.Path, force_overwrite, down download_file_size = s.headers['content-length'] except KeyError: pass - def download_progress(blocks): - if download_progress_callback and download_file_size: - return download_progress_callback(int(blocks/int(download_file_size) * 100)) + def download_progress(downloaded_bytes): + if download_progress_callback: + return download_progress_callback(int(downloaded_bytes), int(download_file_size)) return False with download_path.open('wb') as f: download_cancelled = copyfileobj(s, f, download_progress) @@ -157,12 +157,12 @@ def download_file(parsed_uri, download_path: pathlib.Path, force_overwrite, down return 0 -def download_zip_file(parsed_uri, download_zip_path: pathlib.Path, download_progress_callback = None) -> int: +def download_zip_file(parsed_uri, download_zip_path: pathlib.Path, force_overwrite: bool, download_progress_callback = None) -> int: """ :param parsed_uri: uniform resource identifier to zip file to download :param download_zip_path: path to output zip file """ - download_file_result = download_file(parsed_uri, download_zip_path, download_progress_callback) + download_file_result = download_file(parsed_uri, download_zip_path, force_overwrite, download_progress_callback) if download_file_result != 0: return download_file_result