diff --git a/.clang-format b/.clang-format index 565f28130e..ef3ce64192 100644 --- a/.clang-format +++ b/.clang-format @@ -13,6 +13,7 @@ AllowShortFunctionsOnASingleLine: None AllowShortLambdasOnASingleLine: None AlwaysBreakAfterReturnType: None AlwaysBreakTemplateDeclarations: true +BinPackParameters: false BreakBeforeBraces: Custom BraceWrapping: AfterClass: true @@ -46,7 +47,7 @@ SortIncludes: true SpaceAfterLogicalNot: false SpaceAfterTemplateKeyword: false SpaceBeforeAssignmentOperators: true -SpaceBeforeCpp11BracedList: true +SpaceBeforeCpp11BracedList: false SpaceBeforeCtorInitializerColon: true SpaceBeforeInheritanceColon: true SpaceBeforeParens: ControlStatements diff --git a/.gitignore b/.gitignore index c3af907e97..664680c5bf 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ __pycache__ AssetProcessorTemp/** [Bb]uild/** [Cc]ache/ +/install/ Editor/EditorEventLog.xml Editor/EditorLayout.xml **/*egg-info/** @@ -19,3 +20,4 @@ _savebackup/ TestResults/** *.swatches /imgui.ini +/scripts/project_manager/logs/ diff --git a/Assets/Engine/SeedAssetList.seed b/Assets/Engine/SeedAssetList.seed index 45a02f7682..77ec509721 100644 --- a/Assets/Engine/SeedAssetList.seed +++ b/Assets/Engine/SeedAssetList.seed @@ -67,106 +67,98 @@ - + - + - + - + - + - - - - - - - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -264,109 +256,101 @@ - - - - - - - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -387,498 +371,474 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - - - - - - - - + - + - + - + - + - + - + - + - + - + - + - + - - - - - - - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - - - - - - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -896,14 +856,6 @@ - - - - - - - - @@ -928,29 +880,13 @@ - - - - - - - - - - - - - - - - - + - + @@ -1451,146 +1387,146 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -1699,42 +1635,42 @@ - + - + - + - + - + - + - + - + - + - + diff --git a/AutomatedTesting/EngineFinder.cmake b/AutomatedTesting/EngineFinder.cmake index 1fdcef2b56..fbbe3d8cfe 100644 --- a/AutomatedTesting/EngineFinder.cmake +++ b/AutomatedTesting/EngineFinder.cmake @@ -20,31 +20,49 @@ if(json_error) message(FATAL_ERROR "Unable to read key 'engine' from 'project.json', error: ${json_error}") endif() -# Read the list of paths from ~.o3de/o3de_manifest.json -file(TO_CMAKE_PATH "$ENV{USERPROFILE}" home_directory) # Windows -if((NOT home_directory) OR (NOT EXISTS ${home_directory})) - file(TO_CMAKE_PATH "$ENV{HOME}" home_directory)# Unix +if(DEFINED ENV{USERPROFILE} AND EXISTS $ENV{USERPROFILE}) + set(manifest_path $ENV{USERPROFILE}/.o3de/o3de_manifest.json) # Windows +else() + set(manifest_path $ENV{HOME}/.o3de/o3de_manifest.json) # Unix endif() -if (NOT home_directory) - message(FATAL_ERROR "Cannot find user home directory, the o3de manifest cannot be found") -endif() -# Set manifest path to path in the user home directory -set(manifest_path ${home_directory}/.o3de/o3de_manifest.json) - +# Read the ~/.o3de/o3de_manifest.json file and look through the 'engines_path' object. +# Find a key that matches LY_ENGINE_NAME_TO_USE and use that as the engine path. if(EXISTS ${manifest_path}) file(READ ${manifest_path} manifest_json) - string(JSON engines_count ERROR_VARIABLE json_error LENGTH ${manifest_json} engines) + + string(JSON engines_path_count ERROR_VARIABLE json_error LENGTH ${manifest_json} engines_path) if(json_error) - message(FATAL_ERROR "Unable to read key 'engines' from '${manifest_path}', error: ${json_error}") + message(FATAL_ERROR "Unable to read key 'engines_path' from '${manifest_path}', error: ${json_error}") endif() - math(EXPR engines_count "${engines_count}-1") - foreach(engine_path_index RANGE ${engines_count}) - string(JSON engine_path ERROR_VARIABLE json_error GET ${manifest_json} engines ${engine_path_index}) - if(${json_error}) - message(FATAL_ERROR "Unable to read engines[${engine_path_index}] '${manifest_path}', error: ${json_error}") + string(JSON engines_path_type ERROR_VARIABLE json_error TYPE ${manifest_json} engines_path) + if(json_error OR NOT ${engines_path_type} STREQUAL "OBJECT") + message(FATAL_ERROR "Type of 'engines_path' in '${manifest_path}' is not a JSON Object, error: ${json_error}") + endif() + + math(EXPR engines_path_count "${engines_path_count}-1") + foreach(engine_path_index RANGE ${engines_path_count}) + string(JSON engine_name ERROR_VARIABLE json_error MEMBER ${manifest_json} engines_path ${engine_path_index}) + if(json_error) + message(FATAL_ERROR "Unable to read 'engines_path/${engine_path_index}' from '${manifest_path}', error: ${json_error}") + endif() + + if(LY_ENGINE_NAME_TO_USE STREQUAL engine_name) + string(JSON engine_path ERROR_VARIABLE json_error GET ${manifest_json} engines_path ${engine_name}) + if(json_error) + message(FATAL_ERROR "Unable to read value from 'engines_path/${engine_name}', error: ${json_error}") + endif() + + if(engine_path) + list(APPEND CMAKE_MODULE_PATH "${engine_path}/cmake") + break() + endif() endif() - list(APPEND CMAKE_MODULE_PATH "${engine_path}/cmake") endforeach() +else() + # If the user is passing CMAKE_MODULE_PATH we assume thats where we will find the engine + if(NOT CMAKE_MODULE_PATH) + message(FATAL_ERROR "Engine registration is required before configuring a project. Please register an engine by running 'scripts/o3de register --this-engine'") + endif() endif() diff --git a/AutomatedTesting/Gem/Code/CMakeLists.txt b/AutomatedTesting/Gem/Code/CMakeLists.txt index 2bcc304bde..548aa51ad1 100644 --- a/AutomatedTesting/Gem/Code/CMakeLists.txt +++ b/AutomatedTesting/Gem/Code/CMakeLists.txt @@ -28,30 +28,41 @@ ly_add_target( Gem::Atom_AtomBridge.Static ) +# if enabled, AutomatedTesting is used by all kinds of applications +ly_create_alias(NAME AutomatedTesting.Builders NAMESPACE Gem TARGETS Gem::AutomatedTesting) +ly_create_alias(NAME AutomatedTesting.Tools NAMESPACE Gem TARGETS Gem::AutomatedTesting) +ly_create_alias(NAME AutomatedTesting.Clients NAMESPACE Gem TARGETS Gem::AutomatedTesting) +ly_create_alias(NAME AutomatedTesting.Servers NAMESPACE Gem TARGETS Gem::AutomatedTesting) + ################################################################################ # Gem dependencies ################################################################################ -ly_add_project_dependencies( - PROJECT_NAME - AutomatedTesting - TARGETS - AutomatedTesting.GameLauncher - DEPENDENCIES_FILES - runtime_dependencies.cmake - ${pal_dir}/runtime_dependencies.cmake -) -if(PAL_TRAIT_BUILD_HOST_TOOLS) - ly_add_project_dependencies( - PROJECT_NAME - AutomatedTesting - TARGETS - AssetBuilder - AssetProcessor - AssetProcessorBatch - Editor - DEPENDENCIES_FILES - tool_dependencies.cmake - ${pal_dir}/tool_dependencies.cmake - ) +# The GameLauncher uses "Clients" gem variants: +ly_enable_gems(PROJECT_NAME AutomatedTesting GEM_FILE enabled_gems.cmake + TARGETS AutomatedTesting.GameLauncher + VARIANTS Clients) + +# If we build a server, then apply the gems to the server +if(PAL_TRAIT_BUILD_SERVER_SUPPORTED) + # if we're making a server, then add the "Server" gem variants to it: + ly_enable_gems(PROJECT_NAME AutomatedTesting GEM_FILE enabled_gems.cmake + TARGETS AutomatedTesting.ServerLauncher + VARIANTS Servers) + + set_property(GLOBAL APPEND PROPERTY LY_LAUNCHER_SERVER_PROJECTS AutomatedTesting) +endif() + +if (PAL_TRAIT_BUILD_HOST_TOOLS) + # The Editor uses "Tools" gem variants: + ly_enable_gems( + PROJECT_NAME AutomatedTesting GEM_FILE enabled_gems.cmake + TARGETS Editor + VARIANTS Tools) + + # The pipeline tools use "Builders" gem variants: + ly_enable_gems( + PROJECT_NAME AutomatedTesting GEM_FILE enabled_gems.cmake + TARGETS AssetBuilder AssetProcessor AssetProcessorBatch + VARIANTS Builders) endif() diff --git a/AutomatedTesting/Gem/Code/enabled_gems.cmake b/AutomatedTesting/Gem/Code/enabled_gems.cmake new file mode 100644 index 0000000000..d99d17b55e --- /dev/null +++ b/AutomatedTesting/Gem/Code/enabled_gems.cmake @@ -0,0 +1,58 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +set(ENABLED_GEMS + ImGui + ScriptEvents + ExpressionEvaluation + Gestures + CertificateManager + DebugDraw + SceneProcessing + GraphCanvas + InAppPurchases + AutomatedTesting + EditorPythonBindings + QtForPython + PythonAssetBuilder + Metastream + AudioSystem + Camera + EMotionFX + PhysX + CameraFramework + StartingPointMovement + StartingPointCamera + ScriptCanvas + ScriptCanvasPhysics + ScriptCanvasTesting + LyShineExamples + StartingPointInput + PhysXDebug + WhiteBox + FastNoise + SurfaceData + GradientSignal + Vegetation + GraphModel + LandscapeCanvas + NvCloth + Blast + Maestro + TextureAtlas + LmbrCentral + LyShine + HttpRequestor + Atom_AtomBridge + AWSCore + AWSClientAuth + AWSMetrics +) diff --git a/AutomatedTesting/Gem/Code/runtime_dependencies.cmake b/AutomatedTesting/Gem/Code/runtime_dependencies.cmake deleted file mode 100644 index 280c25bcf7..0000000000 --- a/AutomatedTesting/Gem/Code/runtime_dependencies.cmake +++ /dev/null @@ -1,48 +0,0 @@ -# -# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -# its licensors. -# -# For complete copyright and license terms please see the LICENSE at the root of this -# distribution (the License). All use of this software is governed by the License, -# or, if provided, by the license below or the license accompanying this file. Do not -# remove or modify any license notices. This file is distributed on an AS IS BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# - -# Extracted from Game -set(GEM_DEPENDENCIES - Gem::Maestro - Gem::TextureAtlas - Gem::LmbrCentral - Gem::LyShine - Gem::HttpRequestor - Gem::ScriptEvents - Gem::ExpressionEvaluation - Gem::Gestures - Gem::CertificateManager - Gem::DebugDraw - Gem::AudioSystem - Gem::InAppPurchases - Gem::AutomatedTesting - Gem::Metastream - Gem::Camera - Gem::EMotionFX - Gem::PhysX - Gem::CameraFramework - Gem::StartingPointMovement - Gem::StartingPointCamera - Gem::ScriptCanvas - Gem::ImGui - Gem::LyShineExamples - Gem::StartingPointInput - Gem::ScriptCanvasPhysics - Gem::PhysXDebug - Gem::WhiteBox - Gem::FastNoise - Gem::SurfaceData - Gem::GradientSignal - Gem::Vegetation - Gem::Atom_AtomBridge - Gem::NvCloth - Gem::Blast -) diff --git a/AutomatedTesting/Gem/Code/tool_dependencies.cmake b/AutomatedTesting/Gem/Code/tool_dependencies.cmake deleted file mode 100644 index 1d70c02b1c..0000000000 --- a/AutomatedTesting/Gem/Code/tool_dependencies.cmake +++ /dev/null @@ -1,60 +0,0 @@ -# -# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -# its licensors. -# -# For complete copyright and license terms please see the LICENSE at the root of this -# distribution (the License). All use of this software is governed by the License, -# or, if provided, by the license below or the license accompanying this file. Do not -# remove or modify any license notices. This file is distributed on an AS IS BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# - -# Extracted from Editor.xml -set(GEM_DEPENDENCIES - Gem::Maestro.Editor - Gem::TextureAtlas.Editor - Gem::LmbrCentral.Editor - Gem::LyShine.Editor - Gem::HttpRequestor - Gem::ScriptEvents.Editor - Gem::ExpressionEvaluation - Gem::Gestures - Gem::CertificateManager - Gem::DebugDraw.Editor - Gem::SceneProcessing.Editor - Gem::GraphCanvas.Editor - Gem::InAppPurchases - Gem::AutomatedTesting - Gem::EditorPythonBindings.Editor - Gem::PythonAssetBuilder.Editor - Gem::Metastream - Gem::AudioSystem.Editor - Gem::Camera.Editor - Gem::EMotionFX.Editor - Gem::PhysX.Editor - Gem::CameraFramework - Gem::StartingPointMovement - Gem::StartingPointCamera - Gem::ScriptCanvas.Editor - Gem::ScriptEvents.Editor - Gem::ImGui.Editor - Gem::LyShineExamples - Gem::StartingPointInput.Editor - Gem::ScriptCanvasPhysics - Gem::ScriptCanvasTesting.Editor - Gem::PhysXDebug.Editor - Gem::WhiteBox.Editor - Gem::FastNoise.Editor - Gem::SurfaceData.Editor - Gem::GradientSignal.Editor - Gem::Vegetation.Editor - Gem::GraphModel.Editor - Gem::LandscapeCanvas.Editor - Gem::EMotionFX.Editor - Gem::ImGui.Editor - Gem::Atom_RHI.Private - Gem::Atom_Feature_Common.Editor - Gem::Atom_AtomBridge.Editor - Gem::NvCloth.Editor - Gem::Blast.Editor -) diff --git a/AutomatedTesting/Gem/PythonTests/AWS/Windows/aws_metrics/__init__.py b/AutomatedTesting/Gem/PythonTests/AWS/Windows/aws_metrics/__init__.py new file mode 100644 index 0000000000..cdee4b5a56 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/AWS/Windows/aws_metrics/__init__.py @@ -0,0 +1,10 @@ +""" +All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +its licensors. + +For complete copyright and license terms please see the LICENSE at the root of this +distribution (the "License"). All use of this software is governed by the License, +or, if provided, by the license below or the license accompanying this file. Do not +remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +""" 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 new file mode 100644 index 0000000000..04be31759d --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/AWS/Windows/aws_metrics/aws_metrics_automation_test.py @@ -0,0 +1,237 @@ +""" +All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +its licensors. + +For complete copyright and license terms please see the LICENSE at the root of this +distribution (the "License"). All use of this software is governed by the License, +or, if provided, by the license below or the license accompanying this file. Do not +remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +""" + +import logging +import os +import pytest +import time +import typing + +from datetime import datetime +import ly_test_tools.log.log_monitor + +from assetpipeline.ap_fixtures.asset_processor_fixture import asset_processor as asset_processor +from AWS.common.aws_utils import aws_utils +from AWS.common.aws_credentials import aws_credentials +from AWS.Windows.resource_mappings.resource_mappings import resource_mappings +from AWS.Windows.cdk.cdk import cdk +from .aws_metrics_utils import aws_metrics_utils + +AWS_METRICS_FEATURE_NAME = 'AWSMetrics' +GAME_LOG_NAME = 'Game.log' + +logger = logging.getLogger(__name__) + + +def setup(launcher: ly_test_tools.launchers.Launcher, + cdk: cdk, + asset_processor: asset_processor, + resource_mappings: resource_mappings, + context_variable: str = '') -> typing.Tuple[ly_test_tools.log.log_monitor.LogMonitor, str, str]: + """ + Set up the CDK application and start the log monitor. + :param launcher: Client launcher for running the test level. + :param cdk: CDK application for deploying the AWS resources. + :param asset_processor: asset_processor fixture. + :param resource_mappings: resource_mappings fixture. + :param context_variable: context_variable for enable optional CDK feature. + :return log monitor object, metrics file path and the metrics stack name. + """ + logger.info(f'Cdk stack names:\n{cdk.list()}') + stacks = cdk.deploy(context_variable=context_variable) + resource_mappings.populate_output_keys(stacks) + + asset_processor.start() + asset_processor.wait_for_idle() + + metrics_file_path = os.path.join(launcher.workspace.paths.project(), 'user', + AWS_METRICS_FEATURE_NAME, 'metrics.json') + remove_file(metrics_file_path) + + file_to_monitor = os.path.join(launcher.workspace.paths.project_log(), GAME_LOG_NAME) + remove_file(file_to_monitor) + + # Initialize the log monitor. + log_monitor = ly_test_tools.log.log_monitor.LogMonitor(launcher=launcher, log_file_path=file_to_monitor) + + return log_monitor, metrics_file_path, stacks[0] + + +def monitor_metrics_submission(log_monitor: ly_test_tools.log.log_monitor.LogMonitor) -> None: + """ + Monitor the messages and notifications for submitting metrics. + :param log_monitor: Log monitor to check the log messages. + """ + expected_lines = [ + '(Script) - Submitted metrics without buffer.', + '(Script) - Submitted metrics with buffer.', + '(Script) - Metrics is sent successfully.' + ] + + unexpected_lines = [ + '(Script) - Failed to submit metrics without buffer.', + '(Script) - Failed to submit metrics with buffer.', + '(Script) - Failed to send metrics.' + ] + + result = log_monitor.monitor_log_for_lines( + expected_lines=expected_lines, + unexpected_lines=unexpected_lines, + halt_on_unexpected=True) + + # Assert the log monitor detected expected lines and did not detect any unexpected lines. + assert result, ( + f'Log monitoring failed. Used expected_lines values: {expected_lines} & ' + f'unexpected_lines values: {unexpected_lines}') + + +def remove_file(file_path: str) -> None: + """ + Remove a local file and its directory. + :param file_path: Path to the local file. + """ + if os.path.exists(file_path): + os.remove(file_path) + + file_dir = os.path.dirname(file_path) + if os.path.exists(file_dir) and len(os.listdir(file_dir)) == 0: + os.rmdir(file_dir) + + +@pytest.mark.SUITE_periodic +@pytest.mark.usefixtures('automatic_process_killer') +@pytest.mark.parametrize('project', ['AutomatedTesting']) +@pytest.mark.parametrize('level', ['AWS/Metrics']) +@pytest.mark.parametrize('feature_name', [AWS_METRICS_FEATURE_NAME]) +@pytest.mark.parametrize('resource_mappings_filename', ['aws_resource_mappings.json']) +@pytest.mark.parametrize('profile_name', ['AWSAutomationTest']) +@pytest.mark.parametrize('region_name', ['us-west-2']) +@pytest.mark.parametrize('assume_role_arn', ['arn:aws:iam::645075835648:role/o3de-automation-tests']) +@pytest.mark.parametrize('session_name', ['o3de-Automation-session']) +class TestAWSMetrics_Windows(object): + def test_AWSMetrics_RealTimeAnalytics_MetricsSentToCloudWatch(self, + level: str, + launcher: ly_test_tools.launchers.Launcher, + asset_processor: pytest.fixture, + workspace: pytest.fixture, + aws_utils: aws_utils, + aws_credentials: aws_credentials, + resource_mappings: resource_mappings, + cdk: cdk, + aws_metrics_utils: aws_metrics_utils, + ): + """ + Tests that the submitted metrics are sent to CloudWatch for real-time analytics. + """ + log_monitor, metrics_file_path, stack_name = setup(launcher, cdk, asset_processor, resource_mappings) + + # Start the Kinesis Data Analytics application for real-time analytics. + analytics_application_name = f'{stack_name}-AnalyticsApplication' + aws_metrics_utils.start_kinesis_data_analytics_application(analytics_application_name) + + launcher.args = ['+LoadLevel', level] + launcher.args.extend(['-rhi=null']) + + with launcher.start(launch_ap=False): + start_time = datetime.utcnow() + monitor_metrics_submission(log_monitor) + # Verify that operational health metrics are delivered to CloudWatch. + aws_metrics_utils.verify_cloud_watch_delivery( + 'AWS/Lambda', + 'Invocations', + [{'Name': 'FunctionName', + 'Value': f'{stack_name}-AnalyticsProcessingLambda'}], + start_time) + logger.info('Operational health metrics sent to CloudWatch.') + + aws_metrics_utils.verify_cloud_watch_delivery( + AWS_METRICS_FEATURE_NAME, + 'TotalLogins', + [], + start_time) + logger.info('Real-time metrics sent to CloudWatch.') + + # Stop the Kinesis Data Analytics application. + aws_metrics_utils.stop_kinesis_data_analytics_application(analytics_application_name) + + def test_AWSMetrics_UnauthorizedUser_RequestRejected(self, + level: str, + launcher: ly_test_tools.launchers.Launcher, + cdk: cdk, + aws_credentials: aws_credentials, + asset_processor: pytest.fixture, + resource_mappings: resource_mappings, + workspace: pytest.fixture): + """ + Tests that unauthorized users cannot send metrics events to the AWS backed backend. + """ + log_monitor, metrics_file_path, stack_name = setup(launcher, cdk, asset_processor, resource_mappings) + # Set invalid AWS credentials. + launcher.args = ['+LoadLevel', level, '+cl_awsAccessKey', 'AKIAIOSFODNN7EXAMPLE', + '+cl_awsSecretKey', 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY'] + launcher.args.extend(['-rhi=null']) + + with launcher.start(launch_ap=False): + result = log_monitor.monitor_log_for_lines( + expected_lines=['(Script) - Failed to send metrics.'], + unexpected_lines=['(Script) - Metrics is sent successfully.'], + halt_on_unexpected=True) + assert result, 'Metrics events are sent successfully by unauthorized user' + logger.info('Unauthorized user is rejected to send metrics.') + + def test_AWSMetrics_BatchAnalytics_MetricsDeliveredToS3(self, + level: str, + launcher: ly_test_tools.launchers.Launcher, + cdk: cdk, + aws_credentials: aws_credentials, + asset_processor: pytest.fixture, + resource_mappings: resource_mappings, + aws_utils: aws_utils, + aws_metrics_utils: aws_metrics_utils, + workspace: pytest.fixture): + """ + Tests that the submitted metrics are sent to the data lake for batch analytics. + """ + log_monitor, metrics_file_path, stack_name = setup(launcher, cdk, asset_processor, resource_mappings, + context_variable='batch_processing=true') + + analytics_bucket_name = aws_metrics_utils.get_analytics_bucket_name(stack_name) + + launcher.args = ['+LoadLevel', level] + launcher.args.extend(['-rhi=null']) + + with launcher.start(launch_ap=False): + start_time = datetime.utcnow() + monitor_metrics_submission(log_monitor) + # Verify that operational health metrics are delivered to CloudWatch. + aws_metrics_utils.verify_cloud_watch_delivery( + 'AWS/Lambda', + 'Invocations', + [{'Name': 'FunctionName', + 'Value': f'{stack_name}-EventsProcessingLambda'}], + start_time) + logger.info('Operational health metrics sent to CloudWatch.') + + aws_metrics_utils.verify_s3_delivery(analytics_bucket_name) + logger.info('Metrics sent to S3.') + + # Run the glue crawler to populate the AWS Glue Data Catalog with tables. + aws_metrics_utils.run_glue_crawler(f'{stack_name}-EventsCrawler') + # Run named queries on the table to verify the batch analytics. + aws_metrics_utils.run_named_queries(f'{stack_name}-AthenaWorkGroup') + logger.info('Query metrics from S3 successfully.') + + # Kinesis Data Firehose buffers incoming data before it delivers it to Amazon S3. Sleep for the + # default interval (60s) to make sure that all the metrics are sent to the bucket before cleanup. + time.sleep(60) + # Empty the S3 bucket. S3 buckets can only be deleted successfully when it doesn't contain any object. + aws_metrics_utils.empty_s3_bucket(analytics_bucket_name) + diff --git a/AutomatedTesting/Gem/PythonTests/AWS/Windows/aws_metrics/aws_metrics_utils.py b/AutomatedTesting/Gem/PythonTests/AWS/Windows/aws_metrics/aws_metrics_utils.py new file mode 100644 index 0000000000..686feda3d9 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/AWS/Windows/aws_metrics/aws_metrics_utils.py @@ -0,0 +1,252 @@ +""" +All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +its licensors. + +For complete copyright and license terms please see the LICENSE at the root of this +distribution (the "License"). All use of this software is governed by the License, +or, if provided, by the license below or the license accompanying this file. Do not +remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +""" + +import logging +import pathlib +import pytest +import typing + +from datetime import datetime +from botocore.exceptions import WaiterError + +from AWS.common.aws_utils import AwsUtils +from .aws_metrics_waiters import KinesisAnalyticsApplicationUpdatedWaiter, \ + CloudWatchMetricsDeliveredWaiter, DataLakeMetricsDeliveredWaiter, GlueCrawlerReadyWaiter + +logging.getLogger('boto').setLevel(logging.CRITICAL) + +# Expected directory and file extension for the S3 objects. +EXPECTED_S3_DIRECTORY = 'firehose_events/' +EXPECTED_S3_OBJECT_EXTENSION = '.parquet' + + +class AWSMetricsUtils: + """ + Provide utils functions for the AWSMetrics gem to interact with the deployed resources. + """ + + def __init__(self, aws_utils: AwsUtils): + self._aws_util = aws_utils + + def start_kinesis_data_analytics_application(self, application_name: str) -> None: + """ + Start the Kenisis Data Analytics application for real-time analytics. + :param application_name: Name of the Kenisis Data Analytics application. + """ + input_id = self.get_kinesis_analytics_application_input_id(application_name) + assert input_id, 'invalid Kinesis Data Analytics application input.' + + client = self._aws_util.client('kinesisanalytics') + try: + client.start_application( + ApplicationName=application_name, + InputConfigurations=[ + { + 'Id': input_id, + 'InputStartingPositionConfiguration': { + 'InputStartingPosition': 'NOW' + } + }, + ] + ) + except client.exceptions.ResourceInUseException: + # The application has been started. + return + + try: + KinesisAnalyticsApplicationUpdatedWaiter(client, 'RUNNING').wait(application_name=application_name) + except WaiterError as e: + assert False, f'Failed to start the Kinesis Data Analytics application: {str(e)}.' + + def get_kinesis_analytics_application_input_id(self, application_name: str) -> str: + """ + Get the input ID for the Kenisis Data Analytics application. + :param application_name: Name of the Kenisis Data Analytics application. + :return: Input ID for the Kenisis Data Analytics application. + """ + client = self._aws_util.client('kinesisanalytics') + response = client.describe_application( + ApplicationName=application_name + ) + if not response: + return '' + input_descriptions = response.get('ApplicationDetail', {}).get('InputDescriptions', []) + if len(input_descriptions) != 1: + return '' + + return input_descriptions[0].get('InputId', '') + + def stop_kinesis_data_analytics_application(self, application_name: str) -> None: + """ + Stop the Kenisis Data Analytics application. + :param application_name: Name of the Kenisis Data Analytics application. + """ + client = self._aws_util.client('kinesisanalytics') + client.stop_application( + ApplicationName=application_name + ) + + try: + KinesisAnalyticsApplicationUpdatedWaiter(client, 'READY').wait(application_name=application_name) + except WaiterError as e: + assert False, f'Failed to stop the Kinesis Data Analytics application: {str(e)}.' + + def verify_cloud_watch_delivery(self, namespace: str, metrics_name: str, + dimensions: typing.List[dict], start_time: datetime) -> None: + """ + Verify that the expected metrics is delivered to CloudWatch. + :param namespace: Namespace of the metrics. + :param metrics_name: Name of the metrics. + :param dimensions: Dimensions of the metrics. + :param start_time: Start time for generating the metrics. + """ + client = self._aws_util.client('cloudwatch') + + try: + CloudWatchMetricsDeliveredWaiter(client).wait( + namespace=namespace, + metrics_name=metrics_name, + dimensions=dimensions, + start_time=start_time + ) + except WaiterError as e: + assert False, f'Failed to deliver metrics to CloudWatch: {str(e)}.' + + def verify_s3_delivery(self, analytics_bucket_name: str) -> None: + """ + Verify that metrics are delivered to S3 for batch analytics successfully. + :param analytics_bucket_name: Name of the deployed S3 bucket. + """ + client = self._aws_util.client('s3') + bucket_name = analytics_bucket_name + + try: + DataLakeMetricsDeliveredWaiter(client).wait(bucket_name=bucket_name, prefix=EXPECTED_S3_DIRECTORY) + except WaiterError as e: + assert False, f'Failed to find the S3 directory for storing metrics data: {str(e)}.' + + # Check whether the data is converted to the expected data format. + response = client.list_objects_v2( + Bucket=bucket_name, + Prefix=EXPECTED_S3_DIRECTORY + ) + assert response.get('KeyCount', 0) != 0, f'Failed to deliver metrics to the S3 bucket {bucket_name}.' + + s3_objects = response.get('Contents', []) + for s3_object in s3_objects: + key = s3_object.get('Key', '') + assert pathlib.Path(key).suffix == EXPECTED_S3_OBJECT_EXTENSION, \ + f'Invalid data format is found in the S3 bucket {bucket_name}' + + def run_glue_crawler(self, crawler_name: str) -> None: + """ + Run the Glue crawler and wait for it to finish. + :param crawler_name: Name of the Glue crawler + """ + client = self._aws_util.client('glue') + try: + client.start_crawler( + Name=crawler_name + ) + except client.exceptions.CrawlerRunningException: + # The crawler has already been started. + return + + try: + GlueCrawlerReadyWaiter(client).wait(crawler_name=crawler_name) + except WaiterError as e: + assert False, f'Failed to run the Glue crawler: {str(e)}.' + + def run_named_queries(self, work_group: str) -> None: + """ + Run the named queries under the specific Athena work group. + :param work_group: Name of the Athena work group. + """ + client = self._aws_util.client('athena') + # List all the named queries. + response = client.list_named_queries( + WorkGroup=work_group + ) + named_query_ids = response.get('NamedQueryIds', []) + + # Run each of the queries. + for named_query_id in named_query_ids: + get_named_query_response = client.get_named_query( + NamedQueryId=named_query_id + ) + named_query = get_named_query_response.get('NamedQuery', {}) + + start_query_execution_response = client.start_query_execution( + QueryString=named_query.get('QueryString', ''), + QueryExecutionContext={ + 'Database': named_query.get('Database', '') + }, + WorkGroup=work_group + ) + + # Wait for the query to finish. + state = 'RUNNING' + while state == 'QUEUED' or state == 'RUNNING': + get_query_execution_response = client.get_query_execution( + QueryExecutionId=start_query_execution_response.get('QueryExecutionId', '') + ) + + state = get_query_execution_response.get('QueryExecution', {}).get('Status', {}).get('State', '') + + assert state == 'SUCCEEDED', f'Failed to run the named query {named_query.get("Name", {})}' + + def empty_s3_bucket(self, bucket_name: str) -> None: + """ + Empty the S3 bucket following: + https://boto3.amazonaws.com/v1/documentation/api/latest/guide/migrations3.html + + :param bucket_name: Name of the S3 bucket. + """ + + s3 = self._aws_util.resource('s3') + bucket = s3.Bucket(bucket_name) + + for key in bucket.objects.all(): + key.delete() + + def get_analytics_bucket_name(self, stack_name: str) -> str: + """ + Get the name of the deployed S3 bucket. + :param stack_name: Name of the CloudFormation stack. + :return: Name of the deployed S3 bucket. + """ + + client = self._aws_util.client('cloudformation') + + response = client.describe_stack_resources( + StackName=stack_name + ) + resources = response.get('StackResources', []) + + for resource in resources: + if resource.get('ResourceType') == 'AWS::S3::Bucket': + return resource.get('PhysicalResourceId', '') + + return '' + + +@pytest.fixture(scope='function') +def aws_metrics_utils( + request: pytest.fixture, + aws_utils: pytest.fixture): + """ + Fixture for the AWS metrics util functions. + :param request: _pytest.fixtures.SubRequest class that handles getting + a pytest fixture from a pytest function/fixture. + :param aws_utils: aws_utils fixture. + """ + aws_utils_obj = AWSMetricsUtils(aws_utils) + return aws_utils_obj diff --git a/AutomatedTesting/Gem/PythonTests/AWS/Windows/aws_metrics/aws_metrics_waiters.py b/AutomatedTesting/Gem/PythonTests/AWS/Windows/aws_metrics/aws_metrics_waiters.py new file mode 100644 index 0000000000..7ce5551fd4 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/AWS/Windows/aws_metrics/aws_metrics_waiters.py @@ -0,0 +1,142 @@ +""" +All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +its licensors. + +For complete copyright and license terms please see the LICENSE at the root of this +distribution (the "License"). All use of this software is governed by the License, +or, if provided, by the license below or the license accompanying this file. Do not +remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +""" + +import botocore.client +import logging + +from datetime import timedelta +from AWS.common.custom_waiter import CustomWaiter, WaitState + +logging.getLogger('boto').setLevel(logging.CRITICAL) + + +class KinesisAnalyticsApplicationUpdatedWaiter(CustomWaiter): + """ + Subclass of the base custom waiter class. + Wait for the Kinesis analytics application being updated to a specific status. + """ + def __init__(self, client: botocore.client, status: str): + """ + Initialize the waiter. + + :param client: Boto3 client to use. + :param status: Expected status. + """ + super().__init__( + 'KinesisAnalyticsApplicationUpdated', + 'DescribeApplication', + 'ApplicationDetail.ApplicationStatus', + {status: WaitState.SUCCESS}, + client) + + def wait(self, application_name: str): + """ + Wait for the expected status. + + :param application_name: Name of the Kinesis analytics application. + """ + self._wait(ApplicationName=application_name) + + +class GlueCrawlerReadyWaiter(CustomWaiter): + """ + Subclass of the base custom waiter class. + Wait for the Glue crawler to finish its processing. + """ + def __init__(self, client: botocore.client): + """ + Initialize the waiter. + + :param client: Boto3 client to use. + """ + super().__init__( + 'GlueCrawlerReady', + 'GetCrawler', + 'Crawler.State', + {'READY': WaitState.SUCCESS}, + client) + + def wait(self, crawler_name): + """ + Wait for the expected status. + + :param crawler_name: Name of the Glue crawler. + """ + self._wait(Name=crawler_name) + + +class DataLakeMetricsDeliveredWaiter(CustomWaiter): + """ + Subclass of the base custom waiter class. + Wait for the expected directory being created in the S3 bucket. + """ + def __init__(self, client: botocore.client): + """ + Initialize the waiter. + + :param client: Boto3 client to use. + """ + super().__init__( + 'DataLakeMetricsDelivered', + 'ListObjectsV2', + 'KeyCount > `0`', + {True: WaitState.SUCCESS}, + client) + + def wait(self, bucket_name, prefix): + """ + Wait for the expected directory being created. + + :param bucket_name: Name of the S3 bucket. + :param prefix: Name of the expected directory prefix. + """ + self._wait(Bucket=bucket_name, Prefix=prefix) + + +class CloudWatchMetricsDeliveredWaiter(CustomWaiter): + """ + Subclass of the base custom waiter class. + Wait for the expected metrics being delivered to CloudWatch. + """ + def __init__(self, client: botocore.client): + """ + Initialize the waiter. + + :param client: Boto3 client to use. + """ + super().__init__( + 'CloudWatchMetricsDelivered', + 'GetMetricStatistics', + 'length(Datapoints) > `0`', + {True: WaitState.SUCCESS}, + client) + + def wait(self, namespace, metrics_name, dimensions, start_time): + """ + Wait for the expected metrics being delivered. + + :param namespace: Namespace of the metrics. + :param metrics_name: Name of the metrics. + :param dimensions: Dimensions of the metrics. + :param start_time: Start time for generating the metrics. + """ + self._wait( + Namespace=namespace, + MetricName=metrics_name, + Dimensions=dimensions, + StartTime=start_time, + EndTime=start_time + timedelta(0, self.timeout), + Period=60, + Statistics=[ + 'SampleCount' + ], + Unit='Count' + ) diff --git a/AutomatedTesting/Gem/PythonTests/AWS/Windows/cdk/cdk.py b/AutomatedTesting/Gem/PythonTests/AWS/Windows/cdk/cdk.py index 455b3f94cb..5dbde29b9d 100644 --- a/AutomatedTesting/Gem/PythonTests/AWS/Windows/cdk/cdk.py +++ b/AutomatedTesting/Gem/PythonTests/AWS/Windows/cdk/cdk.py @@ -16,12 +16,15 @@ import boto3 import ly_test_tools.environment.process_utils as process_utils from typing import List +BOOTSTRAP_STACK_NAME = 'CDKToolkit' +BOOTSTRAP_STAGING_BUCKET_LOGIC_ID = 'StagingBucket' class Cdk: """ Cdk class that provides methods to run cdk application commands. Expects system to have NodeJS, AWS CLI and CDK installed globally and have their paths setup as env variables. """ + def __init__(self, cdk_path: str, project: str, account_id: str, workspace: pytest.fixture, session: boto3.session.Session): """ @@ -49,12 +52,24 @@ class Cdk: env=self._cdk_env, shell=True) + def bootstrap(self) -> None: + """ + Deploy the bootstrap stack. + """ + bootstrap_cmd = ['cdk', 'bootstrap', + f'aws://{self._cdk_env["O3DE_AWS_DEPLOY_ACCOUNT"]}/{self._cdk_env["O3DE_AWS_DEPLOY_REGION"]}'] + + process_utils.check_call( + bootstrap_cmd, + cwd=self._cdk_path, + env=self._cdk_env, + shell=True) + def list(self) -> List[str]: """ lists cdk stack names :return List of cdk stack names """ - if not self._cdk_path: return [] @@ -123,6 +138,38 @@ class Cdk: self._stacks = [] self._cdk_path = '' + @staticmethod + def remove_bootstrap_stack(aws_utils: pytest.fixture) -> None: + """ + Remove the CDK bootstrap stack. + :param aws_utils: aws_utils fixture. + """ + # Check if the bootstrap stack exists. + response = aws_utils.client('cloudformation').describe_stacks( + StackName=BOOTSTRAP_STACK_NAME + ) + stacks = response.get('Stacks', []) + if not stacks: + return + + # Clear the bootstrap staging bucket before deleting the bootstrap stack. + response = aws_utils.client('cloudformation').describe_stack_resource( + StackName=BOOTSTRAP_STACK_NAME, + LogicalResourceId=BOOTSTRAP_STAGING_BUCKET_LOGIC_ID + ) + + staging_bucket_name = response.get('StackResourceDetail', {}).get('PhysicalResourceId', '') + if staging_bucket_name: + s3 = aws_utils.resource('s3') + bucket = s3.Bucket(staging_bucket_name) + for key in bucket.objects.all(): + key.delete() + + # Delete the bootstrap stack. + aws_utils.client('cloudformation').delete_stack( + StackName=BOOTSTRAP_STACK_NAME + ) + @pytest.fixture(scope='function') def cdk( @@ -131,6 +178,7 @@ def cdk( feature_name: str, workspace: pytest.fixture, aws_utils: pytest.fixture, + bootstrap_required: bool = True, destroy_stacks_on_teardown: bool = True) -> Cdk: """ Fixture for setting up a Cdk @@ -140,6 +188,8 @@ def cdk( :param feature_name: Feature gem name to expect cdk folder in. :param workspace: ly_test_tools workspace fixture. :param aws_utils: aws_utils fixture. + :param bootstrap_required: Whether the bootstrap stack needs to be created to + provision resources the AWS CDK needs to perform the deployment. :param destroy_stacks_on_teardown: option to control calling destroy ot the end of test. :return Cdk class object. """ @@ -147,9 +197,14 @@ def cdk( cdk_path = f'{workspace.paths.engine_root()}/Gems/{feature_name}/cdk' cdk_obj = Cdk(cdk_path, project, aws_utils.assume_account_id(), workspace, aws_utils.assume_session()) + if bootstrap_required: + cdk_obj.bootstrap() + def teardown(): if destroy_stacks_on_teardown: cdk_obj.destroy() + cdk_obj.remove_bootstrap_stack(aws_utils) + request.addfinalizer(teardown) return cdk_obj diff --git a/AutomatedTesting/Gem/PythonTests/AWS/common/aws_credentials.py b/AutomatedTesting/Gem/PythonTests/AWS/common/aws_credentials.py new file mode 100644 index 0000000000..fbce772d40 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/AWS/common/aws_credentials.py @@ -0,0 +1,134 @@ +""" +All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +its licensors. +For complete copyright and license terms please see the LICENSE at the root of this +distribution (the "License"). All use of this software is governed by the License, +or, if provided, by the license below or the license accompanying this file. Do not +remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +""" + +import boto3 +import configparser +import logging +import os +import pytest +import typing + +logger = logging.getLogger(__name__) +logging.getLogger('boto').setLevel(logging.CRITICAL) + + +class AwsCredentials: + def __init__(self, profile_name: str): + self._profile_name = profile_name + + self._credentials_path = os.environ.get('AWS_SHARED_CREDENTIALS_FILE') + if not self._credentials_path: + # Home directory location varies based on the operating system, but is referred to using the environment + # variables %UserProfile% in Windows and $HOME or ~ (tilde) in Unix-based systems. + self._credentials_path = os.path.join(os.environ.get('UserProfile', os.path.expanduser('~')), + '.aws', 'credentials') + self._credentials_file_exists = os.path.exists(self._credentials_path) + + self._credentials = configparser.ConfigParser() + self._credentials.read(self._credentials_path) + + def get_aws_credentials(self) -> typing.Tuple[str, str, str]: + """ + Get aws credentials stored in the specific named profile. + + :return AWS credentials. + """ + access_key_id = self._get_aws_credential_attribute_value('aws_access_key_id') + secret_access_key = self._get_aws_credential_attribute_value('aws_secret_access_key') + session_token = self._get_aws_credential_attribute_value('aws_session_token') + + return access_key_id, secret_access_key, session_token + + def set_aws_credentials_by_session(self, session: boto3.Session) -> None: + """ + Set AWS credentials stored in the specific named profile using an assumed role session. + + :param session: assumed role session. + """ + credentials = session.get_credentials().get_frozen_credentials() + self.set_aws_credentials(credentials.access_key, credentials.secret_key, credentials.token) + + def set_aws_credentials(self, aws_access_key_id: str, aws_secret_access_key: str, + aws_session_token: str) -> None: + """ + Set AWS credentials stored in the specific named profile. + + :param aws_access_key_id: AWS access key id. + :param aws_secret_access_key: AWS secrete access key. + :param aws_session_token: AWS assumed role session. + """ + self._set_aws_credential_attribute_value('aws_access_key_id', aws_access_key_id) + self._set_aws_credential_attribute_value('aws_secret_access_key', aws_secret_access_key) + self._set_aws_credential_attribute_value('aws_session_token', aws_session_token) + + if (len(self._credentials.sections()) == 0) and (not self._credentials_file_exists): + os.remove(self._credentials_path) + return + + with open(self._credentials_path, 'w+') as credential_file: + self._credentials.write(credential_file) + + def _get_aws_credential_attribute_value(self, attribute_name: str) -> str: + """ + Get the value of an AWS credential attribute stored in the specific named profile. + + :param attribute_name: Name of the AWS credential attribute. + :return Value of the AWS credential attribute. + """ + try: + value = self._credentials.get(self._profile_name, attribute_name) + except configparser.NoSectionError: + # Named profile or key doesn't exist + value = None + except configparser.NoOptionError: + # Named profile doesn't have the specified attribute + value = None + + return value + + def _set_aws_credential_attribute_value(self, attribute_name: str, attribute_value: str) -> None: + """ + Set the value of an AWS credential attribute stored in the specific named profile. + + :param attribute_name: Name of the AWS credential attribute. + :param attribute_value: Value of the AWS credential attribute. + """ + if self._profile_name not in self._credentials: + self._credentials[self._profile_name] = {} + + if attribute_value is None: + self._credentials.remove_option(self._profile_name, attribute_name) + # Remove the named profile if it doesn't have any AWS credential attribute. + if len(self._credentials[self._profile_name]) == 0: + self._credentials.remove_section(self._profile_name) + else: + self._credentials[self._profile_name][attribute_name] = attribute_value + + +@pytest.fixture(scope='function') +def aws_credentials(request: pytest.fixture, aws_utils: pytest.fixture, profile_name: str): + """ + Fixture for setting up temporary AWS credentials from assume role. + + :param request: _pytest.fixtures.SubRequest class that handles getting + a pytest fixture from a pytest function/fixture. + :param aws_utils: aws_utils fixture. + :param profile_name: Named AWS profile to store temporary credentials. + """ + aws_credentials_obj = AwsCredentials(profile_name) + original_access_key, original_secret_access_key, original_token = aws_credentials_obj.get_aws_credentials() + aws_credentials_obj.set_aws_credentials_by_session(aws_utils.assume_session()) + + def teardown(): + # Reset to the named profile using the original AWS credentials + aws_credentials_obj.set_aws_credentials(original_access_key, original_secret_access_key, original_token) + request.addfinalizer(teardown) + + return aws_credentials_obj diff --git a/AutomatedTesting/Gem/PythonTests/AWS/common/aws_utils.py b/AutomatedTesting/Gem/PythonTests/AWS/common/aws_utils.py index 7a15ba0abe..ff33f58d1d 100644 --- a/AutomatedTesting/Gem/PythonTests/AWS/common/aws_utils.py +++ b/AutomatedTesting/Gem/PythonTests/AWS/common/aws_utils.py @@ -1,82 +1,90 @@ -""" -All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -its licensors. -For complete copyright and license terms please see the LICENSE at the root of this -distribution (the "License"). All use of this software is governed by the License, -or, if provided, by the license below or the license accompanying this file. Do not -remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -""" -import boto3 -import pytest -import logging - -logger = logging.getLogger(__name__) - - -class AwsUtils: - - def __init__(self, arn: str, session_name: str, region_name: str): - local_session = boto3.Session(profile_name='default') - local_sts_client = local_session.client('sts') - self._local_account_id = local_sts_client.get_caller_identity()["Account"] - logger.info(f'Local Account Id: {self._local_account_id}') - - response = local_sts_client.assume_role(RoleArn=arn, RoleSessionName=session_name) - - self._assume_session = boto3.Session(aws_access_key_id=response['Credentials']['AccessKeyId'], - aws_secret_access_key=response['Credentials']['SecretAccessKey'], - aws_session_token=response['Credentials']['SessionToken'], - region_name=region_name) - - assume_sts_client = self._assume_session.client('sts') - assume_account_id = assume_sts_client.get_caller_identity()["Account"] - logger.info(f'Assume Account Id: {assume_account_id}') - self._assume_account_id = assume_account_id - - def client(self, service: str): - """ - Get the client for a specific AWS service from configured session - :return: Client for the AWS service. - """ - return self._assume_session.client(service) - - def assume_session(self): - return self._assume_session - - def local_account_id(self): - return self._local_account_id - - def assume_account_id(self): - return self._assume_account_id - - def destroy(self) -> None: - """ - clears stored session - """ - self._assume_session = None - - -@pytest.fixture(scope='function') -def aws_utils( - request: pytest.fixture, - assume_role_arn: str, - session_name: str, - region_name: str): - """ - Fixture for setting up a Cdk - :param request: _pytest.fixtures.SubRequest class that handles getting - a pytest fixture from a pytest function/fixture. - :param assume_role_arn: Role used to fetch temporary aws credentials, configure service clients with obtained credentials. - :param session_name: Session name to set. - :param region_name: AWS account region to set for session. - :return AWSUtils class object. - """ - aws_utils_obj = AwsUtils(assume_role_arn, session_name, region_name) - - def teardown(): - aws_utils_obj.destroy() - - request.addfinalizer(teardown) - - return aws_utils_obj +""" +All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +its licensors. +For complete copyright and license terms please see the LICENSE at the root of this +distribution (the "License"). All use of this software is governed by the License, +or, if provided, by the license below or the license accompanying this file. Do not +remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +""" +import boto3 +import pytest +import logging + +logger = logging.getLogger(__name__) +logging.getLogger('boto').setLevel(logging.CRITICAL) + + +class AwsUtils: + + def __init__(self, arn: str, session_name: str, region_name: str): + local_session = boto3.Session(profile_name='default') + local_sts_client = local_session.client('sts') + self._local_account_id = local_sts_client.get_caller_identity()["Account"] + logger.info(f'Local Account Id: {self._local_account_id}') + + response = local_sts_client.assume_role(RoleArn=arn, RoleSessionName=session_name) + + self._assume_session = boto3.Session(aws_access_key_id=response['Credentials']['AccessKeyId'], + aws_secret_access_key=response['Credentials']['SecretAccessKey'], + aws_session_token=response['Credentials']['SessionToken'], + region_name=region_name) + + assume_sts_client = self._assume_session.client('sts') + assume_account_id = assume_sts_client.get_caller_identity()["Account"] + logger.info(f'Assume Account Id: {assume_account_id}') + self._assume_account_id = assume_account_id + + def client(self, service: str): + """ + Get the client for a specific AWS service from configured session + :return: Client for the AWS service. + """ + return self._assume_session.client(service) + + def resource(self, service: str): + """ + Get the resource for a specific AWS service from configured session + :return: Client for the AWS service. + """ + return self._assume_session.resource(service) + + def assume_session(self): + return self._assume_session + + def local_account_id(self): + return self._local_account_id + + def assume_account_id(self): + return self._assume_account_id + + def destroy(self) -> None: + """ + clears stored session + """ + self._assume_session = None + + +@pytest.fixture(scope='function') +def aws_utils( + request: pytest.fixture, + assume_role_arn: str, + session_name: str, + region_name: str): + """ + Fixture for AWS util functions + :param request: _pytest.fixtures.SubRequest class that handles getting + a pytest fixture from a pytest function/fixture. + :param assume_role_arn: Role used to fetch temporary aws credentials, configure service clients with obtained credentials. + :param session_name: Session name to set. + :param region_name: AWS account region to set for session. + :return AWSUtils class object. + """ + aws_utils_obj = AwsUtils(assume_role_arn, session_name, region_name) + + def teardown(): + aws_utils_obj.destroy() + + request.addfinalizer(teardown) + + return aws_utils_obj diff --git a/AutomatedTesting/Gem/PythonTests/AWS/common/custom_waiter.py b/AutomatedTesting/Gem/PythonTests/AWS/common/custom_waiter.py new file mode 100644 index 0000000000..7c0a65e8a3 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/AWS/common/custom_waiter.py @@ -0,0 +1,91 @@ +""" +All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +its licensors. + +For complete copyright and license terms please see the LICENSE at the root of this +distribution (the "License"). All use of this software is governed by the License, +or, if provided, by the license below or the license accompanying this file. Do not +remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +""" + +from enum import Enum +import botocore.client +import botocore.waiter +import logging + +logging.getLogger('boto').setLevel(logging.CRITICAL) + + +class WaitState(Enum): + SUCCESS = 'success' + FAILURE = 'failure' + + +class CustomWaiter: + """ + Base class for a custom waiter. + + Modified from: + https://docs.aws.amazon.com/code-samples/latest/catalog/python-demo_tools-custom_waiter.py.html + """ + def __init__( + self, name: str, operation: str, argument: str, + acceptors: dict, client: botocore.client, delay: int = 30, max_tries: int = 10, + matcher='path'): + """ + Subclasses should pass specific operations, arguments, and acceptors to + their superclass. + + :param name: The name of the waiter. This can be any descriptive string. + :param operation: The operation to wait for. This must match the casing of + the underlying operation model, which is typically in + CamelCase. + :param argument: The dict keys used to access the result of the operation, in + dot notation. For example, 'Job.Status' will access + result['Job']['Status']. + :param acceptors: The list of acceptors that indicate the wait is over. These + can indicate either success or failure. The acceptor values + are compared to the result of the operation after the + argument keys are applied. + :param client: The Boto3 client. + :param delay: The number of seconds to wait between each call to the operation. Default to 30 seconds. + :param max_tries: The maximum number of tries before exiting. Default to 10. + :param matcher: The kind of matcher to use. Default to 'path'. + """ + self.name = name + self.operation = operation + self.argument = argument + self.client = client + self.waiter_model = botocore.waiter.WaiterModel({ + 'version': 2, + 'waiters': { + name: { + "delay": delay, + "operation": operation, + "maxAttempts": max_tries, + "acceptors": [{ + "state": state.value, + "matcher": matcher, + "argument": argument, + "expected": expected + } for expected, state in acceptors.items()] + }}}) + self.waiter = botocore.waiter.create_waiter_with_client( + self.name, self.waiter_model, self.client) + + self._timeout = delay * max_tries + + def _wait(self, **kwargs): + """ + Starts the botocore wait loop. + + :param kwargs: Keyword arguments that are passed to the operation being polled. + """ + self.waiter.wait(**kwargs) + + @property + def timeout(self): + return self._timeout + + 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 7a85cb1813..34af4d9115 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 @@ -162,7 +162,7 @@ def bundler_batch_setup_fixture(request, workspace, asset_processor, timeout) -> else: cmd.append(f"--{key}") if append_defaults: - cmd.append(f"--project={workspace.project}") + cmd.append(f"--project-path={workspace.project}") return cmd # ****** @@ -300,9 +300,9 @@ def bundler_batch_setup_fixture(request, workspace, asset_processor, timeout) -> workspace.paths.engine_root(), "Code", "Framework", - "AzFramework", - "AzFramework", - "Platform", + "AzCore", + "AzCore", + "PlatformId", "PlatformDefaults.h", ) @@ -318,7 +318,7 @@ def bundler_batch_setup_fixture(request, workspace, asset_processor, timeout) -> if start_gathering: result = get_platform.match(line) # Try the regex if result: - platform_values[result.group(1).lower()] = counter + platform_values[result.group(1).replace("_ID", "").lower()] = counter counter = counter << 1 elif "(Invalid, -1)" in line: # The line right before the first platform start_gathering = True diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/CMakeLists.txt index a2002f2d15..2e7516db27 100644 --- a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/CMakeLists.txt +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/CMakeLists.txt @@ -128,16 +128,5 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) RUNTIME_DEPENDENCIES AZ::AssetProcessorBatch ) - -# Need performance improvements LYN-1218 -# ly_add_pytest( -# NAME AssetPipelineTests.AssetRelocator -# PATH ${CMAKE_CURRENT_LIST_DIR}/asset_relocator_tests.py -# EXCLUDE_TEST_RUN_TARGET_FROM_IDE -# TEST_SUITE periodic -# TEST_SERIAL -# RUNTIME_DEPENDENCIES -# AZ::AssetProcessorBatch -# ) endif() diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_builder_tests.py b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_builder_tests.py index e3d52e8260..fb82f180c6 100755 --- a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_builder_tests.py +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_builder_tests.py @@ -64,6 +64,15 @@ class TestsAssetBuilder_WindowsAndMac(object): ): """ Verifying -debug parameter for AssetBuilder + + Test Steps: + 1. Create temporary workspace + 2. Launch Asset Processor GUI + 3. Add test assets to workspace + 4. Run Asset Builder with debug on an intact slice + 5. Check Asset Builder didn't fail to build + 6. Run Asset Builder with debug on a corrupted slice + 7. Verify corrupted slice produced an error """ env = ap_setup_fixture intact_slice_failed = False 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 8738e8acdf..e1091b9b82 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 @@ -80,6 +80,8 @@ class TestsAssetBundlerBatch_WindowsAndMac(object): def test_WindowsAndMac_RunHelpCmd_ZeroExitCode(self, workspace, bundler_batch_helper): """ Simple calls to all AssetBundlerBatch --help to make sure a non-zero exit codes are returned. + + Test will call each Asset Bundler Batch sub-command with help and will error on a non-0 exit code """ bundler_batch_helper.call_bundlerbatch(help="") bundler_batch_helper.call_seeds(help="") @@ -98,6 +100,12 @@ class TestsAssetBundlerBatch_WindowsAndMac(object): 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 @@ -300,9 +308,18 @@ class TestsAssetBundlerBatch_WindowsAndMac(object): """ Validates destructive overwriting for asset lists and that generating debug information does not affect asset list creation + + 1. Create an asset list from seed_list + 2. Validate asset list was created + 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 + 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 """ helper = bundler_batch_helper - seed_list = os.path.join(workspace.paths.engine_root(), "Engine", "SeedAssetList.seed") # Engine seed list + seed_list = os.path.join(workspace.paths.engine_root(), "Assets", "Engine", "SeedAssetList.seed") # Engine seed list asset = r"levels\testdependencieslevel\level.pak" # Create Asset list @@ -375,9 +392,17 @@ class TestsAssetBundlerBatch_WindowsAndMac(object): """ Validates bundle creation both through the 'bundles' and 'bundlesettings' subcommands. + + Test Steps: + 1. Create an asset list + 2. Create a bundle with the asset list and without a bundle settings file + 3. Create a bundle with the asset list and a bundle settings file + 4. Validate calling bundle doesn't perform destructive overwrite without --allowOverwrites + 5. Calling bundle again with --alowOverwrites performs destructive overwrite + 6. Validate contents of original bundle and overwritten bundle """ helper = bundler_batch_helper - seed_list = os.path.join(workspace.paths.engine_root(), "Engine", "SeedAssetList.seed") # Engine seed list + seed_list = os.path.join(workspace.paths.engine_root(), "Assets", "Engine", "SeedAssetList.seed") # Engine seed list asset = r"levels\testdependencieslevel\level.pak" # Useful bundle locations / names (2 for comparing contents) @@ -457,6 +482,16 @@ class TestsAssetBundlerBatch_WindowsAndMac(object): """ Creates bundles using the same asset list and compares that they are created equally. Also validates that platform bundles exclude/include an expected file. (excluded for WIN, included for MAC) + + Test Steps: + 1. Create an asset list + 2. Create bundles for both PC & Mac + 3. Validate that bundles were created + 4. Verify that expected missing file is not in windows bundle + 5. Verify that expected file is in the mac bundle + 6. Create duplicate bundles with allowOverwrites + 7. Verify that files were generated + 8. Verify original bundle checksums are equal to new bundle checksums """ helper = bundler_batch_helper # fmt:off @@ -465,7 +500,7 @@ class TestsAssetBundlerBatch_WindowsAndMac(object): "Please rerun with commandline option: '--bundle_platforms=pc,mac'" # fmt:on - seed_list = os.path.join(workspace.paths.engine_root(), "Engine", "SeedAssetList.seed") # Engine seed list + seed_list = os.path.join(workspace.paths.engine_root(), "Assets", "Engine", "SeedAssetList.seed") # Engine seed list # Useful bundle / asset list locations bundle_dir = os.path.dirname(helper["bundle_file"]) @@ -502,13 +537,13 @@ class TestsAssetBundlerBatch_WindowsAndMac(object): for bundle_file in bundle_files.values(): assert os.path.isfile(bundle_file) - # This asset is created on mac platform but not on windows - file_to_check = b"engineassets/shading/defaultprobe_cm.dds.5" # [use byte str because file is in binary] + # This asset is created both on mac and windows platform + file_to_check = b"engineassets/shading/defaultprobe_cm_ibldiffuse.tif.streamingimage" # [use byte str because file is in binary] # Extract the delta catalog file from pc archive. {file_to_check} SHOULD NOT be present for PC file_contents = helper.extract_file_content(bundle_files["pc"], "DeltaCatalog.xml") # fmt:off - assert file_to_check not in file_contents, \ + assert file_to_check in file_contents, \ f"{file_to_check} was found in DeltaCatalog.xml in pc bundle file {bundle_files['pc']}" # fmt:on @@ -571,6 +606,24 @@ class TestsAssetBundlerBatch_WindowsAndMac(object): """ Validates that the 'seeds' subcommand can add and remove seeds and seed platforms properly. Also checks that destructive overwrites require the --allowOverwrites flag + + Test Steps: + + 1. Create a PC Seed List from a test asset + 2. Validate that seed list was generated with proper platform flag + 3. Add Mac & PC as platforms to the seed list + 4. Verify that seed has both Mac & PC platform flags + 5. Remove Mac as a platform from the seed list + 6. Verify that seed only has PC as a platform flag + 7. Attempt to add a platform without using the --platform argument + 8. Verify that asset bundler returns False and file contents did not change + 9. Add Mac platform via --addPlatformToSeeds + 10. Validate that seed has both Mac & PC platform flags + 11. Attempt to remove platform without specifying a platform + 12. Validate that seed has both Mac & PC platform flags + 13. Validate that seed list contents did not change + 14. Remove seed + 15. Validate that seed was removed from the seed list """ helper = bundler_batch_helper @@ -619,7 +672,7 @@ class TestsAssetBundlerBatch_WindowsAndMac(object): # Validate both mac and pc are activated for seed # fmt:off check_seed_platform(helper["seed_list_file"], test_asset, - helper["platform_values"]["pc"] + helper["platform_values"]["osx"]) + helper["platform_values"]["pc"] + helper["platform_values"]["mac"]) # fmt:on # Remove MAC platform @@ -651,7 +704,7 @@ class TestsAssetBundlerBatch_WindowsAndMac(object): # Validate Mac platform was added back on. Save file contents # fmt:off all_lines = check_seed_platform(helper["seed_list_file"], test_asset, - helper["platform_values"]["pc"] + helper["platform_values"]["osx"]) + helper["platform_values"]["pc"] + helper["platform_values"]["mac"]) # fmt:on # Try to remove platform without specifying a platform to remove (should fail) @@ -692,6 +745,12 @@ class TestsAssetBundlerBatch_WindowsAndMac(object): """ Tests asset list comparison, both by file and by comparison type. Uses a set of controlled test assets to compare resulting output asset lists + + 1. Create comparison rules files + 2. Create seed files for different sets of test assets + 3. Create assetlist files for seed files + 4. Validate assetlists were created properly + 5. Compare using comparison rules files and just command line arguments """ helper = bundler_batch_helper env = ap_setup_fixture @@ -1021,6 +1080,16 @@ class TestsAssetBundlerBatch_WindowsAndMac(object): """ Tests that assetlists are created equivalent to the output while being created, and makes sure overwriting an existing file without the --allowOverwrites fails + + Test Steps: + 1. Check that Asset List creation requires PC platform flag + 2. Create a PC Asset List using asset info file and default seed lists using --print + 3. Validate all assets output are present in the asset list + 4. Create a seed file + 5. Attempt to overwrite Asset List without using --allowOverwrites + 6. Validate that command returned an error and file contents did not change + 7. Specifying platform but not "add" or "remove" should fail + 8. Verify file Has changed """ helper = bundler_batch_helper @@ -1046,7 +1115,7 @@ class TestsAssetBundlerBatch_WindowsAndMac(object): "--addDefaultSeedListFiles", "--platform=pc", "--print", - f"--project={workspace.project}" + f"--project-path={workspace.project}" ], universal_newlines=True, ) @@ -1102,7 +1171,16 @@ class TestsAssetBundlerBatch_WindowsAndMac(object): def test_WindowsAndMac_AP_BundleProcessing_BundleProcessedAtRuntime(self, workspace, bundler_batch_helper, asset_processor, request): # fmt:on - """Test to make sure the AP GUI will process a newly created bundle file""" + """ + Test to make sure the AP GUI will process a newly created bundle file + + Test Steps: + 1. Make asset list file (used for bundle creation) + 2. Start Asset Processor GUI + 3. Make bundle in /Bundles + 4. Validate file was created in Bundles folder + 5. Make sure bundle now exists in cache + """ # Set up helpers and variables helper = bundler_batch_helper @@ -1115,7 +1193,7 @@ class TestsAssetBundlerBatch_WindowsAndMac(object): bundle_result_path = os.path.join(bundles_folder, helper.platform_file_name("bundle.pak", workspace.asset_processor_platform)) - bundle_cache_path = os.path.join(workspace.paths.platform_cache(), workspace.project, + bundle_cache_path = os.path.join(workspace.paths.platform_cache(), "Bundles", helper.platform_file_name("bundle.pak", workspace.asset_processor_platform)) @@ -1131,6 +1209,8 @@ class TestsAssetBundlerBatch_WindowsAndMac(object): addSeed=level_pak, assetListFile=helper["asset_info_file_request"], ) + + # Run Asset Processor GUI result, _ = asset_processor.gui_process() assert result, "AP GUI failed" @@ -1155,14 +1235,22 @@ class TestsAssetBundlerBatch_WindowsAndMac(object): @pytest.mark.assetpipeline # fmt:off def test_WindowsAndMac_FilesMarkedSkip_FilesAreSkipped(self, workspace, bundler_batch_helper): + """ + Test Steps: + 1. Create an asset list with a file marked as skip + 2. Verify file was created + 3. Verify that only the expected assets are present in the created asset list + """ expected_assets = [ - "libs/particles/milestone2particles.xml", - "textures/milestone2/particles/fx_sparkstreak_01.dds" + "ui/canvases/lyshineexamples/animation/multiplesequences.uicanvas", + "ui/textures/prefab/button_normal.sprite" ] bundler_batch_helper.call_assetLists( assetListFile=bundler_batch_helper['asset_info_file_request'], - addSeed="libs/particles/milestone2particles.xml", - skip="textures/milestone2/particles/fx_launchermuzzlering_01.dds,textures/milestone2/particles/fx_launchermuzzlefront_01.dds" + 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" ) assert os.path.isfile(bundler_batch_helper["asset_info_file_result"]) assets_in_list = [] @@ -1176,6 +1264,12 @@ class TestsAssetBundlerBatch_WindowsAndMac(object): # fmt:off def test_WindowsAndMac_AssetListSkipOneOfTwoParents_SharedDependencyIsIncluded(self, workspace, bundler_batch_helper): + """ + Test Steps: + 1. Create Asset List with a parent asset that is skipped + 2. Verify that Asset List was created + 3. Verify that only the expected assets are present in the asset list + """ expected_assets = [ "testassets/bundlerskiptest_grandparent.dynamicslice", "testassets/bundlerskiptest_parenta.dynamicslice", @@ -1204,6 +1298,13 @@ class TestsAssetBundlerBatch_WindowsAndMac(object): @pytest.mark.assetpipeline # fmt:off def test_WindowsAndMac_AssetLists_SkipRoot_ExcludesAll(self, workspace, bundler_batch_helper): + """ + Negative scenario test that skips the same file being used as the parent seed. + + Test Steps: + 1. Create an asset list that skips the root asset + 2. Verify that asset list was not generated + """ result, _ = bundler_batch_helper.call_assetLists( assetListFile=bundler_batch_helper['asset_info_file_request'], @@ -1220,6 +1321,13 @@ class TestsAssetBundlerBatch_WindowsAndMac(object): @pytest.mark.assetpipeline # fmt:off def test_WindowsAndMac_AssetLists_SkipUniversalWildcard_ExcludesAll(self, workspace, bundler_batch_helper): + """ + Negative scenario test that uses the all wildcard when generating an asset list. + + Test Steps: + 1. Create an Asset List while using the universal all wildcard "*" + 2. Verify that asset list was not generated + """ result, _ = bundler_batch_helper.call_assetLists( assetListFile=bundler_batch_helper['asset_info_file_request'], diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_processor_batch_dependency_tests.py b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_processor_batch_dependency_tests.py index e329846554..0c6924f3a2 100755 --- a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_processor_batch_dependency_tests.py +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_processor_batch_dependency_tests.py @@ -67,7 +67,19 @@ class TestsAssetProcessorBatch_DependenycyTests(object): libs/materialeffects/surfacetypes.xml is listed as an entry engine_dependencies.xml libs/materialeffects/surfacetypes.xml is not listed as a missing dependency in the 'assetprocessorbatch' console output + + Test Steps: + 1. Assets are pre-processed + 2. Verify that engine_dependencies.xml exists + 3. Verify engine_dependencies.xml has surfacetypes.xml present + 4. Run Missing Dependency scanner against the engine_dependenciese.xml + 5. Verify that Surfacetypes.xml is NOT in the missing depdencies output + 6. Add the schema file which allows our xml parser to understand dependencies for our engine_dependencies file + 7. Process assets + 8. Run Missing Dependency scanner against the engine_dependenciese.xml + 9. Verify that surfacetypes.xml is in the missing dependencies out """ + env = ap_setup_fixture BATCH_LOG_PATH = env["ap_batch_log_file"] asset_processor.create_temp_asset_root() @@ -137,6 +149,11 @@ class TestsAssetProcessorBatch_DependenycyTests(object): def test_WindowsMacPlatforms_BatchCheckSchema_ValidateErrorChecking(self, workspace, asset_processor, ap_setup_fixture, folder, schema): # fmt:on + """ + Test Steps: + 1. Run the Missing Dependency Scanner against everything + 2. Verify that there are no missing dependencies. + """ env = ap_setup_fixture def missing_dependency_log_lines(log) -> [str]: diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_processor_batch_dependency_tests2.py b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_processor_batch_dependency_tests2.py index 4f33e0df4e..f184ff2392 100755 --- a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_processor_batch_dependency_tests2.py +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_processor_batch_dependency_tests2.py @@ -60,6 +60,15 @@ class TestsAssetProcessorBatch_DependenycyTests(object): Verify that Schemas can be loaded via Gems utilizing the fonts schema :returns: None + + Test Steps: + 1. Run Missing Dependency Scanner against %fonts%.xml when no fonts are present + 2. Verify fonts are scanned + 3. Verify that missing dependencies are found for fonts + 4. Add fonts to game project + 5. Run Missing Dependency Scanner against %fonts%.xml when fonts are present + 6. Verify that same amount of fonts are scanned + 7. Verify that there are no missing dependencies. """ schema_name = "Font.xmlschema" asset_processor.create_temp_asset_root() diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_processor_batch_tests.py b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_processor_batch_tests.py index 0d830b39e2..3efd9e7fce 100755 --- a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_processor_batch_tests.py +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_processor_batch_tests.py @@ -100,6 +100,14 @@ class TestsAssetProcessorBatch_AllPlatforms(object): @pytest.mark.BAT @pytest.mark.assetpipeline def test_RunAPBatch_TwoPlatforms_ExitCodeZero(self, asset_processor): + """ + Tests Process assets for PC & Mac and verifies that processing exited without error + + Test Steps: + 1. Add Mac and PC as enabled platforms + 2. Process Assets + 3. Validate that AP exited cleanly + """ asset_processor.create_temp_asset_root() asset_processor.enable_asset_processor_platform("pc") asset_processor.enable_asset_processor_platform("mac") @@ -111,6 +119,14 @@ class TestsAssetProcessorBatch_AllPlatforms(object): @pytest.mark.assetpipeline @pytest.mark.test_case_id('C1571826') def test_RunAPBatch_OnlyIncludeInvalidAssets_NoAssetsAdded(self, asset_processor, ap_setup_fixture): + """ + Tests processing invalid assets and validating that no assets were moved to the cache + + Test Steps: + 1. Create a test environment with invalid assets + 2. Run asset processor + 3. Validate that no assets were found in the cache + """ asset_processor.prepare_test_environment(ap_setup_fixture["tests_dir"], "test_ProcessAssets_OnlyIncludeInvalidAssets_NoAssetsAdded") result, _ = asset_processor.batch_process() @@ -127,6 +143,16 @@ class TestsAssetProcessorBatch_AllPlatforms(object): "recognized as failing in the logs. There appears to be a window where the AutoFailJob doesn't complete" "before the shutdown completes and the failure doesn't end up counting") def test_ProcessAssets_IncludeTwoAssetsWithSameProduct_FailingOnSecondAsset(self, asset_processor, ap_setup_fixture): + """ + Tests processing two source assets with the same product file and validates that the second source will error + + Test Steps: + 1. Create a test environment that has two source files with the same product + 2. Run asset processor + 3. Validate that 1 asset failed to process + 4. Validate that only one product file with the expected name is found in the cache + """ + asset_processor.prepare_test_environment(ap_setup_fixture["tests_dir"], "test_ProcessAssets_IncludeTwoAssetsWithSameProduct_FailingOnSecondAsset") result, output = asset_processor.batch_process(capture_output = True, expect_failure = True) @@ -143,6 +169,17 @@ class TestsAssetProcessorBatch_AllPlatforms(object): @pytest.mark.assetpipeline @pytest.mark.test_case_id('C1587615') def test_ProcessAndDeleteCache_APBatchShouldReprocess(self, asset_processor, ap_setup_fixture): + """ + Tests processing once, deleting the generated cache, then processing again and validates the cache is created + + Test Steps: + 1. Run asset processor + 2. Compare the cache with expected output + 3. Delete Cache + 4. Compare the cache with expected output to verify that cache is gone + 5. Run asset processor with fastscan disabled + 6. Compare the cache with expected output + """ # Deleting assets from Cache will make them re-processed in AP (after start) # Copying test assets to project folder and deleting them from cache to make sure APBatch will process them @@ -174,6 +211,18 @@ class TestsAssetProcessorBatch_AllPlatforms(object): @pytest.mark.assetpipeline @pytest.mark.test_case_id('C1591564') def test_ProcessAndChangeSource_APBatchShouldReprocess(self, asset_processor, ap_setup_fixture): + """ + Tests reprocessing of a modified asset and verifies that it was reprocessed + + Test Steps: + 1. Prepare test environment and copy test asset over + 2. Run asset processor + 3. Verify asset processed + 4. Verify asset is in cache + 4. Modify asset + 5. Re-run asset processor + 6. Verify asset was processed + """ # AP Batch Processing changed files (after start) # Copying test assets to project folder and deleting them from cache to make sure APBatch will process them @@ -208,6 +257,18 @@ class TestsAssetProcessorBatch_AllPlatforms(object): @pytest.mark.BAT @pytest.mark.assetpipeline def test_ProcessByBothApAndBatch_Md5ShouldMatch(self, asset_processor, ap_setup_fixture): + """ + Tests that a cache generated by AP GUI is the same as AP Batch + + Test Steps: + 1. Create test environment with test assets + 2. Call asset processor batch + 3. Get checksum for file cache + 4. Clean up test environment + 5. Call asset processor gui with quitonidle + 6. Get checksum for file cache + 7. Verify that checksums are equal + """ # AP Batch and AP app processed assets MD5 sums should be the same # Copying test assets to project folder and deleting them from cache to make sure APBatch will process them @@ -240,6 +301,16 @@ class TestsAssetProcessorBatch_AllPlatforms(object): @pytest.mark.assetpipeline @pytest.mark.test_case_id('C1612446') def test_AddSameAssetsDifferentNames_ShouldProcess(self, asset_processor, ap_setup_fixture): + """ + Tests Asset Processing of duplicate assets with different names and verifies that both assets are processed + + Test Steps: + 1. Create test environment with two identical source assets with different names + 2. Run asset processor + 3. Verify that assets didn't fail to process + 4. Verify the correct number of jobs were performed + 5. Verify that product files are in the cache + """ # Feed two similar slices and texture with different names - should process without any issues # Copying test assets to project folder and deleting them from cache to make sure APBatch will process them @@ -277,6 +348,19 @@ class TestsAssetProcessorBatch_AllPlatforms(object): "recognized as failing in the logs. There appears to be a window where the AutoFailJob doesn't complete" "before the shutdown completes and the failure doesn't end up counting") def test_AddTwoTexturesWithSameName_ShouldProcessAfterRename(self, asset_processor, ap_setup_fixture): + """ + Tests processing of two textures with the same name then verifies that AP will successfully process after + renaming one of the textures + + Test Steps: + 1. Create test environment with two textures that have the same name + 2. Launch Asset Processor + 3. Validate that Asset Processor generates an error + 4. Rename texture files + 5. Run asset processor + 6. Verify that asset processor does not error + 7. Verify that expected product files are in the cache + """ # Feed two different textures with same name (but different extensions) - ap will fail # Rename one of textures and failure should go away @@ -312,6 +396,15 @@ class TestsAssetProcessorBatch_AllPlatforms(object): @pytest.mark.BAT @pytest.mark.assetpipeline def test_InvalidServerAddress_Warning_Logs(self, asset_processor): + """ + Tests running Asset Processor with an invalid server address and verifies that AP returns a warning about + an invalid server address + + Test Steps: + 1. Launch asset processor while providing an invalid server address + 2. Verify asset processor does not fail + 3. Verify that asset processor generated a warning informing the user about an invalid server address + """ asset_processor.create_temp_asset_root() # Launching AP and making sure that the warning exists @@ -327,6 +420,12 @@ class TestsAssetProcessorBatch_AllPlatforms(object): def test_AllSupportedPlatforms_IncludeValidAssets_AssetsProcessed(self, asset_processor, ap_setup_fixture): """ AssetProcessorBatch is successfully processing newly added assets + + Test Steps: + 1. Create a test environment with test assets + 2. Launch Asset Processor + 3. Verify that asset processor does not fail to process + 4. Verify assets are not missing from the cache """ env = ap_setup_fixture @@ -350,6 +449,14 @@ class TestsAssetProcessorBatch_AllPlatforms(object): def test_AllSupportedPlatforms_DeletedAssets_DeletedFromCache(self, asset_processor, ap_setup_fixture): """ AssetProcessor successfully deletes cached items when removed from project + + Test Steps: + 1. Create a test environment with test assets + 2. Run asset processor + 3. Verify expected assets are in the cache + 4. Delete test assets + 5. Run asset processor + 6. Verify expected assets are in the cache """ env = ap_setup_fixture @@ -385,6 +492,10 @@ class TestsAssetProcessorBatch_AllPlatforms(object): """ Tests that when cache is deleted (no cache) and AssetProcessorBatch runs, it successfully starts and processes assets. + + Test Steps: + 1. Run asset processor + 2. Verify asset processor exits cleanly """ asset_processor.create_temp_asset_root() @@ -402,6 +513,14 @@ class TestsAssetProcessorBatch_AllPlatforms(object): # fmt:on """ AssetProcessor successfully recovers assets from cache when deleted. + + Test Steps: + 1. Create test enviornment with test assets + 2. Run Asset Processor and verify it exits cleanly + 3. Make sure cache folder was generated + 4. Delete temp cache assets but leave database behind + 5. Run asset processor and verify it exits cleanly + 6. Verify expected files were generated in the cache """ env = ap_setup_fixture @@ -434,6 +553,14 @@ class TestsAssetProcessorBatch_AllPlatforms(object): @pytest.mark.assetpipeline # fmt:off def test_AllSupportedPlatforms_RunFastScanOnEmptyCache_FullScanRuns(self, ap_setup_fixture, asset_processor): + """ + Tests fast scan processing on an empty cache and verifies that a full analyis will be peformed + + Test Steps: + 1. Create a test environment + 2. Execute asset processor batch with fast scan enabled + 3. Verify that a full analysis is performed + """ # fmt:on env = ap_setup_fixture asset_processor.create_temp_asset_root() @@ -455,6 +582,11 @@ class TestsAssetProcessorBatch_AllPlatforms(object): """ After running the APBatch and AP GUI, Logs directory should exist (C1564055), JobLogs, Batch log, and GUI log should exist in the logs directory (C1564056) + + Test Steps: + 1. Run asset processor batch + 2. Run asset processor gui with quit on idle + 3. Verify that logs exist for both AP Batch & AP GUI """ asset_processor.create_temp_asset_root() LOG_PATH = { @@ -536,6 +668,11 @@ class TestsAssetProcessorBatch_AllPlatforms(object): """ Utilizing corrupted test assets, run the batch process to verify the AP logs the failure to process the corrupted file. + + Test Steps: + 1. Create test environment with corrupted slice + 2. Launch Asset Processor + 3. Verify that asset processor fails to process corrupted slice """ env = ap_setup_fixture error_line_found = False @@ -552,6 +689,15 @@ class TestsAssetProcessorBatch_AllPlatforms(object): @pytest.mark.BAT @pytest.mark.assetpipeline def test_validateDirectPreloadDependency_Found(self, asset_processor, ap_setup_fixture, workspace): + """ + Tests processing an asset with a circular dependency and verifies that Asset Processor will return an error + notifying the user about a circular dependency. + + Test Steps: + 1. Create test environment with an asset that has a circular dependency + 2. Launch asset processor + 3. Verify that error is returned informing the user that the asset has a circular dependency + """ env = ap_setup_fixture error_line_found = False @@ -567,6 +713,15 @@ class TestsAssetProcessorBatch_AllPlatforms(object): @pytest.mark.BAT @pytest.mark.assetpipeline def test_validateNestedPreloadDependency_Found(self, asset_processor, ap_setup_fixture, workspace): + """ + Tests processing of a nested circular dependency and verifies that Asset Processor will return an error + notifying the user about a circular depdency + + Test Steps: + 1. Create test environment with an asset that has a nested circular dependency + 2. Launch asset processor + 3. Verify that error is returned informing the user that the asset has a circular dependency + """ env = ap_setup_fixture error_line_found = False diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_processor_batch_tests_2.py b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_processor_batch_tests_2.py index 5c42af2139..fec5df8eb7 100755 --- a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_processor_batch_tests_2.py +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_processor_batch_tests_2.py @@ -80,6 +80,15 @@ class TestsAssetProcessorBatch_AllPlatforms(object): # fmt:on """ Tests that fast scan mode can be used and is faster than full scan mode. + + Test Steps: + 1. Ensure all assets are processed + 2. Run Asset Processor without fast scan and measure the time it takes to run + 3. Capture Full Analysis was performed and number of assets processed + 4. Run Asset Processor with full scan and measure the time it takes to run + 5. Capture Full Analysis wans't performed and number of assets processed + 6. Verify that fast scan was faster than full scan + 7. Verify that full scan scanned more assets """ asset_processor.create_temp_asset_root() @@ -111,76 +120,23 @@ class TestsAssetProcessorBatch_AllPlatforms(object): assert full_scan_time > fast_scan_time, "Fast scan was slower that full scan" assert full_scan_analysis[0] > fast_scan_analysis[0], "Full scan did not process more assets than fast scan" - @pytest.mark.test_case_id("C18787404") - @pytest.mark.BAT - @pytest.mark.assetpipeline - @pytest.mark.skip(reason="External project is currently broken.") # LY-119863 - def test_AllSupportedPlatforms_ExternalProject_APRuns(self, workspace, ap_external_project_setup_fixture): - - external_resources = ap_external_project_setup_fixture - logger.info(f"Running external project test at path {external_resources['project_dir']}") - # Delete existing "external project" build if it exists - if os.path.exists(external_resources["project_dir"]): - fs.delete([external_resources["project_dir"]], True, True) - - # fmt:off - assert not os.path.exists(external_resources["project_dir"]), \ - f'{external_resources["project_dir"]} was not deleted' - # fmt:on - - lmbr_cmd = [ - workspace.paths.lmbr(), - "projects", - "create", - external_resources["project_name"], - "--template", - "EmptyTemplate", - "--app-root", - external_resources["project_dir"], - ] - - logger.info(f"Running lmbr projects create command '{lmbr_cmd}'") - - try: - subprocess.check_call(lmbr_cmd) - except subprocess.CalledProcessError as e: - assert False, f"lmbr projects create failed\n{e.stderr}" - - logger.info("...lmbr finished") - assert os.path.exists(external_resources["project_dir"]), "Project folder was not created" - - # AssetProcessor for new External project. Uses mock workspace to emulate external project workspace - external_ap = AssetProcessor(external_resources["external_workspace"]) - - # fmt:off - assert external_ap.batch_process(fastscan=False), \ - "Asset Processor Batch failed on external project" - # fmt:on - - # Parse log looking for errors or failures - log = APLogParser(workspace.paths.ap_batch_log()) - failures, errors = log.runs[-1]["Failures"], log.runs[-1]["Errors"] - assert failures == 0, f"There were {failures} asset processing failures" - assert errors == 0, f"There were {errors} asset processing errors" - - # Check that project cache was created (DNE until AP makes it) - project_cache = os.path.join(external_resources["project_dir"], "Cache") - assert os.path.exists(project_cache), f"{project_cache} was not created by AP" - - # Clean up external project - fs.delete([external_resources["project_dir"]], True, True) - - # fmt:off - assert not os.path.exists(external_resources["project_dir"]), \ - f"{external_resources['project_dir']} was not deleted" - # fmt:on - @pytest.mark.test_case_id("C4874121") @pytest.mark.BAT @pytest.mark.assetpipeline @pytest.mark.parametrize("clear_type", ["rewrite", "delete_asset", "delete_dir"]) def test_AllSupportedPlatforms_DeleteBadAssets_BatchFailedJobsCleared( self, workspace, request, ap_setup_fixture, asset_processor, clear_type): + """ + Tests the ability of Asset Processor to recover from processing of bad assets by removing them from scan folder + + Test Steps: + 1. Create testing environment with good and multiple bad assets + 2. Run Asset Processor + 3. Verify that bad assets fail to process + 4. Fix a bad asset & delete the others + 5. Run Asset Processor + 6. Verify Asset Processor does not have any asset failues + """ env = ap_setup_fixture error_search_terms = ["WWWWWWWWWWWW"] @@ -250,6 +206,14 @@ class TestsAssetProcessorBatch_Windows(object): Verify the AP batch and Gui can run and process assets independent of the Editor We do not want or need to kill running Editors here as they can be involved in other tests or simply being run locally in this branch or another + + Test Steps: + 1. Create temporary testing environment + 2. Run asset processor GUI + 3. Verify AP GUI doesn't error + 4. Stop AP GUI + 5. Run Asset Processor Batch with Fast Scan + 5. Verify Asset Processor Batch exits cleanly """ asset_processor.create_temp_asset_root() @@ -272,6 +236,11 @@ class TestsAssetProcessorBatch_Windows(object): """ Request a run for an invalid platform "AssetProcessor: Error: Platform in config file or command line 'notaplatform'" should be present in the logs + + Test Steps: + 1. Create temporary testing environment + 2. Run Asset Processor with an invalid platform + 3. Check that asset processor returns an Error notifying the user that the invalid platform is not supported """ asset_processor.create_temp_asset_root() error_search_terms = 'AssetProcessor: Error: The list of enabled platforms in the settings registry does not contain platform ' \ diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_processor_gui_tests.py b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_processor_gui_tests.py index ed5651755c..88fa1a77b4 100755 --- a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_processor_gui_tests.py +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_processor_gui_tests.py @@ -77,6 +77,13 @@ class TestsAssetProcessorGUI_Windows(object): def test_SendInputOnControlChannel_ReceivedAndResponded(self, asset_processor): """ Test that the control channel connects and that communication works both directions + + Test Steps: + 1. Start Asset Processor + 2. Send a Ping message to Asset Processor + 3. Listen for Asset Processor response + 4. Verify Asset Processor responds + 5. Stop asset Processor """ asset_processor.create_temp_asset_root() @@ -129,7 +136,15 @@ class TestsAssetProcessorGUI_Windows(object): # fmt:on """ Asset Processor Deletes processed assets when source is removed from project folder (while running) + + Test Steps: + 1. Create a temporary test environment + 2. Run Asset Processor GUI set to stay open on idle and verify that it does not fail + 3. Verify that assets were copied to the cache + 4. Delete the source test asset directory + 5. Verify assets are deleted from the cache """ + env = ap_setup_fixture # Copy test assets to project folder and verify test assets folder exists @@ -170,7 +185,18 @@ class TestsAssetProcessorGUI_Windows(object): # fmt:on """ Processing changed files (while running) + + Test Steps: + 1. Create temporary test environment with test assets + 2. Open Asset Processor GUI with set to stay open after idle and verify it does not fail + 3. Verify contents of source asset for later comparison + 4. Verify contents of product asset for later comparison + 5. Modify contents of source asset + 6. Wait for Asset Processor to go back to idle state + 7. Verify contents of source asset are the modified version + 8. Verify contents of product asset are the modified version """ + env = ap_setup_fixture # Copy test assets to project folder and verify test assets folder exists @@ -184,7 +210,7 @@ class TestsAssetProcessorGUI_Windows(object): result, _ = asset_processor.gui_process(quitonidle=False) assert result, "AP GUI failed" - # Verify contents of test asset in project folder before modication + # Verify contents of test asset in project folder before modification with open(project_asset_path, "r") as project_asset_file: assert project_asset_file.read() == "before_state" @@ -217,7 +243,14 @@ class TestsAssetProcessorGUI_Windows(object): def test_WindowsPlatforms_RunAP_ProcessesIdle(self, asset_processor): """ Asset Processor goes idle + + Test Steps: + 1. Create a temporary testing evnironment + 2. Run Asset Processor GUI without quitonidle + 3. Verify AP Goes Idle + 4. Verify AP goes below 1% CPU usage """ + CPU_USAGE_THRESHOLD = 1.0 # CPU usage percentage delimiting idle from active CPU_USAGE_WIND_DOWN = 10 # Time allowed in seconds for idle processes to stop using CPU @@ -245,7 +278,16 @@ class TestsAssetProcessorGUI_Windows(object): ): """ Processing newly added files to project folder (while running) + + Test Steps: + 1. Create a temporary testing environment with test assets + 2. Create a secondary set of testing assets that have not been copied into the the testing environment + 3. Start Asset Processor without quitonidle + 4. While Asset Processor is running add secondary set of testing assets to the testing environment + 5. Wait for Asset Processor to go idle + 6. Verify that all assets are in the cache """ + env = ap_setup_fixture level_name = "C1564064_level" new_asset = "C1564064.scriptcanvas" @@ -316,7 +358,14 @@ class TestsAssetProcessorGUI_Windows(object): def test_WindowsPlatforms_LaunchAP_LogReportsIdle(self, asset_processor, workspace, ap_idle): """ Asset Processor creates a log entry when it goes idle + + Test Steps: + 1. Create temporary testing environment + 2. Run Asset Processor batch to pre-process assets + 3. Run Asset Processor GUI + 4. Check if Asset Processor GUI reports that it has gone idle """ + asset_processor.create_temp_asset_root() # Run batch process to ensure project assets are processed assert asset_processor.batch_process(), "AP Batch failed" @@ -331,6 +380,17 @@ class TestsAssetProcessorGUI_Windows(object): @pytest.mark.assetpipeline def test_APStopTimesOut_ExceptionThrown(self, ap_setup_fixture, asset_processor): + """ + Tests whether or not Asset Processor will Time Out + + Test Steps: + 1. Create a temporary testing environment + 2. Start the Asset Processor + 3. Copy in assets to the test environment + 4. Try to stop the Asset Processor with a timeout of 1 second (This cannot be done manually). + 5. Verify that Asset Processor times out and returns the expected error + """ + asset_processor.create_temp_asset_root() asset_processor.start() @@ -347,9 +407,20 @@ class TestsAssetProcessorGUI_Windows(object): @pytest.mark.assetpipeline def test_APStopDefaultTimeout_NoException(self, asset_processor): - # If this test fails, it means other tests using the default timeout may have issues. - # In that case, either the default timeout should either be raised, or the performance - # of AP launching should be improved. + """ + Tests the default timeout of the Asset Processor + + If this test fails, it means other tests using the default timeout may have issues. + In that case, either the default timeout should either be raised, or the performance + of AP launching should be improved. + + Test Steps: + 1. Create a temporary testing environment + 2. Start the Asset Processor + 3. Stop the asset Processor without sending a timeout to it + 4. Verify that the asset processor times out and returns the expected error + """ + asset_processor.create_temp_asset_root() asset_processor.start() ap_quit_timed_out = False diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_processor_gui_tests_2.py b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_processor_gui_tests_2.py index b25ee081a1..3fb9ae5a81 100755 --- a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_processor_gui_tests_2.py +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_processor_gui_tests_2.py @@ -75,10 +75,17 @@ class TestsAssetProcessorGUI_WindowsAndMac(object): @pytest.mark.test_case_id("C3540434") @pytest.mark.BAT @pytest.mark.assetpipeline - def test_WindowsAndMacPlatforms_AP_GUI_FastScanSettingCreated(self, asset_processor, fast_scan_backup): + def test_WindowsAndMacPlatforms_GUIFastScanNoSettingSet_FastScanSettingCreated(self, asset_processor, fast_scan_backup): """ Tests that a fast scan settings entry gets created for the AP if it does not exist and ensures that the entry is defaulted to fast-scan enabled + + Test Steps: + 1. Create temporary testing environment + 2. Delete existing fast scan setting if exists + 3. Run Asset Processor GUI without setting FastScan setting (default:true) and without quitonidle + 4. Wait and check to see if Windows Registry fast scan setting is created + 5. Verify that Fast Scan setting is set to true """ asset_processor.create_temp_asset_root() @@ -119,6 +126,14 @@ class TestsAssetProcessorGUI_WindowsAndMac(object): Make sure game launcher working with Asset Processor set to turbo mode Validate that no fatal errors (crashes) are reported within a certain time frame for the AP and the GameLauncher + + Test Steps: + 1. Create temporary testing environment + 2. Set fast scan to true + 3. Verify fast scan is set to true + 4. Launch game launcher + 5. Verify launcher has launched without error + 6. Verify that asset processor has launched """ CHECK_ALIVE_SECONDS = 15 @@ -166,6 +181,14 @@ class TestsAssetProcessorGUI_AllPlatforms(object): # fmt:on """ Deleting slices and uicanvases while AP is running + + Test Steps: + 1. Create temporary testing environment with test assets + 2. Launch Asset Processor and wait for it to go idle + 3. Verify product assets were created in the cache + 4. Delete test assets from the cache + 5. Wait for Asset Processor to go idle + 6. Verify product assets were regenerated in the cache """ env = ap_setup_fixture @@ -201,6 +224,15 @@ class TestsAssetProcessorGUI_AllPlatforms(object): ): """ Process slice files and uicanvas files from the additional scanfolder + + Test Steps: + 1. Create temporary testing environment + 2. Run asset processor batch + 3. Validate that product assets were generated in the cache + 4. Create an additional scan folder with assets + 5. Create additional scan folder params to pass to Asset Processor + 6. Run Asset Processor GUI with QuitOnIdle and pass in params for the additional scan folder settings + 7. Verify additional product assets from additional scan folder are present in the cache """ env = ap_setup_fixture # Copy test assets to new folder in dev folder @@ -250,6 +282,12 @@ class TestsAssetProcessorGUI_AllPlatforms(object): """ Launch AP with invalid address in bootstrap.cfg Assets should process regardless of the new address + + Test Steps: + 1. Create a temporary testing environment + 2. Set an invalid ip address in Asset Processor settings file + 3. Launch Asset Processor GUI + 4. Verify that it processes assets and exits cleanly even though it has an invalid IP. """ test_ip_address = "1.1.1.1" # an IP address without Asset Processor @@ -269,6 +307,14 @@ class TestsAssetProcessorGUI_AllPlatforms(object): def test_AllSupportedPlatforms_ModifyAssetInfo_AssetsReprocessed(self, ap_setup_fixture, asset_processor): """ Modifying assetinfo files triggers file reprocessing + + Test Steps: + 1. Create temporary testing environment with test assets + 2. Run Asset Processor GUI + 3. Verify that Asset Processor exited cleanly and product assets are in the cache + 4. Modify the .assetinfo file by adding a newline + 5. Wait for Asset Processor to go idle + 6. Verify that product files were regenerated (Time Stamp compare) """ env = ap_setup_fixture diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_relocator_tests.py b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_relocator_tests.py index 2d3872bf31..30044fa9e2 100755 --- a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_relocator_tests.py +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_relocator_tests.py @@ -85,6 +85,18 @@ class TestsAssetRelocator_WindowsAndMac(object): def test_WindowsMacPlatforms_RelocatorMoveFileWithConfirm_MoveSuccess(self, request, workspace, asset_processor, ap_setup_fixture, testId, readonly, confirm, success): + """ + Tests whether tests with Move File Confirm are successful + + Test Steps: + 1. Create temporary testing environment + 2. Set move location + 3. Determine if confirm flag is set + 4. Attempt to move the files + 5. If confirm flag set: + * Validate Move was successful + * Else: Validate move was not successful + """ env = ap_setup_fixture copied_asset = '' @@ -141,6 +153,11 @@ class TestsAssetRelocator_WindowsAndMac(object): User should be warned that LeaveEmptyFolders needs to be used with the move or delete command :return: None + + Test Steps: + 1. Create temporary testing environment + 2. Attempt to move with --LeaveEmptyFolders set + 3. Verify user is given a message that command requires to be used with --move or --delete """ env = ap_setup_fixture expected_message = "Command --leaveEmptyFolders must be used with command --move or --delete" @@ -162,6 +179,11 @@ class TestsAssetRelocator_WindowsAndMac(object): Asset with UUID/AssetId reference in non-standard format is successfully scanned and relocated to the MoveOutput folder. This test uses a pre-corrupted .slice file. + + Test Steps: + 1. Create temporary testing environment with a corrupted slice + 2. Attempt to move the corrupted slice + 3. Verify that corrupted slice was moved successfully """ env = ap_setup_fixture @@ -194,6 +216,11 @@ class TestsAssetRelocator_WindowsAndMac(object): def test_WindowsMacPlatforms_UpdateReferences_MoveCommandMessage(self, ap_setup_fixture, asset_processor): """ UpdateReferences without move or delete + + Test Steps: + 1. Create temporary testing environment + 2. Attempt to move with UpdateReferences but without move or delete flags + 3. Verify that message is returned to the user that additional flags are required """ env = ap_setup_fixture expected_message = "Command --updateReferences must be used with command --move" @@ -215,6 +242,11 @@ class TestsAssetRelocator_WindowsAndMac(object): """ When running the relocator command --AllowBrokenDependencies without the move or delete flags, the user should be warned that the flags are necessary for the functionality to be used + + Test Steps: + 1. Create temporary testing environment + 2. Attempt to move with AllowBrokenDependencies without the move or delete flag + 3. Verify that message is returned to the user that additional flags are required """ env = ap_setup_fixture @@ -302,10 +334,19 @@ class TestsAssetRelocator_WindowsAndMac(object): project ): """ + Dynamic data test for deleting a file with Asset Relocator: + C21968355 Delete a file with confirm C21968356 Delete a file without confirm C21968359 Delete a file that is marked as ReadOnly C21968360 Delete a file that is not marked as ReadOnly + + Test Steps: + 1. Create temporary testing environment + 2. Set the read-only status of the file based on the test case + 3. Run asset relocator with --delete and the confirm status based on the test case + 4. Assert file existence or nonexistence based on the test case + 5. Validate the relocation report based on expected and unexpected messages """ env = ap_setup_fixture test_file = "testFile.txt" @@ -430,6 +471,15 @@ class TestsAssetRelocator_WindowsAndMac(object): Test the LeaveEmptyFolders flag in various configurations :returns: None + + Test Steps: + 1. Create temporary testing environment + 2. Build the various move/delete commands here based on test data + 3. Run the move command with the various triggers based on test data + 4. Verify the original assets folder still exists based on test data + 5. Verify the files successfully moved to new location based on test data + 6. Verify that the files were removed from original location based on test data + 7. Verify the files have not been deleted or moved from original location based on test data """ # # Start test setup # # env = ap_setup_fixture @@ -517,6 +567,12 @@ class TestsAssetRelocator_WindowsAndMac(object): """ The test will attempt to move test assets that are not tracked under P4 source control using the EnableSCM flag Because the files are not tracked by source control, the relocation should fail + + Test Steps: + 1. Create temporary testing environment + 2. Set ReadOnly or Not-ReadOnly for the test files based on test data + 3. Generate and run the enableSCM command + 4. Verify the move failed and expected messages are present """ # Move the test assets into the project folder env = ap_setup_fixture @@ -1037,6 +1093,13 @@ class TestsAssetRelocator_WindowsAndMac(object): C21968370 AllowBrokenDependencies with move and confirm C21968371 AllowBrokenDependencies with move and without confirm C21968375 AllowBrokenDependencies with delete + + Test Steps: + 1. Create temporary testing environment + 2. Run Asset Processor to Process Assets + 3. Build primary AP Batch parameter value and destination paths + 4. Validate resulting file paths in source and output directories + 5. Validate the log based on expected and unexpected messages """ env = ap_setup_fixture all_test_asset_rel_paths = [ @@ -1254,6 +1317,18 @@ class TestsAssetRelocator_WindowsAndMac(object): @pytest.mark.parametrize("test", tests) def test_WindowsAndMac_MoveMetadataFiles_PathExistenceAndMessage(self, workspace, request, ap_setup_fixture, asset_processor, test): + """ + Tests whether moving metadata files can be moved + + Test Steps: + 1. Create temporary testing environment + 2. Determine if using wildcards on paths or not + 3. Determine if excludeMetaDataFiles is set or not + 4. Build primary AP Batch parameter value and destination paths + 5. Build and run the AP Batch command with parameters + 6. Validate resulting file paths in source and output directories + 7. Validate the log based on expected and unexpected messages + """ env = ap_setup_fixture def teardown(): @@ -1342,7 +1417,7 @@ class TestsAssetRelocator_WindowsAndMac(object): @dataclass class MoveTest: - description: str # test case title directly copied from Testrail + description: str # test case title asset_folder: str # which folder in ./assets will be used for this test encoded_command: str # the command to execute encoded_output_dir: str # the destination directory to validate @@ -1350,7 +1425,7 @@ class MoveTest: name_change_map: dict = None files_that_stay: List[str] = field(default_factory=lambda: []) output_messages: List[str] = field(default_factory=lambda: []) - step: str = None # the step of the test from Testrail + step: str = None # the step of the test from test repository prefix_commands: List[str] = field(default_factory=lambda: ["AssetProcessorBatch", "--zeroAnalysisMode"]) suffix_commands: List[str] = field(default_factory=lambda: ["--confirm"]) env: dict = field(init=False, default=None) # inject the ap_setup_fixture at runtime @@ -3718,7 +3793,18 @@ class TestsAssetProcessorMove_WindowsAndMac: # -k C19462747 @pytest.mark.parametrize("test", move_a_file_tests + move_a_folder_tests) - def test_WindowsMacPlatforms_MoveCommand(self, asset_processor, ap_setup_fixture, test: MoveTest, project): + def test_WindowsMacPlatforms_MoveCommand_CommandResult(self, asset_processor, ap_setup_fixture, test: MoveTest, project): + """ + + Test Steps: + 1. Create temporary testing environment based on test data + 2. Validate that temporary testing environment was created successfully + 3. Execute the move command based upon the test data + 4. Validate that files are where they're expected according to the test data + 5. Validate unexpected files are not found according to the test data + 6. Validate output messages according to the test data + 7. Validate move status according to the test data + """ source_folder, _ = asset_processor.prepare_test_environment(ap_setup_fixture["tests_dir"], test.asset_folder) test.map_env(ap_setup_fixture, source_folder) diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/missing_dependency_tests.py b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/missing_dependency_tests.py index 432b6cdfc8..74f6de1129 100755 --- a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/missing_dependency_tests.py +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/missing_dependency_tests.py @@ -75,6 +75,15 @@ class TestsMissingDependencies_WindowsAndMac(object): def do_missing_dependency_test(self, source_product, expected_dependencies, dsp_param, platforms=None, max_iterations=0): + """ + Test Steps: + 1. Determine what platforms to run against + 2. Process assets for that platform + 3. Determine the missing dependency params to set + 4. Set the max iteration param + 5. Run missing dependency scanner against target platforms and search params based on test data + 6. Validate missing dependencies against test data + """ platforms = platforms or ASSET_PROCESSOR_PLATFORM_MAP[self._workspace.asset_processor_platform] if not isinstance(platforms, list): @@ -104,7 +113,14 @@ class TestsMissingDependencies_WindowsAndMac(object): @pytest.mark.assetpipeline @pytest.mark.test_case_id("C17226567") def test_WindowsAndMac_ValidUUIDNotDependency_ReportsMissingDependency(self): - """Tests that a valid UUID referenced in a file will report any missing dependencies""" + """ + Tests that a valid UUID referenced in a file will report any missing dependencies + + Test Steps: + 1. Set the expected product + 2. Set the expected missing dependencies + 3. Execute test + """ # Relative path to the txt file with missing dependencies expected_product = f"testassets\\validuuidsnotdependency.txt" @@ -141,7 +157,14 @@ class TestsMissingDependencies_WindowsAndMac(object): @pytest.mark.assetpipeline @pytest.mark.test_case_id("C17226567") def test_WindowsAndMac_InvalidUUIDsNotDependencies_NoReportedMessage(self): - """Tests that invalid UUIDs do not count as missing dependencies""" + """ + Tests that invalid UUIDs do not count as missing dependencies + + Test Steps: + 1. Set the expected product + 2. Set the expected missing dependencies + 3. Execute test + """ # Relative path to the txt file with invalid UUIDs expected_product = f"testassets\\invaliduuidnoreport.txt" expected_dependencies = [] # No expected missing dependencies @@ -153,7 +176,14 @@ class TestsMissingDependencies_WindowsAndMac(object): @pytest.mark.assetpipeline @pytest.mark.test_case_id("C17226567") def test_WindowsAndMac_ValidAssetIdsNotDependencies_ReportsMissingDependency(self): - """Tests that valid asset IDs but not dependencies, show missing dependencies""" + """ + Tests that valid asset IDs but not dependencies, show missing dependencies + + Test Steps: + 1. Set the expected product + 2. Set the expected missing dependencies + 3. Execute test + """ # Relative path to the txt file with valid asset ids but not dependencies expected_product = f"testassets\\validassetidnotdependency.txt" @@ -173,7 +203,14 @@ class TestsMissingDependencies_WindowsAndMac(object): @pytest.mark.assetpipeline @pytest.mark.test_case_id("C17226567") def test_WindowsAndMac_InvalidAssetsIDNotDependencies_NoReportedMessage(self): - """Tests that invalid asset IDs do not count as missing dependencies""" + """ + Tests that invalid asset IDs do not count as missing dependencies + + Test Steps: + 1. Set the expected product + 2. Set the expected missing dependencies + 3. Execute test + """ # Relative path to the txt file with invalid asset IDs expected_product = f"testassets\\invalidassetidnoreport.txt" @@ -188,7 +225,14 @@ class TestsMissingDependencies_WindowsAndMac(object): # fmt:off def test_WindowsAndMac_ValidSourcePathsNotDependencies_ReportsMissingDependencies(self): # fmt:on - """Tests that valid source paths can translate to missing dependencies""" + """ + Tests that valid source paths can translate to missing dependencies + + Test Steps: + 1. Set the expected product + 2. Set the expected missing dependencies + 3. Execute test + """ # Relative path to the txt file with missing dependencies as source paths expected_product = f"testassets\\relativesourcepathsnotdependencies.txt" @@ -212,7 +256,14 @@ class TestsMissingDependencies_WindowsAndMac(object): @pytest.mark.assetpipeline @pytest.mark.test_case_id("C17226567") def test_WindowsAndMac_InvalidARelativePathsNotDependencies_NoReportedMessage(self): - """Tests that invalid relative paths do not resolve to missing dependencies""" + """ + Tests that invalid relative paths do not resolve to missing dependencies + + Test Steps: + 1. Set the expected product + 2. Set the expected missing dependencies + 3. Execute test + """ # Relative path to the txt file with invalid relative paths expected_product = f"testassets\\invalidrelativepathsnoreport.txt" @@ -227,7 +278,14 @@ class TestsMissingDependencies_WindowsAndMac(object): # fmt:off def test_WindowsAndMac_ValidProductPathsNotDependencies_ReportsMissingDependencies(self): # fmt:on - """Tests that valid product paths can resolve to missing dependencies""" + """ + Tests that valid product paths can resolve to missing dependencies + + Test Steps: + 1. Set the expected product + 2. Set the expected missing dependencies + 3. Execute test + """ self._asset_processor.add_source_folder_assets(f"Gems\\LyShineExamples\\Assets\\UI\\Fonts\\LyShineExamples") self._asset_processor.add_scan_folder(f"Gems\\LyShineExamples\\Assets") @@ -260,7 +318,14 @@ class TestsMissingDependencies_WindowsAndMac(object): @pytest.mark.assetpipeline @pytest.mark.test_case_id("C17226567") def test_WindowsAndMac_WildcardScan_FindsAllExpectedFiles(self): - """Tests that the wildcard scanning will pick up multiple files""" + """ + Tests that the wildcard scanning will pick up multiple files + + Test Steps: + 1. Set the expected product + 2. Set the expected missing dependencies + 3. Execute test + """ helper = self._missing_dep_helper @@ -291,6 +356,11 @@ class TestsMissingDependencies_WindowsAndMac(object): For these references that are valid, all but one have available, matching dependencies. This test is primarily meant to verify that the missing dependency reporter checks the product dependency table before emitting missing dependencies. + + Test Steps: + 1. Set the expected product + 2. Set the expected missing dependencies + 3. Execute test """ # Relative path to target test file expected_product = f"testassets\\reportonemissingdependency.txt" @@ -305,7 +375,14 @@ class TestsMissingDependencies_WindowsAndMac(object): @pytest.mark.assetpipeline @pytest.mark.test_case_id("C17226567") def test_WindowsAndMac_ReferencesSelfPath_NoReportedMessage(self): - """Tests that a file that references itself via relative path does not report itself as a missing dependency""" + """ + Tests that a file that references itself via relative path does not report itself as a missing dependency + + Test Steps: + 1. Set the expected product + 2. Set the expected missing dependencies + 3. Execute test + """ # Relative path to file that references itself via relative path expected_product = f"testassets\\selfreferencepath.txt" expected_dependencies = [] @@ -317,7 +394,14 @@ class TestsMissingDependencies_WindowsAndMac(object): @pytest.mark.assetpipeline @pytest.mark.test_case_id("C17226567") def test_WindowsAndMac_ReferencesSelfUUID_NoReportedMessage(self): - """Tests that a file that references itself via its UUID does not report itself as a missing dependency""" + """ + Tests that a file that references itself via its UUID does not report itself as a missing dependency + + Test Steps: + 1. Set the expected product + 2. Set the expected missing dependencies + 3. Execute test + """ # Relative path to file that references itself via its UUID expected_product = f"testassets\\selfreferenceuuid.txt" @@ -330,7 +414,14 @@ class TestsMissingDependencies_WindowsAndMac(object): @pytest.mark.assetpipeline @pytest.mark.test_case_id("C17226567") def test_WindowsAndMac_ReferencesSelfAssetID_NoReportedMessage(self): - """Tests that a file that references itself via its Asset ID does not report itself as a missing dependency""" + """ + Tests that a file that references itself via its Asset ID does not report itself as a missing dependency + + Test Steps: + 1. Set the expected product + 2. Set the expected missing dependencies + 3. Execute test + """ # Relative path to file that references itself via its Asset ID expected_product = f"testassets\\selfreferenceassetid.txt" @@ -347,6 +438,11 @@ class TestsMissingDependencies_WindowsAndMac(object): Tests that the scan limit fails to find a missing dependency that is out of reach. The max iteration count is set to just under where a valid missing dependency is on a line in the file, so this will not report any missing dependencies. + + Test Steps: + 1. Set the expected product + 2. Set the expected missing dependencies + 3. Execute test """ # Relative path to file that has a missing dependency at 31 iterations deep @@ -364,7 +460,13 @@ class TestsMissingDependencies_WindowsAndMac(object): Tests that the scan limit succeeds in finding a missing dependency that is barely in reach. In the previous test, the scanner was set to stop recursion just before a missing dependency was found. This test runs with the recursion limit set deep enough to actually find the missing dependency. + + Test Steps: + 1. Set the expected product + 2. Set the expected missing dependencies + 3. Execute test """ + # Relative path to file that has a missing dependency at 31 iterations deep expected_product = f"testassets\\maxiteration31deep.txt" @@ -383,7 +485,14 @@ class TestsMissingDependencies_WindowsAndMac(object): # fmt:off def test_WindowsAndMac_PotentialMatchesLongerThanUUIDString_OnlyReportsCorrectLengthUUIDs(self): # fmt:on - """Tests that dependency references that are longer than expected are ignored""" + """ + Tests that dependency references that are longer than expected are ignored + + Test Steps: + 1. Set the expected product + 2. Set the expected missing dependencies + 3. Execute test + """ # Relative path to text file with varying length UUID references expected_product = f"testassets\\onlymatchescorrectlengthuuids.txt" @@ -408,7 +517,14 @@ class TestsMissingDependencies_WindowsAndMac(object): def test_WindowsAndMac_MissingDependencyScanner_GradImageSuccess( self, ap_setup_fixture ): - """Tests the Missing Dependency Scanner can scan gradimage files""" + """ + Tests the Missing Dependency Scanner can scan gradimage files + + Test Steps: + 1. Create temporary testing environment + 2. Run the move dependency scanner against the gradimage + 2. Validate that the expected product files and and expected depdencies match + """ env = ap_setup_fixture helper = self._missing_dep_helper diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/auxiliary_content_tests/auxiliary_content_tests.py b/AutomatedTesting/Gem/PythonTests/assetpipeline/auxiliary_content_tests/auxiliary_content_tests.py index 7e9f65de60..452dc66352 100755 --- a/AutomatedTesting/Gem/PythonTests/assetpipeline/auxiliary_content_tests/auxiliary_content_tests.py +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/auxiliary_content_tests/auxiliary_content_tests.py @@ -51,6 +51,11 @@ class TestAuxiliaryContent: def test_CreateAuxiliaryContent_DontSkipLevelPaks(self, workspace, level): """ This test ensure that Auxiliary Content contain level.pak files + + Test Steps: + 1. Run auxiliary content against project under test + 2. Validate auxiliary content exists + 3. Verifies that level.pak exists """ path_to_dev = workspace.paths.engine_root() @@ -70,6 +75,11 @@ class TestAuxiliaryContent: def test_CreateAuxiliaryContent_SkipLevelPaks(self, workspace, level): """ This test ensure that Auxiliary Content contain no level.pak file + + Test Steps: + 1. Run auxiliary content against project under test with skiplevelPaks flag + 2. Validate auxiliary content exists + 3. Validate level.pak was added to auxiliary content """ path_to_dev = workspace.paths.engine_root() diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/fbx_tests.py b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/fbx_tests.py index b66984666b..afd7190c1b 100755 --- a/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/fbx_tests.py +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/fbx_tests.py @@ -533,6 +533,14 @@ class TestsFBX_AllPlatforms(object): def test_FBXBlackboxTest_SourceFiles_Processed_ResultInExpectedProducts(self, workspace, ap_setup_fixture, asset_processor, project, blackbox_param): + """ + Please see run_fbx_test(...) for details + + Test Steps: + 1. Determine if blackbox is set to none + 2. Run FBX Test + """ + if blackbox_param == None: return self.run_fbx_test(workspace, ap_setup_fixture, @@ -544,6 +552,15 @@ class TestsFBX_AllPlatforms(object): workspace, ap_setup_fixture, asset_processor, project, blackbox_param): + """ + Please see run_fbx_test(...) for details + + Test Steps: + 1. Determine if blackbox is set to none + 2. Run FBX Test + 2. Re-run FBX test and validate the information in override assets + """ + if blackbox_param == None: return self.run_fbx_test(workspace, ap_setup_fixture, @@ -567,6 +584,19 @@ class TestsFBX_AllPlatforms(object): def run_fbx_test(self, workspace, ap_setup_fixture, asset_processor, project, blackbox_params: BlackboxAssetTest, overrideAsset = False): + """ + These tests work by having the test case ingest the test data and determine the run pattern. + Tests will process scene settings files and will additionally do a verification against a provided debug file + Additionally, if an override is passed, the output is checked against the override. + + Test Steps: + 1. Create temporary test environment + 2. Process Assets + 3. Determine what assets to validate based upon test data + 4. Validate assets were created in cache + 5. If debug file provided, verify scene files were generated correctly + 6. Verify that each given source asset resulted in the expected jobs and products + """ test_assets_folder = blackbox_params.override_asset_folder if overrideAsset else blackbox_params.asset_folder logger.info(f"{blackbox_params.test_name}: Processing assets in folder '" diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/wwise_bank_dependency_tests/bank_info_parser_tests.py b/AutomatedTesting/Gem/PythonTests/assetpipeline/wwise_bank_dependency_tests/bank_info_parser_tests.py index 0db13bf53c..764b7723bf 100755 --- a/AutomatedTesting/Gem/PythonTests/assetpipeline/wwise_bank_dependency_tests/bank_info_parser_tests.py +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/wwise_bank_dependency_tests/bank_info_parser_tests.py @@ -26,6 +26,18 @@ def soundbank_metadata_generator_setup_fixture(workspace): def success_case_test(test_folder, expected_dependencies_dict, bank_info, expected_result_code=0): + """ + Test Steps: + 1. Make sure the return code is what was expected, and that the expected number of banks were returned. + 2. Validate bank is in the expected dependencies dictionary. + 3. Validate the path to output the metadata file to was assembled correctly. + 4. Validate metadata object for this bank is set, and that it has an object assigned to its dependencies field + and its includedEvents field + 5. Validate metadata object has the correct number of dependencies, and validated that every expected dependency + exists in the dependencies list of the metadata object. + 6. Validate metadata object has the correct number of events, and validate that every expected event exists in the + events of the metadata object. + """ expected_bank_count = len(expected_dependencies_dict) banks, result_code = bank_info.generate_metadata( @@ -80,8 +92,17 @@ class TestSoundBankMetadataGenerator: def test_NoMetadataTooFewBanks_ReturnCodeIsError(self, workspace, soundbank_metadata_generator_setup_fixture): - # Trying to generate metadata for banks in a folder with one or fewer banks and no metadata is not possible - # and should fail. + """ + Trying to generate metadata for banks in a folder with one or fewer banks and no metadata is not possible + and should fail. + + Test Steps: + 1. Setup testing environment with only 1 bank file + 2. Get Sound Bank Info + 3. Attempt to generate sound bank metadata + 4. Verify that proper error code is returned + """ + # test_assets_folder = os.path.join(soundbank_metadata_generator_setup_fixture['tests_dir'], 'assets', 'test_NoMetadataTooFewBanks_ReturnCodeIsError') if not os.path.isdir(test_assets_folder): @@ -97,15 +118,30 @@ class TestSoundBankMetadataGenerator: assert error_code is 2, 'Metadata was generated when there were fewer than two banks in the target directory.' def test_NoMetadataNoContentBank_NoMetadataGenerated(self, workspace, soundbank_metadata_generator_setup_fixture): + """ + Test Steps: + 1. Setup testing environment + 2. No expected dependencies + 3. Call success case test + """ test_assets_folder = os.path.join(soundbank_metadata_generator_setup_fixture['tests_dir'], 'assets', 'test_NoMetadataNoContentBank_NoMetadataGenerated') expected_dependencies = dict() success_case_test(test_assets_folder, expected_dependencies, get_bank_info(workspace)) def test_NoMetadataOneContentBank_NoStreamedFiles_OneDependency(self, workspace, soundbank_metadata_generator_setup_fixture): - # When no Wwise metadata is present, and there is only one content bank in the target directory with no wem - # files, then only the content bank should have metadata associated with it. The generated metadata should - # only describe a dependency on the init bank. + """ + When no Wwise metadata is present, and there is only one content bank in the target directory with no wem + files, then only the content bank should have metadata associated with it. The generated metadata should + only describe a dependency on the init bank. + + Test Steps: + 1. Setup testing environment + 2. Get current bank info + 3. Build expected dependencies + 4. Call success case test + """ + test_assets_folder = os.path.join(soundbank_metadata_generator_setup_fixture['tests_dir'], 'assets', 'test_NoMetadataOneContentBank_NoStreamedFiles_OneDependency') @@ -116,9 +152,18 @@ class TestSoundBankMetadataGenerator: def test_NoMetadataOneContentBank_StreamedFiles_MultipleDependencies(self, workspace, soundbank_metadata_generator_setup_fixture): - # When no Wwise metadata is present, and there is only one content bank in the target directory with wem files - # present, then only the content bank should have metadata associated with it. The generated metadata should - # describe a dependency on the init bank and all wem files in the folder. + """ + When no Wwise metadata is present, and there is only one content bank in the target directory with wem files + present, then only the content bank should have metadata associated with it. The generated metadata should + describe a dependency on the init bank and all wem files in the folder. + + Test Steps: + 1. Setup testing environment + 2. Get current bank info + 3. Build expected dependencies + 4. Call success case test + """ + test_assets_folder = os.path.join(soundbank_metadata_generator_setup_fixture['tests_dir'], 'assets', 'test_NoMetadataOneContentBank_StreamedFiles_MultipleDependencies') @@ -136,10 +181,19 @@ class TestSoundBankMetadataGenerator: success_case_test(test_assets_folder, expected_dependencies, get_bank_info(workspace)) def test_NoMetadataMultipleBanks_OneDependency_ReturnCodeIsWarning(self, workspace, soundbank_metadata_generator_setup_fixture): - # When no Wwise metadata is present, and there are multiple content banks in the target directory with wem files - # present, there is no way to tell which bank requires which wem files. A warning should be emitted, - # stating that the full dependency graph could not be created, and only dependencies on the init bank are - # described in the generated metadata files. + """ + When no Wwise metadata is present, and there are multiple content banks in the target directory with wem files + present, there is no way to tell which bank requires which wem files. A warning should be emitted, + stating that the full dependency graph could not be created, and only dependencies on the init bank are + described in the generated metadata files. + + Test Steps: + 1. Setup testing environment + 2. Get current bank info + 3. Build expected dependencies + 4. Call success case test + """ + test_assets_folder = os.path.join(soundbank_metadata_generator_setup_fixture['tests_dir'], 'assets', 'test_NoMetadataMultipleBanks_OneDependency_ReturnCodeIsWarning') bank_info = get_bank_info(workspace) @@ -150,8 +204,17 @@ class TestSoundBankMetadataGenerator: success_case_test(test_assets_folder, expected_dependencies, get_bank_info(workspace), expected_result_code=1) def test_OneContentBank_NoStreamedFiles_OneDependency(self, workspace, soundbank_metadata_generator_setup_fixture): - # Wwise metadata describes one content bank that contains all media needed by its events. Generated metadata - # describes a dependency only on the init bank. + """ + Wwise metadata describes one content bank that contains all media needed by its events. Generated metadata + describes a dependency only on the init bank. + + Test Steps: + 1. Setup testing environment + 2. Get current bank info + 3. Build expected dependencies + 4. Call success case test + """ + test_assets_folder = os.path.join(soundbank_metadata_generator_setup_fixture['tests_dir'], 'assets', 'test_OneContentBank_NoStreamedFiles_OneDependency') @@ -165,8 +228,17 @@ class TestSoundBankMetadataGenerator: success_case_test(test_assets_folder, expected_dependencies, get_bank_info(workspace)) def test_OneContentBank_StreamedFiles_MultipleDependencies(self, workspace, soundbank_metadata_generator_setup_fixture): - # Wwise metadata describes one content bank that references streamed media files needed by its events. Generated - # metadata describes dependencies on the init bank and wems named by the IDs of referenced streamed media. + """ + Wwise metadata describes one content bank that references streamed media files needed by its events. Generated + metadata describes dependencies on the init bank and wems named by the IDs of referenced streamed media. + + Test Steps: + 1. Setup testing environment + 2. Get current bank info + 3. Build expected dependencies + 4. Call success case test + """ + test_assets_folder = os.path.join(soundbank_metadata_generator_setup_fixture['tests_dir'], 'assets', 'test_OneContentBank_StreamedFiles_MultipleDependencies') @@ -187,8 +259,17 @@ class TestSoundBankMetadataGenerator: success_case_test(test_assets_folder, expected_dependencies, get_bank_info(workspace)) def test_MultipleContentBanks_NoStreamedFiles_OneDependency(self, workspace, soundbank_metadata_generator_setup_fixture): - # Wwise metadata describes multiple content banks. Each bank contains all media needed by its events. Generated - # metadata describes each bank having a dependency only on the init bank. + """ + Wwise metadata describes multiple content banks. Each bank contains all media needed by its events. Generated + metadata describes each bank having a dependency only on the init bank. + + Test Steps: + 1. Setup testing environment + 2. Get current bank info + 3. Build expected dependencies + 4. Call success case test + """ + test_assets_folder = os.path.join(soundbank_metadata_generator_setup_fixture['tests_dir'], 'assets', 'test_MultipleContentBanks_NoStreamedFiles_OneDependency') @@ -206,8 +287,17 @@ class TestSoundBankMetadataGenerator: success_case_test(test_assets_folder, expected_dependencies, get_bank_info(workspace)) def test_MultipleContentBanks_Bank1StreamedFiles(self, workspace, soundbank_metadata_generator_setup_fixture): - # Wwise metadata describes multiple content banks. Bank 1 references streamed media files needed by its events, - # while bank 2 contains all media need by its events. + """ + Wwise metadata describes multiple content banks. Bank 1 references streamed media files needed by its events, + while bank 2 contains all media need by its events. + + Test Steps: + 1. Setup testing environment + 2. Get current bank info + 3. Build expected dependencies + 4. Call success case test + """ + test_assets_folder = os.path.join(soundbank_metadata_generator_setup_fixture['tests_dir'], 'assets', 'test_MultipleContentBanks_Bank1StreamedFiles') @@ -228,9 +318,18 @@ class TestSoundBankMetadataGenerator: success_case_test(test_assets_folder, expected_dependencies, get_bank_info(workspace)) def test_MultipleContentBanks_SplitBanks_OnlyBankDependenices(self, workspace, soundbank_metadata_generator_setup_fixture): - # Wwise metadata describes multiple content banks. Bank 3 events require media that is contained in bank 4. - # Generated metadata describes each bank having a dependency on the init bank, while bank 3 has an additional - # dependency on bank 4. + """ + Wwise metadata describes multiple content banks. Bank 3 events require media that is contained in bank 4. + Generated metadata describes each bank having a dependency on the init bank, while bank 3 has an additional + dependency on bank 4. + + Test Steps: + 1. Setup testing environment + 2. Get current bank info + 3. Build expected dependencies + 4. Call success case test + """ + test_assets_folder = os.path.join(soundbank_metadata_generator_setup_fixture['tests_dir'], 'assets', 'test_MultipleContentBanks_SplitBanks_OnlyBankDependenices') @@ -248,9 +347,18 @@ class TestSoundBankMetadataGenerator: success_case_test(test_assets_folder, expected_dependencies, get_bank_info(workspace)) def test_MultipleContentBanks_ReferencedEvent_MediaEmbeddedInBank(self, workspace, soundbank_metadata_generator_setup_fixture): - # Wwise metadata describes multiple content banks. Bank 1 contains all media required by its events, while bank - # 5 contains a reference to an event in bank 1, but no media for that event. Generated metadata describes both - # banks having a dependency on the init bank, while bank 5 has an additional dependency on bank 1. + """ + Wwise metadata describes multiple content banks. Bank 1 contains all media required by its events, while bank + 5 contains a reference to an event in bank 1, but no media for that event. Generated metadata describes both + banks having a dependency on the init bank, while bank 5 has an additional dependency on bank 1. + + Test Steps: + 1. Setup testing environment + 2. Get current bank info + 3. Build expected dependencies + 4. Call success case test + """ + test_assets_folder = os.path.join(soundbank_metadata_generator_setup_fixture['tests_dir'], 'assets', 'test_MultipleContentBanks_ReferencedEvent_MediaEmbeddedInBank') @@ -271,10 +379,19 @@ class TestSoundBankMetadataGenerator: success_case_test(test_assets_folder, expected_dependencies, get_bank_info(workspace)) def test_MultipleContentBanks_ReferencedEvent_MediaStreamed(self, workspace, soundbank_metadata_generator_setup_fixture): - # Wwise metadata describes multiple content banks. Bank 1 references streamed media files needed by its events, - # while bank 5 contains a reference to an event in bank 1. This causes bank 5 to also describe a reference to - # the streamed media file referenced by the event from bank 1. Generated metadata describes both banks having - # dependencies on the init bank, as well as the wem named by the ID of referenced streamed media. + """ + Wwise metadata describes multiple content banks. Bank 1 references streamed media files needed by its events, + while bank 5 contains a reference to an event in bank 1. This causes bank 5 to also describe a reference to + the streamed media file referenced by the event from bank 1. Generated metadata describes both banks having + dependencies on the init bank, as well as the wem named by the ID of referenced streamed media. + + Test Steps: + 1. Setup testing environment + 2. Get current bank info + 3. Build expected dependencies + 4. Call success case test + """ + test_assets_folder = os.path.join(soundbank_metadata_generator_setup_fixture['tests_dir'], 'assets', 'test_MultipleContentBanks_ReferencedEvent_MediaStreamed') @@ -298,11 +415,20 @@ class TestSoundBankMetadataGenerator: success_case_test(test_assets_folder, expected_dependencies, get_bank_info(workspace)) def test_MultipleContentBanks_ReferencedEvent_MixedSources(self, workspace, soundbank_metadata_generator_setup_fixture): - # Wwise metadata describes multiple content banks. Bank 1 references a streamed media files needed by one of its - # events, and contains all media needed for its other events, while bank 5 contains a reference to two events - # in bank 1: one that requires streamed media, and one that requires media embedded in bank 1. Generated - # metadata describes both banks having dependencies on the init bank and the wem named by the ID of referenced - # streamed media, while bank 5 has an additional dependency on bank 1. + """ + Wwise metadata describes multiple content banks. Bank 1 references a streamed media files needed by one of its + events, and contains all media needed for its other events, while bank 5 contains a reference to two events + in bank 1: one that requires streamed media, and one that requires media embedded in bank 1. Generated + metadata describes both banks having dependencies on the init bank and the wem named by the ID of referenced + streamed media, while bank 5 has an additional dependency on bank 1. + + Test Steps: + 1. Setup testing environment + 2. Get current bank info + 3. Build expected dependencies + 4. Call success case test + """ + test_assets_folder = os.path.join(soundbank_metadata_generator_setup_fixture['tests_dir'], 'assets', 'test_MultipleContentBanks_ReferencedEvent_MixedSources') @@ -332,8 +458,17 @@ class TestSoundBankMetadataGenerator: success_case_test(test_assets_folder, expected_dependencies, get_bank_info(workspace)) def test_MultipleContentBanks_VaryingDependencies_MixedSources(self, workspace, soundbank_metadata_generator_setup_fixture): - # Wwise metadata describes multiple content banks that have varying dependencies on each other, and dependencies - # on streamed media files. + """ + Wwise metadata describes multiple content banks that have varying dependencies on each other, and dependencies + on streamed media files. + + Test Steps: + 1. Setup testing environment + 2. Get current bank info + 3. Build expected dependencies + 4. Call success case test + """ + test_assets_folder = os.path.join(soundbank_metadata_generator_setup_fixture['tests_dir'], 'assets', 'test_MultipleContentBanks_VaryingDependencies_MixedSources') diff --git a/AutomatedTesting/Gem/PythonTests/physics/TestSuite_Main.py b/AutomatedTesting/Gem/PythonTests/physics/TestSuite_Main.py index 8f1f2f7481..2cf55c7a58 100644 --- a/AutomatedTesting/Gem/PythonTests/physics/TestSuite_Main.py +++ b/AutomatedTesting/Gem/PythonTests/physics/TestSuite_Main.py @@ -42,6 +42,7 @@ class TestAutomation(TestAutomationBase): self._run_test(request, workspace, editor, test_module) @revert_physics_config + @fm.file_override('physxsystemconfiguration.setreg','C4044459_Material_DynamicFriction.setreg_override', 'AutomatedTesting/Registry') def test_C4044459_Material_DynamicFriction(self, request, workspace, editor, launcher_platform): from . import C4044459_Material_DynamicFriction as test_module self._run_test(request, workspace, editor, test_module) diff --git a/AutomatedTesting/Gem/PythonTests/scripting/ScriptEvent_AddRemoveParameter_ActionsSuccessful.py b/AutomatedTesting/Gem/PythonTests/scripting/ScriptEvent_AddRemoveParameter_ActionsSuccessful.py new file mode 100644 index 0000000000..638c69e8f3 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/scripting/ScriptEvent_AddRemoveParameter_ActionsSuccessful.py @@ -0,0 +1,131 @@ +""" +All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +its licensors. + +For complete copyright and license terms please see the LICENSE at the root of this +distribution (the "License"). All use of this software is governed by the License, +or, if provided, by the license below or the license accompanying this file. Do not +remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +""" + + +# fmt: off +class Tests(): + new_event_created = ("Successfully created a new event", "Failed to create a new event") + child_event_created = ("Successfully created Child Event", "Failed to create Child Event") + file_saved = ("Successfully saved event asset", "Failed to save event asset") + parameter_created = ("Successfully added parameter", "Failed to add parameter") + parameter_removed = ("Successfully removed parameter", "Failed to remove parameter") +# fmt: on + + +def ScriptEvent_AddRemoveParameter_ActionsSuccessful(): + """ + Summary: + Parameter can be removed from a Script Event method + + Expected Behavior: + Upon saving the updated .scriptevents asset the removed paramenter should no longer be present on the Script Event + + Test Steps: + 1) Open Asset Editor + 2) Get Asset Editor Qt object + 3) Create new Script Event Asset + 4) Add Parameter to Event + 5) Remove Parameter from Event + + Note: + - This test file must be called from the Open 3D Engine Editor command terminal + - Any passed and failed tests are written to the Editor.log file. + Parsing the file or running a log_monitor are required to observe the test results. + + :return: None + """ + import os + from PySide2 import QtWidgets + + from editor_python_test_tools.utils import Report + from editor_python_test_tools.utils import TestHelper as helper + import editor_python_test_tools.pyside_utils as pyside_utils + + import azlmbr.bus as bus + import azlmbr.editor as editor + import azlmbr.legacy.general as general + + GENERAL_WAIT = 1.0 # seconds + FILE_PATH = os.path.join("AutomatedTesting", "ScriptCanvas", "test_file.scriptevent") + QtObject = object + + def create_script_event(asset_editor: QtObject, file_path: str) -> None: + action = pyside_utils.find_child_by_pattern(menu_bar, {"type": QtWidgets.QAction, "text": "Script Events"}) + action.trigger() + result = helper.wait_for_condition( + lambda: container.findChild(QtWidgets.QFrame, "Events") is not None, 3 * GENERAL_WAIT + ) + Report.result(Tests.new_event_created, result) + + # Add new child event + add_event = container.findChild(QtWidgets.QFrame, "Events").findChild(QtWidgets.QToolButton, "") + add_event.click() + result = helper.wait_for_condition( + lambda: asset_editor.findChild(QtWidgets.QFrame, "EventName") is not None, GENERAL_WAIT + ) + Report.result(Tests.child_event_created, result) + # Save the Script Event file + editor.AssetEditorWidgetRequestsBus(bus.Broadcast, "SaveAssetAs", file_path) + + # Verify if file is created + result = helper.wait_for_condition(lambda: os.path.exists(file_path), 3 * GENERAL_WAIT) + Report.result(Tests.file_saved, result) + + def create_parameter(file_path: str) -> None: + add_param = container.findChild(QtWidgets.QFrame, "Parameters").findChild(QtWidgets.QToolButton, "") + add_param.click() + result = helper.wait_for_condition( + lambda: asset_editor_widget.findChild(QtWidgets.QFrame, "[0]") is not None, GENERAL_WAIT + ) + Report.result(Tests.parameter_created, result) + editor.AssetEditorWidgetRequestsBus(bus.Broadcast, "SaveAssetAs", file_path) + + def remove_parameter(file_path: str) -> None: + remove_param = container.findChild(QtWidgets.QFrame, "[0]").findChild(QtWidgets.QToolButton, "") + remove_param.click() + result = helper.wait_for_condition( + lambda: asset_editor_widget.findChild(QtWidgets.QFrame, "[0]") is None, GENERAL_WAIT + ) + Report.result(Tests.parameter_removed, result) + editor.AssetEditorWidgetRequestsBus(bus.Broadcast, "SaveAssetAs", file_path) + + # 1) Open Asset Editor + general.idle_enable(True) + # Initially close the Asset Editor and then reopen to ensure we don't have any existing assets open + general.close_pane("Asset Editor") + general.open_pane("Asset Editor") + helper.wait_for_condition(lambda: general.is_pane_visible("Asset Editor"), 5.0) + + # 2) Get Asset Editor Qt object + editor_window = pyside_utils.get_editor_main_window() + asset_editor_widget = editor_window.findChild(QtWidgets.QDockWidget, "Asset Editor").findChild( + QtWidgets.QWidget, "AssetEditorWindowClass" + ) + container = asset_editor_widget.findChild(QtWidgets.QWidget, "ContainerForRows") + menu_bar = asset_editor_widget.findChild(QtWidgets.QMenuBar) + + # 3) Create new Script Event Asset + create_script_event(asset_editor_widget, FILE_PATH) + + # 4) Add Parameter to Event + create_parameter(FILE_PATH) + + # 5) Remove Parameter from Event + remove_parameter(FILE_PATH) + + +if __name__ == "__main__": + import ImportPathHelper as imports + + imports.init() + from editor_python_test_tools.utils import Report + + Report.start_test(ScriptEvent_AddRemoveParameter_ActionsSuccessful) diff --git a/AutomatedTesting/Gem/PythonTests/scripting/ScriptEvents_AllParamDatatypes_CreationSuccess.py b/AutomatedTesting/Gem/PythonTests/scripting/ScriptEvents_AllParamDatatypes_CreationSuccess.py new file mode 100644 index 0000000000..4beedec7cf --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/scripting/ScriptEvents_AllParamDatatypes_CreationSuccess.py @@ -0,0 +1,210 @@ +""" +All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +its licensors. + +For complete copyright and license terms please see the LICENSE at the root of this +distribution (the "License"). All use of this software is governed by the License, +or, if provided, by the license below or the license accompanying this file. Do not +remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +""" + + +# fmt: off +class Tests(): + new_event_created = ("New Script Event created", "New Script Event not created") + child_event_created = ("Child Event created", "Child Event not created") + params_added = ("New parameters added", "New parameters are not added") + file_saved = ("Script event file saved", "Script event file did not save") + node_found = ("Node found in Script Canvas", "Node not found in Script Canvas") +# fmt: on + + +def ScriptEvents_AllParamDatatypes_CreationSuccess(): + """ + Summary: + Parameters of all types can be created. + + Expected Behavior: + The Method handles the large number of Parameters gracefully. + Parameters of all data types can be successfully created. + Updated ScriptEvent toast appears in Script Canvas. + + Test Steps: + 1) Open Asset Editor + 2) Initially create new Script Event file with one method + 3) Add new method and set name to it + 4) Add new parameters of each type + 5) Verify if parameters are added + 6) Expand the parameter rows + 7) Set different names and datatypes for each parameter + 8) Save file and verify node in SC Node Palette + 9) Close Asset Editor + + Note: + - This test file must be called from the Open 3D Engine Editor command terminal + - Any passed and failed tests are written to the Editor.log file. + Parsing the file or running a log_monitor are required to observe the test results. + + :return: None + """ + import os + from utils import TestHelper as helper + import pyside_utils + + # Open 3D Engine imports + import azlmbr.legacy.general as general + import azlmbr.editor as editor + import azlmbr.bus as bus + + # Pyside imports + from PySide2 import QtWidgets, QtTest, QtCore + + GENERAL_WAIT = 1.0 # seconds + + FILE_PATH = os.path.join("AutomatedTesting", "TestAssets", "test_file.scriptevents") + N_VAR_TYPES = 10 # Top 10 variable types + TEST_METHOD_NAME = "test_method_name" + + editor_window = pyside_utils.get_editor_main_window() + asset_editor = asset_editor_widget = container = menu_bar = None + sc = node_palette = tree = search_frame = search_box = None + + def initialize_asset_editor_qt_objects(): + nonlocal asset_editor, asset_editor_widget, container, menu_bar + asset_editor = editor_window.findChild(QtWidgets.QDockWidget, "Asset Editor") + asset_editor_widget = asset_editor.findChild(QtWidgets.QWidget, "AssetEditorWindowClass") + container = asset_editor_widget.findChild(QtWidgets.QWidget, "ContainerForRows") + menu_bar = asset_editor_widget.findChild(QtWidgets.QMenuBar) + + def initialize_sc_qt_objects(): + nonlocal sc, node_palette, tree, search_frame, search_box + sc = editor_window.findChild(QtWidgets.QDockWidget, "Script Canvas") + if sc.findChild(QtWidgets.QDockWidget, "NodePalette") is None: + action = pyside_utils.find_child_by_pattern(sc, {"text": "Node Palette", "type": QtWidgets.QAction}) + action.trigger() + node_palette = sc.findChild(QtWidgets.QDockWidget, "NodePalette") + tree = node_palette.findChild(QtWidgets.QTreeView, "treeView") + search_frame = node_palette.findChild(QtWidgets.QFrame, "searchFrame") + search_box = search_frame.findChild(QtWidgets.QLineEdit, "searchFilter") + + def save_file(): + editor.AssetEditorWidgetRequestsBus(bus.Broadcast, "SaveAssetAs", FILE_PATH) + action = pyside_utils.find_child_by_pattern(menu_bar, {"type": QtWidgets.QAction, "iconText": "Save"}) + action.trigger() + # wait till file is saved, to validate that check the text of QLabel at the bottom of the AssetEditor, + # if there are no unsaved changes we will not have any * in the text + label = asset_editor.findChild(QtWidgets.QLabel, "textEdit") + return helper.wait_for_condition(lambda: "*" not in label.text(), 3.0) + + def expand_container_rows(object_name): + children = container.findChildren(QtWidgets.QFrame, object_name) + for child in children: + check_box = child.findChild(QtWidgets.QCheckBox) + if check_box and not check_box.isChecked(): + QtTest.QTest.mouseClick(check_box, QtCore.Qt.LeftButton, QtCore.Qt.NoModifier) + + def node_palette_search(node_name): + search_box.setText(node_name) + helper.wait_for_condition(lambda: search_box.text() == node_name, 1.0) + # Try clicking ENTER in search box multiple times + for _ in range(20): + QtTest.QTest.keyClick(search_box, QtCore.Qt.Key_Enter, QtCore.Qt.NoModifier) + if pyside_utils.find_child_by_pattern(tree, {"text": node_name}) is not None: + break + + def verify_added_params(): + for index in range(N_VAR_TYPES): + if container.findChild(QtWidgets.QFrame, f"[{index}]") is None: + return False + return True + + # 1) Open Asset Editor + general.idle_enable(True) + # Initially close the Asset Editor and then reopen to ensure we don't have any existing assets open + general.close_pane("Asset Editor") + general.open_pane("Asset Editor") + helper.wait_for_condition(lambda: general.is_pane_visible("Asset Editor"), 5.0) + + # 2) Initially create new Script Event file with one method + initialize_asset_editor_qt_objects() + action = pyside_utils.find_child_by_pattern(menu_bar, {"type": QtWidgets.QAction, "text": "Script Events"}) + action.trigger() + result = helper.wait_for_condition( + lambda: container.findChild(QtWidgets.QFrame, "Events") is not None + and container.findChild(QtWidgets.QFrame, "Events").findChild(QtWidgets.QToolButton, "") is not None, + 3 * GENERAL_WAIT, + ) + Report.result(Tests.new_event_created, result) + + # 3) Add new method and set name to it + add_event = container.findChild(QtWidgets.QFrame, "Events").findChild(QtWidgets.QToolButton, "") + add_event.click() + result = helper.wait_for_condition( + lambda: asset_editor_widget.findChild(QtWidgets.QFrame, "EventName") is not None, GENERAL_WAIT + ) + Report.result(Tests.child_event_created, result) + expand_container_rows("EventName") + expand_container_rows("Name") + initialize_asset_editor_qt_objects() + children = container.findChildren(QtWidgets.QFrame, "Name") + for child in children: + line_edit = child.findChild(QtWidgets.QLineEdit) + if line_edit is not None and line_edit.text() == "MethodName": + line_edit.setText(TEST_METHOD_NAME) + + # 4) Add new parameters of each type + helper.wait_for_condition(lambda: container.findChild(QtWidgets.QFrame, "Parameters") is not None, 2.0) + parameters = container.findChild(QtWidgets.QFrame, "Parameters") + add_param = parameters.findChild(QtWidgets.QToolButton, "") + for _ in range(N_VAR_TYPES): + add_param.click() + + # 5) Verify if parameters are added + result = helper.wait_for_condition(verify_added_params, 3.0) + Report.result(Tests.params_added, result) + + # 6) Expand the parameter rows (to render QFrame 'Type' for each param) + for index in range(N_VAR_TYPES): + expand_container_rows(f"[{index}]") + + # 7) Set different names and datatypes for each parameter + expand_container_rows("Name") + children = container.findChildren(QtWidgets.QFrame, "Name") + index = 0 + for child in children: + line_edit = child.findChild(QtWidgets.QLineEdit) + if line_edit is not None and line_edit.text() == "ParameterName": + line_edit.setText(f"param_{index}") + index += 1 + + children = container.findChildren(QtWidgets.QFrame, "Type") + index = 0 + for child in children: + combo_box = child.findChild(QtWidgets.QComboBox) + if combo_box is not None and index < N_VAR_TYPES: + combo_box.setCurrentIndex(index) + index += 1 + + # 8) Save file and verify node in SC Node Palette + Report.result(Tests.file_saved, save_file()) + general.open_pane("Script Canvas") + helper.wait_for_condition(lambda: general.is_pane_visible("Script Canvas"), 5.0) + initialize_sc_qt_objects() + node_palette_search(TEST_METHOD_NAME) + get_node_index = lambda: pyside_utils.find_child_by_pattern(tree, {"text": TEST_METHOD_NAME}) is not None + result = helper.wait_for_condition(get_node_index, 2.0) + Report.result(Tests.node_found, result) + + # 9) Close Asset Editor + general.close_pane("Asset Editor") + general.close_pane("Script Canvas") + + +if __name__ == "__main__": + import ImportPathHelper as imports + + imports.init() + from utils import Report + + Report.start_test(ScriptEvents_AllParamDatatypes_CreationSuccess) diff --git a/AutomatedTesting/Gem/PythonTests/scripting/TestSuite_Periodic.py b/AutomatedTesting/Gem/PythonTests/scripting/TestSuite_Periodic.py index 85d0b4523f..e3a8d874da 100755 --- a/AutomatedTesting/Gem/PythonTests/scripting/TestSuite_Periodic.py +++ b/AutomatedTesting/Gem/PythonTests/scripting/TestSuite_Periodic.py @@ -113,10 +113,6 @@ class TestAutomation(TestAutomationBase): from . import Debugger_HappyPath_TargetMultipleGraphs as test_module self._run_test(request, workspace, editor, test_module) - def test_Debugging_TargetMultipleGraphs(self, request, workspace, editor, launcher_platform, project): - from . import Debugging_TargetMultipleGraphs as test_module - self._run_test(request, workspace, editor, test_module) - @pytest.mark.parametrize("level", ["tmp_level"]) def test_Debugger_HappyPath_TargetMultipleEntities(self, request, workspace, editor, launcher_platform, project, level): def teardown(): @@ -190,6 +186,18 @@ class TestAutomation(TestAutomationBase): from . import Node_HappyPath_DuplicateNode as test_module self._run_test(request, workspace, editor, test_module) + def test_ScriptEvent_AddRemoveParameter_ActionsSuccessful(self, request, workspace, editor, launcher_platform): + def teardown(): + file_system.delete( + [os.path.join(workspace.paths.project(), "ScriptCanvas", "test_file.scriptevent")], True, True + ) + request.addfinalizer(teardown) + file_system.delete( + [os.path.join(workspace.paths.project(), "ScriptCanvas", "test_file.scriptevent")], True, True + ) + from . import ScriptEvent_AddRemoveParameter_ActionsSuccessful as test_module + self._run_test(request, workspace, editor, test_module) + # NOTE: We had to use hydra_test_utils.py, as TestAutomationBase run_test method # fails because of pyside_utils import @pytest.mark.SUITE_periodic @@ -317,4 +325,30 @@ class TestScriptCanvasTests(object): auto_test_mode=False, timeout=60, ) + + def test_ScriptEvents_AllParamDatatypes_CreationSuccess(self, request, workspace, editor, launcher_platform): + def teardown(): + file_system.delete( + [os.path.join(workspace.paths.project(), "TestAssets", "test_file.scriptevents")], True, True + ) + request.addfinalizer(teardown) + file_system.delete( + [os.path.join(workspace.paths.project(), "TestAssets", "test_file.scriptevents")], True, True + ) + expected_lines = [ + "Success: New Script Event created", + "Success: Child Event created", + "Success: New parameters added", + "Success: Script event file saved", + "Success: Node found in Script Canvas", + ] + hydra.launch_and_validate_results( + request, + TEST_DIRECTORY, + editor, + "ScriptEvents_AllParamDatatypes_CreationSuccess.py", + expected_lines, + auto_test_mode=False, + timeout=60, + ) \ No newline at end of file diff --git a/AutomatedTesting/Gem/PythonTests/smoke/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/smoke/CMakeLists.txt index d351ec0e6c..a3b6e36250 100644 --- a/AutomatedTesting/Gem/PythonTests/smoke/CMakeLists.txt +++ b/AutomatedTesting/Gem/PythonTests/smoke/CMakeLists.txt @@ -14,6 +14,24 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) TEST_SUITE smoke TEST_SERIAL PATH ${CMAKE_CURRENT_LIST_DIR} + PYTEST_MARKS "SUITE_smoke" + TIMEOUT 1500 + RUNTIME_DEPENDENCIES + AZ::AssetProcessor + AZ::PythonBindingsExample + Legacy::Editor + AutomatedTesting.GameLauncher + AutomatedTesting.Assets + COMPONENT + Smoke + ) + + ly_add_pytest( + NAME AutomatedTesting::SandboxTest + TEST_SUITE sandbox + TEST_SERIAL + PATH ${CMAKE_CURRENT_LIST_DIR} + PYTEST_MARKS "SUITE_sandbox" TIMEOUT 1500 RUNTIME_DEPENDENCIES AZ::AssetProcessor diff --git a/AutomatedTesting/Gem/PythonTests/smoke/test_Editor_NewExistingLevels_Works.py b/AutomatedTesting/Gem/PythonTests/smoke/test_Editor_NewExistingLevels_Works.py index 985740307f..e6b072ba58 100644 --- a/AutomatedTesting/Gem/PythonTests/smoke/test_Editor_NewExistingLevels_Works.py +++ b/AutomatedTesting/Gem/PythonTests/smoke/test_Editor_NewExistingLevels_Works.py @@ -15,7 +15,7 @@ from automatedtesting_shared.base import TestAutomationBase import ly_test_tools.environment.file_system as file_system -@pytest.mark.SUITE_smoke +@pytest.mark.SUITE_sandbox @pytest.mark.parametrize("launcher_platform", ["windows_editor"]) @pytest.mark.parametrize("project", ["AutomatedTesting"]) @pytest.mark.parametrize("level", ["temp_level"]) diff --git a/AutomatedTesting/Levels/Simple/Simple.ly b/AutomatedTesting/Levels/Simple/Simple.ly index 0148ee6e34..0a063bf8f8 100644 --- a/AutomatedTesting/Levels/Simple/Simple.ly +++ b/AutomatedTesting/Levels/Simple/Simple.ly @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:804193a2afd68cd1e6bec8155ea11400566f2941fbd6eb0c324839ebcd10192d -size 8492 +oid sha256:302d6172156e8ed665e44e206d81f54f1b0f1008d73327300ea92f8c1159780b +size 11820 diff --git a/AutomatedTesting/Registry/C3510644_Collider_CollisionGroups.setreg_override b/AutomatedTesting/Registry/C3510644_Collider_CollisionGroups.setreg_override index 9fa5e26768..696a0a74da 100644 --- a/AutomatedTesting/Registry/C3510644_Collider_CollisionGroups.setreg_override +++ b/AutomatedTesting/Registry/C3510644_Collider_CollisionGroups.setreg_override @@ -119,6 +119,9 @@ ] } }, + "DefaultMaterial": { + "SurfaceType": "Default_1" + }, "MaterialLibrary": { "assetId": { "guid": "{3A055A3F-8CB7-5FEE-B437-EB365FACD0D4}" diff --git a/AutomatedTesting/Registry/C4044459_Material_DynamicFriction.setreg_override b/AutomatedTesting/Registry/C4044459_Material_DynamicFriction.setreg_override new file mode 100644 index 0000000000..c53b04e5c2 --- /dev/null +++ b/AutomatedTesting/Registry/C4044459_Material_DynamicFriction.setreg_override @@ -0,0 +1,118 @@ +{ + "Amazon": { + "Gems": { + "PhysX": { + "PhysXSystemConfiguration": { + "CollisionConfig": { + "Layers": { + "LayerNames": [ + "Default", + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + "TouchBend" + ] + }, + "Groups": { + "GroupPresets": [ + { + "Name": "All", + "ReadOnly": true + }, + { + "Id": { + "GroupId": "{CDB6B8D8-5CD0-40A8-874D-839B00A92EBB}" + }, + "Name": "None", + "Group": { + "Mask": 0 + }, + "ReadOnly": true + }, + { + "Id": { + "GroupId": "{22769429-5D46-429B-829A-0115239D9AAA}" + }, + "Name": "All_NoTouchBend", + "Group": { + "Mask": 9223372036854775807 + }, + "ReadOnly": true + } + ] + } + }, + "DefaultMaterial": { + "SurfaceType": "Default_1" + }, + "MaterialLibrary": { + "assetId": { + "guid": "{6AA79EE4-7EC3-5717-87AE-EDD7D886FD7F}" + }, + "loadBehavior": "QueueLoad", + "assetHint": "levels/physics/c4044459_material_dynamicfriction/dynamic_friction.physmaterial" + } + } + } + } + } +} \ No newline at end of file diff --git a/AutomatedTesting/Registry/C4976227_Collider_NewGroup.setreg_override b/AutomatedTesting/Registry/C4976227_Collider_NewGroup.setreg_override index afbe6a9d38..5e98e08ede 100644 --- a/AutomatedTesting/Registry/C4976227_Collider_NewGroup.setreg_override +++ b/AutomatedTesting/Registry/C4976227_Collider_NewGroup.setreg_override @@ -107,6 +107,9 @@ ] } }, + "DefaultMaterial": { + "SurfaceType": "Default_1" + }, "MaterialLibrary": { "assetId": { "guid": "{3A055A3F-8CB7-5FEE-B437-EB365FACD0D4}" diff --git a/AutomatedTesting/Registry/C4976244_Collider_SameGroupSameLayerCollision.setreg_override b/AutomatedTesting/Registry/C4976244_Collider_SameGroupSameLayerCollision.setreg_override index 9fa5e26768..696a0a74da 100644 --- a/AutomatedTesting/Registry/C4976244_Collider_SameGroupSameLayerCollision.setreg_override +++ b/AutomatedTesting/Registry/C4976244_Collider_SameGroupSameLayerCollision.setreg_override @@ -119,6 +119,9 @@ ] } }, + "DefaultMaterial": { + "SurfaceType": "Default_1" + }, "MaterialLibrary": { "assetId": { "guid": "{3A055A3F-8CB7-5FEE-B437-EB365FACD0D4}" diff --git a/AutomatedTesting/Registry/C4976245_PhysXCollider_CollisionLayerTest.setreg_override b/AutomatedTesting/Registry/C4976245_PhysXCollider_CollisionLayerTest.setreg_override index 9fa5e26768..696a0a74da 100644 --- a/AutomatedTesting/Registry/C4976245_PhysXCollider_CollisionLayerTest.setreg_override +++ b/AutomatedTesting/Registry/C4976245_PhysXCollider_CollisionLayerTest.setreg_override @@ -119,6 +119,9 @@ ] } }, + "DefaultMaterial": { + "SurfaceType": "Default_1" + }, "MaterialLibrary": { "assetId": { "guid": "{3A055A3F-8CB7-5FEE-B437-EB365FACD0D4}" diff --git a/AutomatedTesting/Registry/C4982593_PhysXCollider_CollisionLayer.setreg_override b/AutomatedTesting/Registry/C4982593_PhysXCollider_CollisionLayer.setreg_override index 9fa5e26768..696a0a74da 100644 --- a/AutomatedTesting/Registry/C4982593_PhysXCollider_CollisionLayer.setreg_override +++ b/AutomatedTesting/Registry/C4982593_PhysXCollider_CollisionLayer.setreg_override @@ -119,6 +119,9 @@ ] } }, + "DefaultMaterial": { + "SurfaceType": "Default_1" + }, "MaterialLibrary": { "assetId": { "guid": "{3A055A3F-8CB7-5FEE-B437-EB365FACD0D4}" diff --git a/AutomatedTesting/Registry/awscoreconfiguration.setreg b/AutomatedTesting/Registry/awscoreconfiguration.setreg index ca110eb103..b7c60b0fb9 100644 --- a/AutomatedTesting/Registry/awscoreconfiguration.setreg +++ b/AutomatedTesting/Registry/awscoreconfiguration.setreg @@ -3,7 +3,7 @@ { "AWSCore": { - "ProfileName": "default", + "ProfileName": "AWSAutomationTest", "ResourceMappingConfigFileName": "aws_resource_mappings.json" } } diff --git a/AutomatedTesting/Registry/physxsystemconfiguration.setreg b/AutomatedTesting/Registry/physxsystemconfiguration.setreg index 02f65b685b..30e9dced44 100644 --- a/AutomatedTesting/Registry/physxsystemconfiguration.setreg +++ b/AutomatedTesting/Registry/physxsystemconfiguration.setreg @@ -101,6 +101,9 @@ ] } }, + "DefaultMaterial": { + "SurfaceType": "Default_1" + }, "MaterialLibrary": { "assetId": { "guid": "{3A055A3F-8CB7-5FEE-B437-EB365FACD0D4}" diff --git a/AutomatedTesting/preview.png b/AutomatedTesting/preview.png index 3d4fe78063..c6928d31fc 100644 --- a/AutomatedTesting/preview.png +++ b/AutomatedTesting/preview.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:40949893ed7009eeaa90b7ce6057cb6be9dfaf7b162e3c26ba9dadf985939d7d -size 2038 +oid sha256:b9cd9d6f67440c193a85969ec5c082c6343e6d1fff3b6f209a0a6931eb22dd47 +size 2949 diff --git a/AutomatedTesting/surfacetypemateriallibrary.physmaterial b/AutomatedTesting/surfacetypemateriallibrary.physmaterial index 3c39d5521e..481cd2fbfa 100644 --- a/AutomatedTesting/surfacetypemateriallibrary.physmaterial +++ b/AutomatedTesting/surfacetypemateriallibrary.physmaterial @@ -1,19 +1,155 @@ - + - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CMakeLists.txt b/CMakeLists.txt index a7e42613cb..387f536966 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,34 +25,13 @@ include(cmake/LySet.cmake) include(cmake/Version.cmake) include(cmake/OutputDirectory.cmake) -# Set the engine_path and engine_json -set(o3de_engine_path ${CMAKE_CURRENT_LIST_DIR}) -set(o3de_engine_json ${o3de_engine_path}/engine.json) - if(NOT PROJECT_NAME) project(O3DE LANGUAGES C CXX VERSION ${LY_VERSION_STRING} ) - - # o3de manifest - include(cmake/o3de_manifest.cmake) endif() -################################################################################ -# Resolve this engines name and restricted path -################################################################################ -o3de_engine_name(${o3de_engine_json} o3de_engine_name) -o3de_restricted_path(${o3de_engine_json} o3de_engine_restricted_path) -message(STATUS "O3DE Engine Name: ${o3de_engine_name}") -message(STATUS "O3DE Engine Path: ${o3de_engine_path}") -if(o3de_engine_restricted_path) - message(STATUS "O3DE Engine Restricted Path: ${o3de_engine_restricted_path}") -endif() - -# add the engines cmake folder to the CMAKE_MODULE_PATH -list(APPEND CMAKE_MODULE_PATH "${o3de_engine_path}/cmake") - ################################################################################ # Initialize ################################################################################ @@ -60,6 +39,7 @@ include(cmake/GeneralSettings.cmake) include(cmake/FileUtil.cmake) include(cmake/PAL.cmake) include(cmake/PALTools.cmake) +include(cmake/RuntimeDependencies.cmake) include(cmake/Install.cmake) include(cmake/Configurations.cmake) # Requires to be after PAL so we get platform variable definitions include(cmake/Dependencies.cmake) @@ -67,92 +47,106 @@ include(cmake/Deployment.cmake) include(cmake/3rdParty.cmake) include(cmake/LYPython.cmake) include(cmake/LYWrappers.cmake) +include(cmake/Gems.cmake) include(cmake/UnitTest.cmake) include(cmake/LYTestWrappers.cmake) include(cmake/Monolithic.cmake) include(cmake/SettingsRegistry.cmake) include(cmake/TestImpactFramework/LYTestImpactFramework.cmake) include(cmake/CMakeFiles.cmake) +include(cmake/O3DEJson.cmake) ################################################################################ # Subdirectory processing ################################################################################ +function(add_engine_json_external_subdirectories) + read_json_external_subdirs(external_subdis ${LY_ROOT_FOLDER}/engine.json) + foreach(external_subdir ${external_subdis}) + file(REAL_PATH ${external_subdir} real_external_subdir BASE_DIRECTORY ${LY_ROOT_FOLDER}) + list(APPEND engine_external_subdirs ${real_external_subdir}) + endforeach() + + set_property(GLOBAL APPEND PROPERTY LY_EXTERNAL_SUBDIRS ${engine_external_subdirs}) +endfunction() + # Add the projects first so the Launcher can find them include(cmake/Projects.cmake) if(NOT INSTALLED_ENGINE) + # Add the rest of the targets add_subdirectory(Code) + add_subdirectory(scripts) + + # SPEC-1417 will investigate and fix this + if(NOT PAL_PLATFORM_NAME STREQUAL "Mac") + add_subdirectory(Tools/LyTestTools/tests/) + add_subdirectory(Tools/RemoteConsole/ly_remote_console/tests/) + endif() + + # Add external subdirectories listed in the engine.json. LY_EXTERNAL_SUBDIRS is a cache variable so the user can add extra + # external subdirectories + add_engine_json_external_subdirectories() + get_property(external_subdirs GLOBAL PROPERTY LY_EXTERNAL_SUBDIRS) + list(APPEND LY_EXTERNAL_SUBDIRS ${external_subdirs}) + + # Loop over the additional external subdirectories and invoke add_subdirectory on them + foreach(external_directory ${LY_EXTERNAL_SUBDIRS}) + # Hash the extenal_directory name and append it to the Binary Directory section of add_subdirectory + # This is to deal with potential situations where multiple external directories has the same last directory name + # For example if D:/Company1/RayTracingGem and F:/Company2/Path/RayTracingGem were both added as a subdirectory + file(REAL_PATH ${external_directory} full_directory_path) + string(SHA256 full_directory_hash ${full_directory_path}) + # Truncate the full_directory_hash down to 8 characters to avoid hitting the Windows 260 character path limit + # when the external subdirectory contains relative paths of significant length + string(SUBSTRING ${full_directory_hash} 0 8 full_directory_hash) + # Use the last directory as the suffix path to use for the Binary Directory + get_filename_component(directory_name ${external_directory} NAME) + add_subdirectory(${external_directory} ${CMAKE_BINARY_DIR}/External/${directory_name}-${full_directory_hash}) + endforeach() + else() ly_find_o3de_packages() endif() -# Add external subdirectories listed in the manifest -list(APPEND LY_EXTERNAL_SUBDIRS ${o3de_engine_external_subdirectories}) - -set(enabled_platforms - ${PAL_PLATFORM_NAME} - ${LY_PAL_TOOLS_ENABLED}) - -# Add any engine restricted platforms as external subdirs -o3de_add_engine_restricted_platform_external_subdirs() - -if(NOT INSTALLED_ENGINE) - add_subdirectory(scripts) -endif() - -# SPEC-1417 will investigate and fix this -if(NOT PAL_PLATFORM_NAME STREQUAL "Mac") - add_subdirectory(Tools/LyTestTools/tests/) - add_subdirectory(Tools/RemoteConsole/ly_remote_console/tests/) -endif() - ################################################################################ # Post-processing ################################################################################ - -# Loop over the additional external subdirectories and invoke add_subdirectory on them -foreach(external_directory ${LY_EXTERNAL_SUBDIRS}) - # Hash the extenal_directory name and append it to the Binary Directory section of add_subdirectory - # This is to deal with potential situations where multiple external directories has the same last directory name - # For example if D:/Company1/RayTracingGem and F:/Company2/Path/RayTracingGem were both added as a subdirectory - file(REAL_PATH ${external_directory} full_directory_path) - string(SHA256 full_directory_hash ${full_directory_path}) - # Truncate the full_directory_hash down to 8 characters to avoid hitting the Windows 260 character path limit - # when the external subdirectory contains relative paths of significant length - string(SUBSTRING ${full_directory_hash} 0 8 full_directory_hash) - # Use the last directory as the suffix path to use for the Binary Directory - get_filename_component(directory_name ${external_directory} NAME) - add_subdirectory(${external_directory} ${CMAKE_BINARY_DIR}/External/${directory_name}-${full_directory_hash}) -endforeach() - # The following steps have to be done after all targets are registered: # Defer generation of the StaticModules.inl file which is needed to create the AZ::Module derived class in monolithic # builds until after all the targets are known ly_delayed_generate_static_modules_inl() -# 1. generate a settings registry .setreg file for all ly_add_project_dependencies() and ly_add_target_dependencies() calls +# 1. Add any dependencies registered via ly_enable_gems +ly_enable_gems_delayed() + +# 2. generate a settings registry .setreg file for all ly_add_project_dependencies() and ly_add_target_dependencies() calls # to provide applications with the filenames of gem modules to load # This must be done before ly_delayed_target_link_libraries() as that inserts BUILD_DEPENDENCIES as MANUALLY_ADDED_DEPENDENCIES # if the build dependency is a MODULE_LIBRARY. That would cause a false load dependency to be generated ly_delayed_generate_settings_registry() -# 2. link targets where the dependency was yet not declared, we need to have the declaration so we do different + +# 3. link targets where the dependency was yet not declared, we need to have the declaration so we do different # linking logic depending on the type of target ly_delayed_target_link_libraries() -# 3. generate a registry file for unit testing for platforms that support unit testing + +# 4. generate a registry file for unit testing for platforms that support unit testing if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) ly_delayed_generate_unit_test_module_registry() endif() -# 4. inject runtime dependencies to the targets. We need to do this after (1) since we are going to walk through -# the dependencies -include(cmake/RuntimeDependencies.cmake) -# 5. Perform test impact framework post steps once all of the targets have been enumerated -ly_test_impact_post_step() -# 6. Generate the O3DE find file and setup install locations for scripts, tools, assets etc., required by the engine -if(NOT INSTALLED_ENGINE) - ly_setup_o3de_install() - # IMPORTANT: must be included last +# 5. inject runtime dependencies to the targets. We need to do this after (1) since we are going to walk through +# the dependencies +ly_delayed_generate_runtime_dependencies() + +# 6. Perform test impact framework post steps once all of the targets have been enumerated +ly_test_impact_post_step() + +# 7. Generate the O3DE find file and setup install locations for scripts, tools, assets etc., required by the engine +if(NOT INSTALLED_ENGINE) + # 8. Generate the O3DE find file and setup install locations for scripts, tools, assets etc., required by the engine + ly_setup_o3de_install() + # 9. CPack information (to be included after install) include(cmake/Packaging.cmake) endif() diff --git a/Code/CryEngine/CrySystem/LevelSystem/SpawnableLevelSystem.cpp b/Code/CryEngine/CrySystem/LevelSystem/SpawnableLevelSystem.cpp index ff6ebc0d17..31d1540d77 100644 --- a/Code/CryEngine/CrySystem/LevelSystem/SpawnableLevelSystem.cpp +++ b/Code/CryEngine/CrySystem/LevelSystem/SpawnableLevelSystem.cpp @@ -36,8 +36,8 @@ namespace LegacyLevelSystem //------------------------------------------------------------------------ static void LoadLevel(const AZ::ConsoleCommandContainer& arguments) { - AZ_Error("SpawnableLevelSystem", arguments.empty(), "LoadLevel requires a level file name to be provided."); - AZ_Error("SpawnableLevelSystem", arguments.size() > 1, "LoadLevel requires a single level file name to be provided."); + AZ_Error("SpawnableLevelSystem", !arguments.empty(), "LoadLevel requires a level file name to be provided."); + AZ_Error("SpawnableLevelSystem", arguments.size() == 1, "LoadLevel requires a single level file name to be provided."); if (!arguments.empty() && gEnv->pSystem && gEnv->pSystem->GetILevelSystem() && !gEnv->IsEditor()) { diff --git a/Code/CryEngine/CrySystem/SystemInit.cpp b/Code/CryEngine/CrySystem/SystemInit.cpp index 25d6b9c601..52744519bb 100644 --- a/Code/CryEngine/CrySystem/SystemInit.cpp +++ b/Code/CryEngine/CrySystem/SystemInit.cpp @@ -864,11 +864,6 @@ bool CSystem::InitShine([[maybe_unused]] const SSystemInitParams& initParams) EBUS_EVENT(UiSystemBus, InitializeSystem); - if (!m_env.pLyShine) - { - AZ_Error(AZ_TRACE_SYSTEM_WINDOW, false, "LYShine System did not initialize correctly. Please check that the LyShine gem is enabled for this project in *_dependencies.cmake."); - return false; - } return true; } @@ -2022,8 +2017,8 @@ void CSystem::CreateSystemVars() REGISTER_CVAR2("sys_streaming_in_blocks", &g_cvars.sys_streaming_in_blocks, 1, VF_NULL, "Streaming of large files happens in blocks"); -#if (defined(WIN32) || defined(WIN64)) && !defined(_RELEASE) - REGISTER_CVAR2("sys_float_exceptions", &g_cvars.sys_float_exceptions, 3, 0, "Use or not use floating point exceptions."); +#if (defined(WIN32) || defined(WIN64)) && defined(_DEBUG) + REGISTER_CVAR2("sys_float_exceptions", &g_cvars.sys_float_exceptions, 2, 0, "Use or not use floating point exceptions."); #else // Float exceptions by default disabled for console builds. REGISTER_CVAR2("sys_float_exceptions", &g_cvars.sys_float_exceptions, 0, 0, "Use or not use floating point exceptions."); #endif diff --git a/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp b/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp index f03b1aac76..7b33359023 100644 --- a/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp +++ b/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp @@ -77,6 +77,27 @@ #endif // defined(AZ_ENABLE_DEBUG_TOOLS) #include +#include + +static void PrintEntityName(const AZ::ConsoleCommandContainer& arguments) +{ + if (arguments.empty()) + { + return; + } + + const auto entityIdStr = AZStd::string(arguments.front()); + const auto entityIdValue = AZStd::stoull(entityIdStr); + + AZStd::string entityName; + AZ::ComponentApplicationBus::BroadcastResult( + entityName, &AZ::ComponentApplicationBus::Events::GetEntityName, AZ::EntityId(entityIdValue)); + + AZ_Printf("Entity Debug", "EntityId: %" PRIu64 ", Entity Name: %s", entityIdValue, entityName.c_str()); +} + +AZ_CONSOLEFREEFUNC( + PrintEntityName, AZ::ConsoleFunctorFlags::Null, "Parameter: EntityId value, Prints the name of the entity to the console"); namespace AZ { @@ -1260,7 +1281,7 @@ namespace AZ // So auto load is turned off if option "AutoLoad" key is bool that is false if (valueName == "AutoLoad" && !value) { - // Strip off the AutoLoead entry from the path + // Strip off the AutoLoad entry from the path auto autoLoadKey = AZ::StringFunc::TokenizeLast(path, "/"); if (!autoLoadKey) { @@ -1330,7 +1351,7 @@ namespace AZ { auto CompareDynamicModuleDescriptor = [&dynamicLibraryPath](const DynamicModuleDescriptor& entry) { - return entry.m_dynamicLibraryPath.contains(dynamicLibraryPath); + return AZ::IO::PathView(entry.m_dynamicLibraryPath).Stem() == AZ::IO::PathView(dynamicLibraryPath).Stem(); }; if (auto moduleIter = AZStd::find_if(gemModules.begin(), gemModules.end(), CompareDynamicModuleDescriptor); moduleIter == gemModules.end()) diff --git a/Code/Framework/AzCore/AzCore/Component/TransformBus.h b/Code/Framework/AzCore/AzCore/Component/TransformBus.h index b180e97332..2a8d82c34c 100644 --- a/Code/Framework/AzCore/AzCore/Component/TransformBus.h +++ b/Code/Framework/AzCore/AzCore/Component/TransformBus.h @@ -219,18 +219,11 @@ namespace AZ //! Scale modifiers //! @{ - //! Set local scale of the transform. - //! @param scale The new scale to set. - virtual void SetLocalScale([[maybe_unused]] const AZ::Vector3& scale) {} - - //! Get the scale value in local space. + //! @deprecated GetLocalScale is deprecated, and is left only to allow migration of legacy vector scale. + //! Get the legacy vector scale value in local space. //! @return The scale value in local space. virtual AZ::Vector3 GetLocalScale() { return AZ::Vector3(FLT_MAX); } - //! Get the scale value in world space. - //! @return The scale value in world space. - virtual AZ::Vector3 GetWorldScale() { return AZ::Vector3(FLT_MAX); } - //! Set the uniform scale value in local space. virtual void SetLocalUniformScale([[maybe_unused]] float scale) {} diff --git a/Code/Framework/AzCore/AzCore/IO/Path/Path.h b/Code/Framework/AzCore/AzCore/IO/Path/Path.h index 61294cd637..6c1b519224 100644 --- a/Code/Framework/AzCore/AzCore/IO/Path/Path.h +++ b/Code/Framework/AzCore/AzCore/IO/Path/Path.h @@ -95,6 +95,12 @@ namespace AZ::IO constexpr int Compare(AZStd::string_view pathString) const noexcept; constexpr int Compare(const value_type* pathString) const noexcept; + // Extension for fixed strings + //! extension: fixed string types with MaxPathLength capacity + //! Returns a new instance of an AZStd::fixed_string with capacity of MaxPathLength + //! made from the internal string + constexpr AZStd::fixed_string FixedMaxPathString() const noexcept; + // decomposition //! Given a windows path of "C:\O3DE\foo\bar\name.txt" and a posix path of //! "/O3DE/foo/bar/name.txt" diff --git a/Code/Framework/AzCore/AzCore/IO/Path/Path.inl b/Code/Framework/AzCore/AzCore/IO/Path/Path.inl index 1e42fc9df7..05a92c5247 100644 --- a/Code/Framework/AzCore/AzCore/IO/Path/Path.inl +++ b/Code/Framework/AzCore/AzCore/IO/Path/Path.inl @@ -915,6 +915,11 @@ namespace AZ::IO return compare_string_view(path); } + constexpr AZStd::fixed_string PathView::FixedMaxPathString() const noexcept + { + return AZStd::fixed_string(m_path.begin(), m_path.end()); + } + // decomposition constexpr auto PathView::RootName() const -> PathView { diff --git a/Code/Framework/AzCore/AzCore/Math/Aabb.cpp b/Code/Framework/AzCore/AzCore/Math/Aabb.cpp index 3f7cb4ecf5..367594be63 100644 --- a/Code/Framework/AzCore/AzCore/Math/Aabb.cpp +++ b/Code/Framework/AzCore/AzCore/Math/Aabb.cpp @@ -227,7 +227,7 @@ namespace AZ // the min and max of each part and sum them to get the min and max co-ordinate of the transformed box. For a given new axis, // the coefficients for what proportion of each original axis is rotated onto that new axis are the same as the components we // would get by performing the inverse rotation on the new axis, so we need to take the conjugate to get the inverse rotation. - axisCoeffs = transform.GetScale() * (transform.GetRotation().GetConjugate().TransformVector(axis)); + axisCoeffs = transform.GetUniformScale() * (transform.GetRotation().GetConjugate().TransformVector(axis)); a = axisCoeffs * m_min; b = axisCoeffs * m_max; diff --git a/Code/Framework/AzCore/AzCore/Math/Obb.cpp b/Code/Framework/AzCore/AzCore/Math/Obb.cpp index eb511669d0..9226ddd28f 100644 --- a/Code/Framework/AzCore/AzCore/Math/Obb.cpp +++ b/Code/Framework/AzCore/AzCore/Math/Obb.cpp @@ -154,7 +154,7 @@ namespace AZ return Obb::CreateFromPositionRotationAndHalfLengths( transform.TransformPoint(obb.GetPosition()), transform.GetRotation() * obb.GetRotation(), - transform.GetScale() * obb.GetHalfLengths() + transform.GetUniformScale() * obb.GetHalfLengths() ); } } diff --git a/Code/Framework/AzCore/AzCore/Math/Transform.cpp b/Code/Framework/AzCore/AzCore/Math/Transform.cpp index 9090a9e94e..62a390c138 100644 --- a/Code/Framework/AzCore/AzCore/Math/Transform.cpp +++ b/Code/Framework/AzCore/AzCore/Math/Transform.cpp @@ -130,8 +130,8 @@ namespace AZ const Transform* transform = reinterpret_cast(classPtr); float data[NumFloats]; transform->GetRotation().StoreToFloat4(data); - transform->GetScale().StoreToFloat3(&data[4]); - transform->GetTranslation().StoreToFloat3(&data[7]); + data[4] = transform->GetUniformScale(); + transform->GetTranslation().StoreToFloat3(&data[5]); for (int i = 0; i < NumFloats; i++) { @@ -159,8 +159,8 @@ namespace AZ size_t TransformSerializer::TextToData(const char* text, unsigned int textVersion, IO::GenericStream& stream, bool isDataBigEndian) { - const size_t dataBufferSize = AZStd::max(NumFloatsVersion0, NumFloats); - const size_t numElements = textVersion < 1 ? NumFloatsVersion0 : NumFloats; + const size_t dataBufferSize = AZStd::max(AZStd::max(NumFloatsVersion1, NumFloatsVersion0), NumFloats); + const size_t numElements = textVersion < 1 ? NumFloatsVersion0 : (textVersion == 1 ? NumFloatsVersion1 : NumFloats); size_t nextNumberIndex = 0; AZStd::array data; @@ -201,7 +201,34 @@ namespace AZ return true; } - // otherwise load as a separate rotation, scale and translation + // version 1 had a quaternion rotation, vector3 scale and vector3 translation + else if (version == 1) + { + float data[NumFloatsVersion1]; + if (stream.GetLength() < sizeof(data)) + { + return false; + } + + stream.Read(sizeof(data), reinterpret_cast(data)); + + for (unsigned int i = 0; i < AZ_ARRAY_SIZE(data); ++i) + { + AZ_SERIALIZE_SWAP_ENDIAN(data[i], isDataBigEndian); + } + + Quaternion rotation = Quaternion::CreateFromFloat4(data); + Vector3 vectorScale = Vector3::CreateFromFloat3(&data[4]); + Vector3 translation = Vector3::CreateFromFloat3(&data[7]); + + float uniformScale = vectorScale.GetMaxElement(); + + *reinterpret_cast(classPtr) = + Transform::CreateFromQuaternionAndTranslation(rotation, translation) * Transform::CreateUniformScale(uniformScale); + return true; + } + + // otherwise load as a quaternion rotation, float scale and vector3 translation float data[NumFloats]; if (stream.GetLength() < sizeof(data)) { @@ -216,11 +243,11 @@ namespace AZ } Quaternion rotation = Quaternion::CreateFromFloat4(data); - Vector3 scale = Vector3::CreateFromFloat3(&data[4]); - Vector3 translation = Vector3::CreateFromFloat3(&data[7]); + float scale = data[4]; + Vector3 translation = Vector3::CreateFromFloat3(&data[5]); *reinterpret_cast(classPtr) = - Transform::CreateFromQuaternionAndTranslation(rotation, translation) * Transform::CreateScale(scale); + Transform::CreateFromQuaternionAndTranslation(rotation, translation) * Transform::CreateUniformScale(scale); return true; } @@ -237,7 +264,7 @@ namespace AZ if (serializeContext) { serializeContext->Class() - ->Version(1) + ->Version(2) ->Serializer(); } @@ -250,7 +277,7 @@ namespace AZ Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::All)-> Attribute(Script::Attributes::Storage, Script::Attributes::StorageType::Value)-> Attribute(Script::Attributes::GenericConstructorOverride, &Internal::TransformDefaultConstructor)-> - Constructor()-> + Constructor()-> Method("GetBasis", &Transform::GetBasis)-> Method("GetBasisX", &Transform::GetBasisX)-> Method("GetBasisY", &Transform::GetBasisY)-> @@ -283,15 +310,10 @@ namespace AZ Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::All)-> Method("GetRotation", &Transform::GetRotation)-> Method("SetRotation", &Transform::SetRotation)-> - Method("GetScale", &Transform::GetScale)-> Method("GetUniformScale", &Transform::GetUniformScale)-> - Method("SetScale", &Transform::SetScale)-> Method("SetUniformScale", &Transform::SetUniformScale)-> - Method("ExtractScale", &Transform::ExtractScale)-> - Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::All)-> Method("ExtractUniformScale", &Transform::ExtractUniformScale)-> Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::All)-> - Method("MultiplyByScale", &Transform::MultiplyByScale)-> Method("MultiplyByUniformScale", &Transform::MultiplyByUniformScale)-> Method("GetInverse", &Transform::GetInverse)-> Method("Invert", &Transform::Invert)-> @@ -310,7 +332,6 @@ namespace AZ Method("CreateFromQuaternionAndTranslation", &Transform::CreateFromQuaternionAndTranslation)-> Method("CreateFromMatrix3x3", &Transform::CreateFromMatrix3x3)-> Method("CreateFromMatrix3x3AndTranslation", &Transform::CreateFromMatrix3x3AndTranslation)-> - Method("CreateScale", &Transform::CreateScale)-> Method("CreateUniformScale", &Transform::CreateUniformScale)-> Method("CreateTranslation", &Transform::CreateTranslation)-> Method("ConstructFromValuesNumeric", &Internal::ConstructTransformFromValues); @@ -321,7 +342,7 @@ namespace AZ { Transform result; Matrix3x3 tmp = value; - result.m_scale = tmp.ExtractScale(); + result.m_scale = tmp.ExtractScale().GetMaxElement(); result.m_rotation = Quaternion::CreateFromMatrix3x3(tmp); result.m_translation = Vector3::CreateZero(); return result; @@ -331,7 +352,7 @@ namespace AZ { Transform result; Matrix3x3 tmp = value; - result.m_scale = tmp.ExtractScale(); + result.m_scale = tmp.ExtractScale().GetMaxElement(); result.m_rotation = Quaternion::CreateFromMatrix3x3(tmp); result.m_translation = p; return result; @@ -341,7 +362,7 @@ namespace AZ { Transform result; Matrix3x4 tmp = value; - result.m_scale = tmp.ExtractScale(); + result.m_scale = tmp.ExtractScale().GetMaxElement(); result.m_rotation = Quaternion::CreateFromMatrix3x4(tmp); result.m_translation = value.GetTranslation(); return result; diff --git a/Code/Framework/AzCore/AzCore/Math/Transform.h b/Code/Framework/AzCore/AzCore/Math/Transform.h index 7ae86edd89..3fe6ddc98a 100644 --- a/Code/Framework/AzCore/AzCore/Math/Transform.h +++ b/Code/Framework/AzCore/AzCore/Math/Transform.h @@ -25,10 +25,13 @@ namespace AZ : public SerializeContext::IDataSerializer { public: - // number of floats in the serialized representation, 4 for rotation, 3 for scale and 3 for translation - static constexpr int NumFloats = 10; + // number of floats in the serialized representation, 4 for rotation, 1 for scale and 3 for translation + static constexpr int NumFloats = 8; - // number of floats in the old format, which stored a 3x4 matrix + // number of floats in version 1, which used 4 for rotation, 3 for scale and 3 for translation + static constexpr int NumFloatsVersion1 = 10; + + // number of floats in version 0, which stored a 3x4 matrix static constexpr int NumFloatsVersion0 = 12; size_t Save(const void* classPtr, IO::GenericStream& stream, bool isDataBigEndian) override; @@ -45,7 +48,7 @@ namespace AZ static constexpr float MaxTransformScale = 1e9f; //! @} - //! The basic transformation class, represented using a quaternion rotation, vector scale and vector translation. + //! The basic transformation class, represented using a quaternion rotation, float scale and vector translation. //! By design, cannot represent skew transformations. class Transform { @@ -63,7 +66,7 @@ namespace AZ Transform() = default; //! Construct a transform from components. - Transform(const Vector3& translation, const Quaternion& rotation, const Vector3& scale); + Transform(const Vector3& translation, const Quaternion& rotation, float scale); //! Creates an identity transform. static Transform CreateIdentity(); @@ -82,16 +85,20 @@ namespace AZ static Transform CreateFromQuaternionAndTranslation(const class Quaternion& q, const Vector3& p); //! Constructs from a Matrix3x3, translation is set to zero. + //! Note that Transform only allows uniform scale, so if the matrix has different scale values along its axes, + //! the largest matrix scale value will be used to uniformly scale the Transform. static Transform CreateFromMatrix3x3(const class Matrix3x3& value); - //! Constructs from a Matrix3x3, translation is set to zero. + //! Constructs from a Matrix3x3 and translation Vector3. + //! Note that Transform only allows uniform scale, so if the matrix has different scale values along its axes, + //! the largest matrix scale value will be used to uniformly scale the Transform. static Transform CreateFromMatrix3x3AndTranslation(const class Matrix3x3& value, const Vector3& p); + //! Constructs from a Matrix3x4. + //! Note that Transform only allows uniform scale, so if the matrix has different scale values along its axes, + //! the largest matrix scale value will be used to uniformly scale the Transform. static Transform CreateFromMatrix3x4(const Matrix3x4& value); - //! Sets the transform to apply scale only, no rotation or translation. - static Transform CreateScale(const AZ::Vector3& scale); - //! Sets the transform to apply (uniform) scale only, no rotation or translation. static Transform CreateUniformScale(const float scale); @@ -122,18 +129,12 @@ namespace AZ const Quaternion& GetRotation() const; void SetRotation(const Quaternion& rotation); - Vector3 GetScale() const; float GetUniformScale() const; - void SetScale(const Vector3& v); void SetUniformScale(const float scale); - //! Sets the transform's scale to a unit value and returns the previous scale value. - Vector3 ExtractScale(); - //! Sets the transform's scale to a unit value and returns the previous scale value. float ExtractUniformScale(); - void MultiplyByScale(const AZ::Vector3& scale); void MultiplyByUniformScale(float scale); Transform operator*(const Transform& rhs) const; @@ -168,7 +169,7 @@ namespace AZ private: Quaternion m_rotation; - Vector3 m_scale; + float m_scale; Vector3 m_translation; }; diff --git a/Code/Framework/AzCore/AzCore/Math/Transform.inl b/Code/Framework/AzCore/AzCore/Math/Transform.inl index a7d5e72749..5f71316b52 100644 --- a/Code/Framework/AzCore/AzCore/Math/Transform.inl +++ b/Code/Framework/AzCore/AzCore/Math/Transform.inl @@ -12,7 +12,7 @@ namespace AZ { - AZ_MATH_INLINE Transform::Transform(const Vector3& translation, const Quaternion& rotation, const Vector3& scale) + AZ_MATH_INLINE Transform::Transform(const Vector3& translation, const Quaternion& rotation, float scale) : m_translation(translation) , m_rotation(rotation) , m_scale(scale) @@ -25,7 +25,7 @@ namespace AZ { Transform result; result.m_rotation = Quaternion::CreateIdentity(); - result.m_scale = Vector3::CreateOne(); + result.m_scale = 1.0f; result.m_translation = Vector3::CreateZero(); return result; } @@ -49,7 +49,7 @@ namespace AZ { Transform result; result.m_rotation = q; - result.m_scale = Vector3::CreateOne(); + result.m_scale = 1.0f; result.m_translation = Vector3::CreateZero(); return result; } @@ -58,26 +58,16 @@ namespace AZ { Transform result; result.m_rotation = q; - result.m_scale = Vector3::CreateOne(); + result.m_scale = 1.0f; result.m_translation = p; return result; } - AZ_MATH_INLINE Transform Transform::CreateScale(const Vector3& scale) - { - AZ_WarningOnce("Transform", false, "CreateScale is deprecated, please use CreateUniformScale instead."); - Transform result; - result.m_rotation = Quaternion::CreateIdentity(); - result.m_scale = scale; - result.m_translation = Vector3::CreateZero(); - return result; - } - AZ_MATH_INLINE Transform Transform::CreateUniformScale(float scale) { Transform result; result.m_rotation = Quaternion::CreateIdentity(); - result.m_scale = Vector3(scale); + result.m_scale = scale; result.m_translation = Vector3::CreateZero(); return result; } @@ -86,7 +76,7 @@ namespace AZ { Transform result; result.m_rotation = Quaternion::CreateIdentity(); - result.m_scale = Vector3::CreateOne(); + result.m_scale = 1.0f; result.m_translation = translation; return result; } @@ -114,17 +104,17 @@ namespace AZ AZ_MATH_INLINE Vector3 Transform::GetBasisX() const { - return m_rotation.TransformVector(Vector3::CreateAxisX(m_scale.GetX())); + return m_rotation.TransformVector(Vector3::CreateAxisX(m_scale)); } AZ_MATH_INLINE Vector3 Transform::GetBasisY() const { - return m_rotation.TransformVector(Vector3::CreateAxisY(m_scale.GetY())); + return m_rotation.TransformVector(Vector3::CreateAxisY(m_scale)); } AZ_MATH_INLINE Vector3 Transform::GetBasisZ() const { - return m_rotation.TransformVector(Vector3::CreateAxisZ(m_scale.GetZ())); + return m_rotation.TransformVector(Vector3::CreateAxisZ(m_scale)); } AZ_MATH_INLINE void Transform::GetBasisAndTranslation(Vector3* basisX, Vector3* basisY, Vector3* basisZ, Vector3* pos) const @@ -160,49 +150,23 @@ namespace AZ m_rotation = rotation; } - AZ_MATH_INLINE Vector3 Transform::GetScale() const - { - AZ_WarningOnce("Transform", false, "GetScale is deprecated, please use GetUniformScale instead."); - return m_scale; - } - AZ_MATH_INLINE float Transform::GetUniformScale() const { - return m_scale.GetMaxElement(); - } - - AZ_MATH_INLINE void Transform::SetScale(const Vector3& scale) - { - AZ_WarningOnce("Transform", false, "SetScale is deprecated, please use SetUniformScale instead."); - m_scale = scale; + return m_scale; } AZ_MATH_INLINE void Transform::SetUniformScale(const float scale) { - m_scale = Vector3(scale); - } - - AZ_MATH_INLINE Vector3 Transform::ExtractScale() - { - AZ_WarningOnce("Transform", false, "ExtractScale is deprecated, please use ExtractUniformScale instead."); - const Vector3 scale = m_scale; - m_scale = Vector3::CreateOne(); - return scale; + m_scale = scale; } AZ_MATH_INLINE float Transform::ExtractUniformScale() { - const float scale = m_scale.GetMaxElement(); - m_scale = Vector3::CreateOne(); + const float scale = m_scale; + m_scale = 1.0f; return scale; } - AZ_MATH_INLINE void Transform::MultiplyByScale(const Vector3& scale) - { - AZ_WarningOnce("Transform", false, "MultiplyByScale is deprecated, please use MultiplyByUniformScale instead."); - m_scale *= scale; - } - AZ_MATH_INLINE void Transform::MultiplyByUniformScale(float scale) { m_scale *= scale; @@ -240,10 +204,9 @@ namespace AZ AZ_MATH_INLINE Transform Transform::GetInverse() const { - // note - need to be careful about how to calculate inverse when there is non-uniform scale Transform out; out.m_rotation = m_rotation.GetConjugate(); - out.m_scale = m_scale.GetReciprocal(); + out.m_scale = 1.0f / m_scale; out.m_translation = -out.m_scale * (out.m_rotation.TransformVector(m_translation)); return out; } @@ -255,27 +218,27 @@ namespace AZ AZ_MATH_INLINE bool Transform::IsOrthogonal(float tolerance) const { - return m_scale.IsClose(Vector3::CreateOne(), tolerance); + return AZ::IsClose(m_scale, 1.0f, tolerance); } AZ_MATH_INLINE Transform Transform::GetOrthogonalized() const { Transform result; result.m_rotation = m_rotation; - result.m_scale = Vector3::CreateOne(); + result.m_scale = 1.0f; result.m_translation = m_translation; return result; } AZ_MATH_INLINE void Transform::Orthogonalize() { - m_scale = Vector3::CreateOne(); + m_scale = 1.0f; } AZ_MATH_INLINE bool Transform::IsClose(const Transform& rhs, float tolerance) const { return m_rotation.IsClose(rhs.m_rotation, tolerance) - && m_scale.IsClose(rhs.m_scale, tolerance) + && AZ::IsClose(m_scale, rhs.m_scale, tolerance) && m_translation.IsClose(rhs.m_translation, tolerance); } @@ -304,21 +267,21 @@ namespace AZ AZ_MATH_INLINE void Transform::SetFromEulerDegrees(const Vector3& eulerDegrees) { m_translation = Vector3::CreateZero(); - m_scale = Vector3::CreateOne(); + m_scale = 1.0f; m_rotation.SetFromEulerDegrees(eulerDegrees); } AZ_MATH_INLINE void Transform::SetFromEulerRadians(const Vector3& eulerRadians) { m_translation = Vector3::CreateZero(); - m_scale = Vector3::CreateOne(); + m_scale = 1.0f; m_rotation.SetFromEulerRadians(eulerRadians); } AZ_MATH_INLINE bool Transform::IsFinite() const { return m_rotation.IsFinite() - && m_scale.IsFinite() + && AZ::IsFiniteFloat(m_scale) && m_translation.IsFinite(); } diff --git a/Code/Framework/AzCore/AzCore/Math/TransformSerializer.cpp b/Code/Framework/AzCore/AzCore/Math/TransformSerializer.cpp index 86bc1c36ea..36c40265af 100644 --- a/Code/Framework/AzCore/AzCore/Math/TransformSerializer.cpp +++ b/Code/Framework/AzCore/AzCore/Math/TransformSerializer.cpp @@ -67,7 +67,7 @@ namespace AZ result.Combine(loadResult); - transformInstance->SetScale(AZ::Vector3(scale)); + transformInstance->SetUniformScale(scale); } return context.Report( diff --git a/Code/Framework/AzCore/AzCore/Module/ModuleManager.cpp b/Code/Framework/AzCore/AzCore/Module/ModuleManager.cpp index fe41050b00..0ce3ee5d8d 100644 --- a/Code/Framework/AzCore/AzCore/Module/ModuleManager.cpp +++ b/Code/Framework/AzCore/AzCore/Module/ModuleManager.cpp @@ -512,7 +512,7 @@ namespace AZ // Load DLLs specified in the application descriptor for (const auto& moduleDescriptor : modules) { - // For each module that is loaded, attempt to set the module's folder as a path for dependent module resolution + // For each module that is loaded, attempt to set the module's folder as a path for dependent module resolution moduleSearchPathHelper.SetModuleSearchPath(moduleDescriptor); LoadModuleOutcome result = LoadDynamicModule(moduleDescriptor.m_dynamicLibraryPath.c_str(), lastStepToPerform, maintainReferences); diff --git a/Code/Framework/AzCore/AzCore/Serialization/EditContextConstants.inl b/Code/Framework/AzCore/AzCore/Serialization/EditContextConstants.inl index 1016027966..dfd0707ed2 100644 --- a/Code/Framework/AzCore/AzCore/Serialization/EditContextConstants.inl +++ b/Code/Framework/AzCore/AzCore/Serialization/EditContextConstants.inl @@ -123,6 +123,7 @@ namespace AZ const static AZ::Crc32 NameLabelOverride = AZ_CRC("NameLabelOverride", 0x9ff79cab); const static AZ::Crc32 AssetPickerTitle = AZ_CRC_CE("AssetPickerTitle"); + const static AZ::Crc32 HideProductFilesInAssetPicker = AZ_CRC_CE("HideProductFilesInAssetPicker"); const static AZ::Crc32 ChildNameLabelOverride = AZ_CRC("ChildNameLabelOverride", 0x73dd2909); //! Container attribute that is used to override labels for its elements given the index of the element const static AZ::Crc32 IndexedChildNameLabelOverride = AZ_CRC("IndexedChildNameLabelOverride", 0x5f313ac2); diff --git a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.cpp b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.cpp index 5870c66633..2abef3f808 100644 --- a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.cpp +++ b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.cpp @@ -88,6 +88,35 @@ namespace AZ::Internal m_enginePaths.emplace_back(EngineInfo{AZ::IO::FixedMaxPath{value}.LexicallyNormal(), {}}); } + AZ::SettingsRegistryInterface::VisitResponse Traverse( + [[maybe_unused]] AZStd::string_view path, AZStd::string_view valueName, + AZ::SettingsRegistryInterface::VisitAction action, AZ::SettingsRegistryInterface::Type type) override + { + auto response = AZ::SettingsRegistryInterface::VisitResponse::Continue; + if (action == AZ::SettingsRegistryInterface::VisitAction::Begin) + { + if (type == AZ::SettingsRegistryInterface::Type::Array) + { + if (valueName.compare("engines") != 0) + { + response = AZ::SettingsRegistryInterface::VisitResponse::Skip; + } + } + } + else if (action == AZ::SettingsRegistryInterface::VisitAction::Value) + { + if (type == AZ::SettingsRegistryInterface::Type::String) + { + if (valueName.compare("path") != 0) + { + response = AZ::SettingsRegistryInterface::VisitResponse::Skip; + } + } + } + + return response; + } + AZStd::vector m_enginePaths{}; }; diff --git a/Code/Framework/AzCore/Tests/AZTestShared/Math/MathTestHelpers.cpp b/Code/Framework/AzCore/Tests/AZTestShared/Math/MathTestHelpers.cpp index 42b77f6976..f9616702f1 100644 --- a/Code/Framework/AzCore/Tests/AZTestShared/Math/MathTestHelpers.cpp +++ b/Code/Framework/AzCore/Tests/AZTestShared/Math/MathTestHelpers.cpp @@ -68,7 +68,7 @@ namespace AZ return os << "translation: " << transform.GetTranslation() << " rotation: " << transform.GetRotation() - << " scale: " << transform.GetScale(); + << " scale: " << transform.GetUniformScale(); } std::ostream& operator<<(std::ostream& os, const Color& color) diff --git a/Code/Framework/AzCore/Tests/Serialization/Json/TransformSerializerTests.cpp b/Code/Framework/AzCore/Tests/Serialization/Json/TransformSerializerTests.cpp index 7eabd6e5e0..750f2ebc9c 100644 --- a/Code/Framework/AzCore/Tests/Serialization/Json/TransformSerializerTests.cpp +++ b/Code/Framework/AzCore/Tests/Serialization/Json/TransformSerializerTests.cpp @@ -44,7 +44,7 @@ namespace JsonSerializationTests AZStd::shared_ptr CreateFullySetInstance() override { return AZStd::make_shared( - AZ::Vector3(1.0f, 2.0f, 3.0f), AZ::Quaternion(0.25f, 0.5f, 0.75f, 1.0f), AZ::Vector3(9.0f)); + AZ::Vector3(1.0f, 2.0f, 3.0f), AZ::Quaternion(0.25f, 0.5f, 0.75f, 1.0f), 9.0f); } AZStd::string_view GetJsonForFullySetInstance() override @@ -95,7 +95,7 @@ namespace JsonSerializationTests AZ::Transform expectedTransform( AZ::Vector3(2.25f, 3.5f, 4.75f), AZ::Quaternion(0.25f, 0.5f, 0.75f, 1.0f), - AZ::Vector3(5.5f)); + 5.5f); rapidjson::Document json; json.Parse(R"({ "Translation": [ 2.25, 3.5, 4.75 ], "Rotation": [ 0.25, 0.5, 0.75, 1.0 ], "Scale": 5.5 })"); diff --git a/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.cpp b/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.cpp index 49adab2252..b3c4f1b256 100644 --- a/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.cpp +++ b/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.cpp @@ -406,21 +406,10 @@ namespace AzFramework return m_localTM.GetRotation(); } - void TransformComponent::SetLocalScale(const AZ::Vector3& scale) - { - AZ::Transform newLocalTM = m_localTM; - newLocalTM.SetScale(scale); - SetLocalTM(newLocalTM); - } - AZ::Vector3 TransformComponent::GetLocalScale() { - return m_localTM.GetScale(); - } - - AZ::Vector3 TransformComponent::GetWorldScale() - { - return m_worldTM.GetScale(); + AZ_WarningOnce("TransformComponent", false, "GetLocalScale is deprecated, please use GetLocalUniformScale instead"); + return AZ::Vector3(m_localTM.GetUniformScale()); } void TransformComponent::SetLocalUniformScale(float scale) @@ -756,11 +745,11 @@ namespace AzFramework ->Event("GetLocalRotationQuaternion", &AZ::TransformBus::Events::GetLocalRotationQuaternion) ->Attribute("Rotation", AZ::Edit::Attributes::PropertyRotation) ->VirtualProperty("Rotation", "GetLocalRotationQuaternion", "SetLocalRotationQuaternion") - ->Event("SetLocalScale", &AZ::TransformBus::Events::SetLocalScale) ->Event("GetLocalScale", &AZ::TransformBus::Events::GetLocalScale) ->Attribute("Scale", AZ::Edit::Attributes::PropertyScale) - ->VirtualProperty("Scale", "GetLocalScale", "SetLocalScale") - ->Event("GetWorldScale", &AZ::TransformBus::Events::GetWorldScale) + ->Event("SetLocalUniformScale", &AZ::TransformBus::Events::SetLocalUniformScale) + ->Event("GetLocalUniformScale", &AZ::TransformBus::Events::GetLocalUniformScale) + ->VirtualProperty("Uniform Scale", "GetLocalUniformScale", "SetLocalUniformScale") ->Event("GetChildren", &AZ::TransformBus::Events::GetChildren) ->Event("GetAllDescendants", &AZ::TransformBus::Events::GetAllDescendants) ->Event("GetEntityAndAllDescendants", &AZ::TransformBus::Events::GetEntityAndAllDescendants) diff --git a/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.h b/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.h index 9009c6bff9..0301334a0d 100644 --- a/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.h +++ b/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.h @@ -128,9 +128,7 @@ namespace AzFramework AZ::Quaternion GetLocalRotationQuaternion() override; // Scale Modifiers - void SetLocalScale(const AZ::Vector3& scale) override; AZ::Vector3 GetLocalScale() override; - AZ::Vector3 GetWorldScale() override; void SetLocalUniformScale(float scale) override; float GetLocalUniformScale() override; diff --git a/Code/Framework/AzFramework/AzFramework/Font/FontInterface.h b/Code/Framework/AzFramework/AzFramework/Font/FontInterface.h index 04a0572bb9..dae5fa9fe7 100644 --- a/Code/Framework/AzFramework/AzFramework/Font/FontInterface.h +++ b/Code/Framework/AzFramework/AzFramework/Font/FontInterface.h @@ -54,7 +54,7 @@ namespace AzFramework AZ::Matrix3x4 m_transform = AZ::Matrix3x4::Identity(); //!< Transform to apply to text quads bool m_monospace = false; //!< disable character proportional spacing bool m_depthTest = false; //!< Test character against the depth buffer - bool m_virtual800x600ScreenSize = true; //!< Text placement and size are scaled relative to a virtual 800x600 resolution + bool m_virtual800x600ScreenSize = false; //!< Text placement and size are scaled relative to a virtual 800x600 resolution bool m_scaleWithWindow = false; //!< Font gets bigger as the window gets bigger bool m_multiline = true; //!< text respects ascii newline characters }; diff --git a/Code/Framework/AzFramework/AzFramework/Physics/ClassConverters.cpp b/Code/Framework/AzFramework/AzFramework/Physics/ClassConverters.cpp index e43bda4c88..4f206858af 100644 --- a/Code/Framework/AzFramework/AzFramework/Physics/ClassConverters.cpp +++ b/Code/Framework/AzFramework/AzFramework/Physics/ClassConverters.cpp @@ -259,11 +259,18 @@ namespace Physics if (success) { - success = success && dataElement.RemoveElementByName(AZ_CRC("MaterialId", 0x9360e002)); + dataElement.RemoveElementByName(AZ_CRC("MaterialId", 0x9360e002)); + success = success && (dataElement.FindElement(AZ_CRC("MaterialId", 0x9360e002)) < 0); success = success && dataElement.AddElementWithData(context, "MaterialIds", AZStd::vector { materialId }); } } + if (success && dataElement.GetVersion() <= 2) + { + dataElement.RemoveElementByName(AZ_CRC_CE("Material")); + success = success && (dataElement.FindElement(AZ_CRC_CE("Material")) < 0); + } + return success; } } // namespace ClassConverters diff --git a/Code/Framework/AzFramework/AzFramework/Physics/Common/PhysicsEvents.h b/Code/Framework/AzFramework/AzFramework/Physics/Common/PhysicsEvents.h index d5a82c0367..a3a34dc1df 100644 --- a/Code/Framework/AzFramework/AzFramework/Physics/Common/PhysicsEvents.h +++ b/Code/Framework/AzFramework/AzFramework/Physics/Common/PhysicsEvents.h @@ -58,9 +58,18 @@ namespace AzPhysics //! When triggered will send the handle to the old Scene (after this call, the Handle will be invalid). using OnSceneRemovedEvent = AZ::Event; - //! Event that triggers when the default material library changes. + //! Event that triggers when the material library changes. //! When triggered the event will send the Asset Id of the new material library. - using OnDefaultMaterialLibraryChangedEvent = AZ::Event; + using OnMaterialLibraryChangedEvent = AZ::Event; + + enum class MaterialLibraryLoadErrorType : uint8_t + { + InvalidId, + ErrorLoading + }; + + //! Event that triggers when the default material library has loaded with errors. + using OnMaterialLibraryLoadErrorEvent = AZ::Event; //! Event that triggers when the default scene configuration changes. //! When triggered the event will send the new default scene configuration. diff --git a/Code/Framework/AzFramework/AzFramework/Physics/Common/PhysicsSimulatedBody.h b/Code/Framework/AzFramework/AzFramework/Physics/Common/PhysicsSimulatedBody.h index ed8a68dc24..9d45a17edc 100644 --- a/Code/Framework/AzFramework/AzFramework/Physics/Common/PhysicsSimulatedBody.h +++ b/Code/Framework/AzFramework/AzFramework/Physics/Common/PhysicsSimulatedBody.h @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -68,6 +69,22 @@ namespace AzPhysics return m_customUserData; } + //! Helper functions for setting frame ID. + //! @param frameId Optionally set frame ID for the systems moving the actors back in time. + void SetFrameId(uint32_t frameId) + { + m_frameId = frameId; + } + + //! Helper functions for getting the set frame ID. + //! @return Will return the frame ID. + uint32_t GetFrameId() const + { + return m_frameId; + } + + static constexpr uint32_t UndefinedFrameId = AZStd::numeric_limits::max(); + //! Perform a ray cast on this Simulated Body. //! @param request The request to make. //! @return Returns the closest hit, if any, against this simulated body. @@ -126,6 +143,7 @@ namespace AzPhysics SimulatedBodyEvents::OnTriggerExit m_triggerExitEvent; void* m_customUserData = nullptr; + uint32_t m_frameId = UndefinedFrameId; // helpers for reflecting to behavior context SimulatedBodyEvents::OnCollisionBegin* GetOnCollisionBeginEvent(); diff --git a/Code/Framework/AzFramework/AzFramework/Physics/Configuration/SystemConfiguration.cpp b/Code/Framework/AzFramework/AzFramework/Physics/Configuration/SystemConfiguration.cpp index cd250b71a9..d7532cbfea 100644 --- a/Code/Framework/AzFramework/AzFramework/Physics/Configuration/SystemConfiguration.cpp +++ b/Code/Framework/AzFramework/AzFramework/Physics/Configuration/SystemConfiguration.cpp @@ -39,6 +39,8 @@ namespace AzPhysics ->Field("ShapecastBufferSize", &SystemConfiguration::m_shapecastBufferSize) ->Field("OverlapBufferSize", &SystemConfiguration::m_overlapBufferSize) ->Field("CollisionConfig", &SystemConfiguration::m_collisionConfig) + ->Field("DefaultMaterial", &SystemConfiguration::m_defaultMaterialConfiguration) + ->Field("MaterialLibrary", &SystemConfiguration::m_materialLibraryAsset) ; if (AZ::EditContext* editContext = serializeContext->GetEditContext()) @@ -79,7 +81,9 @@ namespace AzPhysics m_overlapBufferSize == other.m_overlapBufferSize && AZ::IsClose(m_maxTimestep, other.m_maxTimestep) && AZ::IsClose(m_fixedTimestep, other.m_fixedTimestep) && - m_collisionConfig == other.m_collisionConfig + m_collisionConfig == other.m_collisionConfig && + m_defaultMaterialConfiguration == other.m_defaultMaterialConfiguration && + m_materialLibraryAsset == other.m_materialLibraryAsset ; } diff --git a/Code/Framework/AzFramework/AzFramework/Physics/Configuration/SystemConfiguration.h b/Code/Framework/AzFramework/AzFramework/Physics/Configuration/SystemConfiguration.h index 0a00d627a7..56fe9a68c4 100644 --- a/Code/Framework/AzFramework/AzFramework/Physics/Configuration/SystemConfiguration.h +++ b/Code/Framework/AzFramework/AzFramework/Physics/Configuration/SystemConfiguration.h @@ -13,6 +13,7 @@ #include #include +#include namespace AZ { @@ -45,6 +46,9 @@ namespace AzPhysics //! Each Physics Scene uses this as a base and will override as needed. CollisionConfiguration m_collisionConfig; + Physics::MaterialConfiguration m_defaultMaterialConfiguration; //!< Default material parameters for the project. + AZ::Data::Asset m_materialLibraryAsset = AZ::Data::AssetLoadBehavior::NoLoad; //!< Material Library exposed by the system component SystemBus API. + //! Controls whether the Physics System will self register to the TickBus and call StartSimulation / FinishSimulation on each Scene. //! Disable this to manually control Physics Scene simulation logic. bool m_autoManageSimulationUpdate = true; diff --git a/Code/Framework/AzFramework/AzFramework/Physics/Material.cpp b/Code/Framework/AzFramework/AzFramework/Physics/Material.cpp index 5552cef448..78e0431753 100644 --- a/Code/Framework/AzFramework/AzFramework/Physics/Material.cpp +++ b/Code/Framework/AzFramework/AzFramework/Physics/Material.cpp @@ -49,10 +49,7 @@ namespace Physics { materialSelection->SetMaterialSlots(Physics::MaterialSelection::SlotsArray()); } - if (materialSelection->IsDefaultMaterialLibraryAsset()) - { - materialSelection->SyncSelectionToMaterialLibrary(); - } + materialSelection->SyncSelectionToMaterialLibrary(); } }; @@ -122,6 +119,24 @@ namespace Physics } } + bool MaterialConfiguration::operator==(const MaterialConfiguration& other) const + { + return m_surfaceType == other.m_surfaceType && + AZ::IsClose(m_dynamicFriction, other.m_dynamicFriction) && + AZ::IsClose(m_staticFriction, other.m_staticFriction) && + AZ::IsClose(m_restitution, other.m_restitution) && + AZ::IsClose(m_density, other.m_density) && + m_restitutionCombine == other.m_restitutionCombine && + m_frictionCombine == other.m_frictionCombine && + m_debugColor == other.m_debugColor + ; + } + + bool MaterialConfiguration::operator!=(const MaterialConfiguration& other) const + { + return !(*this == other); + } + AZ::Color MaterialConfiguration::GenerateDebugColor(const char* materialName) { static const AZ::Color colors[] = @@ -191,51 +206,25 @@ namespace Physics ////////////////////////////////////////////////////////////////////////// - void MaterialLibraryAssetReflectionWrapper::Reflect(AZ::ReflectContext* context) - { - AZ::SerializeContext* serializeContext = azrtti_cast(context); - if (serializeContext) - { - serializeContext->Class() - ->Version(1) - ->Field("Asset", &MaterialLibraryAssetReflectionWrapper::m_asset) - ; - - AZ::EditContext* editContext = serializeContext->GetEditContext(); - if (editContext) - { - editContext->Class("", "") - ->ClassElement(AZ::Edit::ClassElements::EditorData, "") - ->Attribute(AZ::Edit::Attributes::AutoExpand, "") - ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) - ->DataElement(AZ::Edit::UIHandlers::Default, &MaterialLibraryAssetReflectionWrapper::m_asset, "Physics Material Library", "Physics Material Library") - ->Attribute("EditButton", "") - ; - } - } - } - - ////////////////////////////////////////////////////////////////////////// - - - void DefaultMaterialLibraryAssetReflectionWrapper::Reflect(AZ::ReflectContext* context) + void MaterialInfoReflectionWrapper::Reflect(AZ::ReflectContext* context) { AZ::SerializeContext* serializeContext = azrtti_cast(context); if (serializeContext) { - serializeContext->Class() + serializeContext->Class() ->Version(1) - ->Field("Asset", &DefaultMaterialLibraryAssetReflectionWrapper::m_asset) + ->Field("DefaultMaterial", &MaterialInfoReflectionWrapper::m_defaultMaterialConfiguration) + ->Field("Asset", &MaterialInfoReflectionWrapper::m_materialLibraryAsset) ; AZ::EditContext* editContext = serializeContext->GetEditContext(); if (editContext) { - editContext->Class("", "") + editContext->Class("Physics Materials", "") ->ClassElement(AZ::Edit::ClassElements::EditorData, "") - ->Attribute(AZ::Edit::Attributes::AutoExpand, "") - ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) - ->DataElement(AZ::Edit::UIHandlers::Default, &DefaultMaterialLibraryAssetReflectionWrapper::m_asset, "Default Physics Material Library", "Library to use by default") + ->Attribute(AZ::Edit::Attributes::AutoExpand, true) + ->DataElement(AZ::Edit::UIHandlers::Default, &MaterialInfoReflectionWrapper::m_defaultMaterialConfiguration, "Default Physics Material", "Material used by default") + ->DataElement(AZ::Edit::UIHandlers::Default, &MaterialInfoReflectionWrapper::m_materialLibraryAsset, "Physics Material Library", "Library to use for the project") ->Attribute(AZ::Edit::Attributes::AllowClearAsset, false) ->Attribute("EditButton", "") ; @@ -269,6 +258,17 @@ namespace Physics } } + bool MaterialFromAssetConfiguration::operator==(const MaterialFromAssetConfiguration& other) const + { + return m_configuration == other.m_configuration && + m_id == other.m_id; + } + + bool MaterialFromAssetConfiguration::operator!=(const MaterialFromAssetConfiguration& other) const + { + return !(*this == other); + } + ////////////////////////////////////////////////////////////////////////// bool MaterialLibraryAsset::GetDataForMaterialId(const MaterialId& materialId, MaterialFromAssetConfiguration& configuration) const @@ -370,9 +370,8 @@ namespace Physics if (auto serializeContext = azrtti_cast(context)) { serializeContext->Class() - ->Version(2, &ClassConverters::MaterialSelectionConverter) + ->Version(3, &ClassConverters::MaterialSelectionConverter) ->EventHandler() - ->Field("Material", &MaterialSelection::m_materialLibrary) ->Field("MaterialIds", &MaterialSelection::m_materialIdsAssignedToSlots) ; @@ -381,14 +380,8 @@ namespace Physics editContext->Class("Physics Material", "Select physics material library and which materials to use for the object") ->ClassElement(AZ::Edit::ClassElements::EditorData, "") ->Attribute(AZ::Edit::Attributes::AutoExpand, true) - ->DataElement(AZ::Edit::UIHandlers::Default, &MaterialSelection::m_materialLibrary, "Library", "Physics material library to use for this object") - ->Attribute(AZ::Edit::Attributes::ContainerCanBeModified, true) - ->Attribute("EditButton", "") - ->Attribute("EditDescription", "Open in Asset Editor") - ->Attribute(AZ::Edit::Attributes::DefaultAsset, &MaterialSelection::GetDefaultMaterialLibraryId) - ->Attribute(AZ::Edit::Attributes::ChangeNotify, &MaterialSelection::OnMaterialLibraryChanged) ->DataElement(AZ::Edit::UIHandlers::Default, &MaterialSelection::m_materialIdsAssignedToSlots, "Mesh Surfaces", "Specify which Physics Material to use for each element of this object") - ->ElementAttribute(Attributes::MaterialLibraryAssetId, &MaterialSelection::GetMaterialLibraryAssetId) + ->ElementAttribute(Attributes::MaterialLibraryAssetId, &MaterialSelection::GetMaterialLibraryId) ->Attribute(AZ::Edit::Attributes::IndexedChildNameLabelOverride, &MaterialSelection::GetMaterialSlotLabel) ->Attribute(AZ::Edit::Attributes::AutoExpand, true) ->ElementAttribute(AZ::Edit::Attributes::ReadOnly, &MaterialSelection::AreMaterialSlotsReadOnly) @@ -398,12 +391,6 @@ namespace Physics } } - AZ::u32 MaterialSelection::OnMaterialLibraryChanged() - { - SyncSelectionToMaterialLibrary(); - return AZ::Edit::PropertyRefreshLevels::EntireTree; - } - AZStd::string MaterialSelection::GetMaterialSlotLabel(int index) { if (index < m_materialSlots.size()) @@ -425,28 +412,9 @@ namespace Physics } } - AZ::Data::AssetId MaterialSelection::GetMaterialLibraryAssetId() const + void MaterialSelection::OnMaterialLibraryChanged([[maybe_unused]] const AZ::Data::AssetId& defaultMaterialLibraryId) { - return GetMaterialLibraryAsset().GetId(); - } - - const Physics::MaterialLibraryAsset* MaterialSelection::GetMaterialLibraryAssetData() const - { - return GetMaterialLibraryAsset().Get(); - } - - const AZStd::string& MaterialSelection::GetMaterialLibraryAssetHint() const - { - return m_materialLibrary.GetHint(); - } - - void MaterialSelection::OnDefaultMaterialLibraryChanged(const AZ::Data::AssetId& defaultMaterialLibraryId) - { - AZ_UNUSED(defaultMaterialLibraryId); - if (IsDefaultMaterialLibraryAsset()) - { - OnMaterialLibraryChanged(); - } + SyncSelectionToMaterialLibrary(); } void MaterialSelection::SetSlotsReadOnly(bool readOnly) @@ -454,45 +422,6 @@ namespace Physics m_slotsReadOnly = readOnly; } - bool MaterialSelection::IsMaterialLibraryValid() const - { - if (GetMaterialLibraryAssetId().IsValid()) - { - auto materialAsset = LoadAsset(); - const auto& materialsData = materialAsset.Get()->GetMaterialsData(); - - if (materialsData.size() != 0) - { - return true; - } - } - return false; - } - - bool MaterialSelection::GetMaterialConfiguration(Physics::MaterialFromAssetConfiguration& configuration, const Physics::MaterialId& materialId) const - { - if (IsMaterialLibraryValid()) - { - auto materialAsset = LoadAsset(); - if (materialAsset.Get()) - { - return materialAsset.Get()->GetDataForMaterialId(materialId, configuration); - } - } - return false; - } - - void MaterialSelection::SetMaterialLibrary(const AZ::Data::AssetId& assetId) - { - m_materialLibrary = AZ::Data::AssetManager::Instance().GetAsset(assetId, m_materialLibrary.GetAutoLoadBehavior()); - m_materialLibrary.BlockUntilLoadComplete(); - } - - void MaterialSelection::ResetToDefaultMaterialLibrary() - { - m_materialLibrary = {}; - } - void MaterialSelection::SetMaterialSlots(const SlotsArray& slots) { if (slots.empty()) @@ -533,74 +462,45 @@ namespace Physics m_materialIdsAssignedToSlots[slotIndex] = materialId; } - AZ::Data::Asset MaterialSelection::LoadAsset() const - { - AZ::Data::Asset asset = AZ::Data::AssetManager::Instance() - .GetAsset(GetMaterialLibraryAssetId(), AZ::Data::AssetLoadBehavior::Default); - - asset.BlockUntilLoadComplete(); - - return asset; - } - void MaterialSelection::SyncSelectionToMaterialLibrary() { - if (GetMaterialLibraryAssetId().IsValid()) + auto* materialLibrary = GetMaterialLibrary().Get(); + if (!materialLibrary) { - auto materialLibraryAsset = AZ::Data::AssetManager::Instance().GetAsset(GetMaterialLibraryAssetId(), AZ::Data::AssetLoadBehavior::Default); + return; + } - materialLibraryAsset.BlockUntilLoadComplete(); - - // We try to check whether existing selection matches any materials in the newly assigned library and do one of the following: - // 1. If previous MaterialId is invalid for this material library, and it is not the Default material, we set it to the Default material from the library. - // 2. If it's valid, or it is the Default material, we don't change it (useful when user accidentally re-assigns the same library: previous selection won't go away). - - if (materialLibraryAsset.Get()) + for (Physics::MaterialId& materialId : m_materialIdsAssignedToSlots) + { + // Leave nulls (default) unchanged. + if (materialId.IsNull()) { - for (Physics::MaterialId& materialId : m_materialIdsAssignedToSlots) - { - if (!materialLibraryAsset.Get()->HasDataForMaterialId(materialId) - && !materialId.IsNull()) // Null materialId is the Default material. - { - materialId = MaterialId(); - } - } + continue; } - else + + // If the material id is not present in the library anymore, set it to default + if (!materialLibrary->HasDataForMaterialId(materialId)) { - AZ_Warning("PhysX", false, "MaterialSelection: invalid material library"); + materialId = MaterialId(); } } } - const AZ::Data::Asset& MaterialSelection::GetMaterialLibraryAsset() const - { - if (IsDefaultMaterialLibraryAsset()) - { - const AZ::Data::Asset& defaultMaterialLibrary = GetDefaultMaterialLibrary(); - return defaultMaterialLibrary; - } - - return m_materialLibrary; - } - - bool MaterialSelection::IsDefaultMaterialLibraryAsset() const - { - return !m_materialLibrary.GetId().IsValid(); - } - - const AZ::Data::Asset& MaterialSelection::GetDefaultMaterialLibrary() + const AZ::Data::Asset& MaterialSelection::GetMaterialLibrary() { if (auto* physicsSystem = AZ::Interface::Get()) { - return physicsSystem->GetDefaultMaterialLibrary(); + if (const auto* physicsConfiguration = physicsSystem->GetConfiguration()) + { + return physicsConfiguration->m_materialLibraryAsset; + } } return s_invalidMaterialLibrary; } - const AZ::Data::AssetId& MaterialSelection::GetDefaultMaterialLibraryId() + const AZ::Data::AssetId& MaterialSelection::GetMaterialLibraryId() { - return GetDefaultMaterialLibrary().GetId(); + return GetMaterialLibrary().GetId(); } bool MaterialSelection::AreMaterialSlotsReadOnly() const diff --git a/Code/Framework/AzFramework/AzFramework/Physics/Material.h b/Code/Framework/AzFramework/AzFramework/Physics/Material.h index e9eaae929f..69edf3ed25 100644 --- a/Code/Framework/AzFramework/AzFramework/Physics/Material.h +++ b/Code/Framework/AzFramework/AzFramework/Physics/Material.h @@ -29,7 +29,6 @@ namespace Physics /// ========================= /// This is the interface to the wrapper around native material type (such as PxMaterial in PhysX gem) /// that stores extra metadata, like Surface Type name. - /// To see more details about PhysX implementation please refer to PhysX::Material class /// /// Usage example /// ------------------------- @@ -37,14 +36,7 @@ namespace Physics /// /// Physics::MaterialConfiguration materialProperties; /// AZStd::shared_ptr newMaterial = AZ::Interface::Get()->CreateMaterial(materialProperties); - /// - /// To get PxMaterial use GetNativePointer function - /// - /// physx::PxMaterial* material = static_cast(newMaterial->GetNativePointer()); - /// - /// You can use retrieved PxMaterial pointer on its own, provided you increment its reference count. - /// If this class goes out of scope, the PxMaterial pointer will be valid, but its userData - /// will be cleaned up to point to nullptr. + /// class Material { public: @@ -63,9 +55,9 @@ namespace Physics /// Returns AZ::Crc32 of the surface name. virtual AZ::Crc32 GetSurfaceType() const = 0; - virtual void SetSurfaceType(AZ::Crc32 surfaceType) = 0; virtual const AZStd::string& GetSurfaceTypeName() const = 0; + virtual void SetSurfaceTypeName(const AZStd::string& surfaceTypeName) = 0; virtual float GetDynamicFriction() const = 0; virtual void SetDynamicFriction(float dynamicFriction) = 0; @@ -85,6 +77,9 @@ namespace Physics virtual float GetDensity() const = 0; virtual void SetDensity(float density) = 0; + virtual AZ::Color GetDebugColor() const = 0; + virtual void SetDebugColor(const AZ::Color& debugColor) = 0; + /// If the name of this material matches the name of one of the CrySurface types, it will return its CrySurface Id.\n /// If there's no match it will return default CrySurface Id.\n /// CrySurface types are defined in libs/materialeffects/surfacetypes.xml @@ -122,6 +117,10 @@ namespace Physics Material::CombineMode m_frictionCombine = Material::CombineMode::Average; AZ::Color m_debugColor = AZ::Colors::White; + + bool operator==(const MaterialConfiguration& other) const; + bool operator!=(const MaterialConfiguration& other) const; + private: static bool VersionConverter(AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement); static AZ::Color GenerateDebugColor(const char* materialName); @@ -147,6 +146,7 @@ namespace Physics static MaterialId FromUUID(const AZ::Uuid& uuid); bool IsNull() const { return m_id.IsNull(); } bool operator==(const MaterialId& other) const { return m_id == other.m_id; } + bool operator!=(const MaterialId& other) const { return !(*this == other); } const AZ::Uuid& GetUuid() const { return m_id; } private: @@ -166,6 +166,9 @@ namespace Physics MaterialConfiguration m_configuration; MaterialId m_id; + + bool operator==(const MaterialFromAssetConfiguration& other) const; + bool operator!=(const MaterialFromAssetConfiguration& other) const; }; /// An asset that holds a list of materials to be edited and assigned in Open 3D Engine Editor @@ -222,40 +225,27 @@ namespace Physics AZStd::vector m_materialLibrary; }; - /// The class is used to expose a MaterialLibraryAsset to Edit Context + /// The class is used to expose a default material and material library asset to Edit Context /// ======================================================================= /// /// Since AZ::Data::Asset doesn't reflect the data to EditContext /// we have to have a wrapper doing it. - class MaterialLibraryAssetReflectionWrapper + class MaterialInfoReflectionWrapper { public: - AZ_CLASS_ALLOCATOR(MaterialLibraryAssetReflectionWrapper, AZ::SystemAllocator, 0); - AZ_TYPE_INFO(Physics::MaterialLibraryAssetReflectionWrapper, "{3D2EF5DF-EFD0-47EB-B88F-3E6FE1FEE5B0}"); + AZ_CLASS_ALLOCATOR(MaterialInfoReflectionWrapper, AZ::SystemAllocator, 0); + AZ_TYPE_INFO(Physics::MaterialInfoReflectionWrapper, "{02AB8CBC-D35B-4E0F-89BA-A96D94DAD4F9}"); static void Reflect(AZ::ReflectContext* context); - AZ::Data::Asset m_asset = + Physics::MaterialConfiguration m_defaultMaterialConfiguration; + AZ::Data::Asset m_materialLibraryAsset = AZ::Data::AssetLoadBehavior::NoLoad; }; - /// Customized material library for use as default material library - class DefaultMaterialLibraryAssetReflectionWrapper : public Physics::MaterialLibraryAssetReflectionWrapper - { - public: - AZ_CLASS_ALLOCATOR(MaterialLibraryAssetReflectionWrapper, AZ::SystemAllocator, 0); - AZ_TYPE_INFO(Physics::DefaultMaterialLibraryAssetReflectionWrapper, "{02AB8CBC-D35B-4E0F-89BA-A96D94DAD4F9}"); - static void Reflect(AZ::ReflectContext* context); - - AZ::Data::Asset m_asset = - AZ::Data::AssetLoadBehavior::NoLoad; - }; - - /// The class is used to store a MaterialLibraryAsset and a vector of MaterialIds selected from the library + /// The class is used to store a vector of MaterialIds selected from the library /// ======================================================================= /// - /// This class is used to store a reference to the library asset and user's - /// selection of the materials from this library.\n - /// It also reflects UI controls for assigning MaterialLibraryAsset and selecting a material from it. + /// This class is used to store the user's selection of the materials from this library. /// You can reflect this class in EditorContext to provide UI for selecting materials /// on any custom component or QWidget. class MaterialSelection @@ -269,27 +259,6 @@ namespace Physics static void Reflect(AZ::ReflectContext* context); - /// Returns whether MaterialLibraryAsset assigned to this selection exists and valid. Attempts to load - /// the library if it's not loaded yet. - /// @return true if MaterialLibraryAsset has a valid AssetId, loaded and isn't empty - bool IsMaterialLibraryValid() const; - - /// Looks up MaterialLibraryAsset for MaterialFromAssetConfiguration with MaterialId that is stored intrenally. - /// @param configuration contains material data if there is a material selected by user - /// and if it exists in the MaterialLibraryAsset - /// @param materialId MaterialId to retrieve MaterialFromAssetConfiguration for - /// @return true if lookup was successful. - bool GetMaterialConfiguration(Physics::MaterialFromAssetConfiguration& configuration, const Physics::MaterialId& materialId) const; - - /// Sets and loads MaterialLibraryAsset with specified AssetId. - /// It is used to construct MaterialSelection at runtime. - /// It is not a typical use case and mostly needed to convert legacy entities and auto-generate material libraries - /// @param assetId AssetId to create MaterialLibraryAsset with - void SetMaterialLibrary(const AZ::Data::AssetId& assetId); - - /// Sets the material library to none, this will cause to use the project-wide default material library - void ResetToDefaultMaterialLibrary(); - /// Sets an array of material slots to pick MaterialIds for. Having multiple slots is required for assigning multiple materials on a mesh /// or heightfield object. SlotsArray can be empty and in this case Default slot will be created. /// @param slots Array of names for slots. Can be empty, in this case Default slot will be created @@ -298,48 +267,34 @@ namespace Physics /// Returns a list of MaterialId that were assigned for each corresponding slot. const AZStd::vector& GetMaterialIdsAssignedToSlots() const; - /// Sets the MaterialId from MaterialLibraryAsset as the selected material at a specific slotIndex. - /// @param materialId MaterialId that user selected from the MaterialLibraryAsset - /// @param slotIndex index of the slot to set MaterialId for + /// Sets the MaterialId as the selected material at a specific slotIndex. + /// @param materialId MaterialId that user selected + /// @param slotIndex Index of the slot to set the MaterialId void SetMaterialId(const Physics::MaterialId& materialId, int slotIndex = 0); - /// Returns the material library asset id. - AZ::Data::AssetId GetMaterialLibraryAssetId() const; - /// Returns the material id assigned to this selection at a specific slotIndex. - /// @param slotIndex index of the slot to retrieve MaterialId for + /// @param slotIndex Index of the slot to retrieve the MaterialId Physics::MaterialId GetMaterialId(int slotIndex = 0) const; - /// Returns the material library asset. - const Physics::MaterialLibraryAsset* GetMaterialLibraryAssetData() const; - - /// Returns the material library asset hint(UI display string) - const AZStd::string& GetMaterialLibraryAssetHint() const; - /// Called when the material library has changed - void OnDefaultMaterialLibraryChanged(const AZ::Data::AssetId& defaultMaterialLibraryId); + void OnMaterialLibraryChanged(const AZ::Data::AssetId& defaultMaterialLibraryId); /// Set if the material slots are editable in the edit context void SetSlotsReadOnly(bool readOnly); private: - AZ::Data::Asset m_materialLibrary { AZ::Data::AssetLoadBehavior::NoLoad }; AZStd::vector m_materialIdsAssignedToSlots; SlotsArray m_materialSlots; bool m_slotsReadOnly = false; - const AZ::Data::Asset& GetMaterialLibraryAsset() const; - AZ::Data::Asset LoadAsset() const; - bool IsDefaultMaterialLibraryAsset() const; void SyncSelectionToMaterialLibrary(); - static const AZ::Data::Asset& GetDefaultMaterialLibrary(); - static const AZ::Data::AssetId& GetDefaultMaterialLibraryId(); + static const AZ::Data::Asset& GetMaterialLibrary(); + static const AZ::Data::AssetId& GetMaterialLibraryId(); bool AreMaterialSlotsReadOnly() const; // EditorContext callbacks - AZ::u32 OnMaterialLibraryChanged(); AZStd::string GetMaterialSlotLabel(int index); }; diff --git a/Code/Framework/AzFramework/AzFramework/Physics/MaterialBus.h b/Code/Framework/AzFramework/AzFramework/Physics/MaterialBus.h index edfa3096d3..a7e4869df1 100644 --- a/Code/Framework/AzFramework/AzFramework/Physics/MaterialBus.h +++ b/Code/Framework/AzFramework/AzFramework/Physics/MaterialBus.h @@ -25,21 +25,26 @@ namespace Physics static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; // Implemented by sole owner of materials, e.g. class MaterialManager in PhysX gem. - /// Get default material + /// Get default material. virtual AZStd::shared_ptr GetGenericDefaultMaterial() = 0; /// Returns weak pointers to physics materials. /// Connect to PhysicsMaterialNotifications::MaterialsReleased to be informed when material pointers are deleted by owner. virtual void GetMaterials(const MaterialSelection& materialSelection - , AZStd::vector>& outMaterials) = 0; + , AZStd::vector>& outMaterials) = 0; + + /// Returns a weak pointer to physics material with the given id. + virtual AZStd::shared_ptr GetMaterialById(Physics::MaterialId id) = 0; /// Returns a weak pointer to physics material with the given name. - virtual AZStd::weak_ptr GetMaterialByName(const AZStd::string& name) = 0; + virtual AZStd::shared_ptr GetMaterialByName(const AZStd::string& name) = 0; - /// Returns index of the first selected material in MaterialSelection's material library. - /// A MaterialSelection can contain multiple material selections. - /// Returned index is 0-based where 0 is the Default material, and materials from the material library are 1 and onwards. - virtual AZ::u32 GetFirstSelectedMaterialIndex(const MaterialSelection& materialSelection) = 0; + /// Updates the material selection from the physics asset or sets it to default if there's no asset provided. + /// @param shapeConfiguration The shape information that contains the physics asset. + /// @param materialSelection The material selection to update. + virtual void UpdateMaterialSelectionFromPhysicsAsset( + const ShapeConfiguration& shapeConfiguration, + MaterialSelection& materialSelection) = 0; }; using PhysicsMaterialRequestBus = AZ::EBus; diff --git a/Code/Framework/AzFramework/AzFramework/Physics/PhysicsSystem.h b/Code/Framework/AzFramework/AzFramework/Physics/PhysicsSystem.h index e3ed449046..36ae4dbecb 100644 --- a/Code/Framework/AzFramework/AzFramework/Physics/PhysicsSystem.h +++ b/Code/Framework/AzFramework/AzFramework/Physics/PhysicsSystem.h @@ -130,13 +130,6 @@ namespace AzPhysics //! @param forceReinitialization Flag to force a reinitialization of the physics system. Default false. virtual void UpdateConfiguration(const SystemConfiguration* newConfig, bool forceReinitialization = false) = 0; - //! Update the default material library. - //! @param materialLibrary The new material library asset to use. - virtual void UpdateDefaultMaterialLibrary(const AZ::Data::Asset& materialLibrary) = 0; - - //! Accessor to get the current Material Library. This is also available in the PhysXSystemConfiguration. - virtual const AZ::Data::Asset& GetDefaultMaterialLibrary() const = 0; - //! Update the current default scene configuration. //! This is the configuration used to to create scenes without a custom configuration. //! @param sceneConfiguration The new configuration to apply. @@ -169,9 +162,12 @@ namespace AzPhysics //! Register to receive notifications when the SystemConfiguration changes. //! @param handler The handler to receive the event. void RegisterSystemConfigurationChangedEvent(SystemEvents::OnConfigurationChangedEvent::Handler& handler) { handler.Connect(m_configChangeEvent); } - //! Register a handler to receive an event when the default material library changes. + //! Register a handler to receive an event when the material library changes. //! @param handler The handler to receive the event. - void RegisterOnDefaultMaterialLibraryChangedEventHandler(SystemEvents::OnDefaultMaterialLibraryChangedEvent::Handler& handler) { handler.Connect(m_onDefaultMaterialLibraryChangedEvent); } + void RegisterOnMaterialLibraryChangedEventHandler(SystemEvents::OnMaterialLibraryChangedEvent::Handler& handler) { handler.Connect(m_onMaterialLibraryChangedEvent); } + //! Register a handler to receive an event when the material library fails to load on startup. + //! @param handler The handler to receive the event. + void RegisterOnMaterialLibraryLoadErrorEventHandler(SystemEvents::OnMaterialLibraryLoadErrorEvent::Handler& handler) { handler.Connect(m_onMaterialLibraryLoadErrorEvent); } //! Register a handler to receive an event when the default SceneConfiguration changes. //! @param handler The handler to receive the event. void RegisterOnDefaultSceneConfigurationChangedEventHandler(SystemEvents::OnDefaultSceneConfigurationChangedEvent::Handler& handler) { handler.Connect(m_onDefaultSceneConfigurationChangedEvent); } @@ -185,7 +181,8 @@ namespace AzPhysics SystemEvents::OnSceneAddedEvent m_sceneAddedEvent; SystemEvents::OnSceneRemovedEvent m_sceneRemovedEvent; SystemEvents::OnConfigurationChangedEvent m_configChangeEvent; - SystemEvents::OnDefaultMaterialLibraryChangedEvent m_onDefaultMaterialLibraryChangedEvent; + SystemEvents::OnMaterialLibraryChangedEvent m_onMaterialLibraryChangedEvent; + SystemEvents::OnMaterialLibraryLoadErrorEvent m_onMaterialLibraryLoadErrorEvent; SystemEvents::OnDefaultSceneConfigurationChangedEvent m_onDefaultSceneConfigurationChangedEvent; }; } // namespace AzPhysics diff --git a/Code/Framework/AzFramework/AzFramework/Physics/ShapeConfiguration.cpp b/Code/Framework/AzFramework/AzFramework/Physics/ShapeConfiguration.cpp index a535f5f65d..275103bc28 100644 --- a/Code/Framework/AzFramework/AzFramework/Physics/ShapeConfiguration.cpp +++ b/Code/Framework/AzFramework/AzFramework/Physics/ShapeConfiguration.cpp @@ -17,6 +17,21 @@ namespace Physics { + namespace Internal + { + bool ShapeConfigurationVersionConverter( + [[maybe_unused]] AZ::SerializeContext& context, + AZ::SerializeContext::DataElementNode& classElement) + { + if (classElement.GetVersion() <= 1) + { + classElement.RemoveElementByName(AZ_CRC_CE("UseMaterialsFromAsset")); + } + + return true; + } + } + void ShapeConfiguration::Reflect(AZ::ReflectContext* context) { if (auto serializeContext = azrtti_cast(context)) @@ -166,10 +181,9 @@ namespace Physics ->RegisterGenericType>(); serializeContext->Class() - ->Version(1) + ->Version(2, &Internal::ShapeConfigurationVersionConverter) ->Field("PhysicsAsset", &PhysicsAssetShapeConfiguration::m_asset) ->Field("AssetScale", &PhysicsAssetShapeConfiguration::m_assetScale) - ->Field("UseMaterialsFromAsset", &PhysicsAssetShapeConfiguration::m_useMaterialsFromAsset) ->Field("SubdivisionLevel", &PhysicsAssetShapeConfiguration::m_subdivisionLevel) ; @@ -182,7 +196,6 @@ namespace Physics ->DataElement(AZ::Edit::UIHandlers::Default, &PhysicsAssetShapeConfiguration::m_assetScale, "Asset Scale", "The scale of the asset shape") ->Attribute(AZ::Edit::Attributes::Min, 0.0f) ->Attribute(AZ::Edit::Attributes::Step, 0.01f) - ->DataElement(AZ::Edit::UIHandlers::Default, &PhysicsAssetShapeConfiguration::m_useMaterialsFromAsset, "Physics Materials from Mesh", "Auto-set physics materials using Mesh's material surfaces names") ; } } diff --git a/Code/Framework/AzFramework/AzFramework/Physics/ShapeConfiguration.h b/Code/Framework/AzFramework/AzFramework/Physics/ShapeConfiguration.h index b3d04a10c9..8234ef9173 100644 --- a/Code/Framework/AzFramework/AzFramework/Physics/ShapeConfiguration.h +++ b/Code/Framework/AzFramework/AzFramework/Physics/ShapeConfiguration.h @@ -140,7 +140,7 @@ namespace Physics AZ::Data::Asset m_asset{ AZ::Data::AssetLoadBehavior::PreLoad }; AZ::Vector3 m_assetScale = AZ::Vector3::CreateOne(); - bool m_useMaterialsFromAsset = true; + bool m_useMaterialsFromAsset = false; // Not reflected or exposed to the user until there is a way to auto-match mesh's materials with physics materials AZ::u8 m_subdivisionLevel = 4; ///< The level of subdivision if a primitive shape is replaced with a convex mesh due to scaling. }; diff --git a/Code/Framework/AzFramework/AzFramework/Physics/SystemBus.h b/Code/Framework/AzFramework/AzFramework/Physics/SystemBus.h index f198551148..8cdd0e0cf0 100644 --- a/Code/Framework/AzFramework/AzFramework/Physics/SystemBus.h +++ b/Code/Framework/AzFramework/AzFramework/Physics/SystemBus.h @@ -142,24 +142,12 @@ namespace Physics virtual AZStd::shared_ptr CreateShape(const ColliderConfiguration& colliderConfiguration, const ShapeConfiguration& configuration) = 0; + virtual AZStd::shared_ptr CreateMaterial(const Physics::MaterialConfiguration& materialConfiguration) = 0; + /// Releases the mesh object created by the physics backend. /// @param nativeMeshObject Pointer to the mesh object. virtual void ReleaseNativeMeshObject(void* nativeMeshObject) = 0; - ////////////////////////////////////////////////////////////////////////// - //// Physics Materials - - virtual AZStd::shared_ptr CreateMaterial(const Physics::MaterialConfiguration& materialConfiguration) = 0; - virtual AZStd::shared_ptr GetDefaultMaterial() = 0; - virtual AZStd::vector> CreateMaterialsFromLibrary(const Physics::MaterialSelection& materialSelection) = 0; - - - /// Updates the collider material selection from the physics asset or sets it to default if there's no asset provided. - /// @param shapeConfiguration The shape information - /// @param colliderConfiguration The collider information - virtual bool UpdateMaterialSelection(const Physics::ShapeConfiguration& shapeConfiguration, - Physics::ColliderConfiguration& colliderConfiguration) = 0; - ////////////////////////////////////////////////////////////////////////// //// Joints diff --git a/Code/Framework/AzFramework/AzFramework/Physics/Utils.cpp b/Code/Framework/AzFramework/AzFramework/Physics/Utils.cpp index b5f113582b..2c3b62bb88 100644 --- a/Code/Framework/AzFramework/AzFramework/Physics/Utils.cpp +++ b/Code/Framework/AzFramework/AzFramework/Physics/Utils.cpp @@ -119,8 +119,7 @@ namespace Physics AzPhysics::SceneConfiguration::Reflect(context); MaterialConfiguration::Reflect(context); MaterialLibraryAsset::Reflect(context); - MaterialLibraryAssetReflectionWrapper::Reflect(context); - DefaultMaterialLibraryAssetReflectionWrapper::Reflect(context); + MaterialInfoReflectionWrapper::Reflect(context); JointLimitConfiguration::Reflect(context); AzPhysics::SimulatedBodyConfiguration::Reflect(context); AzPhysics::RigidBodyConfiguration::Reflect(context); diff --git a/Code/Framework/AzFramework/AzFramework/ProjectManager/ProjectManager.cpp b/Code/Framework/AzFramework/AzFramework/ProjectManager/ProjectManager.cpp index 2742b90f4c..22598595fc 100644 --- a/Code/Framework/AzFramework/AzFramework/ProjectManager/ProjectManager.cpp +++ b/Code/Framework/AzFramework/AzFramework/ProjectManager/ProjectManager.cpp @@ -78,7 +78,7 @@ namespace AzFramework::ProjectManager projectJsonPath.c_str()); } - if (LaunchProjectManager(engineRootPath)) + if (LaunchProjectManager()) { AZ_TracePrintf("ProjectManager", "Project Manager launched successfully, requesting exit."); return ProjectPathCheckResult::ProjectManagerLaunched; @@ -87,7 +87,7 @@ namespace AzFramework::ProjectManager return ProjectPathCheckResult::ProjectManagerLaunchFailed; } - bool LaunchProjectManager([[maybe_unused]] const AZ::IO::FixedMaxPath& engineRootPath) + bool LaunchProjectManager(const AZStd::string& commandLineArgs) { bool launchSuccess = false; #if (AZ_TRAIT_AZFRAMEWORK_USE_PROJECT_MANAGER) @@ -109,7 +109,7 @@ namespace AzFramework::ProjectManager } AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo; - processLaunchInfo.m_commandlineParameters = executablePath.String(); + processLaunchInfo.m_commandlineParameters = executablePath.String() + commandLineArgs; launchSuccess = AzFramework::ProcessLauncher::LaunchUnwatchedProcess(processLaunchInfo); } if (ownsSystemAllocator) diff --git a/Code/Framework/AzFramework/AzFramework/ProjectManager/ProjectManager.h b/Code/Framework/AzFramework/AzFramework/ProjectManager/ProjectManager.h index cc79bd4184..d0ef7172b0 100644 --- a/Code/Framework/AzFramework/AzFramework/ProjectManager/ProjectManager.h +++ b/Code/Framework/AzFramework/AzFramework/ProjectManager/ProjectManager.h @@ -12,6 +12,7 @@ #pragma once #include +#include namespace AzFramework::ProjectManager { @@ -21,8 +22,16 @@ namespace AzFramework::ProjectManager ProjectManagerLaunched = 0, ProjectPathFound = 1 }; - // Check for a project name, if not found, attempts to launch project manager and returns false + + //! Check for a project name, if not found, attempts to launch project manager and returns false + //! @param argc the number of arguments in argv + //! @param argv arguments provided to this executable + //! @return a ProjectPathCheckResult ProjectPathCheckResult CheckProjectPathProvided(const int argc, char* argv[]); - // Attempt to Launch the project manager. Requires locating the engine root, project manager script, and python. - bool LaunchProjectManager(const AZ::IO::FixedMaxPath& engineRootPath); + + //! Attempt to Launch the project manager, assuming the o3de executable exists in same folder as + //! current executable. Requires the o3de cli and python. + //! @param commandLineArgs additional command line arguments to provide to the project manager + //! @return true on success, false if failed to find or launch the executable + bool LaunchProjectManager(const AZStd::string& commandLineArgs = ""); } // AzFramework::ProjectManager diff --git a/Code/Framework/AzFramework/AzFramework/Render/GeometryIntersectionBus.h b/Code/Framework/AzFramework/AzFramework/Render/GeometryIntersectionBus.h index ba1d2d1e06..749f457286 100644 --- a/Code/Framework/AzFramework/AzFramework/Render/GeometryIntersectionBus.h +++ b/Code/Framework/AzFramework/AzFramework/Render/GeometryIntersectionBus.h @@ -12,6 +12,7 @@ #pragma once #include +#include #include namespace AzFramework @@ -35,12 +36,12 @@ namespace AzFramework AzFramework::EntityContextId m_contextId; }; - //! Interface for intersection requests, implement this interface for making your component - //! render geometry intersectable. + //! Interface for intersection requests. + //! Implement this interface to make your component 'intersectable'. class IntersectionRequests : public AZ::EBusTraits { - //! Policy for notifying the Intersector bus of entities connected/disconnected to this ebus + //! Policy for notifying the Intersector bus of entities connected/disconnected to this EBus //! so it updates the internal data of the entities template struct IntersectionRequestsConnectionPolicy diff --git a/Code/Framework/AzFramework/AzFramework/Session/ISessionHandlingRequests.h b/Code/Framework/AzFramework/AzFramework/Session/ISessionHandlingRequests.h index 47388c56c3..a0731626ef 100644 --- a/Code/Framework/AzFramework/AzFramework/Session/ISessionHandlingRequests.h +++ b/Code/Framework/AzFramework/AzFramework/Session/ISessionHandlingRequests.h @@ -12,6 +12,7 @@ #pragma once +#include #include namespace AzFramework @@ -49,13 +50,17 @@ namespace AzFramework class ISessionHandlingClientRequests { public: - // Handle the player join session process + AZ_RTTI(ISessionHandlingClientRequests, "{41DE6BD3-72BC-4443-BFF9-5B1B9396657A}"); + ISessionHandlingClientRequests() = default; + virtual ~ISessionHandlingClientRequests() = default; + + // Request the player join session // @param sessionConnectionConfig The required properties to handle the player join session process // @return The result of player join session process - virtual bool HandlePlayerJoinSession(const SessionConnectionConfig& sessionConnectionConfig) = 0; + virtual bool RequestPlayerJoinSession(const SessionConnectionConfig& sessionConnectionConfig) = 0; - // Handle the player leave session process - virtual void HandlePlayerLeaveSession() = 0; + // Request the connected player leave session + virtual void RequestPlayerLeaveSession() = 0; }; //! ISessionHandlingServerRequests @@ -63,6 +68,10 @@ namespace AzFramework class ISessionHandlingServerRequests { public: + AZ_RTTI(ISessionHandlingServerRequests, "{4F0C17BA-F470-4242-A8CB-EC7EA805257C}"); + ISessionHandlingServerRequests() = default; + virtual ~ISessionHandlingServerRequests() = default; + // Handle the destroy session process virtual void HandleDestroySession() = 0; @@ -74,5 +83,10 @@ namespace AzFramework // Handle the player leave session process // @param playerConnectionConfig The required properties to handle the player leave session process virtual void HandlePlayerLeaveSession(const PlayerConnectionConfig& playerConnectionConfig) = 0; + + // Retrieves the file location of a pem-encoded TLS certificate + // @return If successful, returns the file location of TLS certificate file; if not successful, returns + // empty string. + virtual AZStd::string GetSessionCertificate() = 0; }; } // namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/Session/ISessionRequests.h b/Code/Framework/AzFramework/AzFramework/Session/ISessionRequests.h index 9d21a7f282..da65eb47f0 100644 --- a/Code/Framework/AzFramework/AzFramework/Session/ISessionRequests.h +++ b/Code/Framework/AzFramework/AzFramework/Session/ISessionRequests.h @@ -167,6 +167,9 @@ namespace AzFramework : public AZ::EBusTraits { public: + // Safeguard handler for multi-threaded use case + using MutexType = AZStd::recursive_mutex; + ////////////////////////////////////////////////////////////////////////// // EBusTraits overrides static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple; diff --git a/Code/Framework/AzFramework/AzFramework/Session/SessionNotifications.h b/Code/Framework/AzFramework/AzFramework/Session/SessionNotifications.h index a61c995db7..c472fcb228 100644 --- a/Code/Framework/AzFramework/AzFramework/Session/SessionNotifications.h +++ b/Code/Framework/AzFramework/AzFramework/Session/SessionNotifications.h @@ -24,6 +24,9 @@ namespace AzFramework : public AZ::EBusTraits { public: + // Safeguard handler for multi-threaded use case + using MutexType = AZStd::recursive_mutex; + ////////////////////////////////////////////////////////////////////////// // EBusTraits overrides static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple; diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/Spawnable.cpp b/Code/Framework/AzFramework/AzFramework/Spawnable/Spawnable.cpp index 7ab2d48814..46b3dfe87c 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/Spawnable.cpp +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/Spawnable.cpp @@ -21,22 +21,6 @@ namespace AzFramework { } - Spawnable::Spawnable(Spawnable&& other) - : m_entities(AZStd::move(other.m_entities)) - { - } - - - Spawnable& Spawnable::operator=(Spawnable&& other) - { - if (this != &other) - { - m_entities = AZStd::move(other.m_entities); - } - - return *this; - } - const Spawnable::EntityList& Spawnable::GetEntities() const { return m_entities; diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/Spawnable.h b/Code/Framework/AzFramework/AzFramework/Spawnable/Spawnable.h index 79cea647e4..677f0326cf 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/Spawnable.h +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/Spawnable.h @@ -41,11 +41,11 @@ namespace AzFramework Spawnable() = default; explicit Spawnable(const AZ::Data::AssetId& id, AssetStatus status = AssetStatus::NotLoaded); Spawnable(const Spawnable& rhs) = delete; - Spawnable(Spawnable&& other); + Spawnable(Spawnable&& other) = delete; ~Spawnable() override = default; Spawnable& operator=(const Spawnable& rhs) = delete; - Spawnable& operator=(Spawnable&& other); + Spawnable& operator=(Spawnable&& other) = delete; const EntityList& GetEntities() const; EntityList& GetEntities(); diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableAssetHandler.cpp b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableAssetHandler.cpp index b3ba1568bd..da046ff172 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableAssetHandler.cpp +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableAssetHandler.cpp @@ -10,6 +10,7 @@ * */ +#include #include #include #include @@ -88,4 +89,10 @@ namespace AzFramework { extensions.push_back(Spawnable::FileExtension); } + + uint32_t SpawnableAssetHandler::BuildSubId(AZStd::string_view id) + { + AZ::Uuid subIdHash = AZ::Uuid::CreateData(id.data(), id.size()); + return azlossy_caster(subIdHash.GetHash()); + } } // namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableAssetHandler.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableAssetHandler.h index deef314955..78268bf71a 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableAssetHandler.h +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableAssetHandler.h @@ -47,6 +47,7 @@ namespace AzFramework const char* GetGroup() const override; const char* GetBrowserIcon() const override; void GetAssetTypeExtensions(AZStd::vector& extensions) override; + static uint32_t BuildSubId(AZStd::string_view id); protected: LoadResult LoadAssetData( diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesContainer.cpp b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesContainer.cpp index 673701cac4..808de74e71 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesContainer.cpp +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesContainer.cpp @@ -38,19 +38,20 @@ namespace AzFramework void SpawnableEntitiesContainer::SpawnAllEntities() { AZ_Assert(m_threadData, "Calling SpawnAllEntities on a Spawnable container that's not set."); - SpawnableEntitiesInterface::Get()->SpawnAllEntities(m_threadData->m_spawnedEntitiesTicket); + SpawnableEntitiesInterface::Get()->SpawnAllEntities(m_threadData->m_spawnedEntitiesTicket, SpawnablePriority_Default); } void SpawnableEntitiesContainer::SpawnEntities(AZStd::vector entityIndices) { AZ_Assert(m_threadData, "Calling SpawnEntities on a Spawnable container that's not set."); - SpawnableEntitiesInterface::Get()->SpawnEntities(m_threadData->m_spawnedEntitiesTicket, AZStd::move(entityIndices)); + SpawnableEntitiesInterface::Get()->SpawnEntities( + m_threadData->m_spawnedEntitiesTicket, SpawnablePriority_Default, AZStd::move(entityIndices)); } void SpawnableEntitiesContainer::DespawnAllEntities() { AZ_Assert(m_threadData, "Calling DespawnEntities on a Spawnable container that's not set."); - SpawnableEntitiesInterface::Get()->DespawnAllEntities(m_threadData->m_spawnedEntitiesTicket); + SpawnableEntitiesInterface::Get()->DespawnAllEntities(m_threadData->m_spawnedEntitiesTicket, SpawnablePriority_Default); } void SpawnableEntitiesContainer::Reset(AZ::Data::Asset spawnable) @@ -66,8 +67,10 @@ namespace AzFramework m_monitor.Disconnect(); m_monitor.m_threadData.reset(); - SpawnableEntitiesInterface::Get()->Barrier(m_threadData->m_spawnedEntitiesTicket, - [threadData = m_threadData](EntitySpawnTicket&) mutable + SpawnableEntitiesInterface::Get()->Barrier( + m_threadData->m_spawnedEntitiesTicket, + SpawnablePriority_Default, + [threadData = m_threadData](EntitySpawnTicket::Id) mutable { threadData.reset(); }); @@ -83,8 +86,10 @@ namespace AzFramework void SpawnableEntitiesContainer::Alert(AlertCallback callback) { AZ_Assert(m_threadData, "Calling DespawnEntities on a Spawnable container that's not set."); - SpawnableEntitiesInterface::Get()->Barrier(m_threadData->m_spawnedEntitiesTicket, - [generation = m_threadData->m_generation, callback = AZStd::move(callback)](EntitySpawnTicket&) + SpawnableEntitiesInterface::Get()->Barrier( + m_threadData->m_spawnedEntitiesTicket, + SpawnablePriority_Default, + [generation = m_threadData->m_generation, callback = AZStd::move(callback)](EntitySpawnTicket::Id) { callback(generation); }); @@ -110,6 +115,7 @@ namespace AzFramework AZ_Assert(m_threadData, "SpawnableEntitiesContainer is monitoring a spawnable, but doesn't have the associated data."); AZ_TracePrintf("Spawnables", "Reloading spawnable '%s'.\n", replacementAsset.GetHint().c_str()); - SpawnableEntitiesInterface::Get()->ReloadSpawnable(m_threadData->m_spawnedEntitiesTicket, AZStd::move(replacementAsset)); + SpawnableEntitiesInterface::Get()->ReloadSpawnable( + m_threadData->m_spawnedEntitiesTicket, SpawnablePriority_Default, AZStd::move(replacementAsset)); } } // namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.cpp b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.cpp index 97169ebeb3..ad1bf032c8 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.cpp +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.cpp @@ -239,7 +239,9 @@ namespace AzFramework { auto manager = SpawnableEntitiesInterface::Get(); AZ_Assert(manager, "Attempting to create an entity spawn ticket while the SpawnableEntitiesInterface has no implementation."); - m_payload = manager->CreateTicket(AZStd::move(spawnable)); + AZStd::pair result = manager->CreateTicket(AZStd::move(spawnable)); + m_id = result.first; + m_payload = result.second; } EntitySpawnTicket::~EntitySpawnTicket() @@ -250,6 +252,7 @@ namespace AzFramework AZ_Assert(manager, "Attempting to destroy an entity spawn ticket while the SpawnableEntitiesInterface has no implementation."); manager->DestroyTicket(m_payload); m_payload = nullptr; + m_id = 0; } } @@ -263,12 +266,20 @@ namespace AzFramework AZ_Assert(manager, "Attempting to destroy an entity spawn ticket while the SpawnableEntitiesInterface has no implementation."); manager->DestroyTicket(m_payload); } + m_id = rhs.m_id; + rhs.m_id = 0; + m_payload = rhs.m_payload; rhs.m_payload = nullptr; } return *this; } + auto EntitySpawnTicket::GetId() const -> Id + { + return m_id; + } + bool EntitySpawnTicket::IsValid() const { return m_payload != nullptr; diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h index 69bca8e111..27f45064b6 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h @@ -14,6 +14,7 @@ #include #include +#include #include #include @@ -24,6 +25,14 @@ namespace AZ namespace AzFramework { + AZ_TYPE_SAFE_INTEGRAL(SpawnablePriority, uint8_t); + + inline static constexpr SpawnablePriority SpawnablePriority_Highest { 0 }; + inline static constexpr SpawnablePriority SpawnablePriority_High { 32 }; + inline static constexpr SpawnablePriority SpawnablePriority_Default { 128 }; + inline static constexpr SpawnablePriority SpawnablePriority_Low { 192 }; + inline static constexpr SpawnablePriority SpawnablePriority_Lowest { 255 }; + class SpawnableEntityContainerView { public: @@ -124,16 +133,18 @@ namespace AzFramework SpawnableIndexEntityIterator m_end; }; - //! Requests to the SpawnableEntitiesInterface require a ticket with a valid spawnable that be used as a template. A ticket can - //! be reused for multiple calls on the same spawnable and is safe to use by multiple threads at the same time. Entities created + //! Requests to the SpawnableEntitiesInterface require a ticket with a valid spawnable that is used as a template. A ticket can + //! be reused for multiple calls on the same spawnable and is safe to be used by multiple threads at the same time. Entities created //! from the spawnable may be tracked by the ticket and so using the same ticket is needed to despawn the exact entities created - //! by a call so spawn entities. The life cycle of the spawned entities is tied to the ticket and all entities spawned using a + //! by a call to spawn entities. The life cycle of the spawned entities is tied to the ticket and all entities spawned using a //! ticket will be despawned when it's deleted. class EntitySpawnTicket { public: friend class SpawnableEntitiesDefinition; + using Id = uint64_t; + EntitySpawnTicket() = default; EntitySpawnTicket(const EntitySpawnTicket&) = delete; EntitySpawnTicket(EntitySpawnTicket&& rhs); @@ -143,26 +154,37 @@ namespace AzFramework EntitySpawnTicket& operator=(const EntitySpawnTicket&) = delete; EntitySpawnTicket& operator=(EntitySpawnTicket&& rhs); + Id GetId() const; bool IsValid() const; private: void* m_payload{ nullptr }; + Id m_id { 0 }; //!< An id that uniquely identifies a ticket. }; - using EntitySpawnCallback = AZStd::function; - using EntityPreInsertionCallback = AZStd::function; - using EntityDespawnCallback = AZStd::function; - using ReloadSpawnableCallback = AZStd::function; - using ListEntitiesCallback = AZStd::function; - using ListIndicesEntitiesCallback = AZStd::function; - using ClaimEntitiesCallback = AZStd::function; - using BarrierCallback = AZStd::function; + using EntitySpawnCallback = AZStd::function; + using EntityPreInsertionCallback = AZStd::function; + using EntityDespawnCallback = AZStd::function; + using ReloadSpawnableCallback = AZStd::function; + using ListEntitiesCallback = AZStd::function; + using ListIndicesEntitiesCallback = AZStd::function; + using ClaimEntitiesCallback = AZStd::function; + using BarrierCallback = AZStd::function; //! Interface definition to (de)spawn entities from a spawnable into the game world. + //! //! While the callbacks of the individual calls are being processed they will block processing any other request. Callbacks can be //! issued from threads other than the one that issued the call, including the main thread. + //! //! Calls on the same ticket are guaranteed to be executed in the order they are issued. Note that when issuing requests from //! multiple threads on the same ticket the order in which the requests are assigned to the ticket is not guaranteed. + //! + //! Most calls have a priority with values that range from 0 (highest priority) to 255 (lowest priority). The implementation of this + //! interface may choose to use priority lanes which doesn't guarantee that higher priority requests happen before lower priority + //! requests if they don't pass the priority lane threshold. Priority lanes and their thresholds are implementation specific and may + //! differ between platforms. Note that if a call happened on a ticket with lower priority followed by a one with a higher priority + //! the first lower priority call will still need to complete before the second higher priority call can be executed and the priority + //! of the first call will not be updated. class SpawnableEntitiesDefinition { public: @@ -173,40 +195,48 @@ namespace AzFramework virtual ~SpawnableEntitiesDefinition() = default; //! Spawn instances of all entities in the spawnable. - //! @param spawnable The Spawnable asset that will be used to create entity instances from. //! @param ticket Stores the results of the call. Use this ticket to spawn additional entities or to despawn them. + //! @param priority The priority at which this call will be executed. //! @param completionCallback Optional callback that's called when spawning entities has completed. This can be called from //! a different thread than the one that made the function call. The returned list of entities contains all the newly //! created entities. - virtual void SpawnAllEntities(EntitySpawnTicket& ticket, EntityPreInsertionCallback preInsertionCallback = {}, + virtual void SpawnAllEntities( + EntitySpawnTicket& ticket, SpawnablePriority priority, EntityPreInsertionCallback preInsertionCallback = {}, EntitySpawnCallback completionCallback = {}) = 0; //! Spawn instances of some entities in the spawnable. //! @param ticket Stores the results of the call. Use this ticket to spawn additional entities or to despawn them. + //! @param priority The priority at which this call will be executed. //! @param entityIndices The indices into the template entities stored in the spawnable that will be used to spawn entities from. //! @param completionCallback Optional callback that's called when spawning entities has completed. This can be called from //! a different thread than the one that made this function call. The returned list of entities contains all the newly //! created entities. - virtual void SpawnEntities(EntitySpawnTicket& ticket, AZStd::vector entityIndices, + virtual void SpawnEntities( + EntitySpawnTicket& ticket, SpawnablePriority priority, AZStd::vector entityIndices, EntityPreInsertionCallback preInsertionCallback = {}, EntitySpawnCallback completionCallback = {}) = 0; //! Removes all entities in the provided list from the environment. //! @param ticket The ticket previously used to spawn entities with. + //! @param priority The priority at which this call will be executed. //! @param completionCallback Optional callback that's called when despawning entities has completed. This can be called from //! a different thread than the one that made this function call. - virtual void DespawnAllEntities(EntitySpawnTicket& ticket, EntityDespawnCallback completionCallback = {}) = 0; + virtual void DespawnAllEntities( + EntitySpawnTicket& ticket, SpawnablePriority priority, EntityDespawnCallback completionCallback = {}) = 0; //! Removes all entities in the provided list from the environment and reconstructs the entities from the provided spawnable. - //! @param ticket Stores the results of the call. Use this ticket to spawn additional entities or to despawn them. + //! @param ticket Holds the information on the entities to reload. + //! @param priority The priority at which this call will be executed. //! @param spawnable The spawnable that will replace the existing spawnable. Both need to have the same asset id. //! @param completionCallback Optional callback that's called when the entities have been reloaded. This can be called from //! a different thread than the one that made this function call. The returned list of entities contains all the replacement //! entities. - virtual void ReloadSpawnable(EntitySpawnTicket& ticket, AZ::Data::Asset spawnable, + virtual void ReloadSpawnable( + EntitySpawnTicket& ticket, SpawnablePriority priority, AZ::Data::Asset spawnable, ReloadSpawnableCallback completionCallback = {}) = 0; //! List all entities that are spawned using this ticket. //! @param ticket Only the entities associated with this ticket will be listed. + //! @param priority The priority at which this call will be executed. //! @param listCallback Required callback that will be called to list the entities on. - virtual void ListEntities(EntitySpawnTicket& ticket, ListEntitiesCallback listCallback) = 0; + virtual void ListEntities(EntitySpawnTicket& ticket, SpawnablePriority priority, ListEntitiesCallback listCallback) = 0; //! List all entities that are spawned using this ticket with their spawnable index. //! Spawnables contain a flat list of entities, which are used as templates to spawn entities from. For every spawned entity //! the index of the entity in the spawnable that was used as a template is stored. This version of ListEntities will return @@ -214,17 +244,23 @@ namespace AzFramework //! the same index may appear multiple times as there are no restriction on how many instance of a specific entity can be //! created. //! @param ticket Only the entities associated with this ticket will be listed. + //! @param priority The priority at which this call will be executed. //! @param listCallback Required callback that will be called to list the entities and indices on. - virtual void ListIndicesAndEntities(EntitySpawnTicket& ticket, ListIndicesEntitiesCallback listCallback) = 0; + virtual void ListIndicesAndEntities( + EntitySpawnTicket& ticket, SpawnablePriority priority, ListIndicesEntitiesCallback listCallback) = 0; //! Claim all entities that are spawned using this ticket. Ownership of the entities is transferred from the ticket to the //! caller through the callback. After this call the ticket will have no entities associated with it. The caller of //! this function will need to manage the entities after this call. //! @param ticket Only the entities associated with this ticket will be released. + //! @param priority The priority at which this call will be executed. //! @param listCallback Required callback that will be called to transfer the entities through. - virtual void ClaimEntities(EntitySpawnTicket& ticket, ClaimEntitiesCallback listCallback) = 0; + virtual void ClaimEntities(EntitySpawnTicket& ticket, SpawnablePriority priority, ClaimEntitiesCallback listCallback) = 0; //! Blocks until all operations made on the provided ticket before the barrier call have completed. - virtual void Barrier(EntitySpawnTicket& ticket, BarrierCallback completionCallback) = 0; + //! @param ticket The ticket to monitor. + //! @param priority The priority at which this call will be executed. + //! @param completionCallback Required callback that will be called as soon as the barrier has been reached. + virtual void Barrier(EntitySpawnTicket& ticket, SpawnablePriority priority, BarrierCallback completionCallback) = 0; //! Register a handler for OnSpawned events. virtual void AddOnSpawnedHandler(AZ::Event>::Handler& handler) = 0; @@ -233,7 +269,7 @@ namespace AzFramework virtual void AddOnDespawnedHandler(AZ::Event>::Handler& handler) = 0; protected: - [[nodiscard]] virtual void* CreateTicket(AZ::Data::Asset&& spawnable) = 0; + [[nodiscard]] virtual AZStd::pair CreateTicket(AZ::Data::Asset&& spawnable) = 0; virtual void DestroyTicket(void* ticket) = 0; template diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp index 7e20f7b265..7b767d2a72 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp @@ -10,9 +10,11 @@ * */ +#include #include #include #include +#include #include #include #include @@ -22,128 +24,122 @@ namespace AzFramework { - void SpawnableEntitiesManager::SpawnAllEntities(EntitySpawnTicket& ticket, EntityPreInsertionCallback preInsertionCallback, + template + void SpawnableEntitiesManager::QueueRequest(EntitySpawnTicket& ticket, SpawnablePriority priority, T&& request) + { + request.m_ticket = &GetTicketPayload(ticket); + Queue& queue = priority <= m_highPriorityThreshold ? m_highPriorityQueue : m_regularPriorityQueue; + { + AZStd::scoped_lock queueLock(queue.m_pendingRequestMutex); + request.m_requestId = GetTicketPayload(ticket).m_nextRequestId++; + queue.m_pendingRequest.push(AZStd::move(request)); + } + } + + SpawnableEntitiesManager::SpawnableEntitiesManager() + { + if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr) + { + AZ::u64 value = aznumeric_caster(m_highPriorityThreshold); + settingsRegistry->Get(value, "/O3DE/AzFramework/Spawnables/HighPriorityThreshold"); + m_highPriorityThreshold = aznumeric_cast(AZStd::clamp(value, 0llu, 255llu)); + } + } + + void SpawnableEntitiesManager::SpawnAllEntities( + EntitySpawnTicket& ticket, SpawnablePriority priority, EntityPreInsertionCallback preInsertionCallback, EntitySpawnCallback completionCallback) { AZ_Assert(ticket.IsValid(), "Ticket provided to SpawnAllEntities hasn't been initialized."); SpawnAllEntitiesCommand queueEntry; - queueEntry.m_ticket = &ticket; + queueEntry.m_ticketId = ticket.GetId(); queueEntry.m_completionCallback = AZStd::move(completionCallback); queueEntry.m_preInsertionCallback = AZStd::move(preInsertionCallback); - { - AZStd::scoped_lock queueLock(m_pendingRequestQueueMutex); - queueEntry.m_ticketId = GetTicketPayload(ticket).m_nextTicketId++; - m_pendingRequestQueue.push(AZStd::move(queueEntry)); - } + QueueRequest(ticket, priority, AZStd::move(queueEntry)); } void SpawnableEntitiesManager::SpawnEntities( - EntitySpawnTicket& ticket, AZStd::vector entityIndices, + EntitySpawnTicket& ticket, SpawnablePriority priority, AZStd::vector entityIndices, EntityPreInsertionCallback preInsertionCallback, EntitySpawnCallback completionCallback) { AZ_Assert(ticket.IsValid(), "Ticket provided to SpawnEntities hasn't been initialized."); SpawnEntitiesCommand queueEntry; - queueEntry.m_ticket = &ticket; + queueEntry.m_ticketId = ticket.GetId(); queueEntry.m_entityIndices = AZStd::move(entityIndices); queueEntry.m_completionCallback = AZStd::move(completionCallback); queueEntry.m_preInsertionCallback = AZStd::move(preInsertionCallback); - { - AZStd::scoped_lock queueLock(m_pendingRequestQueueMutex); - queueEntry.m_ticketId = GetTicketPayload(ticket).m_nextTicketId++; - m_pendingRequestQueue.push(AZStd::move(queueEntry)); - } + QueueRequest(ticket, priority, AZStd::move(queueEntry)); } - void SpawnableEntitiesManager::DespawnAllEntities(EntitySpawnTicket& ticket, EntityDespawnCallback completionCallback) + void SpawnableEntitiesManager::DespawnAllEntities( + EntitySpawnTicket& ticket, SpawnablePriority priority, EntityDespawnCallback completionCallback) { AZ_Assert(ticket.IsValid(), "Ticket provided to DespawnAllEntities hasn't been initialized."); DespawnAllEntitiesCommand queueEntry; - queueEntry.m_ticket = &ticket; + queueEntry.m_ticketId = ticket.GetId(); queueEntry.m_completionCallback = AZStd::move(completionCallback); - { - AZStd::scoped_lock queueLock(m_pendingRequestQueueMutex); - queueEntry.m_ticketId = GetTicketPayload(ticket).m_nextTicketId++; - m_pendingRequestQueue.push(AZStd::move(queueEntry)); - } + QueueRequest(ticket, priority, AZStd::move(queueEntry)); } - void SpawnableEntitiesManager::ReloadSpawnable(EntitySpawnTicket& ticket, AZ::Data::Asset spawnable, + void SpawnableEntitiesManager::ReloadSpawnable( + EntitySpawnTicket& ticket, SpawnablePriority priority, AZ::Data::Asset spawnable, ReloadSpawnableCallback completionCallback) { AZ_Assert(ticket.IsValid(), "Ticket provided to ReloadSpawnable hasn't been initialized."); ReloadSpawnableCommand queueEntry; - queueEntry.m_ticket = &ticket; + queueEntry.m_ticketId = ticket.GetId(); queueEntry.m_spawnable = AZStd::move(spawnable); queueEntry.m_completionCallback = AZStd::move(completionCallback); - { - AZStd::scoped_lock queueLock(m_pendingRequestQueueMutex); - queueEntry.m_ticketId = GetTicketPayload(ticket).m_nextTicketId++; - m_pendingRequestQueue.push(AZStd::move(queueEntry)); - } + QueueRequest(ticket, priority, AZStd::move(queueEntry)); } - void SpawnableEntitiesManager::ListEntities(EntitySpawnTicket& ticket, ListEntitiesCallback listCallback) + void SpawnableEntitiesManager::ListEntities(EntitySpawnTicket& ticket, SpawnablePriority priority, ListEntitiesCallback listCallback) { AZ_Assert(listCallback, "ListEntities called on spawnable entities without a valid callback to use."); AZ_Assert(ticket.IsValid(), "Ticket provided to ListEntities hasn't been initialized."); ListEntitiesCommand queueEntry; - queueEntry.m_ticket = &ticket; + queueEntry.m_ticketId = ticket.GetId(); queueEntry.m_listCallback = AZStd::move(listCallback); - { - AZStd::scoped_lock queueLock(m_pendingRequestQueueMutex); - queueEntry.m_ticketId = GetTicketPayload(ticket).m_nextTicketId++; - m_pendingRequestQueue.push(AZStd::move(queueEntry)); - } + QueueRequest(ticket, priority, AZStd::move(queueEntry)); } - void SpawnableEntitiesManager::ListIndicesAndEntities(EntitySpawnTicket& ticket, ListIndicesEntitiesCallback listCallback) + void SpawnableEntitiesManager::ListIndicesAndEntities( + EntitySpawnTicket& ticket, SpawnablePriority priority, ListIndicesEntitiesCallback listCallback) { AZ_Assert(listCallback, "ListEntities called on spawnable entities without a valid callback to use."); AZ_Assert(ticket.IsValid(), "Ticket provided to ListEntities hasn't been initialized."); ListIndicesEntitiesCommand queueEntry; - queueEntry.m_ticket = &ticket; + queueEntry.m_ticketId = ticket.GetId(); queueEntry.m_listCallback = AZStd::move(listCallback); - { - AZStd::scoped_lock queueLock(m_pendingRequestQueueMutex); - queueEntry.m_ticketId = GetTicketPayload(ticket).m_nextTicketId++; - m_pendingRequestQueue.push(AZStd::move(queueEntry)); - } + QueueRequest(ticket, priority, AZStd::move(queueEntry)); } - void SpawnableEntitiesManager::ClaimEntities(EntitySpawnTicket& ticket, ClaimEntitiesCallback listCallback) + void SpawnableEntitiesManager::ClaimEntities(EntitySpawnTicket& ticket, SpawnablePriority priority, ClaimEntitiesCallback listCallback) { AZ_Assert(listCallback, "ClaimEntities called on spawnable entities without a valid callback to use."); AZ_Assert(ticket.IsValid(), "Ticket provided to ClaimEntities hasn't been initialized."); ClaimEntitiesCommand queueEntry; - queueEntry.m_ticket = &ticket; + queueEntry.m_ticketId = ticket.GetId(); queueEntry.m_listCallback = AZStd::move(listCallback); - { - AZStd::scoped_lock queueLock(m_pendingRequestQueueMutex); - queueEntry.m_ticketId = GetTicketPayload(ticket).m_nextTicketId++; - m_pendingRequestQueue.push(AZStd::move(queueEntry)); - } + QueueRequest(ticket, priority, AZStd::move(queueEntry)); } - void SpawnableEntitiesManager::Barrier(EntitySpawnTicket& ticket, BarrierCallback completionCallback) + void SpawnableEntitiesManager::Barrier(EntitySpawnTicket& ticket, SpawnablePriority priority, BarrierCallback completionCallback) { AZ_Assert(completionCallback, "Barrier on spawnable entities called without a valid callback to use."); AZ_Assert(ticket.IsValid(), "Ticket provided to Barrier hasn't been initialized."); BarrierCommand queueEntry; - queueEntry.m_ticket = &ticket; + queueEntry.m_ticketId = ticket.GetId(); queueEntry.m_completionCallback = AZStd::move(completionCallback); - { - AZStd::scoped_lock queueLock(m_pendingRequestQueueMutex); - queueEntry.m_ticketId = GetTicketPayload(ticket).m_nextTicketId++; - m_pendingRequestQueue.push(AZStd::move(queueEntry)); - } + QueueRequest(ticket, priority, AZStd::move(queueEntry)); } void SpawnableEntitiesManager::AddOnSpawnedHandler(AZ::Event>::Handler& handler) @@ -156,34 +152,54 @@ namespace AzFramework handler.Connect(m_onDespawnedEvent); } - auto SpawnableEntitiesManager::ProcessQueue() -> CommandQueueStatus + auto SpawnableEntitiesManager::ProcessQueue(CommandQueuePriority priority) -> CommandQueueStatus + { + CommandQueueStatus result = CommandQueueStatus::NoCommandsLeft; + if ((priority & CommandQueuePriority::High) == CommandQueuePriority::High) + { + if (ProcessQueue(m_highPriorityQueue) == CommandQueueStatus::HasCommandsLeft) + { + result = CommandQueueStatus::HasCommandsLeft; + } + } + if ((priority & CommandQueuePriority::Regular) == CommandQueuePriority::Regular) + { + if (ProcessQueue(m_regularPriorityQueue) == CommandQueueStatus::HasCommandsLeft) + { + result = CommandQueueStatus::HasCommandsLeft; + } + } + return result; + } + + auto SpawnableEntitiesManager::ProcessQueue(Queue& queue) -> CommandQueueStatus { AZStd::queue pendingRequestQueue; { - AZStd::scoped_lock queueLock(m_pendingRequestQueueMutex); - m_pendingRequestQueue.swap(pendingRequestQueue); + AZStd::scoped_lock queueLock(queue.m_pendingRequestMutex); + queue.m_pendingRequest.swap(pendingRequestQueue); } - if (!pendingRequestQueue.empty() || !m_delayedQueue.empty()) + if (!pendingRequestQueue.empty() || !queue.m_delayed.empty()) { AZ::SerializeContext* serializeContext = nullptr; AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext); AZ_Assert(serializeContext, "Failed to retrieve serialization context."); // Only process the requests that are currently in this queue, not the ones that could be re-added if they still can't complete. - size_t delayedSize = m_delayedQueue.size(); + size_t delayedSize = queue.m_delayed.size(); for (size_t i = 0; i < delayedSize; ++i) { - Requests& request = m_delayedQueue.front(); + Requests& request = queue.m_delayed.front(); bool result = AZStd::visit([this, serializeContext](auto&& args) -> bool { return ProcessRequest(args, *serializeContext); }, request); if (!result) { - m_delayedQueue.emplace_back(AZStd::move(request)); + queue.m_delayed.emplace_back(AZStd::move(request)); } - m_delayedQueue.pop_front(); + queue.m_delayed.pop_front(); } do @@ -197,7 +213,7 @@ namespace AzFramework }, request); if (!result) { - m_delayedQueue.emplace_back(AZStd::move(request)); + queue.m_delayed.emplace_back(AZStd::move(request)); } pendingRequestQueue.pop(); } @@ -205,20 +221,22 @@ namespace AzFramework // Spawning entities can result in more entities being queued to spawn. Repeat spawning until the queue is // empty to avoid a chain of entity spawning getting dragged out over multiple frames. { - AZStd::scoped_lock queueLock(m_pendingRequestQueueMutex); - m_pendingRequestQueue.swap(pendingRequestQueue); + AZStd::scoped_lock queueLock(queue.m_pendingRequestMutex); + queue.m_pendingRequest.swap(pendingRequestQueue); } } while (!pendingRequestQueue.empty()); } - return m_delayedQueue.empty() ? CommandQueueStatus::NoCommandLeft : CommandQueueStatus::HasCommandsLeft; + return queue.m_delayed.empty() ? CommandQueueStatus::NoCommandsLeft : CommandQueueStatus::HasCommandsLeft; } - void* SpawnableEntitiesManager::CreateTicket(AZ::Data::Asset&& spawnable) + AZStd::pair SpawnableEntitiesManager::CreateTicket(AZ::Data::Asset&& spawnable) { + static AZStd::atomic_uint64_t idCounter { 1 }; + auto result = aznew Ticket(); result->m_spawnable = AZStd::move(spawnable); - return result; + return AZStd::make_pair(idCounter++, result); } void SpawnableEntitiesManager::DestroyTicket(void* ticket) @@ -226,9 +244,9 @@ namespace AzFramework DestroyTicketCommand queueEntry; queueEntry.m_ticket = reinterpret_cast(ticket); { - AZStd::scoped_lock queueLock(m_pendingRequestQueueMutex); - queueEntry.m_ticketId = reinterpret_cast(ticket)->m_nextTicketId++; - m_pendingRequestQueue.push(AZStd::move(queueEntry)); + AZStd::scoped_lock queueLock(m_regularPriorityQueue.m_pendingRequestMutex); + queueEntry.m_requestId = reinterpret_cast(ticket)->m_nextRequestId++; + m_regularPriorityQueue.m_pendingRequest.push(AZStd::move(queueEntry)); } } @@ -251,8 +269,8 @@ namespace AzFramework bool SpawnableEntitiesManager::ProcessRequest(SpawnAllEntitiesCommand& request, AZ::SerializeContext& serializeContext) { - Ticket& ticket = GetTicketPayload(*request.m_ticket); - if (ticket.m_spawnable.IsReady() && request.m_ticketId == ticket.m_currentTicketId) + Ticket& ticket = *request.m_ticket; + if (ticket.m_spawnable.IsReady() && request.m_requestId == ticket.m_currentRequestId) { AZStd::vector& spawnedEntities = ticket.m_spawnedEntities; AZStd::vector& spawnedEntityIndices = ticket.m_spawnedEntityIndices; @@ -300,7 +318,7 @@ namespace AzFramework // Let other systems know about newly spawned entities for any pre-processing before adding to the scene/game context. if (request.m_preInsertionCallback) { - request.m_preInsertionCallback(*request.m_ticket, SpawnableEntityContainerView( + request.m_preInsertionCallback(request.m_ticketId, SpawnableEntityContainerView( ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end())); } @@ -314,13 +332,13 @@ namespace AzFramework // Let other systems know about newly spawned entities for any post-processing after adding to the scene/game context. if (request.m_completionCallback) { - request.m_completionCallback(*request.m_ticket, SpawnableConstEntityContainerView( + request.m_completionCallback(request.m_ticketId, SpawnableConstEntityContainerView( ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end())); } m_onSpawnedEvent.Signal(ticket.m_spawnable); - ticket.m_currentTicketId++; + ticket.m_currentRequestId++; return true; } else @@ -331,8 +349,8 @@ namespace AzFramework bool SpawnableEntitiesManager::ProcessRequest(SpawnEntitiesCommand& request, AZ::SerializeContext& serializeContext) { - Ticket& ticket = GetTicketPayload(*request.m_ticket); - if (ticket.m_spawnable.IsReady() && request.m_ticketId == ticket.m_currentTicketId) + Ticket& ticket = *request.m_ticket; + if (ticket.m_spawnable.IsReady() && request.m_requestId == ticket.m_currentRequestId) { AZStd::vector& spawnedEntities = ticket.m_spawnedEntities; AZStd::vector& spawnedEntityIndices = ticket.m_spawnedEntityIndices; @@ -367,9 +385,7 @@ namespace AzFramework // Let other systems know about newly spawned entities for any pre-processing before adding to the scene/game context. if (request.m_preInsertionCallback) { - request.m_preInsertionCallback( - *request.m_ticket, - SpawnableEntityContainerView( + request.m_preInsertionCallback(request.m_ticketId, SpawnableEntityContainerView( ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end())); } @@ -382,13 +398,13 @@ namespace AzFramework if (request.m_completionCallback) { - request.m_completionCallback(*request.m_ticket, SpawnableConstEntityContainerView( + request.m_completionCallback(request.m_ticketId, SpawnableConstEntityContainerView( ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end())); } m_onSpawnedEvent.Signal(ticket.m_spawnable); - ticket.m_currentTicketId++; + ticket.m_currentRequestId++; return true; } else @@ -400,8 +416,8 @@ namespace AzFramework bool SpawnableEntitiesManager::ProcessRequest(DespawnAllEntitiesCommand& request, [[maybe_unused]] AZ::SerializeContext& serializeContext) { - Ticket& ticket = GetTicketPayload(*request.m_ticket); - if (request.m_ticketId == ticket.m_currentTicketId) + Ticket& ticket = *request.m_ticket; + if (request.m_requestId == ticket.m_currentRequestId) { for (AZ::Entity* entity : ticket.m_spawnedEntities) { @@ -417,12 +433,12 @@ namespace AzFramework if (request.m_completionCallback) { - request.m_completionCallback(*request.m_ticket); + request.m_completionCallback(request.m_ticketId); } m_onDespawnedEvent.Signal(ticket.m_spawnable); - ticket.m_currentTicketId++; + ticket.m_currentRequestId++; return true; } else @@ -433,11 +449,11 @@ namespace AzFramework bool SpawnableEntitiesManager::ProcessRequest(ReloadSpawnableCommand& request, AZ::SerializeContext& serializeContext) { - Ticket& ticket = GetTicketPayload(*request.m_ticket); + Ticket& ticket = *request.m_ticket; AZ_Assert(ticket.m_spawnable.GetId() == request.m_spawnable.GetId(), "Spawnable is being reloaded, but the provided spawnable has a different asset id. " "This will likely result in unexpected entities being created."); - if (ticket.m_spawnable.IsReady() && request.m_ticketId == ticket.m_currentTicketId) + if (ticket.m_spawnable.IsReady() && request.m_requestId == ticket.m_currentRequestId) { // Delete the original entities. for (AZ::Entity* entity : ticket.m_spawnedEntities) @@ -493,11 +509,11 @@ namespace AzFramework if (request.m_completionCallback) { - request.m_completionCallback(*request.m_ticket, SpawnableConstEntityContainerView( + request.m_completionCallback(request.m_ticketId, SpawnableConstEntityContainerView( ticket.m_spawnedEntities.begin(), ticket.m_spawnedEntities.end())); } - ticket.m_currentTicketId++; + ticket.m_currentRequestId++; m_onSpawnedEvent.Signal(ticket.m_spawnable); @@ -511,12 +527,12 @@ namespace AzFramework bool SpawnableEntitiesManager::ProcessRequest(ListEntitiesCommand& request, [[maybe_unused]] AZ::SerializeContext& serializeContext) { - Ticket& ticket = GetTicketPayload(*request.m_ticket); - if (request.m_ticketId == ticket.m_currentTicketId) + Ticket& ticket = *request.m_ticket; + if (request.m_requestId == ticket.m_currentRequestId) { - request.m_listCallback(*request.m_ticket, SpawnableConstEntityContainerView( + request.m_listCallback(request.m_ticketId, SpawnableConstEntityContainerView( ticket.m_spawnedEntities.begin(), ticket.m_spawnedEntities.end())); - ticket.m_currentTicketId++; + ticket.m_currentRequestId++; return true; } else @@ -527,17 +543,15 @@ namespace AzFramework bool SpawnableEntitiesManager::ProcessRequest(ListIndicesEntitiesCommand& request, [[maybe_unused]] AZ::SerializeContext& serializeContext) { - Ticket& ticket = GetTicketPayload(*request.m_ticket); - if (request.m_ticketId == ticket.m_currentTicketId) + Ticket& ticket = *request.m_ticket; + if (request.m_requestId == ticket.m_currentRequestId) { AZ_Assert( ticket.m_spawnedEntities.size() == ticket.m_spawnedEntityIndices.size(), "Entities and indices on spawnable ticket have gone out of sync."); - request.m_listCallback( - *request.m_ticket, - SpawnableConstIndexEntityContainerView( + request.m_listCallback(request.m_ticketId, SpawnableConstIndexEntityContainerView( ticket.m_spawnedEntities.begin(), ticket.m_spawnedEntityIndices.begin(), ticket.m_spawnedEntities.size())); - ticket.m_currentTicketId++; + ticket.m_currentRequestId++; return true; } else @@ -548,16 +562,16 @@ namespace AzFramework bool SpawnableEntitiesManager::ProcessRequest(ClaimEntitiesCommand& request, [[maybe_unused]] AZ::SerializeContext& serializeContext) { - Ticket& ticket = GetTicketPayload(*request.m_ticket); - if (request.m_ticketId == ticket.m_currentTicketId) + Ticket& ticket = *request.m_ticket; + if (request.m_requestId == ticket.m_currentRequestId) { - request.m_listCallback(*request.m_ticket, SpawnableEntityContainerView( + request.m_listCallback(request.m_ticketId, SpawnableEntityContainerView( ticket.m_spawnedEntities.begin(), ticket.m_spawnedEntities.end())); ticket.m_spawnedEntities.clear(); ticket.m_spawnedEntityIndices.clear(); - ticket.m_currentTicketId++; + ticket.m_currentRequestId++; return true; } else @@ -568,15 +582,15 @@ namespace AzFramework bool SpawnableEntitiesManager::ProcessRequest(BarrierCommand& request, [[maybe_unused]] AZ::SerializeContext& serializeContext) { - Ticket& ticket = GetTicketPayload(*request.m_ticket); - if (request.m_ticketId == ticket.m_currentTicketId) + Ticket& ticket = *request.m_ticket; + if (request.m_requestId == ticket.m_currentRequestId) { if (request.m_completionCallback) { - request.m_completionCallback(*request.m_ticket); + request.m_completionCallback(request.m_ticketId); } - ticket.m_currentTicketId++; + ticket.m_currentRequestId++; return true; } else @@ -587,7 +601,7 @@ namespace AzFramework bool SpawnableEntitiesManager::ProcessRequest(DestroyTicketCommand& request, [[maybe_unused]] AZ::SerializeContext& serializeContext) { - if (request.m_ticketId == request.m_ticket->m_currentTicketId) + if (request.m_requestId == request.m_ticket->m_currentRequestId) { for (AZ::Entity* entity : request.m_ticket->m_spawnedEntities) { @@ -606,24 +620,4 @@ namespace AzFramework return false; } } - - bool SpawnableEntitiesManager::IsEqualTicket(const EntitySpawnTicket* lhs, const EntitySpawnTicket* rhs) - { - return GetTicketPayload(lhs) == GetTicketPayload(rhs); - } - - bool SpawnableEntitiesManager::IsEqualTicket(const Ticket* lhs, const EntitySpawnTicket* rhs) - { - return lhs == GetTicketPayload(rhs); - } - - bool SpawnableEntitiesManager::IsEqualTicket(const EntitySpawnTicket* lhs, const Ticket* rhs) - { - return GetTicketPayload(lhs) == rhs; - } - - bool SpawnableEntitiesManager::IsEqualTicket(const Ticket* lhs, const Ticket* rhs) - { - return lhs = rhs; - } } // namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h index 3481ab180a..afffdab8b5 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h @@ -29,8 +29,6 @@ namespace AZ namespace AzFramework { - using EntityIdMap = AZStd::unordered_map; - class SpawnableEntitiesManager : public SpawnableEntitiesInterface::Registrar { @@ -38,31 +36,47 @@ namespace AzFramework AZ_RTTI(AzFramework::SpawnableEntitiesManager, "{6E14333F-128C-464C-94CA-A63B05A5E51C}"); AZ_CLASS_ALLOCATOR(SpawnableEntitiesManager, AZ::SystemAllocator, 0); + using EntityIdMap = AZStd::unordered_map; + enum class CommandQueueStatus : bool { HasCommandsLeft, - NoCommandLeft + NoCommandsLeft }; + enum class CommandQueuePriority + { + High = 1 << 0, + Regular = 1 << 1 + }; + + SpawnableEntitiesManager(); ~SpawnableEntitiesManager() override = default; // // The following functions are thread safe // - void SpawnAllEntities(EntitySpawnTicket& ticket, EntityPreInsertionCallback preInsertionCallback = {}, EntitySpawnCallback completionCallback = {}) override; - void SpawnEntities(EntitySpawnTicket& ticket, AZStd::vector entityIndices, EntityPreInsertionCallback preInsertionCallback = {}, + void SpawnAllEntities( + EntitySpawnTicket& ticket, SpawnablePriority priority, EntityPreInsertionCallback preInsertionCallback = {}, EntitySpawnCallback completionCallback = {}) override; - void DespawnAllEntities(EntitySpawnTicket& ticket, EntityDespawnCallback completionCallback = {}) override; + void SpawnEntities( + EntitySpawnTicket& ticket, SpawnablePriority priority, AZStd::vector entityIndices, + EntityPreInsertionCallback preInsertionCallback = {}, + EntitySpawnCallback completionCallback = {}) override; + void DespawnAllEntities( + EntitySpawnTicket& ticket, SpawnablePriority priority, EntityDespawnCallback completionCallback = {}) override; - void ReloadSpawnable(EntitySpawnTicket& ticket, AZ::Data::Asset spawnable, + void ReloadSpawnable( + EntitySpawnTicket& ticket, SpawnablePriority priority, AZ::Data::Asset spawnable, ReloadSpawnableCallback completionCallback = {}) override; - void ListEntities(EntitySpawnTicket& ticket, ListEntitiesCallback listCallback) override; - void ListIndicesAndEntities(EntitySpawnTicket& ticket, ListIndicesEntitiesCallback listCallback) override; - void ClaimEntities(EntitySpawnTicket& ticket, ClaimEntitiesCallback listCallback) override; + void ListEntities(EntitySpawnTicket& ticket, SpawnablePriority priority, ListEntitiesCallback listCallback) override; + void ListIndicesAndEntities( + EntitySpawnTicket& ticket, SpawnablePriority priority, ListIndicesEntitiesCallback listCallback) override; + void ClaimEntities(EntitySpawnTicket& ticket, SpawnablePriority priority, ClaimEntitiesCallback listCallback) override; - void Barrier(EntitySpawnTicket& spawnInfo, BarrierCallback completionCallback) override; + void Barrier(EntitySpawnTicket& spawnInfo, SpawnablePriority priority, BarrierCallback completionCallback) override; void AddOnSpawnedHandler(AZ::Event>::Handler& handler) override; void AddOnDespawnedHandler(AZ::Event>::Handler& handler) override; @@ -71,13 +85,9 @@ namespace AzFramework // The following function is thread safe but intended to be run from the main thread. // - CommandQueueStatus ProcessQueue(); + CommandQueueStatus ProcessQueue(CommandQueuePriority priority); protected: - void* CreateTicket(AZ::Data::Asset&& spawnable) override; - void DestroyTicket(void* ticket) override; - - private: struct Ticket { AZ_CLASS_ALLOCATOR(Ticket, AZ::ThreadPoolAllocator, 0); @@ -86,8 +96,8 @@ namespace AzFramework AZStd::vector m_spawnedEntities; AZStd::vector m_spawnedEntityIndices; AZ::Data::Asset m_spawnable; - uint32_t m_nextTicketId{ 0 }; //!< Next id for this ticket. - uint32_t m_currentTicketId{ 0 }; //!< The id for the command that should be executed. + uint32_t m_nextRequestId{ 0 }; //!< Next id for this ticket. + uint32_t m_currentRequestId { 0 }; //!< The id for the command that should be executed. bool m_loadAll{ true }; }; @@ -95,64 +105,86 @@ namespace AzFramework { EntitySpawnCallback m_completionCallback; EntityPreInsertionCallback m_preInsertionCallback; - EntitySpawnTicket* m_ticket; - uint32_t m_ticketId; + Ticket* m_ticket; + EntitySpawnTicket::Id m_ticketId; + uint32_t m_requestId; }; struct SpawnEntitiesCommand { AZStd::vector m_entityIndices; EntitySpawnCallback m_completionCallback; EntityPreInsertionCallback m_preInsertionCallback; - EntitySpawnTicket* m_ticket; - uint32_t m_ticketId; + Ticket* m_ticket; + EntitySpawnTicket::Id m_ticketId; + uint32_t m_requestId; }; struct DespawnAllEntitiesCommand { EntityDespawnCallback m_completionCallback; - EntitySpawnTicket* m_ticket; - uint32_t m_ticketId; + Ticket* m_ticket; + EntitySpawnTicket::Id m_ticketId; + uint32_t m_requestId; }; struct ReloadSpawnableCommand { AZ::Data::Asset m_spawnable; ReloadSpawnableCallback m_completionCallback; - EntitySpawnTicket* m_ticket; - uint32_t m_ticketId; + Ticket* m_ticket; + EntitySpawnTicket::Id m_ticketId; + uint32_t m_requestId; }; struct ListEntitiesCommand { ListEntitiesCallback m_listCallback; - EntitySpawnTicket* m_ticket; - uint32_t m_ticketId; + Ticket* m_ticket; + EntitySpawnTicket::Id m_ticketId; + uint32_t m_requestId; }; struct ListIndicesEntitiesCommand { ListIndicesEntitiesCallback m_listCallback; - EntitySpawnTicket* m_ticket; - uint32_t m_ticketId; + Ticket* m_ticket; + EntitySpawnTicket::Id m_ticketId; + uint32_t m_requestId; }; struct ClaimEntitiesCommand { ClaimEntitiesCallback m_listCallback; - EntitySpawnTicket* m_ticket; - uint32_t m_ticketId; + Ticket* m_ticket; + EntitySpawnTicket::Id m_ticketId; + uint32_t m_requestId; }; struct BarrierCommand { BarrierCallback m_completionCallback; - EntitySpawnTicket* m_ticket; - uint32_t m_ticketId; + Ticket* m_ticket; + EntitySpawnTicket::Id m_ticketId; + uint32_t m_requestId; }; struct DestroyTicketCommand { Ticket* m_ticket; - uint32_t m_ticketId; + uint32_t m_requestId; }; using Requests = AZStd::variant< SpawnAllEntitiesCommand, SpawnEntitiesCommand, DespawnAllEntitiesCommand, ReloadSpawnableCommand, ListEntitiesCommand, ListIndicesEntitiesCommand, ClaimEntitiesCommand, BarrierCommand, DestroyTicketCommand>; + struct Queue + { + AZStd::deque m_delayed; //!< Requests that were processed before, but couldn't be completed. + AZStd::queue m_pendingRequest; //!< Requests waiting to be processed for the first time. + AZStd::mutex m_pendingRequestMutex; + }; + + template + void QueueRequest(EntitySpawnTicket& ticket, SpawnablePriority priority, T&& request); + AZStd::pair CreateTicket(AZ::Data::Asset&& spawnable) override; + void DestroyTicket(void* ticket) override; + + CommandQueueStatus ProcessQueue(Queue& queue); + AZ::Entity* SpawnSingleEntity(const AZ::Entity& entityTemplate, AZ::SerializeContext& serializeContext); @@ -169,16 +201,18 @@ namespace AzFramework bool ProcessRequest(BarrierCommand& request, AZ::SerializeContext& serializeContext); bool ProcessRequest(DestroyTicketCommand& request, AZ::SerializeContext& serializeContext); - [[nodiscard]] static bool IsEqualTicket(const EntitySpawnTicket* lhs, const EntitySpawnTicket* rhs); - [[nodiscard]] static bool IsEqualTicket(const Ticket* lhs, const EntitySpawnTicket* rhs); - [[nodiscard]] static bool IsEqualTicket(const EntitySpawnTicket* lhs, const Ticket* rhs); - [[nodiscard]] static bool IsEqualTicket(const Ticket* lhs, const Ticket* rhs); - - AZStd::deque m_delayedQueue; //!< Requests that were processed before, but couldn't be completed. - AZStd::queue m_pendingRequestQueue; - AZStd::mutex m_pendingRequestQueueMutex; + Queue m_highPriorityQueue; + Queue m_regularPriorityQueue; AZ::Event> m_onSpawnedEvent; AZ::Event> m_onDespawnedEvent; + + //! The threshold used to determine if a request goes in the regular (if bigger than the value) or high priority queue (if smaller + //! or equal to this value). The starting value of 64 is chosen as it's between default values SpawnablePriority_High and + //! SpawnablePriority_Default which gives users a bit of room to fine tune the priorities as this value can be configured + //! through the Settings Registry under the key "/O3DE/AzFramework/Spawnables/HighPriorityThreshold". + SpawnablePriority m_highPriorityThreshold { 64 }; }; + + AZ_DEFINE_ENUM_BITWISE_OPERATORS(AzFramework::SpawnableEntitiesManager::CommandQueuePriority); } // namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableSystemComponent.cpp b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableSystemComponent.cpp index 262f006f15..300ff1441e 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableSystemComponent.cpp +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableSystemComponent.cpp @@ -48,10 +48,23 @@ namespace AzFramework void SpawnableSystemComponent::OnTick(float /*deltaTime*/, AZ::ScriptTimePoint /*time*/) { - m_entitiesManager.ProcessQueue(); + m_entitiesManager.ProcessQueue( + SpawnableEntitiesManager::CommandQueuePriority::High | SpawnableEntitiesManager::CommandQueuePriority::Regular); RootSpawnableNotificationBus::ExecuteQueuedEvents(); } + int SpawnableSystemComponent::GetTickOrder() + { + return AZ::ComponentTickBus::TICK_GAME; + } + + void SpawnableSystemComponent::OnSystemTick() + { + // Handle only high priority spawning events such as those created from network. These need to happen even if the client + // doesn't have focus to avoid time-out issues for instance. + m_entitiesManager.ProcessQueue(SpawnableEntitiesManager::CommandQueuePriority::High); + } + void SpawnableSystemComponent::OnCatalogLoaded([[maybe_unused]] const char* catalogFile) { if (!m_catalogAvailable) @@ -168,7 +181,8 @@ namespace AzFramework SpawnableEntitiesManager::CommandQueueStatus queueStatus; do { - queueStatus = m_entitiesManager.ProcessQueue(); + queueStatus = m_entitiesManager.ProcessQueue( + SpawnableEntitiesManager::CommandQueuePriority::High | SpawnableEntitiesManager::CommandQueuePriority::Regular); } while (queueStatus == SpawnableEntitiesManager::CommandQueueStatus::HasCommandsLeft); } diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableSystemComponent.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableSystemComponent.h index b29fbea5e4..1549b86385 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableSystemComponent.h +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableSystemComponent.h @@ -28,6 +28,7 @@ namespace AzFramework class SpawnableSystemComponent : public AZ::Component , public AZ::TickBus::Handler + , public AZ::SystemTickBus::Handler , public AssetCatalogEventBus::Handler , public RootSpawnableInterface::Registrar , public RootSpawnableNotificationBus::Handler @@ -58,6 +59,13 @@ namespace AzFramework // void OnTick(float deltaTime, AZ::ScriptTimePoint time) override; + int GetTickOrder() override; + + // + // SystemTickBus + // + + void OnSystemTick() override; // // AssetCatalogEventBus diff --git a/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp b/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp index e4833ccb3c..559f7ce460 100644 --- a/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp +++ b/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp @@ -29,7 +29,7 @@ namespace AzFramework AZ_CVAR(float, ed_cameraSystemOrbitDollyScrollSpeed, 0.02f, nullptr, AZ::ConsoleFunctorFlags::Null, ""); AZ_CVAR(float, ed_cameraSystemOrbitDollyCursorSpeed, 0.01f, nullptr, AZ::ConsoleFunctorFlags::Null, ""); AZ_CVAR(float, ed_cameraSystemScrollTranslateSpeed, 0.02f, nullptr, AZ::ConsoleFunctorFlags::Null, ""); - AZ_CVAR(float, ed_cameraSystemMinOrbitDistance, 6.0f, nullptr, AZ::ConsoleFunctorFlags::Null, ""); + AZ_CVAR(float, ed_cameraSystemMinOrbitDistance, 10.0f, nullptr, AZ::ConsoleFunctorFlags::Null, ""); AZ_CVAR(float, ed_cameraSystemMaxOrbitDistance, 50.0f, nullptr, AZ::ConsoleFunctorFlags::Null, ""); AZ_CVAR(float, ed_cameraSystemLookSmoothness, 5.0f, nullptr, AZ::ConsoleFunctorFlags::Null, ""); AZ_CVAR(float, ed_cameraSystemTranslateSmoothness, 5.0f, nullptr, AZ::ConsoleFunctorFlags::Null, ""); @@ -37,7 +37,6 @@ namespace AzFramework AZ_CVAR(float, ed_cameraSystemPanSpeed, 0.01f, nullptr, AZ::ConsoleFunctorFlags::Null, ""); AZ_CVAR(bool, ed_cameraSystemPanInvertX, true, nullptr, AZ::ConsoleFunctorFlags::Null, ""); AZ_CVAR(bool, ed_cameraSystemPanInvertY, true, nullptr, AZ::ConsoleFunctorFlags::Null, ""); - AZ_CVAR(float, ed_cameraSystemLookDeadzone, 2.0f, nullptr, AZ::ConsoleFunctorFlags::Null, ""); AZ_CVAR( AZ::CVarFixedString, ed_cameraSystemTranslateForwardKey, "keyboard_key_alphanumeric_W", nullptr, AZ::ConsoleFunctorFlags::Null, ""); @@ -144,7 +143,7 @@ namespace AzFramework z = AZStd::atan2(-orientation.GetElement(1, 2), orientation.GetElement(1, 1)); } - return {x, y, z}; + return { x, y, z }; } void UpdateCameraFromTransform(Camera& camera, const AZ::Transform& transform) @@ -179,7 +178,7 @@ namespace AzFramework { const auto nextCamera = m_cameras.StepCamera(targetCamera, m_motionDelta, m_scrollDelta, deltaTime); - m_motionDelta = ScreenVector{0, 0}; + m_motionDelta = ScreenVector{ 0, 0 }; m_scrollDelta = 0.0f; return nextCamera; @@ -213,7 +212,10 @@ namespace AzFramework auto& cameraInput = m_idleCameraInputs[i]; const bool canBegin = cameraInput->Beginning() && AZStd::all_of(m_activeCameraInputs.cbegin(), m_activeCameraInputs.cend(), - [](const auto& input) { return !input->Exclusive(); }) && + [](const auto& input) + { + return !input->Exclusive(); + }) && (!cameraInput->Exclusive() || (cameraInput->Exclusive() && m_activeCameraInputs.empty())); if (canBegin) @@ -231,7 +233,8 @@ namespace AzFramework const Camera nextCamera = AZStd::accumulate( AZStd::begin(m_activeCameraInputs), AZStd::end(m_activeCameraInputs), targetCamera, - [cursorDelta, scrollDelta, deltaTime](Camera acc, auto& camera) { + [cursorDelta, scrollDelta, deltaTime](Camera acc, auto& camera) + { acc = camera->StepCamera(acc, cursorDelta, scrollDelta, deltaTime); return acc; }); @@ -284,7 +287,8 @@ namespace AzFramework bool RotateCameraInput::HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, [[maybe_unused]] float scrollDelta) { - const ClickDetector::ClickEvent clickEvent = [&event, this] { + const ClickDetector::ClickEvent clickEvent = [&event, this] + { if (const auto& input = AZStd::get_if(&event)) { if (input->m_channelId == m_rotateChannelId) @@ -330,7 +334,10 @@ namespace AzFramework nextCamera.m_pitch -= float(cursorDelta.m_y) * ed_cameraSystemRotateSpeed; nextCamera.m_yaw -= float(cursorDelta.m_x) * ed_cameraSystemRotateSpeed; - const auto clampRotation = [](const float angle) { return AZStd::fmod(angle + AZ::Constants::TwoPi, AZ::Constants::TwoPi); }; + const auto clampRotation = [](const float angle) + { + return AZStd::fmod(angle + AZ::Constants::TwoPi, AZ::Constants::TwoPi); + }; nextCamera.m_yaw = clampRotation(nextCamera.m_yaw); // clamp pitch to be +-90 degrees @@ -377,9 +384,10 @@ namespace AzFramework const auto deltaPanX = float(cursorDelta.m_x) * panAxes.m_horizontalAxis * ed_cameraSystemPanSpeed; const auto deltaPanY = float(cursorDelta.m_y) * panAxes.m_verticalAxis * ed_cameraSystemPanSpeed; - const auto inv = [](const bool invert) { - constexpr float Dir[] = {1.0f, -1.0f}; - return Dir[static_cast(invert)]; + const auto inv = [](const bool invert) + { + constexpr float Dir[] = { 1.0f, -1.0f }; + return Dir[aznumeric_cast(invert)]; }; nextCamera.m_lookAt += deltaPanX * inv(ed_cameraSystemPanInvertX); @@ -475,7 +483,8 @@ namespace AzFramework const auto axisY = translationBasis.GetBasisY(); const auto axisZ = translationBasis.GetBasisZ(); - const float speed = [boost = m_boost]() { + const float speed = [boost = m_boost]() + { return ed_cameraSystemTranslateSpeed * (boost ? ed_cameraSystemBoostMultiplier : 1.0f); }(); @@ -555,10 +564,12 @@ namespace AzFramework if (Beginning()) { - const auto hasLookAt = [&nextCamera, &targetCamera, &lookAtFn = m_lookAtFn] { + const auto hasLookAt = [&nextCamera, &targetCamera, &lookAtFn = m_lookAtFn] + { if (lookAtFn) { - if (const auto lookAt = lookAtFn()) + // pass through the camera's position and look vector for use in the lookAt function + if (const auto lookAt = lookAtFn(targetCamera.Translation(), targetCamera.Rotation().GetBasisY())) { auto transform = AZ::Transform::CreateLookAt(targetCamera.m_lookAt, *lookAt); nextCamera.m_lookDist = -lookAt->GetDistance(targetCamera.m_lookAt); @@ -692,14 +703,20 @@ namespace AzFramework Camera SmoothCamera(const Camera& currentCamera, const Camera& targetCamera, const float deltaTime) { - const auto clamp_rotation = [](const float angle) { return AZStd::fmod(angle + AZ::Constants::TwoPi, AZ::Constants::TwoPi); }; + const auto clamp_rotation = [](const float angle) + { + return AZStd::fmod(angle + AZ::Constants::TwoPi, AZ::Constants::TwoPi); + }; // keep yaw in 0 - 360 range float targetYaw = clamp_rotation(targetCamera.m_yaw); const float currentYaw = clamp_rotation(currentCamera.m_yaw); // return the sign of the float input (-1, 0, 1) - const auto sign = [](const float value) { return aznumeric_cast((0.0f < value) - (value < 0.0f)); }; + const auto sign = [](const float value) + { + return aznumeric_cast((0.0f < value) - (value < 0.0f)); + }; // ensure smooth transition when moving across 0 - 360 boundary const float yawDelta = targetYaw - currentYaw; @@ -727,26 +744,28 @@ namespace AzFramework const auto& inputChannelId = inputChannel.GetInputChannelId(); const auto& inputDeviceId = inputChannel.GetInputDevice().GetInputDeviceId(); - const bool wasMouseButton = - AZStd::any_of(InputDeviceMouse::Button::All.begin(), InputDeviceMouse::Button::All.end(), [inputChannelId](const auto& button) { + const bool wasMouseButton = AZStd::any_of( + InputDeviceMouse::Button::All.begin(), InputDeviceMouse::Button::All.end(), + [inputChannelId](const auto& button) + { return button == inputChannelId; }); if (inputChannelId == InputDeviceMouse::Movement::X) { - return HorizontalMotionEvent{(int)inputChannel.GetValue()}; + return HorizontalMotionEvent{ aznumeric_cast(inputChannel.GetValue()) }; } else if (inputChannelId == InputDeviceMouse::Movement::Y) { - return VerticalMotionEvent{(int)inputChannel.GetValue()}; + return VerticalMotionEvent{ aznumeric_cast(inputChannel.GetValue()) }; } else if (inputChannelId == InputDeviceMouse::Movement::Z) { - return ScrollEvent{inputChannel.GetValue()}; + return ScrollEvent{ inputChannel.GetValue() }; } else if (wasMouseButton || InputDeviceKeyboard::IsKeyboardDevice(inputDeviceId)) { - return DiscreteInputEvent{inputChannelId, inputChannel.GetState()}; + return DiscreteInputEvent{ inputChannelId, inputChannel.GetState() }; } return AZStd::monostate{}; diff --git a/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.h b/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.h index ec70fc00de..a02f796899 100644 --- a/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.h +++ b/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.h @@ -34,9 +34,9 @@ namespace AzFramework AZ::Vector3 m_lookAt = AZ::Vector3::CreateZero(); //!< Position of camera when m_lookDist is zero, //!< or position of m_lookAt when m_lookDist is greater //!< than zero. - float m_yaw{0.0}; - float m_pitch{0.0}; - float m_lookDist{0.0}; //!< Zero gives first person free look, otherwise orbit about m_lookAt + float m_yaw{ 0.0 }; + float m_pitch{ 0.0 }; + float m_lookDist{ 0.0 }; //!< Zero gives first person free look, otherwise orbit about m_lookAt //! View camera transform (v in MVP). AZ::Transform View() const; @@ -195,7 +195,11 @@ namespace AzFramework inline bool Cameras::Exclusive() const { return AZStd::any_of( - m_activeCameraInputs.begin(), m_activeCameraInputs.end(), [](const auto& cameraInput) { return cameraInput->Exclusive(); }); + m_activeCameraInputs.begin(), m_activeCameraInputs.end(), + [](const auto& cameraInput) + { + return cameraInput->Exclusive(); + }); } //! Responsible for updating a series of cameras given various inputs. @@ -209,7 +213,7 @@ namespace AzFramework private: ScreenVector m_motionDelta; //!< The delta used for look/orbit/pan (rotation + translation) - two dimensional. - float m_scrollDelta = 0.0f; //!< The delta used for dolly/movement (translation) - one dimensional. + float m_scrollDelta = 0.0f; //!< The delta used for dolly/movement (translation) - one dimensional. }; class RotateCameraInput : public CameraInput @@ -237,7 +241,7 @@ namespace AzFramework inline PanAxes LookPan(const Camera& camera) { const AZ::Matrix3x3 orientation = camera.Rotation(); - return {orientation.GetBasisX(), orientation.GetBasisZ()}; + return { orientation.GetBasisX(), orientation.GetBasisZ() }; } inline PanAxes OrbitPan(const Camera& camera) @@ -245,12 +249,13 @@ namespace AzFramework const AZ::Matrix3x3 orientation = camera.Rotation(); const auto basisX = orientation.GetBasisX(); - const auto basisY = [&orientation] { + const auto basisY = [&orientation] + { const auto forward = orientation.GetBasisY(); return AZ::Vector3(forward.GetX(), forward.GetY(), 0.0f).GetNormalized(); }(); - return {basisX, basisY}; + return { basisX, basisY }; } class PanCameraInput : public CameraInput @@ -285,7 +290,8 @@ namespace AzFramework const AZ::Matrix3x3 orientation = camera.Rotation(); const auto basisX = orientation.GetBasisX(); - const auto basisY = [&orientation] { + const auto basisY = [&orientation] + { const auto forward = orientation.GetBasisY(); return AZ::Vector3(forward.GetX(), forward.GetY(), 0.0f).GetNormalized(); }(); @@ -398,7 +404,7 @@ namespace AzFramework class OrbitCameraInput : public CameraInput { public: - using LookAtFn = AZStd::function()>; + using LookAtFn = AZStd::function(const AZ::Vector3& position, const AZ::Vector3& direction)>; // CameraInput overrides ... bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override; diff --git a/Code/Framework/AzFramework/Platform/Linux/AzFramework/Asset/AssetSystemComponentHelper_Linux.cpp b/Code/Framework/AzFramework/Platform/Linux/AzFramework/Asset/AssetSystemComponentHelper_Linux.cpp index 1ae3945bd6..d501271f59 100644 --- a/Code/Framework/AzFramework/Platform/Linux/AzFramework/Asset/AssetSystemComponentHelper_Linux.cpp +++ b/Code/Framework/AzFramework/Platform/Linux/AzFramework/Asset/AssetSystemComponentHelper_Linux.cpp @@ -35,7 +35,8 @@ namespace AzFramework::AssetSystem::Platform if (!AZ::IO::SystemFile::Exists(assetProcessorPath.c_str())) { // Check for existence of one under a "bin" directory, i.e. engineRoot is an SDK structure. - assetProcessorPath = AZ::IO::FixedMaxPath{engineRoot} / "bin" / AZ_BUILD_CONFIGURATION_TYPE / "AssetProcessor"; + assetProcessorPath = + AZ::IO::FixedMaxPath{engineRoot} / "bin" / AZ_TRAIT_OS_PLATFORM_NAME / AZ_BUILD_CONFIGURATION_TYPE / "AssetProcessor"; if (!AZ::IO::SystemFile::Exists(assetProcessorPath.c_str())) { diff --git a/Code/Framework/AzFramework/Platform/Mac/AzFramework/Asset/AssetSystemComponentHelper_Mac.cpp b/Code/Framework/AzFramework/Platform/Mac/AzFramework/Asset/AssetSystemComponentHelper_Mac.cpp index 6f1f860932..890b6b32c3 100644 --- a/Code/Framework/AzFramework/Platform/Mac/AzFramework/Asset/AssetSystemComponentHelper_Mac.cpp +++ b/Code/Framework/AzFramework/Platform/Mac/AzFramework/Asset/AssetSystemComponentHelper_Mac.cpp @@ -34,7 +34,8 @@ namespace AzFramework::AssetSystem::Platform if (!AZ::IO::SystemFile::Exists(assetProcessorPath.c_str())) { // Check for existence of one under a "bin" directory, i.e. engineRoot is an SDK structure. - assetProcessorPath = AZ::IO::FixedMaxPath{engineRoot} / "bin" / AZ_BUILD_CONFIGURATION_TYPE / "AssetProcessor.app"; + assetProcessorPath = + AZ::IO::FixedMaxPath{engineRoot} / "bin" / AZ_TRAIT_OS_PLATFORM_NAME / AZ_BUILD_CONFIGURATION_TYPE / "AssetProcessor.app"; if (!AZ::IO::SystemFile::Exists(assetProcessorPath.c_str())) { diff --git a/Code/Framework/AzFramework/Platform/Windows/AzFramework/Asset/AssetSystemComponentHelper_Windows.cpp b/Code/Framework/AzFramework/Platform/Windows/AzFramework/Asset/AssetSystemComponentHelper_Windows.cpp index b716778cf4..b0debfd3b0 100644 --- a/Code/Framework/AzFramework/Platform/Windows/AzFramework/Asset/AssetSystemComponentHelper_Windows.cpp +++ b/Code/Framework/AzFramework/Platform/Windows/AzFramework/Asset/AssetSystemComponentHelper_Windows.cpp @@ -71,7 +71,8 @@ namespace AzFramework::AssetSystem::Platform if (!AZ::IO::SystemFile::Exists(assetProcessorPath.c_str())) { // Check for existence of one under a "bin" directory, i.e. engineRoot is an SDK structure. - assetProcessorPath = AZ::IO::FixedMaxPath{engineRoot} / "bin" / AZ_BUILD_CONFIGURATION_TYPE / "AssetProcessor.exe"; + assetProcessorPath = + AZ::IO::FixedMaxPath{engineRoot} / "bin" / AZ_TRAIT_OS_PLATFORM_NAME / AZ_BUILD_CONFIGURATION_TYPE / "AssetProcessor.exe"; if (!AZ::IO::SystemFile::Exists(assetProcessorPath.c_str())) { diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/Menu.qss b/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/Menu.qss index af9c675f23..7f48637cc8 100644 --- a/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/Menu.qss +++ b/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/Menu.qss @@ -49,7 +49,7 @@ QMenu::right-arrow QMenu::icon { - right: 8px; + right: 20px; } QMenu::indicator:checked diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Images/Notifications/link.svg b/Code/Framework/AzQtComponents/AzQtComponents/Images/Notifications/link.svg new file mode 100644 index 0000000000..dfd21d157f --- /dev/null +++ b/Code/Framework/AzQtComponents/AzQtComponents/Images/Notifications/link.svg @@ -0,0 +1,4 @@ + + + + diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Images/resources.qrc b/Code/Framework/AzQtComponents/AzQtComponents/Images/resources.qrc index dbbf0e78e2..7b0c6530ab 100644 --- a/Code/Framework/AzQtComponents/AzQtComponents/Images/resources.qrc +++ b/Code/Framework/AzQtComponents/AzQtComponents/Images/resources.qrc @@ -13,5 +13,6 @@ Notifications/checkmark.svg Notifications/download.svg + Notifications/link.svg diff --git a/Code/Framework/AzTest/AzTest/Platform/Linux/AzTest_Traits_Linux.h b/Code/Framework/AzTest/AzTest/Platform/Linux/AzTest_Traits_Linux.h index 0090ce066b..855b4fe416 100644 --- a/Code/Framework/AzTest/AzTest/Platform/Linux/AzTest_Traits_Linux.h +++ b/Code/Framework/AzTest/AzTest/Platform/Linux/AzTest_Traits_Linux.h @@ -39,4 +39,3 @@ #define AZ_TRAIT_DISABLE_FAILED_EMOTION_FX_EDITOR_TESTS true #define AZ_TRAIT_DISABLE_FAILED_METRICS_TESTS true -#define AZ_TRAIT_DISABLE_ASSET_JOB_PARALLEL_TESTS true diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/AssetEditor/AssetEditorWidget.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/AssetEditor/AssetEditorWidget.cpp index 706d8243e2..a59b29ddf8 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/AssetEditor/AssetEditorWidget.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/AssetEditor/AssetEditorWidget.cpp @@ -483,6 +483,8 @@ namespace AzToolsFramework } } + m_dirty = false; + AddRecentPath(targetFilePath); SetStatusText(Status::assetCreated); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.cpp index 439789f11b..7fd11ff9bf 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.cpp @@ -57,7 +57,6 @@ namespace AzToolsFramework "Couldn't get prefab loader interface, it's a requirement for PrefabEntityOwnership system to work"); m_rootInstance = AZStd::unique_ptr(m_prefabSystemComponent->CreatePrefab({}, {}, "NewLevel.prefab")); - m_sliceOwnershipService.BusConnect(m_entityContextId); m_sliceOwnershipService.m_shouldAssertForLegacySlicesUsage = m_shouldAssertForLegacySlicesUsage; m_editorSliceOwnershipService.BusConnect(); @@ -91,14 +90,17 @@ namespace AzToolsFramework void PrefabEditorEntityOwnershipService::Reset() { - Prefab::TemplateId templateId = m_rootInstance->GetTemplateId(); - if (templateId != Prefab::InvalidTemplateId) + if (m_rootInstance) { - m_rootInstance->SetTemplateId(Prefab::InvalidTemplateId); - m_prefabSystemComponent->RemoveTemplate(templateId); + Prefab::TemplateId templateId = m_rootInstance->GetTemplateId(); + if (templateId != Prefab::InvalidTemplateId) + { + m_rootInstance->SetTemplateId(Prefab::InvalidTemplateId); + m_prefabSystemComponent->RemoveTemplate(templateId); + } + m_rootInstance->Reset(); + m_rootInstance->SetContainerEntityName("Level"); } - m_rootInstance->Reset(); - m_rootInstance->SetContainerEntityName("Level"); AzFramework::EntityOwnershipServiceNotificationBus::Event( m_entityContextId, &AzFramework::EntityOwnershipServiceNotificationBus::Events::OnEntityOwnershipServiceReset); @@ -202,7 +204,7 @@ namespace AzToolsFramework } m_rootInstance->SetTemplateId(templateId); - m_rootInstance->SetTemplateSourcePath(m_loaderInterface->GetRelativePathToProject(filename)); + m_rootInstance->SetTemplateSourcePath(m_loaderInterface->GenerateRelativePath(filename)); m_rootInstance->SetContainerEntityName("Level"); m_prefabSystemComponent->PropagateTemplateChanges(templateId); @@ -220,7 +222,7 @@ namespace AzToolsFramework bool PrefabEditorEntityOwnershipService::SaveToStream(AZ::IO::GenericStream& stream, AZStd::string_view filename) { - AZ::IO::Path relativePath = m_loaderInterface->GetRelativePathToProject(filename); + AZ::IO::Path relativePath = m_loaderInterface->GenerateRelativePath(filename); AzToolsFramework::Prefab::TemplateId templateId = m_prefabSystemComponent->GetTemplateIdFromFilePath(relativePath); m_rootInstance->SetTemplateSourcePath(relativePath); @@ -267,7 +269,7 @@ namespace AzToolsFramework void PrefabEditorEntityOwnershipService::CreateNewLevelPrefab(AZStd::string_view filename, const AZStd::string& templateFilename) { - AZ::IO::Path relativePath = m_loaderInterface->GetRelativePathToProject(filename); + AZ::IO::Path relativePath = m_loaderInterface->GenerateRelativePath(filename); AzToolsFramework::Prefab::TemplateId templateId = m_prefabSystemComponent->GetTemplateIdFromFilePath(relativePath); m_rootInstance->SetTemplateSourcePath(relativePath); @@ -378,7 +380,12 @@ namespace AzToolsFramework Prefab::InstanceOptionalReference PrefabEditorEntityOwnershipService::GetRootPrefabInstance() { AZ_Assert(m_rootInstance, "A valid root prefab instance couldn't be found in PrefabEditorEntityOwnershipService."); - return *m_rootInstance; + if (m_rootInstance) + { + return *m_rootInstance; + } + + return AZStd::nullopt; } const AZStd::vector>& PrefabEditorEntityOwnershipService::GetPlayInEditorAssetData() diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Manipulators/ManipulatorSpace.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Manipulators/ManipulatorSpace.cpp index cd08a95af7..b3f691a62f 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Manipulators/ManipulatorSpace.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Manipulators/ManipulatorSpace.cpp @@ -39,7 +39,7 @@ namespace AzToolsFramework AZ::Transform result; result.SetRotation(m_space.GetRotation() * localTransform.GetRotation()); result.SetTranslation(m_space.TransformPoint(m_nonUniformScale * localTransform.GetTranslation())); - result.SetScale(m_space.GetScale() * localTransform.GetUniformScale()); + result.SetUniformScale(m_space.GetUniformScale() * localTransform.GetUniformScale()); return result; } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceSerializer.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceSerializer.cpp index 836140eb74..39351df486 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceSerializer.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceSerializer.cpp @@ -124,7 +124,7 @@ namespace AzToolsFramework "PrefabLoaderInterface could not be found. It is required to load Prefab Instances"); // Make sure we have a relative path - instance->m_templateSourcePath = loaderInterface->GetRelativePathToProject(instance->m_templateSourcePath); + instance->m_templateSourcePath = loaderInterface->GenerateRelativePath(instance->m_templateSourcePath); TemplateId templateId = prefabSystemComponentInterface->GetTemplateIdFromFilePath(instance->GetTemplateSourcePath()); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabLoader.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabLoader.cpp index 2965148172..d7de634c11 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabLoader.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabLoader.cpp @@ -18,7 +18,9 @@ #include #include +#include #include +#include #include #include #include @@ -112,7 +114,7 @@ namespace AzToolsFramework return InvalidTemplateId; } - AZ::IO::Path relativePath = GetRelativePathToProject(originPath); + AZ::IO::Path relativePath = GenerateRelativePath(originPath); // Cyclical dependency detected if the prefab file is already part of the progressed // file path set. @@ -301,6 +303,45 @@ namespace AzToolsFramework return true; } + bool PrefabLoader::SaveTemplateToFile(TemplateId templateId, AZ::IO::PathView absolutePath) + { + AZ_Assert(absolutePath.IsAbsolute(), "SaveTemplateToFile requires an absolute path for saving the initial prefab file."); + + const auto& domAndFilepath = StoreTemplateIntoFileFormat(templateId); + if (!domAndFilepath) + { + return false; + } + + // Verify that the absolute path provided to this matches the relative path saved in the template. + // Otherwise, the saved prefab won't be able to be loaded. + auto relativePath = GenerateRelativePath(absolutePath); + if (relativePath != domAndFilepath->second) + { + AZ_Error( + "Prefab", false, + "PrefabLoader::SaveTemplateToFile - " + "Failed to save template '%s' to location '%.*s'." + "Error: Relative path '%.*s' for location didn't match template name.", + domAndFilepath->second.c_str(), AZ_STRING_ARG(absolutePath.Native()), AZ_STRING_ARG(relativePath.Native())); + return false; + } + + auto outcome = AzFramework::FileFunc::WriteJsonFile(domAndFilepath->first, absolutePath); + if (!outcome.IsSuccess()) + { + AZ_Error( + "Prefab", false, + "PrefabLoader::SaveTemplateToFile - " + "Failed to save template '%s' to location '%.*s'." + "Error: %s", + domAndFilepath->second.c_str(), AZ_STRING_ARG(absolutePath.Native()), outcome.GetError().c_str()); + return false; + } + m_prefabSystemComponentInterface->SetTemplateDirtyFlag(templateId, false); + return true; + } + bool PrefabLoader::SaveTemplateToString(TemplateId templateId, AZStd::string& output) { const auto& domAndFilepath = StoreTemplateIntoFileFormat(templateId); @@ -385,21 +426,100 @@ namespace AzToolsFramework AZ::IO::Path pathWithOSSeparator = AZ::IO::Path(path).MakePreferred(); if (pathWithOSSeparator.IsAbsolute()) { + // If an absolute path was passed in, just return it as-is. return path; } - return AZ::IO::Path(m_projectPathWithOsSeparator).Append(pathWithOSSeparator); + // A relative path was passed in, so try to turn it back into an absolute path. + + AZ::IO::Path fullPath; + + bool pathFound = false; + AZ::Data::AssetInfo assetInfo; + AZStd::string rootFolder; + AZStd::string inputPath(path.Native()); + + // Given an input path that's expected to exist, try to look it up. + AzToolsFramework::AssetSystemRequestBus::BroadcastResult( + pathFound, &AzToolsFramework::AssetSystemRequestBus::Events::GetSourceInfoBySourcePath, + inputPath.c_str(), assetInfo, rootFolder); + + if (pathFound) + { + // The asset system provided us with a valid root folder and relative path, so return it. + fullPath = AZ::IO::Path(rootFolder) / assetInfo.m_relativePath; + } + else + { + // If for some reason the Asset system couldn't provide a relative path, provide some fallback logic. + + // Check to see if the AssetProcessor is ready. If it *is* and we didn't get a path, print an error then follow + // the fallback logic. If it's *not* ready, we're probably either extremely early in a tool startup flow or inside + // a unit test, so just execute the fallback logic without an error. + [[maybe_unused]] bool assetProcessorReady = false; + AzFramework::AssetSystemRequestBus::BroadcastResult( + assetProcessorReady, &AzFramework::AssetSystemRequestBus::Events::AssetProcessorIsReady); + + AZ_Error( + "Prefab", !assetProcessorReady, "Full source path for '%.*s' could not be determined. Using fallback logic.", + AZ_STRING_ARG(path.Native())); + + // If a relative path was passed in, make it relative to the project root. + fullPath = AZ::IO::Path(m_projectPathWithOsSeparator).Append(pathWithOSSeparator); + } + + return fullPath; } - AZ::IO::Path PrefabLoader::GetRelativePathToProject(AZ::IO::PathView path) + AZ::IO::Path PrefabLoader::GenerateRelativePath(AZ::IO::PathView path) { - AZ::IO::Path pathWithOSSeparator = AZ::IO::Path(path.Native()).MakePreferred(); - if (!pathWithOSSeparator.IsAbsolute()) + bool pathFound = false; + + AZStd::string relativePath; + AZStd::string rootFolder; + AZ::IO::Path finalPath; + + // The asset system allows for paths to be relative to multiple root folders, using a priority system. + // This request will make the input path relative to the most appropriate, highest-priority root folder. + AzToolsFramework::AssetSystemRequestBus::BroadcastResult( + pathFound, &AzToolsFramework::AssetSystemRequestBus::Events::GenerateRelativeSourcePath, path.Native(), + relativePath, rootFolder); + + if (pathFound && !relativePath.empty()) { - return path; + // A relative path was generated successfully, so return it. + finalPath = relativePath; + } + else + { + // If for some reason the Asset system couldn't provide a relative path, provide some fallback logic. + + // Check to see if the AssetProcessor is ready. If it *is* and we didn't get a path, print an error then follow + // the fallback logic. If it's *not* ready, we're probably either extremely early in a tool startup flow or inside + // a unit test, so just execute the fallback logic without an error. + [[maybe_unused]] bool assetProcessorReady = false; + AzFramework::AssetSystemRequestBus::BroadcastResult( + assetProcessorReady, &AzFramework::AssetSystemRequestBus::Events::AssetProcessorIsReady); + + AZ_Error("Prefab", !assetProcessorReady, + "Relative source path for '%.*s' could not be determined. Using project path as relative root.", + AZ_STRING_ARG(path.Native())); + + AZ::IO::Path pathWithOSSeparator = AZ::IO::Path(path.Native()).MakePreferred(); + + if (pathWithOSSeparator.IsAbsolute()) + { + // If an absolute path was passed in, make it relative to the project path. + finalPath = AZ::IO::Path(path.Native(), '/').MakePreferred().LexicallyRelative(m_projectPathWithSlashSeparator); + } + else + { + // If a relative path was passed in, just return it. + finalPath = path; + } } - return AZ::IO::Path(path.Native(), '/').MakePreferred().LexicallyRelative(m_projectPathWithSlashSeparator); + return finalPath; } AZ::IO::Path PrefabLoaderInterface::GeneratePath() diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabLoader.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabLoader.h index d11cb62ca3..3722e14a97 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabLoader.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabLoader.h @@ -72,6 +72,16 @@ namespace AzToolsFramework */ bool SaveTemplate(TemplateId templateId) override; + /** + * Saves a Prefab Template to the provided absolute source path, which needs to match the relative path in the template. + * Converts Prefab Template form into .prefab form by collapsing nested Template info + * into a source path and patches. + * @param templateId Id of the template to be saved + * @param absolutePath Absolute path to save the file to + * @return bool on whether the operation succeeded or not + */ + bool SaveTemplateToFile(TemplateId templateId, AZ::IO::PathView absolutePath) override; + /** * Saves a Prefab Template into the provided output string. * Converts Prefab Template form into .prefab form by collapsing nested Template info @@ -91,9 +101,11 @@ namespace AzToolsFramework //! The path will always have the correct separator for the current OS AZ::IO::Path GetFullPath(AZ::IO::PathView path) override; - //! Converts path into a relative path to the project, this will be the paths in .prefab file. - //! The path will always have '/' separator. - AZ::IO::Path GetRelativePathToProject(AZ::IO::PathView path) override; + //! Converts path into a path that's relative to the highest-priority containing folder of all the folders registered + //! with the engine. + //! This path will be the path that appears in the .prefab file. + //! The path will always use the '/' separator. + AZ::IO::Path GenerateRelativePath(AZ::IO::PathView path) override; //! Returns if the path is a valid path for a prefab static bool IsValidPrefabPath(AZ::IO::PathView path); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabLoaderInterface.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabLoaderInterface.h index a4055fb15a..0e551cee6b 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabLoaderInterface.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabLoaderInterface.h @@ -60,6 +60,16 @@ namespace AzToolsFramework */ virtual bool SaveTemplate(TemplateId templateId) = 0; + /** + * Saves a Prefab Template to the provided absolute source path, which needs to match the relative path in the template. + * Converts Prefab Template form into .prefab form by collapsing nested Template info + * into a source path and patches. + * @param templateId Id of the template to be saved + * @param absolutePath Absolute path to save the file to + * @return bool on whether the operation succeeded or not + */ + virtual bool SaveTemplateToFile(TemplateId templateId, AZ::IO::PathView absolutePath) = 0; + /** * Saves a Prefab Template into the provided output string. * Converts Prefab Template form into .prefab form by collapsing nested Template info @@ -74,9 +84,11 @@ namespace AzToolsFramework //! The path will always have the correct separator for the current OS virtual AZ::IO::Path GetFullPath(AZ::IO::PathView path) = 0; - //! Converts path into a relative path to the current project, this will be the paths in .prefab file. - //! The path will always have '/' separator. - virtual AZ::IO::Path GetRelativePathToProject(AZ::IO::PathView path) = 0; + //! Converts path into a path that's relative to the highest-priority containing folder of all the folders registered + //! with the engine. + //! This path will be the path that appears in the .prefab file. + //! The path will always use the '/' separator. + virtual AZ::IO::Path GenerateRelativePath(AZ::IO::PathView path) = 0; protected: diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp index 0181050a32..fadcc1b81f 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp @@ -64,7 +64,7 @@ namespace AzToolsFramework m_prefabUndoCache.Destroy(); } - PrefabOperationResult PrefabPublicHandler::CreatePrefab(const AZStd::vector& entityIds, AZ::IO::PathView filePath) + PrefabOperationResult PrefabPublicHandler::CreatePrefab(const AZStd::vector& entityIds, AZ::IO::PathView absolutePath) { EntityList inputEntityList, topLevelEntities; AZ::EntityId commonRootEntityId; @@ -76,6 +76,8 @@ namespace AzToolsFramework return findCommonRootOutcome; } + AZ_Assert(absolutePath.IsAbsolute(), "CreatePrefab requires an absolute path for saving the initial prefab file."); + InstanceOptionalReference instanceToCreate; { // Initialize Undo Batch object @@ -144,7 +146,8 @@ namespace AzToolsFramework // Create the Prefab instanceToCreate = prefabEditorEntityOwnershipInterface->CreatePrefab( - entities, AZStd::move(instancePtrs), filePath, commonRootEntityOwningInstance); + entities, AZStd::move(instancePtrs), m_prefabLoaderInterface->GenerateRelativePath(absolutePath), + commonRootEntityOwningInstance); if (!instanceToCreate) { @@ -254,7 +257,7 @@ namespace AzToolsFramework } // Save Template to file - m_prefabLoaderInterface->SaveTemplate(instanceToCreate->get().GetTemplateId()); + m_prefabLoaderInterface->SaveTemplateToFile(instanceToCreate->get().GetTemplateId(), absolutePath); return AZ::Success(); } @@ -318,7 +321,7 @@ namespace AzToolsFramework } //Detect whether this instantiation would produce a cyclical dependency - auto relativePath = m_prefabLoaderInterface->GetRelativePathToProject(filePath); + auto relativePath = m_prefabLoaderInterface->GenerateRelativePath(filePath); Prefab::TemplateId templateId = m_prefabSystemComponentInterface->GetTemplateIdFromFilePath(relativePath); if (templateId == InvalidTemplateId) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h index 7e2357dd44..e68a3e0b1e 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h @@ -46,7 +46,7 @@ namespace AzToolsFramework void UnregisterPrefabPublicHandlerInterface(); // PrefabPublicInterface... - PrefabOperationResult CreatePrefab(const AZStd::vector& entityIds, AZ::IO::PathView filePath) override; + PrefabOperationResult CreatePrefab(const AZStd::vector& entityIds, AZ::IO::PathView absolutePath) override; PrefabOperationResult InstantiatePrefab(AZStd::string_view filePath, AZ::EntityId parent, const AZ::Vector3& position) override; PrefabOperationResult SavePrefab(AZ::IO::Path filePath) override; PrefabEntityResult CreateEntity(AZ::EntityId parentId, const AZ::Vector3& position) override; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicInterface.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicInterface.h index 0750c4d264..2e9152fd1b 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicInterface.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicInterface.h @@ -46,10 +46,10 @@ namespace AzToolsFramework * Create a prefab out of the entities provided, at the path provided. * Automatically detects descendants of entities, and discerns between entities and child instances. * @param entityIds The entities that should form the new prefab (along with their descendants). - * @param filePath The path for the new prefab file. + * @param filePath The absolute path for the new prefab file. * @return An outcome object; on failure, it comes with an error message detailing the cause of the error. */ - virtual PrefabOperationResult CreatePrefab(const AZStd::vector& entityIds, AZ::IO::PathView filePath) = 0; + virtual PrefabOperationResult CreatePrefab(const AZStd::vector& entityIds, AZ::IO::PathView absolutePath) = 0; /** * Instantiate a prefab from a prefab file. diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.cpp index c4e6415b02..0136f791b3 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.cpp @@ -95,7 +95,7 @@ namespace AzToolsFramework const AZStd::vector& entities, AZStd::vector>&& instancesToConsume, AZ::IO::PathView filePath, AZStd::unique_ptr containerEntity, bool shouldCreateLinks) { - AZ::IO::Path relativeFilePath = m_prefabLoader.GetRelativePathToProject(filePath); + AZ::IO::Path relativeFilePath = m_prefabLoader.GenerateRelativePath(filePath); if (GetTemplateIdFromFilePath(relativeFilePath) != InvalidTemplateId) { AZ_Error("Prefab", false, diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/ProcesedObjectStore.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/ProcesedObjectStore.cpp index 78d1332a71..050afd813d 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/ProcesedObjectStore.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/ProcesedObjectStore.cpp @@ -10,7 +10,7 @@ * */ -#include +#include #include namespace AzToolsFramework::Prefab::PrefabConversionUtils @@ -73,8 +73,7 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils uint32_t ProcessedObjectStore::BuildSubId(AZStd::string_view id) { - AZ::Uuid subIdHash = AZ::Uuid::CreateData(id.data(), id.size()); - return azlossy_caster(subIdHash.GetHash()); + return AzFramework::SpawnableAssetHandler::BuildSubId(id); } const AZStd::string& ProcessedObjectStore::GetId() const diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/SpawnableUtils.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/SpawnableUtils.cpp index 716c3098d9..3c91f99b05 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/SpawnableUtils.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/SpawnableUtils.cpp @@ -24,17 +24,6 @@ namespace AzToolsFramework::Prefab::SpawnableUtils { - - AzFramework::Spawnable CreateSpawnable(const PrefabDom& prefabDom) - { - AzFramework::Spawnable spawnable; - AZStd::vector> referencedAssets; - [[maybe_unused]] bool result = CreateSpawnable(spawnable, prefabDom, referencedAssets); - AZ_Assert(result, - "Failed to Load Prefab Instance from given Prefab DOM while Spawnable creation."); - return spawnable; - } - bool CreateSpawnable(AzFramework::Spawnable& spawnable, const PrefabDom& prefabDom) { AZStd::vector> referencedAssets; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/SpawnableUtils.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/SpawnableUtils.h index 3b5ea488cb..cdf07346d0 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/SpawnableUtils.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/SpawnableUtils.h @@ -17,7 +17,6 @@ namespace AzToolsFramework::Prefab::SpawnableUtils { - AzFramework::Spawnable CreateSpawnable(const PrefabDom& prefabDom); bool CreateSpawnable(AzFramework::Spawnable& spawnable, const PrefabDom& prefabDom); bool CreateSpawnable(AzFramework::Spawnable& spawnable, const PrefabDom& prefabDom, AZStd::vector>& referencedAssets); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/EditorNonUniformScaleComponentMode.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/EditorNonUniformScaleComponentMode.cpp index 97e27ac748..497bcf15d7 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/EditorNonUniformScaleComponentMode.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/EditorNonUniformScaleComponentMode.cpp @@ -28,7 +28,7 @@ namespace AzToolsFramework AZ::Transform worldFromLocal = AZ::Transform::CreateIdentity(); AZ::TransformBus::EventResult(worldFromLocal, m_entityComponentIdPair.GetEntityId(), &AZ::TransformBus::Events::GetWorldTM); - worldFromLocal.ExtractScale(); + worldFromLocal.ExtractUniformScale(); m_manipulators = AZStd::make_unique(worldFromLocal); m_manipulators->Register(g_mainManipulatorManagerId); m_manipulators->SetAxes(AZ::Vector3::CreateAxisX(), AZ::Vector3::CreateAxisY(), AZ::Vector3::CreateAxisZ()); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/TransformComponent.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/TransformComponent.cpp index 285d962b46..3e13e6226b 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/TransformComponent.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/TransformComponent.cpp @@ -32,7 +32,6 @@ #include #include #include -#include #include #include #include @@ -50,10 +49,10 @@ namespace AzToolsFramework { const AZ::u32 ParentEntityCRC = AZ_CRC("Parent Entity", 0x5b1b276c); - // Decompose a transform into euler angles in degrees, scale (along basis, any shear will be dropped), and translation. - void DecomposeTransform(const AZ::Transform& transform, AZ::Vector3& translation, AZ::Vector3& rotation, AZ::Vector3& scale) + // Decompose a transform into euler angles in degrees, uniform scale, and translation. + void DecomposeTransform(const AZ::Transform& transform, AZ::Vector3& translation, AZ::Vector3& rotation, float& scale) { - scale = transform.GetScale(); + scale = transform.GetUniformScale(); translation = transform.GetTranslation(); rotation = transform.GetRotation().GetEulerDegrees(); } @@ -120,7 +119,7 @@ namespace AzToolsFramework // Decompose the old slice-relative transform and set it as a our editor transform, // since the entity is now our parent. EditorTransform editorTransform; - DecomposeTransform(sliceRelTransform, editorTransform.m_translate, editorTransform.m_rotate, editorTransform.m_scale); + DecomposeTransform(sliceRelTransform, editorTransform.m_translate, editorTransform.m_rotate, editorTransform.m_uniformScale); editorTransformElement.Convert(context); editorTransformElement.SetData(context, editorTransform); } @@ -170,6 +169,23 @@ namespace AzToolsFramework return true; } + + bool EditorTransformDataConverter(AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement) + { + if (classElement.GetVersion() < 3) + { + // version 3 replaces vector scale with uniform scale but does not yet delete the legacy scale data + // in order to allow for migration + AZ::Vector3 vectorScale; + if (classElement.FindSubElementAndGetData(AZ_CRC_CE("Scale"), vectorScale)) + { + const float uniformScale = vectorScale.GetMaxElement(); + classElement.AddElementWithData(context, "UniformScale", uniformScale); + } + } + + return true; + } } // namespace Internal TransformComponent::TransformComponent() @@ -357,7 +373,7 @@ namespace AzToolsFramework AZ::Transform TransformComponent::GetLocalScaleTM() const { - return AZ::Transform::CreateUniformScale(m_editorTransform.m_scale.GetMaxElement()); + return AZ::Transform::CreateUniformScale(m_editorTransform.m_uniformScale); } const AZ::Transform& TransformComponent::GetLocalTM() @@ -374,12 +390,13 @@ namespace AzToolsFramework // given a local transform, update local transform. void TransformComponent::SetLocalTM(const AZ::Transform& finalTx) { - AZ::Vector3 tx, rot, scale; - Internal::DecomposeTransform(finalTx, tx, rot, scale); + AZ::Vector3 tx, rot; + float uniformScale; + Internal::DecomposeTransform(finalTx, tx, rot, uniformScale); m_editorTransform.m_translate = tx; m_editorTransform.m_rotate = rot; - m_editorTransform.m_scale = scale; + m_editorTransform.m_uniformScale = uniformScale; TransformChanged(); } @@ -599,31 +616,21 @@ namespace AzToolsFramework return result; } - void TransformComponent::SetLocalScale(const AZ::Vector3& scale) - { - m_editorTransform.m_scale = scale; - TransformChanged(); - } - AZ::Vector3 TransformComponent::GetLocalScale() { - return m_editorTransform.m_scale; - } - - AZ::Vector3 TransformComponent::GetWorldScale() - { - return GetWorldTM().GetScale(); + AZ_WarningOnce("TransformComponent", false, "GetLocalScale is deprecated, please use GetLocalUniformScale instead"); + return m_editorTransform.m_legacyScale; } void TransformComponent::SetLocalUniformScale(float scale) { - m_editorTransform.m_scale = AZ::Vector3(scale); + m_editorTransform.m_uniformScale = scale; TransformChanged(); } float TransformComponent::GetLocalUniformScale() { - return m_editorTransform.m_scale.GetMaxElement(); + return m_editorTransform.m_uniformScale; } float TransformComponent::GetWorldUniformScale() @@ -1141,9 +1148,10 @@ namespace AzToolsFramework serializeContext->Class()-> Field("Translate", &EditorTransform::m_translate)-> Field("Rotate", &EditorTransform::m_rotate)-> - Field("Scale", &EditorTransform::m_scale)-> + Field("Scale", &EditorTransform::m_legacyScale)-> Field("Locked", &EditorTransform::m_locked)-> - Version(2); + Field("UniformScale", &EditorTransform::m_uniformScale)-> + Version(3, &Internal::EditorTransformDataConverter); serializeContext->Class()-> Field("Parent Entity", &TransformComponent::m_parentEntityId)-> @@ -1202,7 +1210,7 @@ namespace AzToolsFramework Attribute(AZ::Edit::Attributes::Suffix, " deg")-> Attribute(AZ::Edit::Attributes::ReadOnly, &EditorTransform::m_locked)-> Attribute(AZ::Edit::Attributes::SliceFlags, AZ::Edit::SliceFlags::NotPushableOnSliceRoot)-> - DataElement(TransformScaleHandler, &EditorTransform::m_scale, "Scale", "Local Scale")-> + DataElement(AZ::Edit::UIHandlers::Default, &EditorTransform::m_uniformScale, "Uniform Scale", "Local Uniform Scale")-> Attribute(AZ::Edit::Attributes::Step, 0.1f)-> Attribute(AZ::Edit::Attributes::ReadOnly, &EditorTransform::m_locked) ; @@ -1230,7 +1238,8 @@ namespace AzToolsFramework { AzToolsFramework::ScopedUndoBatch undo("Reset transform values"); m_editorTransform.m_translate = AZ::Vector3::CreateZero(); - m_editorTransform.m_scale = AZ::Vector3::CreateOne(); + m_editorTransform.m_legacyScale = AZ::Vector3::CreateOne(); + m_editorTransform.m_uniformScale = 1.0f; m_editorTransform.m_rotate = AZ::Vector3::CreateZero(); OnTransformChanged(); SetDirty(); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/TransformComponent.h b/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/TransformComponent.h index f772b608c1..80db5e10fb 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/TransformComponent.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/TransformComponent.h @@ -115,9 +115,7 @@ namespace AzToolsFramework AZ::Quaternion GetLocalRotationQuaternion() override; // Scale Modifiers - void SetLocalScale(const AZ::Vector3& scale) override; AZ::Vector3 GetLocalScale() override; - AZ::Vector3 GetWorldScale() override; void SetLocalUniformScale(float scale) override; float GetLocalUniformScale() override; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/TransformComponentBus.h b/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/TransformComponentBus.h index 437a39b1a0..26fa4d758e 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/TransformComponentBus.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/TransformComponentBus.h @@ -30,7 +30,8 @@ namespace AzToolsFramework EditorTransform() { m_translate = AZ::Vector3::CreateZero(); - m_scale = AZ::Vector3::CreateOne(); + m_legacyScale = AZ::Vector3::CreateOne(); + m_uniformScale = 1.0f; m_rotate = AZ::Vector3::CreateZero(); m_locked = false; } @@ -40,9 +41,10 @@ namespace AzToolsFramework return EditorTransform(); } - AZ::Vector3 m_translate; //! Translation in engine units (meters) - AZ::Vector3 m_scale; - AZ::Vector3 m_rotate; //! Rotation in degrees + AZ::Vector3 m_translate; //!< Translation in engine units (meters) + AZ::Vector3 m_legacyScale; //!< Legacy vector scale value, retained only for migration. + float m_uniformScale; //!< Single scale value applied uniformly. + AZ::Vector3 m_rotate; //!< Rotation in degrees bool m_locked; }; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/TransformScalePropertyHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/TransformScalePropertyHandler.cpp deleted file mode 100644 index 94d0113bcf..0000000000 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/TransformScalePropertyHandler.cpp +++ /dev/null @@ -1,82 +0,0 @@ -/* -* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -* its licensors. -* -* For complete copyright and license terms please see the LICENSE at the root of this -* distribution (the "License"). All use of this software is governed by the License, -* or, if provided, by the license below or the license accompanying this file. Do not -* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* -*/ - -#include "AzToolsFramework_precompiled.h" -#include -#include -#include - -namespace AzToolsFramework -{ - void RegisterTransformScaleHandler() - { - PropertyTypeRegistrationMessages::Bus::Broadcast(&PropertyTypeRegistrationMessages::RegisterPropertyType, aznew Components::TransformScalePropertyHandler()); - } - - namespace Components - { - AZ::u32 TransformScalePropertyHandler::GetHandlerName(void) const - { - return TransformScaleHandler; - } - - QWidget* TransformScalePropertyHandler::CreateGUI(QWidget* parent) - { - AzQtComponents::DoubleSpinBox* newCtrl = new AzQtComponents::DoubleSpinBox(parent); - connect(newCtrl, QOverload::of(&AzQtComponents::DoubleSpinBox::valueChanged), newCtrl, [newCtrl]() - { - AzToolsFramework::PropertyEditorGUIMessages::Bus::Broadcast(&AzToolsFramework::PropertyEditorGUIMessages::RequestWrite, newCtrl); - }); - - newCtrl->setMinimum(AZ::MinTransformScale); - newCtrl->setMaximum(AZ::MaxTransformScale); - - return newCtrl; - } - - void TransformScalePropertyHandler::ConsumeAttribute(AzQtComponents::DoubleSpinBox* GUI, AZ::u32 attrib, - AzToolsFramework::PropertyAttributeReader* attrValue, [[maybe_unused]] const char* debugName) - { - if (attrib == AZ::Edit::Attributes::Suffix) - { - AZStd::string label; - if (attrValue->Read(label)) - { - GUI->setSuffix(label.c_str()); - } - } - } - - void TransformScalePropertyHandler::WriteGUIValuesIntoProperty([[maybe_unused]] size_t index, AzQtComponents::DoubleSpinBox* GUI, - AZ::Vector3& instance, [[maybe_unused]] AzToolsFramework::InstanceDataNode* node) - { - const float value = aznumeric_cast(GUI->value()); - const float currentMaxElement = instance.GetMaxElement(); - if (currentMaxElement != 0.0f) - { - instance *= value / currentMaxElement; - } - else - { - instance = AZ::Vector3(value); - } - } - - bool TransformScalePropertyHandler::ReadValuesIntoGUI([[maybe_unused]] size_t index, AzQtComponents::DoubleSpinBox* GUI, - const AZ::Vector3& instance, [[maybe_unused]] AzToolsFramework::InstanceDataNode* node) - { - QSignalBlocker signalBlocker(GUI); - GUI->setValue(instance.GetMaxElement()); - return true; - } - } // namespace Components -} // namespace AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/TransformScalePropertyHandler.h b/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/TransformScalePropertyHandler.h deleted file mode 100644 index f13aa37904..0000000000 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/TransformScalePropertyHandler.h +++ /dev/null @@ -1,56 +0,0 @@ -/* -* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -* its licensors. -* -* For complete copyright and license terms please see the LICENSE at the root of this -* distribution (the "License"). All use of this software is governed by the License, -* or, if provided, by the license below or the license accompanying this file. Do not -* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* -*/ - -#pragma once - -#if !defined(Q_MOC_RUN) -#include -#include -#include -#endif - -namespace AzToolsFramework -{ - namespace Components - { - static const AZ::Crc32 TransformScaleHandler = AZ_CRC_CE("TransformScale"); - - //! Handler to allow the scale field inside the Transform Component to be represented as a single value in - //! the editor, but stored internally as a Vector3. - //! The purpose for this is to prevent any new entities being created with non-uniform scale on the Transform - //! Component, but preserve the data required for migrating any existing entities to use the Non-Uniform Scale - //! Component, until all migration work is completed. - //! The value shown in the editor will be the maximum value from the scale vector, and changing the value in - //! the editor will update the vector so that its maximum value matches the newly edited value, but its - //! components retain their existing proportion. - //! For example, if the current vector scale is (2, 3, 4), the value in the editor will appear as 4. If the value - //! in the editor is updated to 2, then the vector scale will update to (1, 1.5, 2), keeping the same proportion - //! between the x, y and z components. - class TransformScalePropertyHandler - : public QObject - , public AzToolsFramework::PropertyHandler - { - Q_OBJECT //AUTOMOC - public: - AZ_CLASS_ALLOCATOR(TransformScalePropertyHandler, AZ::SystemAllocator, 0); - - AZ::u32 GetHandlerName(void) const override; - QWidget* CreateGUI(QWidget* parent) override; - void ConsumeAttribute(AzQtComponents::DoubleSpinBox* GUI, AZ::u32 attrib, - AzToolsFramework::PropertyAttributeReader* attrValue, const char* debugName) override; - void WriteGUIValuesIntoProperty(size_t index, AzQtComponents::DoubleSpinBox* GUI, - AZ::Vector3& instance, AzToolsFramework::InstanceDataNode* node) override; - bool ReadValuesIntoGUI(size_t index, AzQtComponents::DoubleSpinBox* GUI, - const AZ::Vector3& instance, AzToolsFramework::InstanceDataNode* node) override; - }; - } // namespace Components -} // namespace AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerWidget.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerWidget.cpp index cc65b61908..8293614525 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerWidget.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerWidget.cpp @@ -172,7 +172,7 @@ namespace AzToolsFramework const int autoExpandDelayMilliseconds = 2500; m_gui->m_objectTree->setSelectionMode(QAbstractItemView::ExtendedSelection); - m_gui->m_objectTree->setEditTriggers(QAbstractItemView::EditKeyPressed); + SetDefaultTreeViewEditTriggers(); m_gui->m_objectTree->setAutoExpandDelay(autoExpandDelayMilliseconds); m_gui->m_objectTree->setDragEnabled(true); m_gui->m_objectTree->setDropIndicatorShown(true); @@ -850,6 +850,11 @@ namespace AzToolsFramework addAction(m_actionGoToEntitiesInViewport); } + void EntityOutlinerWidget::SetDefaultTreeViewEditTriggers() + { + m_gui->m_objectTree->setEditTriggers(QAbstractItemView::SelectedClicked | QAbstractItemView::EditKeyPressed); + } + void EntityOutlinerWidget::OnEntityPickModeStarted() { m_gui->m_objectTree->setDragEnabled(false); @@ -862,7 +867,7 @@ namespace AzToolsFramework { m_gui->m_objectTree->setDragEnabled(true); m_gui->m_objectTree->setSelectionMode(QAbstractItemView::ExtendedSelection); - m_gui->m_objectTree->setEditTriggers(QAbstractItemView::SelectedClicked | QAbstractItemView::DoubleClicked | QAbstractItemView::EditKeyPressed); + SetDefaultTreeViewEditTriggers(); m_inObjectPickMode = false; } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerWidget.hxx b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerWidget.hxx index 9a02febab2..6e3979a21e 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerWidget.hxx +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerWidget.hxx @@ -166,6 +166,8 @@ namespace AzToolsFramework // to a given entity void QueueScrollToNewContent(const AZ::EntityId& entityId) override; + void SetDefaultTreeViewEditTriggers(); + void ScrollToNewContent(); bool m_scrollToNewContentQueued; bool m_scrollToSelectedEntity; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp index 3edc190fb7..021b97a7dd 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp @@ -333,7 +333,7 @@ namespace AzToolsFramework } } - auto createPrefabOutcome = s_prefabPublicInterface->CreatePrefab(selectedEntities, s_prefabLoaderInterface->GetRelativePathToProject(prefabFilePath.data())); + auto createPrefabOutcome = s_prefabPublicInterface->CreatePrefab(selectedEntities, prefabFilePath.data()); if (!createPrefabOutcome.IsSuccess()) { diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyAssetCtrl.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyAssetCtrl.cpp index 23f8378df5..169a90497b 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyAssetCtrl.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyAssetCtrl.cpp @@ -777,6 +777,23 @@ namespace AzToolsFramework selection.SetDefaultDirectory(defaultDirectory); } + if (m_hideProductFilesInAssetPicker) + { + FilterConstType displayFilter = selection.GetDisplayFilter(); + + EntryTypeFilter* productsFilter = new EntryTypeFilter(); + productsFilter->SetEntryType(AssetBrowserEntry::AssetEntryType::Product); + + InverseFilter* noProductsFilter = new InverseFilter(); + noProductsFilter->SetFilter(FilterConstType(productsFilter)); + + CompositeFilter* compFilter = new CompositeFilter(CompositeFilter::LogicOperatorType::AND); + compFilter->AddFilter(FilterConstType(displayFilter)); + compFilter->AddFilter(FilterConstType(noProductsFilter)); + + selection.SetDisplayFilter(FilterConstType(compFilter)); + } + AssetBrowserComponentRequestBus::Broadcast(&AssetBrowserComponentRequests::PickAssets, selection, parentWidget()); if (selection.IsValid()) { @@ -936,11 +953,16 @@ namespace AzToolsFramework return; } - const AZ::Data::AssetId assetID = GetCurrentAssetID(); - m_currentAssetHint = ""; - - if (!m_unnamedType) + const AZStd::string& folderPath = GetFolderSelection(); + if (!folderPath.empty()) { + m_currentAssetHint = folderPath; + } + else + { + const AZ::Data::AssetId assetID = GetCurrentAssetID(); + m_currentAssetHint = ""; + AZ::Outcome jobOutcome = AZ::Failure(); AssetSystemJobRequestBus::BroadcastResult(jobOutcome, &AssetSystemJobRequestBus::Events::GetAssetJobsInfoByAssetID, assetID, false, false); @@ -954,7 +976,7 @@ namespace AzToolsFramework if (!jobs.empty()) { - // The default behavior is show to the source filename. + // The default behavior is to show the source filename. assetPath = jobs[0].m_sourceFile; AZStd::string errorLog; @@ -1172,6 +1194,16 @@ namespace AzToolsFramework return m_showProductAssetName; } + void PropertyAssetCtrl::SetHideProductFilesInAssetPicker(bool hide) + { + m_hideProductFilesInAssetPicker = hide; + } + + bool PropertyAssetCtrl::GetHideProductFilesInAssetPicker() const + { + return m_hideProductFilesInAssetPicker; + } + void PropertyAssetCtrl::SetShowThumbnail(bool enable) { m_showThumbnail = enable; @@ -1297,6 +1329,14 @@ namespace AzToolsFramework GUI->SetShowProductAssetName(showProductAssetName); } } + else if(attrib == AZ::Edit::Attributes::HideProductFilesInAssetPicker) + { + bool hideProductFilesInAssetPicker = false; + if (attrValue->Read(hideProductFilesInAssetPicker)) + { + GUI->SetHideProductFilesInAssetPicker(hideProductFilesInAssetPicker); + } + } else if (attrib == AZ::Edit::Attributes::ClearNotify) { PropertyAssetCtrl::ClearCallbackType* func = azdynamic_cast(attrValue->GetAttribute()); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyAssetCtrl.hxx b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyAssetCtrl.hxx index 37af3d0594..5a6310eb35 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyAssetCtrl.hxx +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyAssetCtrl.hxx @@ -158,6 +158,10 @@ namespace AzToolsFramework //! Assets can be either source or product assets generated from source assets. By default, source assets are shown in the property asset. You can override that with this flag. bool m_showProductAssetName = true; + //! Assets can be either source or product assets generated from source assets. + //! By default the asset picker shows both on an AZ::Asset<> property. You can hide product assets with this flag. + bool m_hideProductFilesInAssetPicker = false; + bool m_showThumbnail = false; bool m_showThumbnailDropDownButton = false; EditCallbackType* m_thumbnailCallback = nullptr; @@ -211,6 +215,9 @@ namespace AzToolsFramework void SetShowProductAssetName(bool enable); bool GetShowProductAssetName() const; + void SetHideProductFilesInAssetPicker(bool hide); + bool GetHideProductFilesInAssetPicker() const; + void SetShowThumbnail(bool enable); bool GetShowThumbnail() const; void SetShowThumbnailDropDownButton(bool enable); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyManagerComponent.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyManagerComponent.cpp index bd61e6ceed..6dc5bdd001 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyManagerComponent.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/PropertyManagerComponent.cpp @@ -16,7 +16,6 @@ #include #include #include -#include namespace AzToolsFramework { @@ -38,7 +37,6 @@ namespace AzToolsFramework void RegisterButtonPropertyHandlers(); void RegisterMultiLineEditHandler(); void RegisterCrcHandler(); - void RegisterTransformScaleHandler(); void ReflectPropertyEditor(AZ::ReflectContext* context); namespace Components @@ -192,7 +190,6 @@ namespace AzToolsFramework RegisterVectorHandlers(); RegisterButtonPropertyHandlers(); RegisterMultiLineEditHandler(); - RegisterTransformScaleHandler(); // GenericComboBoxHandlers RegisterGenericComboBoxHandler(); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp index 3507f532b5..91ac495e0e 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp @@ -2471,7 +2471,17 @@ namespace AzToolsFramework break; } }); - + + AddAction( + m_actions, { QKeySequence(Qt::Key_U) }, + /*ID_VIEWPORTUI_VISIBLE=*/50040, "Toggle ViewportUI", "Hide/Unhide Viewport UI", + [this]() + { + SetViewportUiClusterVisible(m_transformModeClusterId, !m_viewportUiVisible); + SetViewportUiClusterVisible(m_spaceCluster.m_spaceClusterId, !m_viewportUiVisible); + m_viewportUiVisible = !m_viewportUiVisible; + }); + EditorMenuRequestBus::Broadcast(&EditorMenuRequests::RestoreEditMenuToDefault); } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.h b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.h index 2bc4d7cbf6..2ec7fa2489 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.h @@ -306,6 +306,7 @@ namespace AzToolsFramework AzFramework::ClickDetector m_clickDetector; //!< Detect different types of mouse click. AzFramework::CursorState m_cursorState; //!< Track the mouse position and delta movement each frame. SpaceCluster m_spaceCluster; //!< Related viewport ui state for controlling the current reference space. + bool m_viewportUiVisible = true; //!< Used to hide/show the viewport ui elements. }; //! The ETCS (EntityTransformComponentSelection) namespace contains functions and data used exclusively by diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake b/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake index aaf5c86d33..8d0180f6ce 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake @@ -293,8 +293,6 @@ set(FILES ToolsComponents/TransformComponent.h ToolsComponents/TransformComponent.cpp ToolsComponents/TransformComponentBus.h - ToolsComponents/TransformScalePropertyHandler.cpp - ToolsComponents/TransformScalePropertyHandler.h ToolsComponents/ScriptEditorComponent.cpp ToolsComponents/ScriptEditorComponent.h ToolsComponents/ToolsAssetCatalogComponent.cpp diff --git a/Code/Framework/AzToolsFramework/Tests/Prefab/Benchmark/SpawnableCreateBenchmarks.cpp b/Code/Framework/AzToolsFramework/Tests/Prefab/Benchmark/SpawnableCreateBenchmarks.cpp index 821daaba82..8e4c2eaad7 100644 --- a/Code/Framework/AzToolsFramework/Tests/Prefab/Benchmark/SpawnableCreateBenchmarks.cpp +++ b/Code/Framework/AzToolsFramework/Tests/Prefab/Benchmark/SpawnableCreateBenchmarks.cpp @@ -34,7 +34,8 @@ namespace Benchmark { state.PauseTiming(); - auto spawnable = ::AzToolsFramework::Prefab::SpawnableUtils::CreateSpawnable(prefabDom); + AzFramework::Spawnable spawnable; + AzToolsFramework::Prefab::SpawnableUtils::CreateSpawnable(spawnable, prefabDom); state.ResumeTiming(); } diff --git a/Code/Framework/AzToolsFramework/Tests/Prefab/SpawnableCreateTests.cpp b/Code/Framework/AzToolsFramework/Tests/Prefab/SpawnableCreateTests.cpp index 2e1cd14b2a..af178ec7a4 100644 --- a/Code/Framework/AzToolsFramework/Tests/Prefab/SpawnableCreateTests.cpp +++ b/Code/Framework/AzToolsFramework/Tests/Prefab/SpawnableCreateTests.cpp @@ -40,7 +40,8 @@ namespace UnitTest //Create Spawnable auto& prefabDom = m_prefabSystemComponent->FindTemplateDom(instance->GetTemplateId()); - auto spawnable = ::AzToolsFramework::Prefab::SpawnableUtils::CreateSpawnable(prefabDom); + AzFramework::Spawnable spawnable; + AzToolsFramework::Prefab::SpawnableUtils::CreateSpawnable(spawnable, prefabDom); EXPECT_EQ(spawnable.GetEntities().size() - 1, normalEntityCount); // 1 for container entity const auto& spawnableEntities = spawnable.GetEntities(); @@ -84,7 +85,8 @@ namespace UnitTest //Create Spawnable auto& prefabDom = m_prefabSystemComponent->FindTemplateDom(thirdInstance->GetTemplateId()); - auto spawnable = ::AzToolsFramework::Prefab::SpawnableUtils::CreateSpawnable(prefabDom); + AzFramework::Spawnable spawnable; + AzToolsFramework::Prefab::SpawnableUtils::CreateSpawnable(spawnable, prefabDom); EXPECT_EQ(spawnable.GetEntities().size() - 1, normalEntityCount); // 1 for container entity const auto& spawnableEntities = spawnable.GetEntities(); diff --git a/Code/Framework/AzToolsFramework/Tests/SliceStabilityTests/SliceStabilityTestFramework.cpp b/Code/Framework/AzToolsFramework/Tests/SliceStabilityTests/SliceStabilityTestFramework.cpp index 8455e6d669..5dcdaa045c 100644 --- a/Code/Framework/AzToolsFramework/Tests/SliceStabilityTests/SliceStabilityTestFramework.cpp +++ b/Code/Framework/AzToolsFramework/Tests/SliceStabilityTests/SliceStabilityTestFramework.cpp @@ -141,7 +141,7 @@ namespace UnitTest // Set the new entity's transform to non zero values // This helps validate in comparison tests that the transform values of created entities persist during slice operations - entityTransform->SetLocalScale(AZ::Vector3(5, 5, 5)); + entityTransform->SetLocalUniformScale(5); entityTransform->SetLocalRotation(AZ::Vector3RadToDeg(AZ::Vector3(90, 90, 90))); entityTransform->SetLocalTranslation(AZ::Vector3(100, 100, 100)); diff --git a/Code/Framework/Tests/Spawnable/SpawnableEntitiesManagerTests.cpp b/Code/Framework/Tests/Spawnable/SpawnableEntitiesManagerTests.cpp index 1fa50fff52..484b7f46d7 100644 --- a/Code/Framework/Tests/Spawnable/SpawnableEntitiesManagerTests.cpp +++ b/Code/Framework/Tests/Spawnable/SpawnableEntitiesManagerTests.cpp @@ -55,7 +55,11 @@ namespace UnitTest delete m_ticket; m_ticket = nullptr; // One more tick on the spawnable entities manager in order to delete the ticket fully. - m_manager->ProcessQueue(); + while (m_manager->ProcessQueue( + AzFramework::SpawnableEntitiesManager::CommandQueuePriority::High | + AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular) != + AzFramework::SpawnableEntitiesManager::CommandQueueStatus::NoCommandsLeft) + ; delete m_spawnableAsset; m_spawnableAsset = nullptr; @@ -85,6 +89,10 @@ namespace UnitTest TestApplication* m_application { nullptr }; }; + // + // SpawnAllEntitities + // + TEST_F(SpawnableEntitiesManagerTest, SpawnAllEntities_Call_AllEntitiesSpawned) { static constexpr size_t NumEntities = 4; @@ -92,16 +100,72 @@ namespace UnitTest size_t spawnedEntitiesCount = 0; auto callback = - [&spawnedEntitiesCount](AzFramework::EntitySpawnTicket&, AzFramework::SpawnableConstEntityContainerView entities) + [&spawnedEntitiesCount](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities) { spawnedEntitiesCount += entities.size(); }; - m_manager->SpawnAllEntities(*m_ticket, {}, AZStd::move(callback)); - m_manager->ProcessQueue(); + m_manager->SpawnAllEntities(*m_ticket, AzFramework::SpawnablePriority_Default, {}, AZStd::move(callback)); + m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular); EXPECT_EQ(NumEntities, spawnedEntitiesCount); } + TEST_F(SpawnableEntitiesManagerTest, SpawnAllEntities_DeleteTicketBeforeCall_NoCrash) + { + { + AzFramework::EntitySpawnTicket ticket(*m_spawnableAsset); + m_manager->SpawnAllEntities(ticket, AzFramework::SpawnablePriority_Default); + } + m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular); + } + + + // + // SpawnEntities + // + + TEST_F(SpawnableEntitiesManagerTest, SpawnEntities_DeleteTicketBeforeCall_NoCrash) + { + { + AzFramework::EntitySpawnTicket ticket(*m_spawnableAsset); + m_manager->SpawnEntities(ticket, AzFramework::SpawnablePriority_Default, {}); + } + m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular); + } + + + // + // DespawnAllEntities + // + + TEST_F(SpawnableEntitiesManagerTest, DespawnAllEntities_DeleteTicketBeforeCall_NoCrash) + { + { + AzFramework::EntitySpawnTicket ticket(*m_spawnableAsset); + m_manager->DespawnAllEntities(ticket, AzFramework::SpawnablePriority_Default); + } + m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular); + } + + + // + // ReloadSpawnable + // + + TEST_F(SpawnableEntitiesManagerTest, ReloadSpawnable_DeleteTicketBeforeCall_NoCrash) + { + { + AzFramework::EntitySpawnTicket ticket(*m_spawnableAsset); + m_manager->ReloadSpawnable(ticket, AzFramework::SpawnablePriority_Default, *m_spawnableAsset); + } + m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular); + } + + + // + // ListEntitities + // + TEST_F(SpawnableEntitiesManagerTest, ListEntities_Call_AllEntitiesAreReported) { static constexpr size_t NumEntities = 4; @@ -110,7 +174,7 @@ namespace UnitTest bool allValidEntityIds = true; size_t spawnedEntitiesCount = 0; auto callback = [&allValidEntityIds, &spawnedEntitiesCount] - (AzFramework::EntitySpawnTicket&, AzFramework::SpawnableConstEntityContainerView entities) + (AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities) { for (auto&& entity : entities) { @@ -119,14 +183,30 @@ namespace UnitTest spawnedEntitiesCount += entities.size(); }; - m_manager->SpawnAllEntities(*m_ticket); - m_manager->ListEntities(*m_ticket, AZStd::move(callback)); - m_manager->ProcessQueue(); + m_manager->SpawnAllEntities(*m_ticket, AzFramework::SpawnablePriority_Default); + m_manager->ListEntities(*m_ticket, AzFramework::SpawnablePriority_Default, AZStd::move(callback)); + m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular); EXPECT_TRUE(allValidEntityIds); EXPECT_EQ(NumEntities, spawnedEntitiesCount); } + TEST_F(SpawnableEntitiesManagerTest, ListEntities_DeleteTicketBeforeCall_NoCrash) + { + auto callback = [](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView) {}; + + { + AzFramework::EntitySpawnTicket ticket(*m_spawnableAsset); + m_manager->ListEntities(ticket, AzFramework::SpawnablePriority_Default, AZStd::move(callback)); + } + m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular); + } + + + // + // ListIndicesAndEntities + // + TEST_F(SpawnableEntitiesManagerTest, ListIndicesAndEntities_Call_AllEntitiesAreReportedAndIncrementByOne) { static constexpr size_t NumEntities = 4; @@ -135,7 +215,7 @@ namespace UnitTest bool allValidEntityIds = true; size_t spawnedEntitiesCount = 0; auto callback = [&allValidEntityIds, &spawnedEntitiesCount] - (AzFramework::EntitySpawnTicket&, AzFramework::SpawnableConstIndexEntityContainerView entities) + (AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstIndexEntityContainerView entities) { for (auto&& indexEntityPair : entities) { @@ -148,11 +228,121 @@ namespace UnitTest } }; - m_manager->SpawnAllEntities(*m_ticket); - m_manager->ListIndicesAndEntities(*m_ticket, AZStd::move(callback)); - m_manager->ProcessQueue(); + m_manager->SpawnAllEntities(*m_ticket, AzFramework::SpawnablePriority_Default); + m_manager->ListIndicesAndEntities(*m_ticket, AzFramework::SpawnablePriority_Default, AZStd::move(callback)); + m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular); EXPECT_TRUE(allValidEntityIds); EXPECT_EQ(NumEntities, spawnedEntitiesCount); } + + TEST_F(SpawnableEntitiesManagerTest, ListIndicesAndEntities_DeleteTicketBeforeCall_NoCrash) + { + auto callback = [](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstIndexEntityContainerView) {}; + + { + AzFramework::EntitySpawnTicket ticket(*m_spawnableAsset); + m_manager->ListIndicesAndEntities(ticket, AzFramework::SpawnablePriority_Default, AZStd::move(callback)); + } + m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular); + } + + + // + // ClaimEntities + // + + TEST_F(SpawnableEntitiesManagerTest, ClaimEntities_DeleteTicketBeforeCall_NoCrash) + { + auto callback = [](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableEntityContainerView) {}; + + { + AzFramework::EntitySpawnTicket ticket(*m_spawnableAsset); + m_manager->ClaimEntities(ticket, AzFramework::SpawnablePriority_Default, AZStd::move(callback)); + } + m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular); + } + + + // + // Barrier + // + + TEST_F(SpawnableEntitiesManagerTest, Barrier_DeleteTicketBeforeCall_NoCrash) + { + auto callback = [](AzFramework::EntitySpawnTicket::Id) {}; + + { + AzFramework::EntitySpawnTicket ticket(*m_spawnableAsset); + m_manager->Barrier(ticket, AzFramework::SpawnablePriority_Default, AZStd::move(callback)); + } + m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular); + } + + + // + // Misc. - Priority tests + // + + TEST_F(SpawnableEntitiesManagerTest, Priority_HighBeforeDefault_HigherPriorityCallHappensBeforeDefaultPriorityEvenWhenQueuedLater) + { + static constexpr size_t NumEntities = 4; + FillSpawnable(NumEntities); + + AzFramework::EntitySpawnTicket highPriorityTicket(*m_spawnableAsset); + + size_t callCounter = 1; + size_t highPriorityCallId = 0; + size_t defaultPriorityCallId = 0; + auto highCallback = [&callCounter, &highPriorityCallId] + (AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView) + { + highPriorityCallId = callCounter++; + }; + auto defaultCallback = [&callCounter, &defaultPriorityCallId] + (AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView) + { + defaultPriorityCallId = callCounter++; + }; + + m_manager->SpawnAllEntities(*m_ticket, AzFramework::SpawnablePriority_Default, {}, AZStd::move(defaultCallback)); + m_manager->SpawnAllEntities(highPriorityTicket, AzFramework::SpawnablePriority_High, {}, AZStd::move(highCallback)); + m_manager->ProcessQueue( + AzFramework::SpawnableEntitiesManager::CommandQueuePriority::High | + AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular); + + EXPECT_LT(highPriorityCallId, defaultPriorityCallId); + } + + TEST_F(SpawnableEntitiesManagerTest, Priority_SameTicket_DefaultPriorityCallHappensBeforeHighPriority) + { + static constexpr size_t NumEntities = 4; + FillSpawnable(NumEntities); + + size_t callCounter = 1; + size_t highPriorityCallId = 0; + size_t defaultPriorityCallId = 0; + auto highCallback = + [&callCounter, &highPriorityCallId](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView) + { + highPriorityCallId = callCounter++; + }; + auto defaultCallback = + [&callCounter, &defaultPriorityCallId](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView) + { + defaultPriorityCallId = callCounter++; + }; + + m_manager->SpawnAllEntities(*m_ticket, AzFramework::SpawnablePriority_Default, {}, AZStd::move(defaultCallback)); + m_manager->SpawnAllEntities(*m_ticket, AzFramework::SpawnablePriority_High, {}, AZStd::move(highCallback)); + m_manager->ProcessQueue( + AzFramework::SpawnableEntitiesManager::CommandQueuePriority::High | + AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular); + // Run a second time as the high priority task will be pending at this point. + m_manager->ProcessQueue( + AzFramework::SpawnableEntitiesManager::CommandQueuePriority::High | + AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular); + + EXPECT_LT(defaultPriorityCallId, highPriorityCallId); + } } // namespace UnitTest diff --git a/Code/LauncherUnified/Launcher.cpp b/Code/LauncherUnified/Launcher.cpp index acb5cb9dba..c73cdd1981 100644 --- a/Code/LauncherUnified/Launcher.cpp +++ b/Code/LauncherUnified/Launcher.cpp @@ -504,8 +504,8 @@ namespace O3DELauncher const AZStd::string_view buildTargetName = GetBuildTargetName(); AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddBuildSystemTargetSpecialization(*settingsRegistry, buildTargetName); - AZ_TracePrintf("Launcher", R"(Running project "%.*s.)" "\n" - R"(The project name value has been successfully set in the Settings Registry at key "%s/project_name)" + AZ_TracePrintf("Launcher", R"(Running project "%.*s")" "\n" + R"(The project name has been successfully set in the Settings Registry at key "%s/project_name")" R"( for Launcher target "%.*s")" "\n", aznumeric_cast(launcherProjectName.size()), launcherProjectName.data(), AZ::SettingsRegistryMergeUtils::ProjectSettingsRootKey, @@ -659,7 +659,8 @@ namespace O3DELauncher if (gEnv && gEnv->pConsole) { // Execute autoexec.cfg to load the initial level - AZ::Interface::Get()->ExecuteConfigFile("autoexec.cfg"); + auto autoExecFile = AZ::IO::FixedMaxPath{pathToAssets} / "autoexec.cfg"; + AZ::Interface::Get()->ExecuteConfigFile(autoExecFile.Native()); // Find out if console command file was passed // via --console-command-file=%filename% and execute it diff --git a/Code/LauncherUnified/Platform/Windows/launcher_project_windows.cmake b/Code/LauncherUnified/Platform/Windows/launcher_project_windows.cmake index bcef59ec5a..35c89caf15 100644 --- a/Code/LauncherUnified/Platform/Windows/launcher_project_windows.cmake +++ b/Code/LauncherUnified/Platform/Windows/launcher_project_windows.cmake @@ -10,6 +10,11 @@ # set(ICON_FILE ${project_real_path}/Gem/Resources/GameSDK.ico) +if(NOT EXISTS ${ICON_FILE}) + # Try another project-relative path + set(ICON_FILE ${project_real_path}/Resources/GameSDK.ico) +endif() + if(NOT EXISTS ${ICON_FILE}) # Try the common LauncherUnified icon instead set(ICON_FILE Resources/GameSDK.ico) diff --git a/Code/LauncherUnified/launcher_generator.cmake b/Code/LauncherUnified/launcher_generator.cmake index edb6655411..c5d60eb29e 100644 --- a/Code/LauncherUnified/launcher_generator.cmake +++ b/Code/LauncherUnified/launcher_generator.cmake @@ -179,6 +179,7 @@ function(ly_delayed_generate_static_modules_inl) ${launcher_unified_binary_dir}/${project_name}.GameLauncher/Includes/StaticModules.inl ) + ly_target_link_libraries(${project_name}.GameLauncher PRIVATE ${all_game_gem_dependencies}) if(PAL_TRAIT_BUILD_SERVER_SUPPORTED) get_property(server_gem_dependencies GLOBAL PROPERTY LY_STATIC_MODULE_PROJECTS_DEPENDENCIES_${project_name}.ServerLauncher) @@ -204,6 +205,7 @@ function(ly_delayed_generate_static_modules_inl) ${launcher_unified_binary_dir}/${project_name}.ServerLauncher/Includes/StaticModules.inl ) + ly_target_link_libraries(${project_name}.ServerLauncher PRIVATE ${all_server_gem_dependencies}) endif() endforeach() endif() diff --git a/Code/Sandbox/Editor/AboutDialog.ui b/Code/Sandbox/Editor/AboutDialog.ui index 67767c0de1..0b86f15542 100644 --- a/Code/Sandbox/Editor/AboutDialog.ui +++ b/Code/Sandbox/Editor/AboutDialog.ui @@ -75,14 +75,14 @@ - 161 - 49 + 175 + 66 - 161 - 49 + 175 + 66 diff --git a/Code/Sandbox/Editor/CMakeLists.txt b/Code/Sandbox/Editor/CMakeLists.txt index 4706a61d08..01b58e3f77 100644 --- a/Code/Sandbox/Editor/CMakeLists.txt +++ b/Code/Sandbox/Editor/CMakeLists.txt @@ -129,6 +129,8 @@ ly_add_target( 3rdParty::AWSNativeSDK::Core 3rdParty::Qt::Network Legacy::EditorCore + RUNTIME_DEPENDENCIES + Gem::AtomViewportDisplayInfo ) ly_add_source_properties( SOURCES CryEdit.cpp @@ -175,6 +177,11 @@ ly_add_target( Legacy::EditorLib ProjectManager ) +set_property(SOURCE + CryEdit.cpp + APPEND PROPERTY + COMPILE_DEFINITIONS LY_CMAKE_TARGET="Editor" +) ly_add_translations( TARGETS Editor PREFIX Translations @@ -184,15 +191,8 @@ ly_add_translations( ) ly_add_dependencies(Editor AssetProcessor) -if(TARGET Editor) - set_property(SOURCE - CryEdit.cpp - APPEND PROPERTY - COMPILE_DEFINITIONS LY_CMAKE_TARGET="Editor" - ) -else() - message(FATAL_ERROR "Cannot set LY_CMAKE_TARGET define to Editor as the target doesn't exist anymore." - " Perhaps it has been renamed") +if(LY_FIRST_PROJECT_PATH) + set_property(TARGET Editor APPEND PROPERTY VS_DEBUGGER_COMMAND_ARGUMENTS "--project-path=\"${LY_FIRST_PROJECT_PATH}\"") endif() ################################################################################ @@ -244,7 +244,8 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) Legacy::CryCommon AZ::AzToolsFramework Legacy::EditorLib - Gem::LmbrCentral + RUNTIME_DEPENDENCIES + Gem::LmbrCentral ) ly_add_googletest( NAME Legacy::EditorLib.Tests diff --git a/Code/Sandbox/Editor/Core/LevelEditorMenuHandler.cpp b/Code/Sandbox/Editor/Core/LevelEditorMenuHandler.cpp index 0ce3fa55f5..8f6e927a84 100644 --- a/Code/Sandbox/Editor/Core/LevelEditorMenuHandler.cpp +++ b/Code/Sandbox/Editor/Core/LevelEditorMenuHandler.cpp @@ -421,17 +421,18 @@ QMenu* LevelEditorMenuHandler::CreateFileMenu() fileMenu.AddSeparator(); // Project Settings - auto projectSettingMenu = fileMenu.AddMenu(tr("Project Settings")); + fileMenu.AddAction(ID_FILE_PROJECT_MANAGER_SETTINGS); - // Project Settings Tool + // Platform Settings - Project Settings Tool // Shortcut must be set while adding the action otherwise it doesn't work - projectSettingMenu.Get()->addAction( + fileMenu.Get()->addAction( tr(LyViewPane::ProjectSettingsTool), []() { QtViewPaneManager::instance()->OpenPane(LyViewPane::ProjectSettingsTool); }, tr("Ctrl+Shift+P")); - projectSettingMenu.AddSeparator(); - + fileMenu.AddSeparator(); + fileMenu.AddAction(ID_FILE_PROJECT_MANAGER_NEW); + fileMenu.AddAction(ID_FILE_PROJECT_MANAGER_OPEN); fileMenu.AddSeparator(); // NEWMENUS: NEEDS IMPLEMENTATION diff --git a/Code/Sandbox/Editor/CryEdit.cpp b/Code/Sandbox/Editor/CryEdit.cpp index a0ff5d7eff..c723e6049a 100644 --- a/Code/Sandbox/Editor/CryEdit.cpp +++ b/Code/Sandbox/Editor/CryEdit.cpp @@ -58,6 +58,7 @@ AZ_POP_DISABLE_WARNING #include #include #include +#include // AzToolsFramework #include @@ -280,6 +281,8 @@ BOOL CCryDocManager::DoPromptFileName(QString& fileName, [[maybe_unused]] UINT n [[maybe_unused]] DWORD lFlags, BOOL bOpenFileDialog, [[maybe_unused]] CDocTemplate* pTemplate) { CLevelFileDialog levelFileDialog(bOpenFileDialog); + levelFileDialog.show(); + levelFileDialog.adjustSize(); if (levelFileDialog.exec() == QDialog::Accepted) { @@ -477,6 +480,11 @@ void CCryEditApp::RegisterActionHandlers() ON_COMMAND(ID_FILE_SAVE_LEVEL, OnFileSave) ON_COMMAND(ID_FILE_EXPORTOCCLUSIONMESH, OnFileExportOcclusionMesh) + + // Project Manager + ON_COMMAND(ID_FILE_PROJECT_MANAGER_SETTINGS, OnOpenProjectManagerSettings) + ON_COMMAND(ID_FILE_PROJECT_MANAGER_NEW, OnOpenProjectManagerNew) + ON_COMMAND(ID_FILE_PROJECT_MANAGER_OPEN, OnOpenProjectManager) } CCryEditApp* CCryEditApp::s_currentInstance = nullptr; @@ -2073,6 +2081,8 @@ void CCryEditApp::OnDocumentationAWSSupport() void CCryEditApp::OnDocumentationFeedback() { FeedbackDialog dialog; + dialog.show(); + dialog.adjustSize(); dialog.exec(); } @@ -2854,6 +2864,34 @@ void CCryEditApp::OnPreferences() */ } +void CCryEditApp::OnOpenProjectManagerSettings() +{ + OpenProjectManager("UpdateProject"); +} + +void CCryEditApp::OnOpenProjectManagerNew() +{ + OpenProjectManager("CreateProject"); +} + +void CCryEditApp::OnOpenProjectManager() +{ + OpenProjectManager("Projects"); +} + +void CCryEditApp::OpenProjectManager(const AZStd::string& screen) +{ + // provide the current project path for in case we want to update the project + AZ::IO::FixedMaxPathString projectPath = AZ::Utils::GetProjectPath(); + const AZStd::string commandLineOptions = AZStd::string::format(" --screen %s --project_path %s", screen.c_str(), projectPath.c_str()); + bool launchSuccess = AzFramework::ProjectManager::LaunchProjectManager(commandLineOptions); + if (!launchSuccess) + { + QMessageBox::critical(AzToolsFramework::GetActiveWindow(), QObject::tr("Failed to launch O3DE Project Manager"), QObject::tr("Failed to find or start the O3dE Project Manager")); + } +} + + ////////////////////////////////////////////////////////////////////////// void CCryEditApp::OnUndo() { @@ -3313,6 +3351,8 @@ void CCryEditApp::OnCreateSlice() void CCryEditApp::OnOpenLevel() { CLevelFileDialog levelFileDialog(true); + levelFileDialog.show(); + levelFileDialog.adjustSize(); if (levelFileDialog.exec() == QDialog::Accepted) { diff --git a/Code/Sandbox/Editor/CryEdit.h b/Code/Sandbox/Editor/CryEdit.h index d4c1304b6a..dc4f015faf 100644 --- a/Code/Sandbox/Editor/CryEdit.h +++ b/Code/Sandbox/Editor/CryEdit.h @@ -229,6 +229,9 @@ public: void OnFileResaveSlices(); void OnFileEditEditorini(); void OnPreferences(); + void OnOpenProjectManagerSettings(); + void OnOpenProjectManagerNew(); + void OnOpenProjectManager(); void OnRedo(); void OnUpdateRedo(QAction* action); void OnUpdateUndo(QAction* action); @@ -366,6 +369,7 @@ private: AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING friend struct PythonTestOutputHandler; + void OpenProjectManager(const AZStd::string& screen); void OnWireframe(); void OnUpdateWireframe(QAction* action); void OnViewConfigureLayout(); diff --git a/Code/Sandbox/Editor/EditorViewportWidget.cpp b/Code/Sandbox/Editor/EditorViewportWidget.cpp index ecd11da817..24d1590808 100644 --- a/Code/Sandbox/Editor/EditorViewportWidget.cpp +++ b/Code/Sandbox/Editor/EditorViewportWidget.cpp @@ -463,16 +463,20 @@ void EditorViewportWidget::Update() m_renderViewport->GetViewportContext()->SetCameraTransform(LYTransformToAZTransform(m_Camera.GetMatrix())); } - AZ::Matrix4x4 clipMatrix; - AZ::MakePerspectiveFovMatrixRH( - clipMatrix, - m_Camera.GetFov(), - aznumeric_cast(width()) / aznumeric_cast(height()), - m_Camera.GetNearPlane(), - m_Camera.GetFarPlane(), - true - ); - m_renderViewport->GetViewportContext()->SetCameraProjectionMatrix(clipMatrix); + // Don't override the game mode FOV + if (!GetIEditor()->IsInGameMode()) + { + AZ::Matrix4x4 clipMatrix; + AZ::MakePerspectiveFovMatrixRH( + clipMatrix, + GetFOV(), + aznumeric_cast(width()) / aznumeric_cast(height()), + m_Camera.GetNearPlane(), + m_Camera.GetFarPlane(), + true + ); + m_renderViewport->GetViewportContext()->SetCameraProjectionMatrix(clipMatrix); + } m_updatingCameraPosition = false; @@ -870,6 +874,13 @@ void EditorViewportWidget::OnBeginPrepareRender() int w = m_rcClient.width(); int h = m_rcClient.height(); + // Don't bother doing an FOV calculation if we don't have a valid viewport + // This prevents frustum calculation bugs with a null viewport + if (w <= 1 || h <= 1) + { + return; + } + float fov = gSettings.viewports.fDefaultFov; // match viewport fov to default / selected title menu fov @@ -1221,50 +1232,73 @@ void EditorViewportWidget::SetViewportId(int id) AzFramework::ReloadCameraKeyBindings(); auto controller = AZStd::make_shared(); - controller->SetCameraListBuilderCallback([](AzFramework::Cameras& cameras) - { - auto firstPersonRotateCamera = AZStd::make_shared(AzFramework::CameraFreeLookButton); - auto firstPersonPanCamera = - AZStd::make_shared(AzFramework::CameraFreePanButton, AzFramework::LookPan); - auto firstPersonTranslateCamera = AZStd::make_shared(AzFramework::LookTranslation); - auto firstPersonWheelCamera = AZStd::make_shared(); + controller->SetCameraListBuilderCallback( + [](AzFramework::Cameras& cameras) + { + auto firstPersonRotateCamera = AZStd::make_shared(AzFramework::CameraFreeLookButton); + auto firstPersonPanCamera = + AZStd::make_shared(AzFramework::CameraFreePanButton, AzFramework::LookPan); + auto firstPersonTranslateCamera = AZStd::make_shared(AzFramework::LookTranslation); + auto firstPersonWheelCamera = AZStd::make_shared(); - auto orbitCamera = AZStd::make_shared(); - orbitCamera->SetLookAtFn([]() -> AZStd::optional { - AZStd::optional manipulatorTransform; - AzToolsFramework::EditorTransformComponentSelectionRequestBus::EventResult( - manipulatorTransform, AzToolsFramework::GetEntityContextId(), - &AzToolsFramework::EditorTransformComponentSelectionRequestBus::Events::GetManipulatorTransform); + auto orbitCamera = AZStd::make_shared(); + orbitCamera->SetLookAtFn( + [](const AZ::Vector3& position, const AZ::Vector3& direction) -> AZStd::optional + { + AZStd::optional manipulatorTransform; + AzToolsFramework::EditorTransformComponentSelectionRequestBus::EventResult( + manipulatorTransform, AzToolsFramework::GetEntityContextId(), + &AzToolsFramework::EditorTransformComponentSelectionRequestBus::Events::GetManipulatorTransform); - if (manipulatorTransform) - { - return manipulatorTransform->GetTranslation(); - } + // initially attempt to use manipulator transform if one exists (there is a selection) + if (manipulatorTransform) + { + return manipulatorTransform->GetTranslation(); + } - return {}; + const float RayDistance = 1000.0f; + AzFramework::RenderGeometry::RayRequest ray; + ray.m_startWorldPosition = position; + ray.m_endWorldPosition = position + direction * RayDistance; + ray.m_onlyVisible = true; + + AzFramework::RenderGeometry::RayResult renderGeometryIntersectionResult; + AzFramework::RenderGeometry::IntersectorBus::EventResult( + renderGeometryIntersectionResult, AzToolsFramework::GetEntityContextId(), + &AzFramework::RenderGeometry::IntersectorInterface::RayIntersect, ray); + + // attempt a ray intersection with any visible mesh and return the intersection position if successful + if (renderGeometryIntersectionResult) + { + return renderGeometryIntersectionResult.m_worldPosition; + } + + // if there is no selection or no intersection, fallback to default camera orbit behavior (ground plane + // intersection) + return {}; + }); + + auto orbitRotateCamera = AZStd::make_shared(AzFramework::CameraOrbitLookButton); + auto orbitTranslateCamera = AZStd::make_shared(AzFramework::OrbitTranslation); + auto orbitDollyWheelCamera = AZStd::make_shared(); + auto orbitDollyMoveCamera = + AZStd::make_shared(AzFramework::CameraOrbitDollyButton); + auto orbitPanCamera = + AZStd::make_shared(AzFramework::CameraOrbitPanButton, AzFramework::OrbitPan); + + orbitCamera->m_orbitCameras.AddCamera(orbitRotateCamera); + orbitCamera->m_orbitCameras.AddCamera(orbitTranslateCamera); + orbitCamera->m_orbitCameras.AddCamera(orbitDollyWheelCamera); + orbitCamera->m_orbitCameras.AddCamera(orbitDollyMoveCamera); + orbitCamera->m_orbitCameras.AddCamera(orbitPanCamera); + + cameras.AddCamera(firstPersonRotateCamera); + cameras.AddCamera(firstPersonPanCamera); + cameras.AddCamera(firstPersonTranslateCamera); + cameras.AddCamera(firstPersonWheelCamera); + cameras.AddCamera(orbitCamera); }); - auto orbitRotateCamera = AZStd::make_shared(AzFramework::CameraOrbitLookButton); - auto orbitTranslateCamera = AZStd::make_shared(AzFramework::OrbitTranslation); - auto orbitDollyWheelCamera = AZStd::make_shared(); - auto orbitDollyMoveCamera = - AZStd::make_shared(AzFramework::CameraOrbitDollyButton); - auto orbitPanCamera = - AZStd::make_shared(AzFramework::CameraOrbitPanButton, AzFramework::OrbitPan); - - orbitCamera->m_orbitCameras.AddCamera(orbitRotateCamera); - orbitCamera->m_orbitCameras.AddCamera(orbitTranslateCamera); - orbitCamera->m_orbitCameras.AddCamera(orbitDollyWheelCamera); - orbitCamera->m_orbitCameras.AddCamera(orbitDollyMoveCamera); - orbitCamera->m_orbitCameras.AddCamera(orbitPanCamera); - - cameras.AddCamera(firstPersonRotateCamera); - cameras.AddCamera(firstPersonPanCamera); - cameras.AddCamera(firstPersonTranslateCamera); - cameras.AddCamera(firstPersonWheelCamera); - cameras.AddCamera(orbitCamera); - }); - m_renderViewport->GetControllerList()->Add(controller); } else @@ -1759,9 +1793,6 @@ void EditorViewportWidget::SetViewTM(const Matrix34& viewTM, bool bMoveOnly) cameraObject->SetWorldTM(camMatrix * AZMatrix3x3ToLYMatrix3x3(lookThroughEntityCorrection)); } } - - using namespace AzToolsFramework; - ComponentEntityObjectRequestBus::Event(cameraObject, &ComponentEntityObjectRequestBus::Events::UpdatePreemptiveUndoCache); } else if (m_viewEntityId.IsValid()) { diff --git a/Code/Sandbox/Editor/LyViewPaneNames.h b/Code/Sandbox/Editor/LyViewPaneNames.h index e95191ce06..b94cda3c52 100644 --- a/Code/Sandbox/Editor/LyViewPaneNames.h +++ b/Code/Sandbox/Editor/LyViewPaneNames.h @@ -30,7 +30,7 @@ namespace LyViewPane static const char* const EntityInspector = "Entity Inspector"; static const char* const EntityInspectorPinned = "Pinned Entity Inspector"; static const char* const LevelInspector = "Level Inspector"; - static const char* const ProjectSettingsTool = "Project Settings Tool"; + static const char* const ProjectSettingsTool = "Edit Platform Settings..."; static const char* const ErrorReport = "Error Report"; static const char* const Console = "Console"; static const char* const ConsoleMenuName = "&Console"; diff --git a/Code/Sandbox/Editor/MainWindow.cpp b/Code/Sandbox/Editor/MainWindow.cpp index 8086293207..9e983c3593 100644 --- a/Code/Sandbox/Editor/MainWindow.cpp +++ b/Code/Sandbox/Editor/MainWindow.cpp @@ -748,6 +748,9 @@ void MainWindow::InitActions() am->AddAction(ID_FILE_EXPORTOCCLUSIONMESH, tr("Export Occlusion Mesh")); am->AddAction(ID_FILE_EDITLOGFILE, tr("Show Log File")); am->AddAction(ID_FILE_RESAVESLICES, tr("Resave All Slices")); + am->AddAction(ID_FILE_PROJECT_MANAGER_SETTINGS, tr("Edit Project Settings...")); + am->AddAction(ID_FILE_PROJECT_MANAGER_NEW, tr("New Project...")); + am->AddAction(ID_FILE_PROJECT_MANAGER_OPEN, tr("Open Project...")); am->AddAction(ID_GAME_PC_ENABLEVERYHIGHSPEC, tr("Very High")).SetCheckable(true) .RegisterUpdateCallback(cryEdit, &CCryEditApp::OnUpdateGameSpec); am->AddAction(ID_GAME_PC_ENABLEHIGHSPEC, tr("High")).SetCheckable(true) diff --git a/Code/Sandbox/Editor/RenderViewport.cpp b/Code/Sandbox/Editor/RenderViewport.cpp index f199590e4f..8cef9e802c 100644 --- a/Code/Sandbox/Editor/RenderViewport.cpp +++ b/Code/Sandbox/Editor/RenderViewport.cpp @@ -1259,9 +1259,6 @@ CBaseObject* CRenderViewport::GetCameraObject() const ////////////////////////////////////////////////////////////////////////// void CRenderViewport::OnEditorNotifyEvent(EEditorNotifyEvent event) { - static ICVar* outputToHMD = gEnv->pConsole->GetCVar("output_to_hmd"); - AZ_Assert(outputToHMD, "cvar output_to_hmd is undeclared"); - switch (event) { case eNotify_OnBeginGameMode: @@ -1282,7 +1279,6 @@ void CRenderViewport::OnEditorNotifyEvent(EEditorNotifyEvent event) if (deviceInfo) { - outputToHMD->Set(1); m_previousContext = SetCurrentContext(deviceInfo->renderWidth, deviceInfo->renderHeight); if (m_renderer->GetIStereoRenderer()) { @@ -1313,10 +1309,6 @@ void CRenderViewport::OnEditorNotifyEvent(EEditorNotifyEvent event) // failed to set the context back when done, or set it back to the wrong one. CryWarning(VALIDATOR_MODULE_3DENGINE, VALIDATOR_WARNING, "RenderViewport render context was not correctly restored by someone else."); } - if (gSettings.bEnableGameModeVR) - { - outputToHMD->Set(0); - } RestorePreviousContext(m_previousContext); m_bInRotateMode = false; m_bInMoveMode = false; diff --git a/Code/Sandbox/Editor/Resource.h b/Code/Sandbox/Editor/Resource.h index 31fc9909f1..98a6e56d14 100644 --- a/Code/Sandbox/Editor/Resource.h +++ b/Code/Sandbox/Editor/Resource.h @@ -313,6 +313,9 @@ #define ID_CREATE_LEVEL_FG_MODULE_FROM_SELECTION 35077 #define ID_GRAPHVIEW_ADD_BLACK_BOX 35078 #define ID_GRAPHVIEW_UNGROUP 35079 +#define ID_FILE_PROJECT_MANAGER_NEW 35080 +#define ID_FILE_PROJECT_MANAGER_OPEN 35081 +#define ID_FILE_PROJECT_MANAGER_SETTINGS 35082 #define ID_TV_TRACKS_TOOLBAR_BASE 35083 // range between ID_TV_TRACKS_TOOLBAR_BASE to ID_TV_TRACKS_TOOLBAR_LAST reserved #define ID_TV_TRACKS_TOOLBAR_LAST 35183 // for up to 100 "Add Tracks..." dynamically added Track View Track buttons #define ID_OPEN_TERRAIN_EDITOR 36007 @@ -366,3 +369,4 @@ #define ID_TOOLBAR_WIDGET_SPACER_RIGHT 50013 #define ID_TOOLBAR_WIDGET_PLAYCONSOLE_LABEL 50014 #define ID_TOOLBAR_WIDGET_LAST 50020 +#define ID_VIEWPORTUI_VISIBLE 50040 diff --git a/Code/Sandbox/Editor/StartupLogoDialog.ui b/Code/Sandbox/Editor/StartupLogoDialog.ui index 6e01808a84..0815fa8b18 100644 --- a/Code/Sandbox/Editor/StartupLogoDialog.ui +++ b/Code/Sandbox/Editor/StartupLogoDialog.ui @@ -42,14 +42,14 @@ - 161 - 49 + 175 + 66 - 161 - 50 + 175 + 66 diff --git a/Code/Sandbox/Editor/TrackView/TrackViewAnimNode.cpp b/Code/Sandbox/Editor/TrackView/TrackViewAnimNode.cpp index ae7077b4fc..35306b9535 100644 --- a/Code/Sandbox/Editor/TrackView/TrackViewAnimNode.cpp +++ b/Code/Sandbox/Editor/TrackView/TrackViewAnimNode.cpp @@ -2012,9 +2012,9 @@ void CTrackViewAnimNode::SetPosRotScaleTracksDefaultValues(bool positionAllowed, } if (scaleAllowed) { - AZ::Vector3 scale = AZ::Vector3::CreateOne(); - AZ::TransformBus::EventResult(scale, entityId, &AZ::TransformBus::Events::GetWorldScale); - m_animNode->SetScale(time, AZVec3ToLYVec3(scale)); + float scale = 1.0f; + AZ::TransformBus::EventResult(scale, entityId, &AZ::TransformBus::Events::GetWorldUniformScale); + m_animNode->SetScale(time, Vec3(scale, scale, scale)); } } } diff --git a/Code/Sandbox/Editor/TrackView/TrackViewSequence.cpp b/Code/Sandbox/Editor/TrackView/TrackViewSequence.cpp index f915c804f8..d26c8fd973 100644 --- a/Code/Sandbox/Editor/TrackView/TrackViewSequence.cpp +++ b/Code/Sandbox/Editor/TrackView/TrackViewSequence.cpp @@ -828,7 +828,7 @@ void CTrackViewSequence::SyncSelectedTracksToBase() const Vec3 scale = pAnimNode->GetScale(); AZ::Transform transform = AZ::Transform::CreateIdentity(); - transform.SetScale(LYVec3ToAZVec3(scale)); + transform.SetUniformScale(LYVec3ToAZVec3(scale).GetMaxElement()); transform.SetRotation(LYQuaternionToAZQuaternion(rotation)); transform.SetTranslation(LYVec3ToAZVec3(position)); @@ -870,7 +870,7 @@ void CTrackViewSequence::SyncSelectedTracksFromBase() pAnimNode->SetPos(AZVec3ToLYVec3(transform.GetTranslation())); pAnimNode->SetRotation(AZQuaternionToLYQuaternion(transform.GetRotation())); - pAnimNode->SetScale(AZVec3ToLYVec3(transform.GetScale())); + pAnimNode->SetScale(AZVec3ToLYVec3(AZ::Vector3(transform.GetUniformScale()))); bNothingWasSynced = false; } diff --git a/Code/Sandbox/Editor/o3de_logo.svg b/Code/Sandbox/Editor/o3de_logo.svg index ba44566ce8..35a880c5c8 100644 --- a/Code/Sandbox/Editor/o3de_logo.svg +++ b/Code/Sandbox/Editor/o3de_logo.svg @@ -1,22 +1,35 @@ - - Group 12 - - - - - - - - - - - - - - - - + + Artboard + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + \ No newline at end of file diff --git a/Code/Sandbox/Plugins/ComponentEntityEditorPlugin/CMakeLists.txt b/Code/Sandbox/Plugins/ComponentEntityEditorPlugin/CMakeLists.txt index d02c656b8f..53e04e1dee 100644 --- a/Code/Sandbox/Plugins/ComponentEntityEditorPlugin/CMakeLists.txt +++ b/Code/Sandbox/Plugins/ComponentEntityEditorPlugin/CMakeLists.txt @@ -35,10 +35,12 @@ ly_add_target( AZ::AzToolsFramework Legacy::CryCommon Legacy::EditorLib - Gem::LmbrCentral AZ::AtomCore Gem::Atom_RPI.Public Gem::AtomToolsFramework.Static + Gem::LmbrCentral.Editor + RUNTIME_DEPENDENCIES + Gem::LmbrCentral.Editor ) ly_add_dependencies(Editor ComponentEntityEditorPlugin) @@ -68,7 +70,9 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) AZ::AzToolsFrameworkTestCommon Legacy::CryCommon Legacy::EditorLib - Gem::LmbrCentral + Gem::LmbrCentral.Editor + RUNTIME_DEPENDENCIES + Gem::LmbrCentral.Editor ) ly_add_googletest( NAME Legacy::ComponentEntityEditorPlugin.Tests diff --git a/Code/Tools/Android/ProjectBuilder/build.gradle.in b/Code/Tools/Android/ProjectBuilder/build.gradle.in index 66f58294ab..5980984516 100644 --- a/Code/Tools/Android/ProjectBuilder/build.gradle.in +++ b/Code/Tools/Android/ProjectBuilder/build.gradle.in @@ -15,14 +15,14 @@ android { ${SIGNING_CONFIGS} compileSdkVersion sdkVer buildToolsVersion buildToolsVer - + ndkVersion ndkPlatformVer lintOptions { abortOnError false checkReleaseBuilds false } defaultConfig { - minSdkVersion ndkPlatformVer + minSdkVersion minSdkVer targetSdkVersion sdkVer ${NATIVE_CMAKE_SECTION_DEFAULT_CONFIG} } diff --git a/Code/Tools/Android/ProjectBuilder/local.properties.in b/Code/Tools/Android/ProjectBuilder/local.properties.in index 559ea67bcb..4e82cb2940 100644 --- a/Code/Tools/Android/ProjectBuilder/local.properties.in +++ b/Code/Tools/Android/ProjectBuilder/local.properties.in @@ -16,6 +16,5 @@ # For customization when using a Version Control System, please read the # header note. # ${GENERATION_TIMESTAMP} -ndk.dir=${ANDROID_NDK_PATH} sdk.dir=${ANDROID_SDK_PATH} ${CMAKE_DIR_LINE} diff --git a/Code/Tools/Android/ProjectBuilder/root.build.gradle.in b/Code/Tools/Android/ProjectBuilder/root.build.gradle.in index 782a1f26b5..dfce99c3c7 100644 --- a/Code/Tools/Android/ProjectBuilder/root.build.gradle.in +++ b/Code/Tools/Android/ProjectBuilder/root.build.gradle.in @@ -12,10 +12,9 @@ buildscript { repositories { google() jcenter() - } dependencies { - classpath 'com.android.tools.build:gradle:3.6.4' + classpath 'com.android.tools.build:gradle:${ANDROID_GRADLE_PLUGIN_VERSION}' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files @@ -26,14 +25,14 @@ allprojects { repositories { google() jcenter() - } } subprojects { ext { + minSdkVer = ${MIN_SDK_VER} sdkVer = ${SDK_VER} - ndkPlatformVer = ${NDK_PLATFORM_VER} + ndkPlatformVer = '${NDK_VERSION}' buildToolsVer = '${SDK_BUILD_TOOL_VER}' lyEngineRoot = '${LY_ENGINE_ROOT}' } diff --git a/Code/Tools/AssetProcessor/CMakeLists.txt b/Code/Tools/AssetProcessor/CMakeLists.txt index 5d4980eed4..6c12ab6024 100644 --- a/Code/Tools/AssetProcessor/CMakeLists.txt +++ b/Code/Tools/AssetProcessor/CMakeLists.txt @@ -125,6 +125,10 @@ ly_add_target( AZ::AssetProcessorBatch.Static ) +if(LY_FIRST_PROJECT_PATH) + set_property(TARGET AssetProcessor AssetProcessorBatch APPEND PROPERTY VS_DEBUGGER_COMMAND_ARGUMENTS "--project-path=\"${LY_FIRST_PROJECT_PATH}\"") +endif() + # Adds the AssetProcessorBatch target as a C preprocessor define so that it can be used as a Settings Registry # specialization in order to look up the generated .setreg which contains the dependencies # specified for the target. diff --git a/Code/Tools/AssetProcessor/native/ui/ProductAssetTreeModel.cpp b/Code/Tools/AssetProcessor/native/ui/ProductAssetTreeModel.cpp index 5efef8166c..68ba64ea1a 100644 --- a/Code/Tools/AssetProcessor/native/ui/ProductAssetTreeModel.cpp +++ b/Code/Tools/AssetProcessor/native/ui/ProductAssetTreeModel.cpp @@ -14,6 +14,7 @@ #include "ProductAssetTreeItemData.h" #include +#include #include namespace AssetProcessor @@ -159,31 +160,33 @@ namespace AssetProcessor return; } + AZ::IO::Path productNamePath(product.m_productName, AZ::IO::PosixPathSeparator); - AZStd::vector tokens; - AzFramework::StringFunc::Tokenize(product.m_productName.c_str(), tokens, AZ_CORRECT_DATABASE_SEPARATOR, false, true); - - if (tokens.empty()) + if (productNamePath.empty()) { AZ_Warning("AssetProcessor", false, "Product id %d has an invalid name: %s", product.m_productID, product.m_productName.c_str()); return; } AssetTreeItem* parentItem = m_root.get(); - AZStd::string fullFolderName; - for (int i = 0; i < tokens.size() - 1; ++i) + AZ::IO::Path currentFullFolderPath; + const AZ::IO::PathView filename = productNamePath.Filename(); + const AZ::IO::PathView fullPathWithoutFilename = productNamePath.RemoveFilename(); + AZStd::fixed_string currentPath; + for (auto pathIt = fullPathWithoutFilename.begin(); pathIt != fullPathWithoutFilename.end(); ++pathIt) { - AzFramework::StringFunc::AssetDatabasePath::Join(fullFolderName.c_str(), tokens[i].c_str(), fullFolderName); - AssetTreeItem* nextParent = parentItem->GetChildFolder(tokens[i].c_str()); + currentPath = pathIt->FixedMaxPathString(); + currentFullFolderPath /= currentPath; + AssetTreeItem* nextParent = parentItem->GetChildFolder(currentPath.c_str()); if (!nextParent) { if (!modelIsResetting) { - QModelIndex parentIndex = parentItem == m_root.get() ? QModelIndex() : createIndex(parentItem->GetRow(), 0, parentItem); + QModelIndex parentIndex = createIndex(parentItem->GetRow(), 0, parentItem); beginInsertRows(parentIndex, parentItem->getChildCount(), parentItem->getChildCount()); } - nextParent = parentItem->CreateChild(ProductAssetTreeItemData::MakeShared(nullptr, fullFolderName, tokens[i].c_str(), true, AZ::Uuid::CreateNull())); - m_productToTreeItem[fullFolderName] = nextParent; + nextParent = parentItem->CreateChild(ProductAssetTreeItemData::MakeShared(nullptr, currentFullFolderPath.Native(), currentPath.c_str(), true, AZ::Uuid::CreateNull())); + m_productToTreeItem[currentFullFolderPath.Native()] = nextParent; // m_productIdToTreeItem is not used for folders, folders don't have product IDs. if (!modelIsResetting) @@ -205,12 +208,12 @@ namespace AssetProcessor if (!modelIsResetting) { - QModelIndex parentIndex = parentItem == m_root.get() ? QModelIndex() : createIndex(parentItem->GetRow(), 0, parentItem); + QModelIndex parentIndex = createIndex(parentItem->GetRow(), 0, parentItem); beginInsertRows(parentIndex, parentItem->getChildCount(), parentItem->getChildCount()); } AZStd::shared_ptr productItemData = - ProductAssetTreeItemData::MakeShared(&product, product.m_productName, tokens[tokens.size() - 1].c_str(), false, sourceId); + ProductAssetTreeItemData::MakeShared(&product, product.m_productName, AZStd::fixed_string(filename.Native()).c_str(), false, sourceId); m_productToTreeItem[product.m_productName] = parentItem->CreateChild(productItemData); m_productIdToTreeItem[product.m_productID] = m_productToTreeItem[product.m_productName]; diff --git a/Code/Tools/AssetProcessor/native/ui/SourceAssetTreeModel.cpp b/Code/Tools/AssetProcessor/native/ui/SourceAssetTreeModel.cpp index dda0a58837..69e60f6733 100644 --- a/Code/Tools/AssetProcessor/native/ui/SourceAssetTreeModel.cpp +++ b/Code/Tools/AssetProcessor/native/ui/SourceAssetTreeModel.cpp @@ -63,8 +63,7 @@ namespace AssetProcessor } - auto fullPath = AZ::IO::Path(scanFolder.m_scanFolder) / source.m_sourceName; - + AZ::IO::Path fullPath = AZ::IO::Path(scanFolder.m_scanFolder, AZ::IO::PosixPathSeparator) / source.m_sourceName; // It's common for Open 3D Engine game projects and scan folders to be in a subfolder // of the engine install. To improve readability of the source files, strip out @@ -78,34 +77,35 @@ namespace AssetProcessor AzFramework::StringFunc::Replace(fullPath.Native(), m_assetRoot.absolutePath().toUtf8(), ""); } - - AZStd::vector tokens; - AzFramework::StringFunc::Tokenize(fullPath.c_str(), tokens, AZ_CORRECT_DATABASE_SEPARATOR, false, true); - - if (tokens.empty()) + if (fullPath.empty()) { - AZ_Warning("AssetProcessor", false, "Source id %s has an invalid name: %s", - source.m_sourceGuid.ToString().c_str(), source.m_sourceName.c_str()); + AZ_Warning( + "AssetProcessor", false, "Source id %s has an invalid name: %s", source.m_sourceGuid.ToString().c_str(), + source.m_sourceName.c_str()); return; } QModelIndex newIndicesStart; AssetTreeItem* parentItem = m_root.get(); - AZStd::string fullFolderName; - for (int i = 0; i < tokens.size() - 1; ++i) + AZ::IO::Path currentFullFolderPath; + const AZ::IO::PathView filename = fullPath.Filename(); + const AZ::IO::PathView fullPathWithoutFilename = fullPath.RemoveFilename(); + AZStd::fixed_string currentPath; + for (auto pathIt = fullPathWithoutFilename.begin(); pathIt != fullPathWithoutFilename.end(); ++pathIt) { - AzFramework::StringFunc::AssetDatabasePath::Join(fullFolderName.c_str(), tokens[i].c_str(), fullFolderName); - AssetTreeItem* nextParent = parentItem->GetChildFolder(tokens[i].c_str()); + currentPath = pathIt->FixedMaxPathString(); + currentFullFolderPath /= currentPath; + AssetTreeItem* nextParent = parentItem->GetChildFolder(currentPath.c_str()); if (!nextParent) { if (!modelIsResetting) { - QModelIndex parentIndex = parentItem == m_root.get() ? QModelIndex() : createIndex(parentItem->GetRow(), 0, parentItem); + QModelIndex parentIndex = createIndex(parentItem->GetRow(), 0, parentItem); beginInsertRows(parentIndex, parentItem->getChildCount(), parentItem->getChildCount()); } - nextParent = parentItem->CreateChild(SourceAssetTreeItemData::MakeShared(nullptr, nullptr, fullFolderName, tokens[i].c_str(), true)); - m_sourceToTreeItem[fullFolderName] = nextParent; + nextParent = parentItem->CreateChild(SourceAssetTreeItemData::MakeShared(nullptr, nullptr, currentFullFolderPath.Native(), currentPath.c_str(), true)); + m_sourceToTreeItem[currentFullFolderPath.Native()] = nextParent; // Folders don't have source IDs, don't add to m_sourceIdToTreeItem if (!modelIsResetting) { @@ -117,12 +117,12 @@ namespace AssetProcessor if (!modelIsResetting) { - QModelIndex parentIndex = parentItem == m_root.get() ? QModelIndex() : createIndex(parentItem->GetRow(), 0, parentItem); + QModelIndex parentIndex = createIndex(parentItem->GetRow(), 0, parentItem); beginInsertRows(parentIndex, parentItem->getChildCount(), parentItem->getChildCount()); } m_sourceToTreeItem[source.m_sourceName] = - parentItem->CreateChild(SourceAssetTreeItemData::MakeShared(&source, &scanFolder, source.m_sourceName, tokens[tokens.size() - 1].c_str(), false)); + parentItem->CreateChild(SourceAssetTreeItemData::MakeShared(&source, &scanFolder, source.m_sourceName, AZStd::fixed_string(filename.Native()).c_str(), false)); m_sourceIdToTreeItem[source.m_sourceID] = m_sourceToTreeItem[source.m_sourceName]; if (!modelIsResetting) { diff --git a/Code/Tools/ProjectManager/CMakeLists.txt b/Code/Tools/ProjectManager/CMakeLists.txt index e2b5aaf696..aeb7be9793 100644 --- a/Code/Tools/ProjectManager/CMakeLists.txt +++ b/Code/Tools/ProjectManager/CMakeLists.txt @@ -25,7 +25,6 @@ ly_add_target( OUTPUT_NAME o3de NAMESPACE AZ AUTOMOC - AUTOUIC AUTORCC FILES_CMAKE project_manager_files.cmake @@ -37,11 +36,8 @@ ly_add_target( PRIVATE PY_PACKAGE="${python_package_name}" INCLUDE_DIRECTORIES - PUBLIC - . PRIVATE Source - BUILD_DEPENDENCIES PRIVATE 3rdParty::Qt::Core diff --git a/Code/Tools/ProjectManager/Resources/ProjectManager.qss b/Code/Tools/ProjectManager/Resources/ProjectManager.qss index 5eb92964dd..224574f522 100644 --- a/Code/Tools/ProjectManager/Resources/ProjectManager.qss +++ b/Code/Tools/ProjectManager/Resources/ProjectManager.qss @@ -232,6 +232,18 @@ QTabBar::tab:pressed margin-left:30px; } +#projectSettingsTab::tab-bar { + left: 60px; +} + +#projectSettingsTabBar::tab { + height:50px; +} + +#projectSettingsTopFrame { + background-color:#1E252F; +} + /************** Projects **************/ #firstTimeContent > #titleLabel { font-size:60px; @@ -336,4 +348,31 @@ QTabBar::tab:pressed max-width:210px;; min-height:278px; max-height:278px; -} \ No newline at end of file +} + +/************** Gem Catalog **************/ + +#GemCatalogTitle { + font-size: 18px; +} + +/************** Gem Catalog (Inspector) **************/ + +#GemCatalogInspector { + background-color: #444444; +} + +/************** Gem Catalog (Filter/left pane) **************/ + +#GemCatalogFilterWidget { + background-color: #444444; +} + +#GemCatalogHeaderWidget { + background-color: #1E252F; +} + +#GemCatalogFilterCategoryTitle { + font-size: 12px; + font-weight: 600; +} diff --git a/Code/Tools/ProjectManager/Source/CreateProjectCtrl.cpp b/Code/Tools/ProjectManager/Source/CreateProjectCtrl.cpp index 69f0a3983d..85e34aeced 100644 --- a/Code/Tools/ProjectManager/Source/CreateProjectCtrl.cpp +++ b/Code/Tools/ProjectManager/Source/CreateProjectCtrl.cpp @@ -15,7 +15,6 @@ #include #include #include -#include #include #include @@ -42,9 +41,10 @@ namespace O3DE::ProjectManager m_stack = new QStackedWidget(this); m_stack->setObjectName("body"); - m_stack->setSizePolicy(QSizePolicy(QSizePolicy::Preferred,QSizePolicy::Expanding)); + m_stack->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding)); m_stack->addWidget(new NewProjectSettingsScreen()); - m_stack->addWidget(new GemCatalogScreen()); + m_gemCatalog = new GemCatalogScreen(); + m_stack->addWidget(m_gemCatalog); vLayout->addWidget(m_stack); QDialogButtonBox* backNextButtons = new QDialogButtonBox(); @@ -67,6 +67,15 @@ namespace O3DE::ProjectManager return ProjectManagerScreen::CreateProject; } + void CreateProjectCtrl::NotifyCurrentScreen() + { + ScreenWidget* currentScreen = reinterpret_cast(m_stack->currentWidget()); + if (currentScreen) + { + currentScreen->NotifyCurrentScreen(); + } + } + void CreateProjectCtrl::HandleBackButton() { if (m_stack->currentIndex() > 0) @@ -79,6 +88,7 @@ namespace O3DE::ProjectManager emit GotoPreviousScreenRequest(); } } + void CreateProjectCtrl::HandleNextButton() { ScreenWidget* currentScreen = reinterpret_cast(m_stack->currentWidget()); @@ -97,6 +107,9 @@ namespace O3DE::ProjectManager m_projectInfo = newProjectScreen->GetProjectInfo(); m_projectTemplatePath = newProjectScreen->GetProjectTemplatePath(); + + // The next page is the gem catalog. Gather the available gems that will be shown in the gem catalog. + m_gemCatalog->ReinitForProject(m_projectInfo.m_path, /*isNewProject=*/true); } } @@ -110,6 +123,9 @@ namespace O3DE::ProjectManager auto result = PythonBindingsInterface::Get()->CreateProject(m_projectTemplatePath, m_projectInfo); if (result.IsSuccess()) { + // automatically register the project + PythonBindingsInterface::Get()->AddProject(m_projectInfo.m_path); + // adding gems is not implemented yet because we don't know what targets to add or how to add them emit ChangeScreenRequest(ProjectManagerScreen::Projects); } @@ -117,6 +133,9 @@ namespace O3DE::ProjectManager { QMessageBox::critical(this, tr("Project creation failed"), tr("Failed to create project.")); } + + // Enable/disable gems for the newly created project. + m_gemCatalog->EnableDisableGemsForProject(m_projectInfo.m_path); } } diff --git a/Code/Tools/ProjectManager/Source/CreateProjectCtrl.h b/Code/Tools/ProjectManager/Source/CreateProjectCtrl.h index 01e3349b21..89d18a9ebc 100644 --- a/Code/Tools/ProjectManager/Source/CreateProjectCtrl.h +++ b/Code/Tools/ProjectManager/Source/CreateProjectCtrl.h @@ -14,6 +14,7 @@ #if !defined(Q_MOC_RUN) #include #include +#include #endif QT_FORWARD_DECLARE_CLASS(QStackedWidget) @@ -31,6 +32,7 @@ namespace O3DE::ProjectManager explicit CreateProjectCtrl(QWidget* parent = nullptr); ~CreateProjectCtrl() = default; ProjectManagerScreen GetScreenEnum() override; + void NotifyCurrentScreen() override; protected slots: void HandleBackButton(); @@ -47,6 +49,8 @@ namespace O3DE::ProjectManager QString m_projectTemplatePath; ProjectInfo m_projectInfo; + + GemCatalogScreen* m_gemCatalog = nullptr; }; } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/FormLineEditWidget.cpp b/Code/Tools/ProjectManager/Source/FormLineEditWidget.cpp index 7ef7e3c7d8..6c08393910 100644 --- a/Code/Tools/ProjectManager/Source/FormLineEditWidget.cpp +++ b/Code/Tools/ProjectManager/Source/FormLineEditWidget.cpp @@ -78,6 +78,14 @@ namespace O3DE::ProjectManager m_errorLabel->setText(labelText); } + void FormLineEditWidget::setErrorLabelVisible(bool visible) + { + m_errorLabel->setVisible(visible); + m_frame->setProperty("Valid", !visible); + + refreshStyle(); + } + QLineEdit* FormLineEditWidget::lineEdit() const { return m_lineEdit; diff --git a/Code/Tools/ProjectManager/Source/FormLineEditWidget.h b/Code/Tools/ProjectManager/Source/FormLineEditWidget.h index 3094442cbd..76534f46f7 100644 --- a/Code/Tools/ProjectManager/Source/FormLineEditWidget.h +++ b/Code/Tools/ProjectManager/Source/FormLineEditWidget.h @@ -39,6 +39,7 @@ namespace O3DE::ProjectManager //! Set the error message for to display when invalid. void setErrorLabelText(const QString& labelText); + void setErrorLabelVisible(bool visible); //! Returns a pointer to the underlying LineEdit. QLineEdit* lineEdit() const; diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.cpp index 6e9ad42017..6402121e4a 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.cpp @@ -25,10 +25,12 @@ namespace O3DE::ProjectManager hLayout->setMargin(0); setLayout(hLayout); - setStyleSheet("background-color: #1E252F;"); + setObjectName("GemCatalogHeaderWidget"); + + hLayout->addSpacing(7); QLabel* titleLabel = new QLabel(tr("Gem Catalog")); - titleLabel->setStyleSheet("font-size: 21px;"); + titleLabel->setObjectName("GemCatalogTitle"); hLayout->addWidget(titleLabel); hLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding)); @@ -42,7 +44,7 @@ namespace O3DE::ProjectManager hLayout->addWidget(filterLineEdit); hLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding)); - hLayout->addSpacerItem(new QSpacerItem(220, 0, QSizePolicy::Fixed)); + hLayout->addSpacerItem(new QSpacerItem(140, 0, QSizePolicy::Fixed)); setFixedHeight(60); } diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp index 2d243e7f8b..aa36c1b0ab 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp @@ -15,13 +15,12 @@ #include #include #include -#include #include #include #include #include - -//#define USE_TESTGEMDATA +#include +#include namespace O3DE::ProjectManager { @@ -29,47 +28,32 @@ namespace O3DE::ProjectManager : ScreenWidget(parent) { m_gemModel = new GemModel(this); - GemSortFilterProxyModel* proxyModel = new GemSortFilterProxyModel(m_gemModel, this); + m_proxModel = new GemSortFilterProxyModel(m_gemModel, this); QVBoxLayout* vLayout = new QVBoxLayout(); vLayout->setMargin(0); vLayout->setSpacing(0); setLayout(vLayout); - GemCatalogHeaderWidget* headerWidget = new GemCatalogHeaderWidget(proxyModel); + GemCatalogHeaderWidget* headerWidget = new GemCatalogHeaderWidget(m_proxModel); vLayout->addWidget(headerWidget); QHBoxLayout* hLayout = new QHBoxLayout(); hLayout->setMargin(0); vLayout->addLayout(hLayout); - m_gemListView = new GemListView(proxyModel, proxyModel->GetSelectionModel(), this); + m_gemListView = new GemListView(m_proxModel, m_proxModel->GetSelectionModel(), this); m_gemInspector = new GemInspector(m_gemModel, this); - m_gemInspector->setFixedWidth(320); + m_gemInspector->setFixedWidth(240); - // Start: Temporary gem test data -#ifdef USE_TESTGEMDATA - QVector testGemData = GenerateTestData(); - for (const GemInfo& gemInfo : testGemData) - { - m_gemModel->AddGem(gemInfo); - } -#else - // End: Temporary gem test data - auto result = PythonBindingsInterface::Get()->GetGems(); - if (result.IsSuccess()) - { - for (auto gemInfo : result.GetValue()) - { - m_gemModel->AddGem(gemInfo); - } - } -#endif + QWidget* filterWidget = new QWidget(this); + filterWidget->setFixedWidth(240); + m_filterWidgetLayout = new QVBoxLayout(); + m_filterWidgetLayout->setMargin(0); + m_filterWidgetLayout->setSpacing(0); + filterWidget->setLayout(m_filterWidgetLayout); - GemFilterWidget* filterWidget = new GemFilterWidget(proxyModel); - filterWidget->setFixedWidth(250); - - GemListHeaderWidget* listHeaderWidget = new GemListHeaderWidget(proxyModel); + GemListHeaderWidget* listHeaderWidget = new GemListHeaderWidget(m_proxModel); QVBoxLayout* middleVLayout = new QVBoxLayout(); middleVLayout->setMargin(0); @@ -80,98 +64,111 @@ namespace O3DE::ProjectManager hLayout->addWidget(filterWidget); hLayout->addLayout(middleVLayout); hLayout->addWidget(m_gemInspector); - - proxyModel->InvalidateFilter(); } - QVector GemCatalogScreen::GenerateTestData() + void GemCatalogScreen::ReinitForProject(const QString& projectPath, bool isNewProject) { - QVector result; + m_gemModel->clear(); + FillModel(projectPath, isNewProject); - GemInfo gem("EMotion FX", - "O3DE Foundation", - "EMFX is a real-time character animation system. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", - (GemInfo::Android | GemInfo::iOS | GemInfo::macOS | GemInfo::Windows | GemInfo::Linux), - true); - gem.m_directoryLink = "C:/"; - gem.m_documentationLink = "http://www.amazon.com"; - gem.m_dependingGemUuids = QStringList({"EMotionFX", "Atom"}); - gem.m_conflictingGemUuids = QStringList({"Vegetation", "Camera", "ScriptCanvas", "CloudCanvas", "Networking"}); - gem.m_types = (GemInfo::Code | GemInfo::Asset); - gem.m_version = "v1.01"; - gem.m_lastUpdatedDate = "24th April 2021"; - gem.m_binarySizeInKB = 40; - gem.m_features = QStringList({"Animation", "Assets", "Physics"}); - gem.m_gemOrigin = GemInfo::O3DEFoundation; - result.push_back(gem); + if (m_filterWidget) + { + m_filterWidget->hide(); + m_filterWidget->deleteLater(); + } - gem.m_name = "Atom"; - gem.m_creator = "O3DE Seattle"; - gem.m_summary = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."; - gem.m_platforms = (GemInfo::Android | GemInfo::Windows | GemInfo::Linux | GemInfo::macOS); - gem.m_isAdded = true; - gem.m_directoryLink = "C:/"; - gem.m_documentationLink = "https://aws.amazon.com/gametech/"; - gem.m_dependingGemUuids = QStringList({"EMotionFX", "Core", "AudioSystem", "Camera", "Particles"}); - gem.m_conflictingGemUuids = QStringList({"CloudCanvas", "NovaNet"}); - gem.m_version = "v2.31"; - gem.m_lastUpdatedDate = "24th November 2020"; - gem.m_features = QStringList({"Assets", "Rendering", "UI", "VR", "Debug", "Environment"}); - gem.m_binarySizeInKB = 2087; - result.push_back(gem); + m_filterWidget = new GemFilterWidget(m_proxModel); + m_filterWidgetLayout->addWidget(m_filterWidget); - gem.m_name = "Physics"; - gem.m_creator = "O3DE London"; - gem.m_summary = "Lorem ipsum dolor sit amet, consectetur adipiscing elit."; - gem.m_platforms = (GemInfo::Android | GemInfo::Linux | GemInfo::macOS); - gem.m_isAdded = true; - gem.m_directoryLink = "C:/"; - gem.m_documentationLink = "https://aws.amazon.com/gametech/"; - gem.m_dependingGemUuids = QStringList({"GraphCanvas", "ExpressionEvaluation", "UI Lib", "Multiplayer", "GameStateSamples"}); - gem.m_conflictingGemUuids = QStringList({"Cloud Canvas", "EMotion FX", "Streaming", "MessagePopup", "Cloth", "Graph Canvas", "Twitch Integration"}); - gem.m_version = "v1.5.102145"; - gem.m_lastUpdatedDate = "1st January 2021"; - gem.m_binarySizeInKB = 2000000; - gem.m_features = QStringList({"Physics", "Gameplay", "Debug", "Assets"}); - result.push_back(gem); + m_proxModel->InvalidateFilter(); - result.push_back(O3DE::ProjectManager::GemInfo("Certificate Manager", - "O3DE Irvine", - "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", - GemInfo::Windows, - false)); + // 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); + }); + } - result.push_back(O3DE::ProjectManager::GemInfo("Cloud Gem Framework", - "O3DE Seattle", - "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", - GemInfo::iOS | GemInfo::Linux, - false)); + void GemCatalogScreen::FillModel(const QString& projectPath, bool isNewProject) + { + AZ::Outcome, AZStd::string> allGemInfosResult; + if (isNewProject) + { + allGemInfosResult = PythonBindingsInterface::Get()->GetEngineGemInfos(); + } + else + { + allGemInfosResult = PythonBindingsInterface::Get()->GetAllGemInfos(projectPath); + } - result.push_back(O3DE::ProjectManager::GemInfo("Cloud Gem Core", - "O3DE Foundation", - "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", - GemInfo::Android | GemInfo::Windows | GemInfo::Linux, - true)); + if (allGemInfosResult.IsSuccess()) + { + // Add all available gems to the model. + const QVector allGemInfos = allGemInfosResult.GetValue(); + for (const GemInfo& gemInfo : allGemInfos) + { + m_gemModel->AddGem(gemInfo); + } - result.push_back(O3DE::ProjectManager::GemInfo("Gestures", - "O3DE Foundation", - "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", - GemInfo::Android | GemInfo::Windows | GemInfo::Linux, - false)); + // Gather enabled gems for the given project. + auto enabledGemNamesResult = PythonBindingsInterface::Get()->GetEnabledGemNames(projectPath); + if (enabledGemNamesResult.IsSuccess()) + { + const QVector enabledGemNames = enabledGemNamesResult.GetValue(); + for (const AZStd::string& enabledGemName : enabledGemNames) + { + const QModelIndex modelIndex = m_gemModel->FindIndexByNameString(enabledGemName.c_str()); + if (modelIndex.isValid()) + { + GemModel::SetWasPreviouslyAdded(*m_gemModel, modelIndex, true); + GemModel::SetIsAdded(*m_gemModel, modelIndex, true); + } + else + { + AZ_Warning("ProjectManager::GemCatalog", false, + "Cannot find entry for gem with name '%s'. The CMake target name probably does not match the specified name in the gem.json.", + enabledGemName.c_str()); + } + } + } + else + { + QMessageBox::critical(nullptr, tr("Operation failed"), QString("Cannot retrieve enabled gems for project %1.\n\nError:\n%2").arg(projectPath, enabledGemNamesResult.GetError().c_str())); + } + } + else + { + QMessageBox::critical(nullptr, tr("Operation failed"), QString("Cannot retrieve gems for %1.\n\nError:\n%2").arg(projectPath, allGemInfosResult.GetError().c_str())); + } + } - result.push_back(O3DE::ProjectManager::GemInfo("Effects System", - "O3DE Foundation", - "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", - GemInfo::Android | GemInfo::Windows | GemInfo::Linux, - true)); + void GemCatalogScreen::EnableDisableGemsForProject(const QString& projectPath) + { + IPythonBindings* pythonBindings = PythonBindingsInterface::Get(); + QVector toBeAdded = m_gemModel->GatherGemsToBeAdded(); + QVector toBeRemoved = m_gemModel->GatherGemsToBeRemoved(); - result.push_back(O3DE::ProjectManager::GemInfo("Microphone", - "O3DE Foundation", - "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus euismod ligula vitae dui dictum, a sodales dolor luctus. Sed id elit dapibus, finibus neque sed, efficitur mi. Nam facilisis ligula at eleifend pellentesque. Praesent non ex consectetur, blandit tellus in, venenatis lacus. Duis nec neque in urna ullamcorper euismod id eu leo. Nam efficitur dolor sed odio vehicula venenatis. Suspendisse nec est non velit commodo cursus in sit amet dui. Ut bibendum nisl et libero hendrerit dapibus. Vestibulum ultrices ullamcorper urna, placerat porttitor est lobortis in. Interdum et malesuada fames ac ante ipsum primis in faucibus. Integer a magna ac tellus sollicitudin porttitor. Phasellus lobortis viverra justo id bibendum. Etiam ac pharetra risus. Nulla vitae justo nibh. Nulla viverra leo et molestie interdum. Duis sit amet bibendum nulla, sit amet vehicula augue.", - GemInfo::Android | GemInfo::Windows | GemInfo::Linux, - false)); + for (const QModelIndex& modelIndex : toBeAdded) + { + const QString gemPath = GemModel::GetPath(modelIndex); + const AZ::Outcome result = pythonBindings->AddGemToProject(gemPath, projectPath); + if (!result.IsSuccess()) + { + QMessageBox::critical(nullptr, "Operation failed", + QString("Cannot add gem %1 to project.\n\nError:\n%2").arg(GemModel::GetName(modelIndex), result.GetError().c_str())); + } + } - return result; + for (const QModelIndex& modelIndex : toBeRemoved) + { + const QString gemPath = GemModel::GetPath(modelIndex); + const AZ::Outcome result = pythonBindings->RemoveGemFromProject(gemPath, projectPath); + if (!result.IsSuccess()) + { + QMessageBox::critical(nullptr, "Operation failed", + QString("Cannot remove gem %1 from project.\n\nError:\n%2").arg(GemModel::GetName(modelIndex), result.GetError().c_str())); + } + } } ProjectManagerScreen GemCatalogScreen::GetScreenEnum() diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h index 44e0727c7e..0847d9b74e 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h @@ -14,9 +14,11 @@ #if !defined(Q_MOC_RUN) #include +#include #include #include #include +#include #endif namespace O3DE::ProjectManager @@ -29,11 +31,17 @@ namespace O3DE::ProjectManager ~GemCatalogScreen() = default; ProjectManagerScreen GetScreenEnum() override; + void ReinitForProject(const QString& projectPath, bool isNewProject); + void EnableDisableGemsForProject(const QString& projectPath); + private: - QVector GenerateTestData(); + void FillModel(const QString& projectPath, bool isNewProject); GemListView* m_gemListView = nullptr; GemInspector* m_gemInspector = nullptr; GemModel* m_gemModel = nullptr; + GemSortFilterProxyModel* m_proxModel = nullptr; + QVBoxLayout* m_filterWidgetLayout = nullptr; + GemFilterWidget* m_filterWidget = nullptr; }; } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemFilterWidget.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemFilterWidget.cpp index 3ece7760cf..a6a4e95ff9 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemFilterWidget.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemFilterWidget.cpp @@ -43,7 +43,6 @@ namespace O3DE::ProjectManager m_collapseButton->setFlat(true); m_collapseButton->setFocusPolicy(Qt::NoFocus); m_collapseButton->setFixedWidth(s_collapseButtonSize); - m_collapseButton->setStyleSheet("border: 0px; border-radius: 0px;"); connect(m_collapseButton, &QPushButton::clicked, this, [=]() { UpdateCollapseState(); @@ -52,7 +51,7 @@ namespace O3DE::ProjectManager // Category title QLabel* headerLabel = new QLabel(header); - headerLabel->setStyleSheet("font-size: 11pt;"); + headerLabel->setObjectName("GemCatalogFilterCategoryTitle"); collapseLayout->addWidget(headerLabel); vLayout->addLayout(collapseLayout); @@ -79,14 +78,14 @@ namespace O3DE::ProjectManager elementWidget->setLayout(elementLayout); QCheckBox* checkbox = new QCheckBox(elementNames[i]); - checkbox->setStyleSheet("font-size: 11pt;"); + checkbox->setStyleSheet("font-size: 12px;"); m_buttonGroup->addButton(checkbox); elementLayout->addWidget(checkbox); elementLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding)); QLabel* countLabel = new QLabel(QString::number(elementCounts[i])); - countLabel->setStyleSheet("font-size: 11pt; background-color: #333333; border-radius: 3px; color: #94D2FF;"); + countLabel->setStyleSheet("font-size: 12px; background-color: #333333; border-radius: 3px; color: #94D2FF;"); elementLayout->addWidget(countLabel); m_elementWidgets.push_back(elementWidget); @@ -110,6 +109,8 @@ namespace O3DE::ProjectManager } } + vLayout->addSpacing(5); + // Separating line QFrame* hLine = new QFrame(); hLine->setFrameShape(QFrame::HLine); @@ -181,6 +182,8 @@ namespace O3DE::ProjectManager : QScrollArea(parent) , m_filterProxyModel(filterProxyModel) { + setObjectName("GemCatalogFilterWidget"); + m_gemModel = m_filterProxyModel->GetSourceModel(); setWidgetResizable(true); @@ -195,7 +198,7 @@ namespace O3DE::ProjectManager mainWidget->setLayout(m_mainLayout); QLabel* filterByLabel = new QLabel("Filter by"); - filterByLabel->setStyleSheet("font-size: 15pt;"); + filterByLabel->setStyleSheet("font-size: 16px;"); m_mainLayout->addWidget(filterByLabel); AddGemOriginFilter(); diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.cpp index 791085f47a..c96d31c3c2 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.cpp @@ -25,7 +25,7 @@ namespace O3DE::ProjectManager bool GemInfo::IsValid() const { - return !m_path.isEmpty() && !m_uuid.IsNull(); + return !m_name.isEmpty() && !m_path.isEmpty(); } QString GemInfo::GetPlatformString(Platform platform) @@ -79,4 +79,9 @@ namespace O3DE::ProjectManager { return (m_platforms & platform); } + + bool GemInfo::operator<(const GemInfo& gemInfo) const + { + return (m_displayName < gemInfo.m_displayName); + } } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h index 06b0adad32..722783ece1 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h @@ -61,10 +61,11 @@ namespace O3DE::ProjectManager bool IsValid() const; + bool operator<(const GemInfo& gemInfo) const; + QString m_path; QString m_name = "Unknown Gem Name"; QString m_displayName = "Unknown Gem Name"; - AZ::Uuid m_uuid; QString m_creator = "Unknown Creator"; GemOrigin m_gemOrigin = Local; bool m_isAdded = false; //! Is the gem currently added and enabled in the project? diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.cpp index 6276ddc996..3ecc18231e 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.cpp @@ -23,6 +23,7 @@ namespace O3DE::ProjectManager : QScrollArea(parent) , m_model(model) { + setObjectName("GemCatalogInspector"); setWidgetResizable(true); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); @@ -85,7 +86,7 @@ namespace O3DE::ProjectManager QLabel* GemInspector::CreateStyledLabel(QLayout* layout, int fontSize, const QString& colorCodeString) { QLabel* result = new QLabel(); - result->setStyleSheet(QString("font-size: %1pt; color: %2;").arg(QString::number(fontSize), colorCodeString)); + result->setStyleSheet(QString("font-size: %1px; color: %2;").arg(QString::number(fontSize), colorCodeString)); layout->addWidget(result); return result; } @@ -93,13 +94,13 @@ namespace O3DE::ProjectManager void GemInspector::InitMainWidget() { // Gem name, creator and summary - m_nameLabel = CreateStyledLabel(m_mainLayout, 17, s_headerColor); + m_nameLabel = CreateStyledLabel(m_mainLayout, 18, s_headerColor); m_creatorLabel = CreateStyledLabel(m_mainLayout, 12, s_creatorColor); m_mainLayout->addSpacing(5); // TODO: QLabel seems to have issues determining the right sizeHint() for our font with the given font size. // This results into squeezed elements in the layout in case the text is a little longer than a sentence. - m_summaryLabel = new QLabel();//CreateLabel(m_mainLayout, 12, s_textColor); + m_summaryLabel = CreateStyledLabel(m_mainLayout, 12, s_textColor); m_mainLayout->addWidget(m_summaryLabel); m_summaryLabel->setWordWrap(true); m_mainLayout->addSpacing(5); @@ -146,9 +147,9 @@ namespace O3DE::ProjectManager QLabel* additionalInfoLabel = CreateStyledLabel(m_mainLayout, 14, s_headerColor); additionalInfoLabel->setText("Additional Information"); - m_versionLabel = CreateStyledLabel(m_mainLayout, 11, s_textColor); - m_lastUpdatedLabel = CreateStyledLabel(m_mainLayout, 11, s_textColor); - m_binarySizeLabel = CreateStyledLabel(m_mainLayout, 11, s_textColor); + m_versionLabel = CreateStyledLabel(m_mainLayout, 12, s_textColor); + m_lastUpdatedLabel = CreateStyledLabel(m_mainLayout, 12, s_textColor); + m_binarySizeLabel = CreateStyledLabel(m_mainLayout, 12, s_textColor); } GemInspector::GemsSubWidget::GemsSubWidget(QWidget* parent) @@ -159,8 +160,8 @@ namespace O3DE::ProjectManager m_layout->setMargin(0); setLayout(m_layout); - m_titleLabel = GemInspector::CreateStyledLabel(m_layout, 15, s_headerColor); - m_textLabel = GemInspector::CreateStyledLabel(m_layout, 9, s_textColor); + m_titleLabel = GemInspector::CreateStyledLabel(m_layout, 16, s_headerColor); + m_textLabel = GemInspector::CreateStyledLabel(m_layout, 10, s_textColor); m_textLabel->setWordWrap(true); m_tagWidget = new TagContainerWidget(); diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.cpp index a40e5eb447..0fc0d89fcb 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.cpp @@ -49,7 +49,7 @@ namespace O3DE::ProjectManager painter->setRenderHint(QPainter::Antialiasing); QRect fullRect, itemRect, contentRect; - CalcRects(options, modelIndex, fullRect, itemRect, contentRect); + CalcRects(options, fullRect, itemRect, contentRect); QFont standardFont(options.font); standardFont.setPixelSize(s_fontSize); @@ -99,7 +99,7 @@ namespace O3DE::ProjectManager painter->drawText(gemCreatorRect, Qt::TextSingleLine, gemCreator); // Gem summary - const QSize summarySize = QSize(contentRect.width() - s_summaryStartX - s_buttonWidth - s_itemMargins.right() * 4, contentRect.height()); + const QSize summarySize = QSize(contentRect.width() - s_summaryStartX - s_buttonWidth - s_itemMargins.right() * 3, contentRect.height()); const QRect summaryRect = QRect(/*topLeft=*/QPoint(contentRect.left() + s_summaryStartX, contentRect.top()), summarySize); painter->setFont(standardFont); @@ -131,15 +131,29 @@ namespace O3DE::ProjectManager return false; } + if (event->type() == QEvent::MouseButtonPress) + { + QMouseEvent* mouseEvent = static_cast(event); + + QRect fullRect, itemRect, contentRect; + CalcRects(option, fullRect, itemRect, contentRect); + const QRect buttonRect = CalcButtonRect(contentRect); + + if (buttonRect.contains(mouseEvent->pos())) + { + const bool isAdded = GemModel::IsAdded(modelIndex); + GemModel::SetIsAdded(*model, modelIndex, !isAdded); + return true; + } + } + return QStyledItemDelegate::editorEvent(event, model, option, modelIndex); } - void GemItemDelegate::CalcRects(const QStyleOptionViewItem& option, const QModelIndex& modelIndex, QRect& outFullRect, QRect& outItemRect, QRect& outContentRect) const + void GemItemDelegate::CalcRects(const QStyleOptionViewItem& option, QRect& outFullRect, QRect& outItemRect, QRect& outContentRect) const { - const bool isFirst = modelIndex.row() == 0; - outFullRect = QRect(option.rect); - outItemRect = QRect(outFullRect.adjusted(s_itemMargins.left(), isFirst ? s_itemMargins.top() * 2 : s_itemMargins.top(), -s_itemMargins.right(), -s_itemMargins.bottom())); + outItemRect = QRect(outFullRect.adjusted(s_itemMargins.left(), s_itemMargins.top(), -s_itemMargins.right(), -s_itemMargins.bottom())); outContentRect = QRect(outItemRect.adjusted(s_contentMargins.left(), s_contentMargins.top(), -s_contentMargins.right(), -s_contentMargins.bottom())); } @@ -194,12 +208,12 @@ namespace O3DE::ProjectManager painter->setBrush(m_buttonEnabledColor); painter->setPen(m_buttonEnabledColor); - circleCenter = buttonRect.center() + QPoint(buttonRect.width() / 2 - s_buttonBorderRadius, 1); + circleCenter = buttonRect.center() + QPoint(buttonRect.width() / 2 - s_buttonBorderRadius + 1, 1); buttonText = "Added"; } else { - circleCenter = buttonRect.center() + QPoint(-buttonRect.width() / 2 + s_buttonBorderRadius + 1, 1); + circleCenter = buttonRect.center() + QPoint(-buttonRect.width() / 2 + s_buttonBorderRadius, 1); buttonText = "Get"; } diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.h index d43b5d15f6..48f173ec3f 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.h @@ -45,25 +45,25 @@ namespace O3DE::ProjectManager const QColor m_buttonEnabledColor = QColor("#00B931"); // Item - inline constexpr static int s_height = 135; // Gem item total height - inline constexpr static qreal s_gemNameFontSize = 16.0; - inline constexpr static qreal s_fontSize = 15.0; - inline constexpr static int s_summaryStartX = 200; + inline constexpr static int s_height = 105; // Gem item total height + inline constexpr static qreal s_gemNameFontSize = 13.0; + inline constexpr static qreal s_fontSize = 12.0; + inline constexpr static int s_summaryStartX = 150; // Margin and borders - inline constexpr static QMargins s_itemMargins = QMargins(/*left=*/20, /*top=*/10, /*right=*/20, /*bottom=*/10); // Item border distances - inline constexpr static QMargins s_contentMargins = QMargins(/*left=*/15, /*top=*/12, /*right=*/12, /*bottom=*/12); // Distances of the elements within an item to the item borders + inline constexpr static QMargins s_itemMargins = QMargins(/*left=*/16, /*top=*/8, /*right=*/16, /*bottom=*/8); // Item border distances + inline constexpr static QMargins s_contentMargins = QMargins(/*left=*/20, /*top=*/12, /*right=*/15, /*bottom=*/12); // Distances of the elements within an item to the item borders inline constexpr static int s_borderWidth = 4; // Button - inline constexpr static int s_buttonWidth = 70; - inline constexpr static int s_buttonHeight = 24; - inline constexpr static int s_buttonBorderRadius = 12; - inline constexpr static int s_buttonCircleRadius = s_buttonBorderRadius - 3; - inline constexpr static qreal s_buttonFontSize = 12.0; + inline constexpr static int s_buttonWidth = 55; + inline constexpr static int s_buttonHeight = 18; + inline constexpr static int s_buttonBorderRadius = 9; + inline constexpr static int s_buttonCircleRadius = s_buttonBorderRadius - 2; + inline constexpr static qreal s_buttonFontSize = 10.0; private: - void CalcRects(const QStyleOptionViewItem& option, const QModelIndex& modelIndex, QRect& outFullRect, QRect& outItemRect, QRect& outContentRect) const; + void CalcRects(const QStyleOptionViewItem& option, QRect& outFullRect, QRect& outItemRect, QRect& outContentRect) const; QRect GetTextRect(QFont& font, const QString& text, qreal fontSize) const; QRect CalcButtonRect(const QRect& contentRect) const; void DrawPlatformIcons(QPainter* painter, const QRect& contentRect, const QModelIndex& modelIndex) const; @@ -73,7 +73,7 @@ namespace O3DE::ProjectManager // Platform icons void AddPlatformIcon(GemInfo::Platform platform, const QString& iconPath); - inline constexpr static int s_platformIconSize = 16; + inline constexpr static int s_platformIconSize = 12; QHash m_platformIcons; }; } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemListHeaderWidget.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemListHeaderWidget.cpp index 128fb93345..bc287e3c61 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemListHeaderWidget.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemListHeaderWidget.cpp @@ -35,7 +35,7 @@ namespace O3DE::ProjectManager topLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding)); QLabel* showCountLabel = new QLabel(); - showCountLabel->setStyleSheet("font-size: 11pt; font: italic;"); + showCountLabel->setStyleSheet("font-size: 12px; font: italic;"); topLayout->addWidget(showCountLabel); connect(proxyModel, &GemSortFilterProxyModel::OnInvalidated, this, [=] { @@ -61,16 +61,17 @@ namespace O3DE::ProjectManager QHBoxLayout* columnHeaderLayout = new QHBoxLayout(); columnHeaderLayout->setAlignment(Qt::AlignLeft); - columnHeaderLayout->addSpacing(31); + const int gemNameStartX = GemItemDelegate::s_itemMargins.left() + GemItemDelegate::s_contentMargins.left() - 3; + columnHeaderLayout->addSpacing(gemNameStartX); QLabel* gemNameLabel = new QLabel(tr("Gem Name")); - gemNameLabel->setStyleSheet("font-size: 11pt;"); + gemNameLabel->setStyleSheet("font-size: 12px;"); columnHeaderLayout->addWidget(gemNameLabel); - columnHeaderLayout->addSpacing(111); + columnHeaderLayout->addSpacing(77); QLabel* gemSummaryLabel = new QLabel(tr("Gem Summary")); - gemSummaryLabel->setStyleSheet("font-size: 11pt;"); + gemSummaryLabel->setStyleSheet("font-size: 12px;"); columnHeaderLayout->addWidget(gemSummaryLabel); vLayout->addLayout(columnHeaderLayout); diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.cpp index 724a8fa630..6c09c95572 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.cpp @@ -33,13 +33,12 @@ namespace O3DE::ProjectManager item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); item->setData(gemInfo.m_name, RoleName); - const QString uuidString = gemInfo.m_uuid.ToString().c_str(); - item->setData(uuidString, RoleUuid); item->setData(gemInfo.m_creator, RoleCreator); item->setData(gemInfo.m_gemOrigin, RoleGemOrigin); item->setData(aznumeric_cast(gemInfo.m_platforms), RolePlatforms); item->setData(aznumeric_cast(gemInfo.m_types), RoleTypes); item->setData(gemInfo.m_summary, RoleSummary); + item->setData(false, RoleWasPreviouslyAdded); item->setData(gemInfo.m_isAdded, RoleIsAdded); item->setData(gemInfo.m_directoryLink, RoleDirectoryLink); item->setData(gemInfo.m_documentationLink, RoleDocLink); @@ -49,11 +48,12 @@ namespace O3DE::ProjectManager item->setData(gemInfo.m_lastUpdatedDate, RoleLastUpdated); item->setData(gemInfo.m_binarySizeInKB, RoleBinarySize); item->setData(gemInfo.m_features, RoleFeatures); + item->setData(gemInfo.m_path, RolePath); appendRow(item); const QModelIndex modelIndex = index(rowCount()-1, 0); - m_uuidToIndexMap[uuidString] = modelIndex; + m_nameToIndexMap[gemInfo.m_name] = modelIndex; } void GemModel::Clear() @@ -76,11 +76,6 @@ namespace O3DE::ProjectManager return static_cast(modelIndex.data(RoleGemOrigin).toInt()); } - QString GemModel::GetUuidString(const QModelIndex& modelIndex) - { - return modelIndex.data(RoleUuid).toString(); - } - GemInfo::Platforms GemModel::GetPlatforms(const QModelIndex& modelIndex) { return static_cast(modelIndex.data(RolePlatforms).toInt()); @@ -96,11 +91,6 @@ namespace O3DE::ProjectManager return modelIndex.data(RoleSummary).toString(); } - bool GemModel::IsAdded(const QModelIndex& modelIndex) - { - return modelIndex.data(RoleIsAdded).toBool(); - } - QString GemModel::GetDirectoryLink(const QModelIndex& modelIndex) { return modelIndex.data(RoleDirectoryLink).toString(); @@ -111,10 +101,10 @@ namespace O3DE::ProjectManager return modelIndex.data(RoleDocLink).toString(); } - QModelIndex GemModel::FindIndexByUuidString(const QString& uuidString) const + QModelIndex GemModel::FindIndexByNameString(const QString& nameString) const { - const auto iterator = m_uuidToIndexMap.find(uuidString); - if (iterator != m_uuidToIndexMap.end()) + const auto iterator = m_nameToIndexMap.find(nameString); + if (iterator != m_nameToIndexMap.end()) { return iterator.value(); } @@ -122,11 +112,11 @@ namespace O3DE::ProjectManager return {}; } - void GemModel::FindGemNamesByUuidStrings(QStringList& inOutGemNames) + void GemModel::FindGemNamesByNameStrings(QStringList& inOutGemNames) { for (QString& dependingGemString : inOutGemNames) { - QModelIndex modelIndex = FindIndexByUuidString(dependingGemString); + QModelIndex modelIndex = FindIndexByNameString(dependingGemString); if (modelIndex.isValid()) { dependingGemString = GetName(modelIndex); @@ -147,7 +137,7 @@ namespace O3DE::ProjectManager return {}; } - FindGemNamesByUuidStrings(result); + FindGemNamesByNameStrings(result); return result; } @@ -164,7 +154,7 @@ namespace O3DE::ProjectManager return {}; } - FindGemNamesByUuidStrings(result); + FindGemNamesByNameStrings(result); return result; } @@ -187,4 +177,62 @@ namespace O3DE::ProjectManager { return modelIndex.data(RoleFeatures).toStringList(); } + + QString GemModel::GetPath(const QModelIndex& modelIndex) + { + return modelIndex.data(RolePath).toString(); + } + + bool GemModel::IsAdded(const QModelIndex& modelIndex) + { + return modelIndex.data(RoleIsAdded).toBool(); + } + + void GemModel::SetIsAdded(QAbstractItemModel& model, const QModelIndex& modelIndex, bool isAdded) + { + model.setData(modelIndex, isAdded, RoleIsAdded); + } + + void GemModel::SetWasPreviouslyAdded(QAbstractItemModel& model, const QModelIndex& modelIndex, bool wasAdded) + { + model.setData(modelIndex, wasAdded, RoleWasPreviouslyAdded); + } + + bool GemModel::NeedsToBeAdded(const QModelIndex& modelIndex) + { + return (!modelIndex.data(RoleWasPreviouslyAdded).toBool() && modelIndex.data(RoleIsAdded).toBool()); + } + + bool GemModel::NeedsToBeRemoved(const QModelIndex& modelIndex) + { + return (modelIndex.data(RoleWasPreviouslyAdded).toBool() && !modelIndex.data(RoleIsAdded).toBool()); + } + + QVector GemModel::GatherGemsToBeAdded() const + { + QVector result; + for (int row = 0; row < rowCount(); ++row) + { + const QModelIndex modelIndex = index(row, 0); + if (NeedsToBeAdded(modelIndex)) + { + result.push_back(modelIndex); + } + } + return result; + } + + QVector GemModel::GatherGemsToBeRemoved() const + { + QVector result; + for (int row = 0; row < rowCount(); ++row) + { + const QModelIndex modelIndex = index(row, 0); + if (NeedsToBeRemoved(modelIndex)) + { + result.push_back(modelIndex); + } + } + return result; + } } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h index 480f4c74d3..77f973a91c 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h @@ -33,8 +33,8 @@ namespace O3DE::ProjectManager void AddGem(const GemInfo& gemInfo); void Clear(); - QModelIndex FindIndexByUuidString(const QString& uuidString) const; - void FindGemNamesByUuidStrings(QStringList& inOutGemNames); + QModelIndex FindIndexByNameString(const QString& nameString) const; + void FindGemNamesByNameStrings(QStringList& inOutGemNames); QStringList GetDependingGemUuids(const QModelIndex& modelIndex); QStringList GetDependingGemNames(const QModelIndex& modelIndex); QStringList GetConflictingGemUuids(const QModelIndex& modelIndex); @@ -43,27 +43,35 @@ namespace O3DE::ProjectManager static QString GetName(const QModelIndex& modelIndex); static QString GetCreator(const QModelIndex& modelIndex); static GemInfo::GemOrigin GetGemOrigin(const QModelIndex& modelIndex); - static QString GetUuidString(const QModelIndex& modelIndex); static GemInfo::Platforms GetPlatforms(const QModelIndex& modelIndex); static GemInfo::Types GetTypes(const QModelIndex& modelIndex); static QString GetSummary(const QModelIndex& modelIndex); - static bool IsAdded(const QModelIndex& modelIndex); static QString GetDirectoryLink(const QModelIndex& modelIndex); static QString GetDocLink(const QModelIndex& modelIndex); static QString GetVersion(const QModelIndex& modelIndex); static QString GetLastUpdated(const QModelIndex& modelIndex); static int GetBinarySizeInKB(const QModelIndex& modelIndex); static QStringList GetFeatures(const QModelIndex& modelIndex); + static QString GetPath(const QModelIndex& modelIndex); + + static bool IsAdded(const QModelIndex& modelIndex); + static void SetIsAdded(QAbstractItemModel& model, const QModelIndex& modelIndex, bool isAdded); + static void SetWasPreviouslyAdded(QAbstractItemModel& model, const QModelIndex& modelIndex, bool wasAdded); + static bool NeedsToBeAdded(const QModelIndex& modelIndex); + static bool NeedsToBeRemoved(const QModelIndex& modelIndex); + + QVector GatherGemsToBeAdded() const; + QVector GatherGemsToBeRemoved() const; private: enum UserRole { RoleName = Qt::UserRole, - RoleUuid, RoleCreator, RoleGemOrigin, RolePlatforms, RoleSummary, + RoleWasPreviouslyAdded, RoleIsAdded, RoleDirectoryLink, RoleDocLink, @@ -73,10 +81,11 @@ namespace O3DE::ProjectManager RoleLastUpdated, RoleBinarySize, RoleFeatures, - RoleTypes + RoleTypes, + RolePath }; - QHash m_uuidToIndexMap; + QHash m_nameToIndexMap; QItemSelectionModel* m_selectionModel = nullptr; }; } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/LinkWidget.cpp b/Code/Tools/ProjectManager/Source/LinkWidget.cpp index a6308f6c62..160d9cf7c7 100644 --- a/Code/Tools/ProjectManager/Source/LinkWidget.cpp +++ b/Code/Tools/ProjectManager/Source/LinkWidget.cpp @@ -37,7 +37,7 @@ namespace O3DE::ProjectManager void LinkLabel::enterEvent([[maybe_unused]] QEvent* event) { - setStyleSheet("font-size: 9pt; color: #94D2FF; text-decoration: underline;"); + setStyleSheet("font-size: 10px; color: #94D2FF; text-decoration: underline;"); } void LinkLabel::leaveEvent([[maybe_unused]] QEvent* event) @@ -52,6 +52,6 @@ namespace O3DE::ProjectManager void LinkLabel::SetDefaultStyle() { - setStyleSheet("font-size: 9pt; color: #94D2FF;"); + setStyleSheet("font-size: 10px; color: #94D2FF;"); } } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/NewProjectSettingsScreen.cpp b/Code/Tools/ProjectManager/Source/NewProjectSettingsScreen.cpp index b57a2b35b2..c8dc8451ae 100644 --- a/Code/Tools/ProjectManager/Source/NewProjectSettingsScreen.cpp +++ b/Code/Tools/ProjectManager/Source/NewProjectSettingsScreen.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -33,83 +34,72 @@ namespace O3DE::ProjectManager constexpr const char* k_pathProperty = "Path"; NewProjectSettingsScreen::NewProjectSettingsScreen(QWidget* parent) - : ScreenWidget(parent) + : ProjectSettingsScreen(parent) { - QHBoxLayout* hLayout = new QHBoxLayout(this); - hLayout->setAlignment(Qt::AlignLeft); - hLayout->setContentsMargins(0,0,0,0); + const QString defaultName{ "NewProject" }; + const QString defaultPath = QDir::toNativeSeparators(GetDefaultProjectPath() + "/" + defaultName); - // if we don't provide a parent for this box layout the stylesheet doesn't take - // if we don't set this in a frame (just use a sub-layout) all the content will align incorrectly horizontally - QFrame* projectSettingsFrame = new QFrame(this); - projectSettingsFrame->setObjectName("projectSettings"); - QVBoxLayout* vLayout = new QVBoxLayout(this); + m_projectName->lineEdit()->setText(defaultName); + m_projectPath->lineEdit()->setText(defaultPath); - // you cannot remove content margins in qss - vLayout->setContentsMargins(0,0,0,0); - vLayout->setAlignment(Qt::AlignTop); + // if we don't use a QFrame we cannot "contain" the widgets inside and move them around + // as a group + QFrame* projectTemplateWidget = new QFrame(this); + projectTemplateWidget->setObjectName("projectTemplate"); + QVBoxLayout* containerLayout = new QVBoxLayout(); + containerLayout->setAlignment(Qt::AlignTop); { - m_projectName = new FormLineEditWidget(tr("Project name"), tr("New Project"), this); - m_projectName->setErrorLabelText( - tr("A project with this name already exists at this location. Please choose a new name or location.")); - vLayout->addWidget(m_projectName); + QLabel* projectTemplateLabel = new QLabel(tr("Select a Project Template")); + projectTemplateLabel->setObjectName("projectTemplateLabel"); + containerLayout->addWidget(projectTemplateLabel); - m_projectPath = - new FormBrowseEditWidget(tr("Project Location"), QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), this); - m_projectPath->lineEdit()->setReadOnly(true); - m_projectPath->setErrorLabelText(tr("Please provide a valid path to a folder that exists")); - m_projectPath->lineEdit()->setValidator(new PathValidator(PathValidator::PathMode::ExistingFolder, this)); - vLayout->addWidget(m_projectPath); + QLabel* projectTemplateDetailsLabel = new QLabel(tr("Project templates are pre-configured with relevant Gems that provide " + "additional functionality and content to the project.")); + projectTemplateDetailsLabel->setWordWrap(true); + projectTemplateDetailsLabel->setObjectName("projectTemplateDetailsLabel"); + containerLayout->addWidget(projectTemplateDetailsLabel); - // if we don't use a QFrame we cannot "contain" the widgets inside and move them around - // as a group - QFrame* projectTemplateWidget = new QFrame(this); - projectTemplateWidget->setObjectName("projectTemplate"); - QVBoxLayout* containerLayout = new QVBoxLayout(); - containerLayout->setAlignment(Qt::AlignTop); + QHBoxLayout* templateLayout = new QHBoxLayout(this); + containerLayout->addItem(templateLayout); + + m_projectTemplateButtonGroup = new QButtonGroup(this); + m_projectTemplateButtonGroup->setObjectName("templateButtonGroup"); + auto templatesResult = PythonBindingsInterface::Get()->GetProjectTemplates(); + if (templatesResult.IsSuccess() && !templatesResult.GetValue().isEmpty()) { - QLabel* projectTemplateLabel = new QLabel(tr("Select a Project Template")); - projectTemplateLabel->setObjectName("projectTemplateLabel"); - containerLayout->addWidget(projectTemplateLabel); - - QLabel* projectTemplateDetailsLabel = new QLabel(tr("Project templates are pre-configured with relevant Gems that provide " - "additional functionality and content to the project.")); - projectTemplateDetailsLabel->setWordWrap(true); - projectTemplateDetailsLabel->setObjectName("projectTemplateDetailsLabel"); - containerLayout->addWidget(projectTemplateDetailsLabel); - - QHBoxLayout* templateLayout = new QHBoxLayout(this); - containerLayout->addItem(templateLayout); - - m_projectTemplateButtonGroup = new QButtonGroup(this); - m_projectTemplateButtonGroup->setObjectName("templateButtonGroup"); - auto templatesResult = PythonBindingsInterface::Get()->GetProjectTemplates(); - if (templatesResult.IsSuccess() && !templatesResult.GetValue().isEmpty()) + for (const ProjectTemplateInfo& projectTemplate : templatesResult.GetValue()) { - for (auto projectTemplate : templatesResult.GetValue()) - { - QRadioButton* radioButton = new QRadioButton(projectTemplate.m_name, this); - radioButton->setProperty(k_pathProperty, projectTemplate.m_path); - m_projectTemplateButtonGroup->addButton(radioButton); + QRadioButton* radioButton = new QRadioButton(projectTemplate.m_name, this); + radioButton->setProperty(k_pathProperty, projectTemplate.m_path); + m_projectTemplateButtonGroup->addButton(radioButton); - containerLayout->addWidget(radioButton); - } - - m_projectTemplateButtonGroup->buttons().first()->setChecked(true); + containerLayout->addWidget(radioButton); } - } - projectTemplateWidget->setLayout(containerLayout); - vLayout->addWidget(projectTemplateWidget); - } - projectSettingsFrame->setLayout(vLayout); - hLayout->addWidget(projectSettingsFrame); + m_projectTemplateButtonGroup->buttons().first()->setChecked(true); + } + } + projectTemplateWidget->setLayout(containerLayout); + m_verticalLayout->addWidget(projectTemplateWidget); QWidget* projectTemplateDetails = new QWidget(this); projectTemplateDetails->setObjectName("projectTemplateDetails"); - hLayout->addWidget(projectTemplateDetails); + m_horizontalLayout->addWidget(projectTemplateDetails); + } - this->setLayout(hLayout); + QString NewProjectSettingsScreen::GetDefaultProjectPath() + { + QString defaultPath = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); + AZ::Outcome engineInfoResult = PythonBindingsInterface::Get()->GetEngineInfo(); + if (engineInfoResult.IsSuccess()) + { + QDir path(QDir::toNativeSeparators(engineInfoResult.GetValue().m_defaultProjectsFolder)); + if (path.exists()) + { + defaultPath = path.absolutePath(); + } + } + return defaultPath; } ProjectManagerScreen NewProjectSettingsScreen::GetScreenEnum() @@ -117,40 +107,13 @@ namespace O3DE::ProjectManager return ProjectManagerScreen::NewProjectSettings; } - - ProjectInfo NewProjectSettingsScreen::GetProjectInfo() + void NewProjectSettingsScreen::NotifyCurrentScreen() { - ProjectInfo projectInfo; - projectInfo.m_projectName = m_projectName->lineEdit()->text(); - projectInfo.m_path = QDir::toNativeSeparators(m_projectPath->lineEdit()->text() + "/" + projectInfo.m_projectName); - return projectInfo; + Validate(); } QString NewProjectSettingsScreen::GetProjectTemplatePath() { return m_projectTemplateButtonGroup->checkedButton()->property(k_pathProperty).toString(); } - - bool NewProjectSettingsScreen::Validate() - { - bool projectNameIsValid = true; - if (m_projectName->lineEdit()->text().isEmpty()) - { - projectNameIsValid = false; - } - - bool projectPathIsValid = true; - if (m_projectPath->lineEdit()->text().isEmpty()) - { - projectPathIsValid = false; - } - - QDir path(QDir::toNativeSeparators(m_projectPath->lineEdit()->text() + "/" + m_projectName->lineEdit()->text())); - if (path.exists() && !path.isEmpty()) - { - projectPathIsValid = false; - } - - return projectNameIsValid && projectPathIsValid; - } } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/NewProjectSettingsScreen.h b/Code/Tools/ProjectManager/Source/NewProjectSettingsScreen.h index f0e9609fdc..6a4b6ec57d 100644 --- a/Code/Tools/ProjectManager/Source/NewProjectSettingsScreen.h +++ b/Code/Tools/ProjectManager/Source/NewProjectSettingsScreen.h @@ -12,36 +12,28 @@ #pragma once #if !defined(Q_MOC_RUN) -#include -#include +#include #endif QT_FORWARD_DECLARE_CLASS(QButtonGroup) namespace O3DE::ProjectManager { - QT_FORWARD_DECLARE_CLASS(FormLineEditWidget) - QT_FORWARD_DECLARE_CLASS(FormBrowseEditWidget) - class NewProjectSettingsScreen - : public ScreenWidget + : public ProjectSettingsScreen { public: explicit NewProjectSettingsScreen(QWidget* parent = nullptr); ~NewProjectSettingsScreen() = default; ProjectManagerScreen GetScreenEnum() override; - ProjectInfo GetProjectInfo(); QString GetProjectTemplatePath(); - bool Validate(); - - protected slots: - void HandleBrowseButton(); + void NotifyCurrentScreen() override; private: - FormLineEditWidget* m_projectName; - FormBrowseEditWidget* m_projectPath; + QString GetDefaultProjectPath(); + QButtonGroup* m_projectTemplateButtonGroup; }; diff --git a/Code/Tools/ProjectManager/Source/ProjectButtonWidget.cpp b/Code/Tools/ProjectManager/Source/ProjectButtonWidget.cpp index 4be876e79f..b1dbd984fb 100644 --- a/Code/Tools/ProjectManager/Source/ProjectButtonWidget.cpp +++ b/Code/Tools/ProjectManager/Source/ProjectButtonWidget.cpp @@ -12,6 +12,7 @@ #include + #include #include #include @@ -21,8 +22,6 @@ #include #include -//#define SHOW_ALL_PROJECT_ACTIONS - namespace O3DE::ProjectManager { inline constexpr static int s_projectImageWidth = 210; @@ -58,19 +57,15 @@ namespace O3DE::ProjectManager m_overlayLabel->setText(text); } - ProjectButton::ProjectButton(const QString& projectName, QWidget* parent) + ProjectButton::ProjectButton(const ProjectInfo& projectInfo, QWidget* parent) : QFrame(parent) - , m_projectName(projectName) - , m_projectImagePath(":/Resources/DefaultProjectImage.png") + , m_projectInfo(projectInfo) { - Setup(); - } + if (m_projectInfo.m_imagePath.isEmpty()) + { + m_projectInfo.m_imagePath = ":/DefaultProjectImage.png"; + } - ProjectButton::ProjectButton(const QString& projectName, const QString& projectImage, QWidget* parent) - : QFrame(parent) - , m_projectName(projectName) - , m_projectImagePath(projectImage) - { Setup(); } @@ -85,28 +80,26 @@ namespace O3DE::ProjectManager m_projectImageLabel = new LabelButton(this); m_projectImageLabel->setFixedSize(s_projectImageWidth, s_projectImageHeight); + m_projectImageLabel->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter); vLayout->addWidget(m_projectImageLabel); - m_projectImageLabel->setPixmap(QPixmap(m_projectImagePath).scaled(m_projectImageLabel->size(), Qt::KeepAspectRatioByExpanding)); + m_projectImageLabel->setPixmap( + QPixmap(m_projectInfo.m_imagePath).scaled(m_projectImageLabel->size(), Qt::KeepAspectRatioByExpanding)); QMenu* newProjectMenu = new QMenu(this); m_editProjectAction = newProjectMenu->addAction(tr("Edit Project Settings...")); - -#ifdef SHOW_ALL_PROJECT_ACTIONS - m_editProjectGemsAction = newProjectMenu->addAction(tr("Cutomize Gems...")); newProjectMenu->addSeparator(); m_copyProjectAction = newProjectMenu->addAction(tr("Duplicate")); newProjectMenu->addSeparator(); m_removeProjectAction = newProjectMenu->addAction(tr("Remove from O3DE")); - m_deleteProjectAction = newProjectMenu->addAction(tr("Delete the Project")); -#endif + m_deleteProjectAction = newProjectMenu->addAction(tr("Delete this Project")); QFrame* footer = new QFrame(this); QHBoxLayout* hLayout = new QHBoxLayout(); hLayout->setContentsMargins(0, 0, 0, 0); footer->setLayout(hLayout); { - QLabel* projectNameLabel = new QLabel(m_projectName, this); + QLabel* projectNameLabel = new QLabel(m_projectInfo.m_displayName, this); hLayout->addWidget(projectNameLabel); QPushButton* projectMenuButton = new QPushButton(this); @@ -117,15 +110,11 @@ namespace O3DE::ProjectManager vLayout->addWidget(footer); - connect(m_projectImageLabel, &LabelButton::triggered, [this]() { emit OpenProject(m_projectName); }); - connect(m_editProjectAction, &QAction::triggered, [this]() { emit EditProject(m_projectName); }); - -#ifdef SHOW_ALL_PROJECT_ACTIONS - connect(m_editProjectGemsAction, &QAction::triggered, [this]() { emit EditProjectGems(m_projectName); }); - connect(m_copyProjectAction, &QAction::triggered, [this]() { emit CopyProject(m_projectName); }); - connect(m_removeProjectAction, &QAction::triggered, [this]() { emit RemoveProject(m_projectName); }); - connect(m_deleteProjectAction, &QAction::triggered, [this]() { emit DeleteProject(m_projectName); }); -#endif + connect(m_projectImageLabel, &LabelButton::triggered, [this]() { emit OpenProject(m_projectInfo.m_path); }); + connect(m_editProjectAction, &QAction::triggered, [this]() { emit EditProject(m_projectInfo.m_path); }); + connect(m_copyProjectAction, &QAction::triggered, [this]() { emit CopyProject(m_projectInfo.m_path); }); + connect(m_removeProjectAction, &QAction::triggered, [this]() { emit RemoveProject(m_projectInfo.m_path); }); + connect(m_deleteProjectAction, &QAction::triggered, [this]() { emit DeleteProject(m_projectInfo.m_path); }); } void ProjectButton::SetButtonEnabled(bool enabled) diff --git a/Code/Tools/ProjectManager/Source/ProjectButtonWidget.h b/Code/Tools/ProjectManager/Source/ProjectButtonWidget.h index 671debf6d0..3ac69b7603 100644 --- a/Code/Tools/ProjectManager/Source/ProjectButtonWidget.h +++ b/Code/Tools/ProjectManager/Source/ProjectButtonWidget.h @@ -13,7 +13,8 @@ #pragma once #if !defined(Q_MOC_RUN) -#include +#include + #include #endif @@ -52,8 +53,7 @@ namespace O3DE::ProjectManager Q_OBJECT // AUTOMOC public: - explicit ProjectButton(const QString& projectName, QWidget* parent = nullptr); - explicit ProjectButton(const QString& projectName, const QString& projectImage, QWidget* parent = nullptr); + explicit ProjectButton(const ProjectInfo& m_projectInfo, QWidget* parent = nullptr); ~ProjectButton() = default; void SetButtonEnabled(bool enabled); @@ -62,7 +62,6 @@ namespace O3DE::ProjectManager signals: void OpenProject(const QString& projectName); void EditProject(const QString& projectName); - void EditProjectGems(const QString& projectName); void CopyProject(const QString& projectName); void RemoveProject(const QString& projectName); void DeleteProject(const QString& projectName); @@ -70,11 +69,9 @@ namespace O3DE::ProjectManager private: void Setup(); - QString m_projectName; - QString m_projectImagePath; + ProjectInfo m_projectInfo; LabelButton* m_projectImageLabel; QAction* m_editProjectAction; - QAction* m_editProjectGemsAction; QAction* m_copyProjectAction; QAction* m_removeProjectAction; QAction* m_deleteProjectAction; diff --git a/Code/Tools/ProjectManager/Source/ProjectInfo.cpp b/Code/Tools/ProjectManager/Source/ProjectInfo.cpp index b0b740fad9..f0dc05cc62 100644 --- a/Code/Tools/ProjectManager/Source/ProjectInfo.cpp +++ b/Code/Tools/ProjectManager/Source/ProjectInfo.cpp @@ -25,6 +25,19 @@ namespace O3DE::ProjectManager { } + bool ProjectInfo::operator==(const ProjectInfo& rhs) + { + return m_path == rhs.m_path + && m_projectName == rhs.m_projectName + && m_imagePath == rhs.m_imagePath + && m_backgroundImagePath == rhs.m_backgroundImagePath; + } + + bool ProjectInfo::operator!=(const ProjectInfo& rhs) + { + return !operator==(rhs); + } + bool ProjectInfo::IsValid() const { return !m_path.isEmpty() && !m_projectName.isEmpty(); diff --git a/Code/Tools/ProjectManager/Source/ProjectInfo.h b/Code/Tools/ProjectManager/Source/ProjectInfo.h index 92a7459d78..71fa12b344 100644 --- a/Code/Tools/ProjectManager/Source/ProjectInfo.h +++ b/Code/Tools/ProjectManager/Source/ProjectInfo.h @@ -25,6 +25,8 @@ namespace O3DE::ProjectManager ProjectInfo() = default; ProjectInfo(const QString& path, const QString& projectName, const QString& displayName, const QString& imagePath, const QString& backgroundImagePath, bool isNew); + bool operator==(const ProjectInfo& rhs); + bool operator!=(const ProjectInfo& rhs); bool IsValid() const; diff --git a/Code/Tools/ProjectManager/Source/ProjectManagerWindow.cpp b/Code/Tools/ProjectManager/Source/ProjectManagerWindow.cpp index 76bcc2eb99..cb1398cc61 100644 --- a/Code/Tools/ProjectManager/Source/ProjectManagerWindow.cpp +++ b/Code/Tools/ProjectManager/Source/ProjectManagerWindow.cpp @@ -14,13 +14,16 @@ #include #include +#include #include +#include +#include #include namespace O3DE::ProjectManager { - ProjectManagerWindow::ProjectManagerWindow(QWidget* parent, const AZ::IO::PathView& engineRootPath) + ProjectManagerWindow::ProjectManagerWindow(QWidget* parent, const AZ::IO::PathView& engineRootPath, const AZ::IO::PathView& projectPath, ProjectManagerScreen startScreen) : QMainWindow(parent) { m_pythonBindings = AZStd::make_unique(engineRootPath); @@ -50,7 +53,18 @@ namespace O3DE::ProjectManager // set stylesheet after creating the screens or their styles won't get updated AzQtComponents::StyleManager::setStyleSheet(this, QStringLiteral("style:ProjectManager.qss")); - screensCtrl->ForceChangeToScreen(ProjectManagerScreen::Projects, false); + // always push the projects screen first so we have something to come back to + if (startScreen != ProjectManagerScreen::Projects) + { + screensCtrl->ForceChangeToScreen(ProjectManagerScreen::Projects); + } + screensCtrl->ForceChangeToScreen(startScreen); + + if (!projectPath.empty()) + { + const QString path = QString::fromUtf8(projectPath.Native().data(), aznumeric_cast(projectPath.Native().size())); + emit screensCtrl->NotifyCurrentProject(path); + } } ProjectManagerWindow::~ProjectManagerWindow() diff --git a/Code/Tools/ProjectManager/Source/ProjectManagerWindow.h b/Code/Tools/ProjectManager/Source/ProjectManagerWindow.h index 74db3467c5..758af8fc00 100644 --- a/Code/Tools/ProjectManager/Source/ProjectManagerWindow.h +++ b/Code/Tools/ProjectManager/Source/ProjectManagerWindow.h @@ -14,6 +14,7 @@ #if !defined(Q_MOC_RUN) #include #include +#include #endif namespace O3DE::ProjectManager @@ -24,7 +25,8 @@ namespace O3DE::ProjectManager Q_OBJECT public: - explicit ProjectManagerWindow(QWidget* parent, const AZ::IO::PathView& engineRootPath); + explicit ProjectManagerWindow(QWidget* parent, const AZ::IO::PathView& engineRootPath, const AZ::IO::PathView& projectPath, + ProjectManagerScreen startScreen = ProjectManagerScreen::Projects); ~ProjectManagerWindow(); private: diff --git a/Code/Tools/ProjectManager/Source/ProjectSettingsScreen.cpp b/Code/Tools/ProjectManager/Source/ProjectSettingsScreen.cpp index 76aa1d2897..26711753d4 100644 --- a/Code/Tools/ProjectManager/Source/ProjectSettingsScreen.cpp +++ b/Code/Tools/ProjectManager/Source/ProjectSettingsScreen.cpp @@ -11,45 +11,131 @@ */ #include +#include +#include +#include +#include -#include +#include +#include +#include +#include +#include +#include +#include namespace O3DE::ProjectManager { ProjectSettingsScreen::ProjectSettingsScreen(QWidget* parent) : ScreenWidget(parent) - , m_ui(new Ui::ProjectSettingsClass()) { - m_ui->setupUi(this); + m_horizontalLayout = new QHBoxLayout(this); + m_horizontalLayout->setAlignment(Qt::AlignLeft); + m_horizontalLayout->setContentsMargins(0, 0, 0, 0); - connect(m_ui->gemsButton, &QPushButton::pressed, this, &ProjectSettingsScreen::HandleGemsButton); + // if we don't provide a parent for this box layout the stylesheet doesn't take + // if we don't set this in a frame (just use a sub-layout) all the content will align incorrectly horizontally + QFrame* projectSettingsFrame = new QFrame(this); + projectSettingsFrame->setObjectName("projectSettings"); + m_verticalLayout = new QVBoxLayout(this); + + // you cannot remove content margins in qss + m_verticalLayout->setContentsMargins(0, 0, 0, 0); + m_verticalLayout->setAlignment(Qt::AlignTop); + + m_projectName = new FormLineEditWidget(tr("Project name"), "", this); + connect(m_projectName->lineEdit(), &QLineEdit::textChanged, this, &ProjectSettingsScreen::ValidateProjectName); + m_verticalLayout->addWidget(m_projectName); + + m_projectPath = new FormBrowseEditWidget(tr("Project Location"), "", this); + m_projectPath->lineEdit()->setReadOnly(true); + connect(m_projectPath->lineEdit(), &QLineEdit::textChanged, this, &ProjectSettingsScreen::Validate); + m_verticalLayout->addWidget(m_projectPath); + + projectSettingsFrame->setLayout(m_verticalLayout); + + m_horizontalLayout->addWidget(projectSettingsFrame); + + setLayout(m_horizontalLayout); } ProjectManagerScreen ProjectSettingsScreen::GetScreenEnum() { - return ProjectManagerScreen::ProjectSettings; + return ProjectManagerScreen::Invalid; + } + + QString ProjectSettingsScreen::GetDefaultProjectPath() + { + QString defaultPath = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); + AZ::Outcome engineInfoResult = PythonBindingsInterface::Get()->GetEngineInfo(); + if (engineInfoResult.IsSuccess()) + { + QDir path(QDir::toNativeSeparators(engineInfoResult.GetValue().m_defaultProjectsFolder)); + if (path.exists()) + { + defaultPath = path.absolutePath(); + } + } + return defaultPath; } ProjectInfo ProjectSettingsScreen::GetProjectInfo() { - // Impl pending next PR - return ProjectInfo(); + ProjectInfo projectInfo; + projectInfo.m_projectName = m_projectName->lineEdit()->text(); + projectInfo.m_path = m_projectPath->lineEdit()->text(); + return projectInfo; } - void ProjectSettingsScreen::SetProjectInfo() + bool ProjectSettingsScreen::ValidateProjectName() { - // Impl pending next PR + bool projectNameIsValid = true; + if (m_projectName->lineEdit()->text().isEmpty()) + { + projectNameIsValid = false; + m_projectName->setErrorLabelText(tr("Please provide a project name.")); + } + else + { + // this validation should roughly match the utils.validate_identifier which the cli + // uses to validate project names + QRegExp validProjectNameRegex("[A-Za-z][A-Za-z0-9_-]{0,63}"); + const bool result = validProjectNameRegex.exactMatch(m_projectName->lineEdit()->text()); + if (!result) + { + projectNameIsValid = false; + m_projectName->setErrorLabelText( + tr("Project names must start with a letter and consist of up to 64 letter, number, '_' or '-' characters")); + } + } + + m_projectName->setErrorLabelVisible(!projectNameIsValid); + return projectNameIsValid; + } + bool ProjectSettingsScreen::ValidateProjectPath() + { + bool projectPathIsValid = true; + if (m_projectPath->lineEdit()->text().isEmpty()) + { + projectPathIsValid = false; + m_projectPath->setErrorLabelText(tr("Please provide a valid location.")); + } + else + { + QDir path(m_projectPath->lineEdit()->text()); + if (path.exists() && !path.isEmpty()) + { + projectPathIsValid = false; + m_projectPath->setErrorLabelText(tr("This folder exists and isn't empty. Please choose a different location.")); + } + } + + m_projectPath->setErrorLabelVisible(!projectPathIsValid); + return projectPathIsValid; } bool ProjectSettingsScreen::Validate() { - // Impl pending next PR - return true; + return ValidateProjectName() && ValidateProjectPath(); } - - void ProjectSettingsScreen::HandleGemsButton() - { - emit ChangeScreenRequest(ProjectManagerScreen::GemCatalog); - } - } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/ProjectSettingsScreen.h b/Code/Tools/ProjectManager/Source/ProjectSettingsScreen.h index a4cafcd93a..0d75bbbc64 100644 --- a/Code/Tools/ProjectManager/Source/ProjectSettingsScreen.h +++ b/Code/Tools/ProjectManager/Source/ProjectSettingsScreen.h @@ -12,17 +12,18 @@ #pragma once #if !defined(Q_MOC_RUN) -#include #include +#include #endif -namespace Ui -{ - class ProjectSettingsClass; -} +QT_FORWARD_DECLARE_CLASS(QHBoxLayout) +QT_FORWARD_DECLARE_CLASS(QVBoxLayout) namespace O3DE::ProjectManager { + QT_FORWARD_DECLARE_CLASS(FormLineEditWidget) + QT_FORWARD_DECLARE_CLASS(FormBrowseEditWidget) + class ProjectSettingsScreen : public ScreenWidget { @@ -32,15 +33,20 @@ namespace O3DE::ProjectManager ProjectManagerScreen GetScreenEnum() override; ProjectInfo GetProjectInfo(); - void SetProjectInfo(); bool Validate(); protected slots: - void HandleGemsButton(); + virtual bool ValidateProjectName(); + virtual bool ValidateProjectPath(); - private: - QScopedPointer m_ui; + protected: + QString GetDefaultProjectPath(); + + QHBoxLayout* m_horizontalLayout; + QVBoxLayout* m_verticalLayout; + FormLineEditWidget* m_projectName; + FormBrowseEditWidget* m_projectPath; }; } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/ProjectSettingsScreen.ui b/Code/Tools/ProjectManager/Source/ProjectSettingsScreen.ui deleted file mode 100644 index 934238a257..0000000000 --- a/Code/Tools/ProjectManager/Source/ProjectSettingsScreen.ui +++ /dev/null @@ -1,113 +0,0 @@ - - - ProjectSettingsClass - - - - 0 - 0 - 782 - 579 - - - - Form - - - - - - - - Project Settings - - - - - - - Gems - - - - - - - Qt::Horizontal - - - - 761 - 20 - - - - - - - - - - - - - - Project Name - - - - - - - - - - Project Location - - - - - - - - - - Project Image Location - - - - - - - - - - Project Background Image Location - - - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - diff --git a/Code/Tools/ProjectManager/Source/ProjectUtils.cpp b/Code/Tools/ProjectManager/Source/ProjectUtils.cpp new file mode 100644 index 0000000000..58e4c5c60f --- /dev/null +++ b/Code/Tools/ProjectManager/Source/ProjectUtils.cpp @@ -0,0 +1,207 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include +#include + +#include +#include +#include +#include + +namespace O3DE::ProjectManager +{ + namespace ProjectUtils + { + static bool WarnDirectoryOverwrite(const QString& path, QWidget* parent) + { + if (!QDir(path).isEmpty()) + { + QMessageBox::StandardButton warningResult = QMessageBox::warning( + parent, + QObject::tr("Overwrite Directory"), + QObject::tr("Directory is not empty! Are you sure you want to overwrite it?"), + QMessageBox::No | QMessageBox::Yes + ); + + if (warningResult != QMessageBox::Yes) + { + return false; + } + } + + return true; + } + + static bool IsDirectoryDescedent(const QString& possibleAncestorPath, const QString& possibleDecedentPath) + { + QDir ancestor(possibleAncestorPath); + QDir descendent(possibleDecedentPath); + + do + { + if (ancestor == descendent) + { + return false; + } + + descendent.cdUp(); + } + while (!descendent.isRoot()); + + return true; + } + + static bool CopyDirectory(const QString& origPath, const QString& newPath) + { + QDir original(origPath); + if (!original.exists()) + { + return false; + } + + for (QString directory : original.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) + { + QString newDirectoryPath = newPath + QDir::separator() + directory; + original.mkpath(newDirectoryPath); + + if (!CopyDirectory(origPath + QDir::separator() + directory, newDirectoryPath)) + { + return false; + } + } + + for (QString file : original.entryList(QDir::Files)) + { + if (!QFile::copy(origPath + QDir::separator() + file, newPath + QDir::separator() + file)) + return false; + } + + return true; + } + + bool AddProjectDialog(QWidget* parent) + { + QString path = QDir::toNativeSeparators(QFileDialog::getExistingDirectory(parent, QObject::tr("Select Project Directory"))); + if (!path.isEmpty()) + { + return RegisterProject(path); + } + + return false; + } + + bool RegisterProject(const QString& path) + { + return PythonBindingsInterface::Get()->AddProject(path); + } + + bool UnregisterProject(const QString& path) + { + return PythonBindingsInterface::Get()->RemoveProject(path); + } + + bool CopyProjectDialog(const QString& origPath, QWidget* parent) + { + bool copyResult = false; + + QDir parentOrigDir(origPath); + parentOrigDir.cdUp(); + QString newPath = QDir::toNativeSeparators( + QFileDialog::getExistingDirectory(parent, QObject::tr("Select New Project Directory"), parentOrigDir.path())); + if (!newPath.isEmpty()) + { + if (!WarnDirectoryOverwrite(newPath, parent)) + { + return false; + } + + // TODO: Block UX and Notify User they need to wait + + copyResult = CopyProject(origPath, newPath); + } + + return copyResult; + } + + bool CopyProject(const QString& origPath, const QString& newPath) + { + // Disallow copying from or into subdirectory + if (!IsDirectoryDescedent(origPath, newPath) || !IsDirectoryDescedent(newPath, origPath)) + { + return false; + } + + if (!CopyDirectory(origPath, newPath)) + { + // Cleanup whatever mess was made + DeleteProjectFiles(newPath, true); + return false; + } + + if (!RegisterProject(newPath)) + { + DeleteProjectFiles(newPath, true); + } + + return true; + } + + bool DeleteProjectFiles(const QString& path, bool force) + { + QDir projectDirectory(path); + if (projectDirectory.exists()) + { + // Check if there is an actual project hereor just force it + if (force || PythonBindingsInterface::Get()->GetProject(path).IsSuccess()) + { + return projectDirectory.removeRecursively(); + } + } + + return false; + } + + bool MoveProject(const QString& origPath, const QString& newPath, QWidget* parent) + { + if (!WarnDirectoryOverwrite(newPath, parent) || !UnregisterProject(origPath)) + { + return false; + } + + QDir directory; + if (directory.rename(origPath, newPath)) + { + return directory.rename(origPath, newPath); + } + + if (!RegisterProject(newPath)) + { + return false; + } + + return true; + } + + ProjectManagerScreen GetProjectManagerScreen(const QString& screen) + { + auto iter = s_ProjectManagerStringNames.find(screen); + if (iter != s_ProjectManagerStringNames.end()) + { + return iter.value(); + } + + return ProjectManagerScreen::Invalid; + } + + } // namespace ProjectUtils +} // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/ProjectUtils.h b/Code/Tools/ProjectManager/Source/ProjectUtils.h new file mode 100644 index 0000000000..d556d682f2 --- /dev/null +++ b/Code/Tools/ProjectManager/Source/ProjectUtils.h @@ -0,0 +1,30 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ +#pragma once + +#include +#include + +namespace O3DE::ProjectManager +{ + namespace ProjectUtils + { + bool AddProjectDialog(QWidget* parent = nullptr); + bool RegisterProject(const QString& path); + bool UnregisterProject(const QString& path); + bool CopyProjectDialog(const QString& origPath, QWidget* parent = nullptr); + bool CopyProject(const QString& origPath, const QString& newPath); + bool DeleteProjectFiles(const QString& path, bool force = false); + bool MoveProject(const QString& origPath, const QString& newPath, QWidget* parent = nullptr); + ProjectManagerScreen GetProjectManagerScreen(const QString& screen); + } // namespace ProjectUtils +} // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/ProjectsScreen.cpp b/Code/Tools/ProjectManager/Source/ProjectsScreen.cpp index 5f1c0e2b36..425aa8514d 100644 --- a/Code/Tools/ProjectManager/Source/ProjectsScreen.cpp +++ b/Code/Tools/ProjectManager/Source/ProjectsScreen.cpp @@ -14,6 +14,7 @@ #include #include +#include #include #include @@ -65,9 +66,6 @@ namespace O3DE::ProjectManager m_stack->addWidget(m_projectsContent); vLayout->addWidget(m_stack); - - connect(m_createNewProjectAction, &QAction::triggered, this, &ProjectsScreen::HandleNewProjectButton); - connect(m_addExistingProjectAction, &QAction::triggered, this, &ProjectsScreen::HandleAddProjectButton); } QFrame* ProjectsScreen::CreateFirstTimeContent() @@ -167,28 +165,23 @@ namespace O3DE::ProjectManager #endif { ProjectButton* projectButton; + QString projectPreviewPath = project.m_path + m_projectPreviewImagePath; QFileInfo doesPreviewExist(projectPreviewPath); if (doesPreviewExist.exists() && doesPreviewExist.isFile()) { - projectButton = new ProjectButton(project.m_projectName, projectPreviewPath, this); - } - else - { - projectButton = new ProjectButton(project.m_projectName, this); + project.m_imagePath = projectPreviewPath; } + projectButton = new ProjectButton(project, this); + flowLayout->addWidget(projectButton); connect(projectButton, &ProjectButton::OpenProject, this, &ProjectsScreen::HandleOpenProject); connect(projectButton, &ProjectButton::EditProject, this, &ProjectsScreen::HandleEditProject); - - #ifdef DISPLAY_PROJECT_DEV_DATA - connect(projectButton, &ProjectButton::EditProjectGems, this, &ProjectsScreen::HandleEditProjectGems); connect(projectButton, &ProjectButton::CopyProject, this, &ProjectsScreen::HandleCopyProject); connect(projectButton, &ProjectButton::RemoveProject, this, &ProjectsScreen::HandleRemoveProject); connect(projectButton, &ProjectButton::DeleteProject, this, &ProjectsScreen::HandleDeleteProject); - #endif } layout->addWidget(projectsScrollArea); @@ -242,7 +235,11 @@ namespace O3DE::ProjectManager } void ProjectsScreen::HandleAddProjectButton() { - // Do nothing for now + if (ProjectUtils::AddProjectDialog(this)) + { + emit ResetScreenRequest(ProjectManagerScreen::Projects); + emit ChangeScreenRequest(ProjectManagerScreen::Projects); + } } void ProjectsScreen::HandleOpenProject(const QString& projectPath) { @@ -292,26 +289,38 @@ namespace O3DE::ProjectManager void ProjectsScreen::HandleEditProject(const QString& projectPath) { emit NotifyCurrentProject(projectPath); - emit ResetScreenRequest(ProjectManagerScreen::UpdateProject); emit ChangeScreenRequest(ProjectManagerScreen::UpdateProject); } - void ProjectsScreen::HandleEditProjectGems(const QString& projectPath) - { - emit NotifyCurrentProject(projectPath); - emit ChangeScreenRequest(ProjectManagerScreen::GemCatalog); - } - void ProjectsScreen::HandleCopyProject([[maybe_unused]] const QString& projectPath) + void ProjectsScreen::HandleCopyProject(const QString& projectPath) { // Open file dialog and choose location for copied project then register copy with O3DE + if (ProjectUtils::CopyProjectDialog(projectPath, this)) + { + emit ResetScreenRequest(ProjectManagerScreen::Projects); + emit ChangeScreenRequest(ProjectManagerScreen::Projects); + } } - void ProjectsScreen::HandleRemoveProject([[maybe_unused]] const QString& projectPath) + void ProjectsScreen::HandleRemoveProject(const QString& projectPath) { - // Unregister Project from O3DE + // Unregister Project from O3DE and reload projects + if (ProjectUtils::UnregisterProject(projectPath)) + { + emit ResetScreenRequest(ProjectManagerScreen::Projects); + emit ChangeScreenRequest(ProjectManagerScreen::Projects); + } } - void ProjectsScreen::HandleDeleteProject([[maybe_unused]] const QString& projectPath) + void ProjectsScreen::HandleDeleteProject(const QString& projectPath) { - // Remove project from 03DE and delete from disk - ProjectsScreen::HandleRemoveProject(projectPath); + QMessageBox::StandardButton warningResult = QMessageBox::warning( + this, tr("Delete Project"), tr("Are you sure?\nProject will be removed from O3DE and directory will be deleted!"), + QMessageBox::No | QMessageBox::Yes); + + if (warningResult == QMessageBox::Yes) + { + // Remove project from O3DE and delete from disk + HandleRemoveProject(projectPath); + ProjectUtils::DeleteProjectFiles(projectPath); + } } void ProjectsScreen::NotifyCurrentScreen() @@ -322,6 +331,16 @@ namespace O3DE::ProjectManager } else { + // refresh the projects content by re-creating it for now + if (m_projectsContent) + { + m_stack->removeWidget(m_projectsContent); + m_projectsContent->deleteLater(); + } + + m_projectsContent = CreateProjectsContent(); + + m_stack->addWidget(m_projectsContent); m_stack->setCurrentWidget(m_projectsContent); } } diff --git a/Code/Tools/ProjectManager/Source/ProjectsScreen.h b/Code/Tools/ProjectManager/Source/ProjectsScreen.h index d88ba8398d..e02b34525b 100644 --- a/Code/Tools/ProjectManager/Source/ProjectsScreen.h +++ b/Code/Tools/ProjectManager/Source/ProjectsScreen.h @@ -41,7 +41,6 @@ namespace O3DE::ProjectManager void HandleAddProjectButton(); void HandleOpenProject(const QString& projectPath); void HandleEditProject(const QString& projectPath); - void HandleEditProjectGems(const QString& projectPath); void HandleCopyProject(const QString& projectPath); void HandleRemoveProject(const QString& projectPath); void HandleDeleteProject(const QString& projectPath); diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.cpp b/Code/Tools/ProjectManager/Source/PythonBindings.cpp index 8c79a153c8..7062d886da 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.cpp +++ b/Code/Tools/ProjectManager/Source/PythonBindings.cpp @@ -220,7 +220,7 @@ namespace RedirectOutput } } // namespace RedirectOutput -namespace O3DE::ProjectManager +namespace O3DE::ProjectManager { PythonBindings::PythonBindings(const AZ::IO::PathView& enginePath) : m_enginePath(enginePath) @@ -283,8 +283,15 @@ namespace O3DE::ProjectManager AZ_Warning("ProjectManagerWindow", result != -1, "Append to sys path failed"); // import required modules - m_registration = pybind11::module::import("cmake.Tools.registration"); - m_engineTemplate = pybind11::module::import("cmake.Tools.engine_template"); + m_cmake = pybind11::module::import("o3de.cmake"); + m_register = pybind11::module::import("o3de.register"); + m_manifest = pybind11::module::import("o3de.manifest"); + m_engineTemplate = pybind11::module::import("o3de.engine_template"); + m_enableGemProject = pybind11::module::import("o3de.enable_gem"); + m_disableGemProject = pybind11::module::import("o3de.disable_gem"); + + // make sure the engine is registered + RegisterThisEngine(); return result == 0 && !PyErr_Occurred(); } catch ([[maybe_unused]] const std::exception& e) @@ -308,7 +315,37 @@ namespace O3DE::ProjectManager return !PyErr_Occurred(); } - bool PythonBindings::ExecuteWithLock(AZStd::function executionCallback) + bool PythonBindings::RegisterThisEngine() + { + bool registrationResult = true; // already registered is considered successful + bool pythonResult = ExecuteWithLock( + [&] + { + // check current engine path against all other registered engines + // to see if we are already registered + auto allEngines = m_manifest.attr("get_engines")(); + if (pybind11::isinstance(allEngines)) + { + for (auto engine : allEngines) + { + AZ::IO::FixedMaxPath enginePath(Py_To_String(engine["path"])); + if (enginePath.Compare(m_enginePath) == 0) + { + return; + } + } + } + + auto result = m_register.attr("register")(m_enginePath.c_str()); + registrationResult = (result.cast() == 0); + }); + + bool finalResult = (registrationResult && pythonResult); + AZ_Assert(finalResult, "Registration of this engine failed!"); + return finalResult; + } + + AZ::Outcome PythonBindings::ExecuteWithLockErrorHandling(AZStd::function executionCallback) { AZStd::lock_guard lock(m_lock); pybind11::gil_scoped_release release; @@ -317,39 +354,45 @@ namespace O3DE::ProjectManager try { executionCallback(); - return true; } catch ([[maybe_unused]] const std::exception& e) { AZ_Warning("PythonBindings", false, "Python exception %s", e.what()); - return false; + return AZ::Failure(e.what()); } + + return AZ::Success(); } - AZ::Outcome PythonBindings::GetEngineInfo() + bool PythonBindings::ExecuteWithLock(AZStd::function executionCallback) + { + return ExecuteWithLockErrorHandling(executionCallback).IsSuccess(); + } + + AZ::Outcome PythonBindings::GetEngineInfo() { EngineInfo engineInfo; bool result = ExecuteWithLock([&] { - pybind11::str enginePath = m_registration.attr("get_this_engine_path")(); + pybind11::str enginePath = m_manifest.attr("get_this_engine_path")(); - auto o3deData = m_registration.attr("load_o3de_manifest")(); + auto o3deData = m_manifest.attr("load_o3de_manifest")(); if (pybind11::isinstance(o3deData)) { - engineInfo.m_path = Py_To_String(enginePath); - engineInfo.m_defaultGemsFolder = Py_To_String(o3deData["default_gems_folder"]); - engineInfo.m_defaultProjectsFolder = Py_To_String(o3deData["default_projects_folder"]); - engineInfo.m_defaultRestrictedFolder = Py_To_String(o3deData["default_restricted_folder"]); - engineInfo.m_defaultTemplatesFolder = Py_To_String(o3deData["default_templates_folder"]); - engineInfo.m_thirdPartyPath = Py_To_String_Optional(o3deData,"third_party_path",""); + engineInfo.m_path = Py_To_String(enginePath); + engineInfo.m_defaultGemsFolder = Py_To_String(o3deData["default_gems_folder"]); + engineInfo.m_defaultProjectsFolder = Py_To_String(o3deData["default_projects_folder"]); + engineInfo.m_defaultRestrictedFolder = Py_To_String(o3deData["default_restricted_folder"]); + engineInfo.m_defaultTemplatesFolder = Py_To_String(o3deData["default_templates_folder"]); + engineInfo.m_thirdPartyPath = Py_To_String_Optional(o3deData,"third_party_path",""); } - auto engineData = m_registration.attr("get_engine_data")(pybind11::none(), enginePath); + auto engineData = m_manifest.attr("get_engine_json_data")(pybind11::none(), enginePath); if (pybind11::isinstance(engineData)) { try { - engineInfo.m_version = Py_To_String_Optional(engineData,"O3DEVersion","0.0.0.0"); - engineInfo.m_name = Py_To_String_Optional(engineData,"engine_name","O3DE"); + engineInfo.m_version = Py_To_String_Optional(engineData,"O3DEVersion","0.0.0.0"); + engineInfo.m_name = Py_To_String_Optional(engineData,"engine_name","O3DE"); } catch ([[maybe_unused]] const std::exception& e) { @@ -364,13 +407,13 @@ namespace O3DE::ProjectManager } else { - return AZ::Success(AZStd::move(engineInfo)); + return AZ::Success(AZStd::move(engineInfo)); } return AZ::Failure(); } - bool PythonBindings::SetEngineInfo(const EngineInfo& engineInfo) + bool PythonBindings::SetEngineInfo(const EngineInfo& engineInfo) { bool result = ExecuteWithLock([&] { pybind11::str enginePath = engineInfo.m_path.toStdString(); @@ -378,17 +421,18 @@ namespace O3DE::ProjectManager pybind11::str defaultGemsFolder = engineInfo.m_defaultGemsFolder.toStdString(); pybind11::str defaultTemplatesFolder = engineInfo.m_defaultTemplatesFolder.toStdString(); - auto registrationResult = m_registration.attr("register")( + auto registrationResult = m_register.attr("register")( enginePath, // engine_path pybind11::none(), // project_path - pybind11::none(), // gem_path + pybind11::none(), // gem_path + pybind11::none(), // external_subdir_path pybind11::none(), // template_path pybind11::none(), // restricted_path pybind11::none(), // repo_uri pybind11::none(), // default_engines_folder defaultProjectsFolder, - defaultGemsFolder, - defaultTemplatesFolder + defaultGemsFolder, + defaultTemplatesFolder ); if (registrationResult.cast() != 0) @@ -396,13 +440,13 @@ namespace O3DE::ProjectManager result = false; } - auto manifest = m_registration.attr("load_o3de_manifest")(); + auto manifest = m_manifest.attr("load_o3de_manifest")(); if (pybind11::isinstance(manifest)) { try { manifest["third_party_path"] = engineInfo.m_thirdPartyPath.toStdString(); - m_registration.attr("save_o3de_manifest")(manifest); + m_manifest.attr("save_o3de_manifest")(manifest); } catch ([[maybe_unused]] const std::exception& e) { @@ -415,12 +459,12 @@ namespace O3DE::ProjectManager return result; } - AZ::Outcome PythonBindings::GetGem(const QString& path) + AZ::Outcome PythonBindings::GetGemInfo(const QString& path) { GemInfo gemInfo = GemInfoFromPath(pybind11::str(path.toStdString())); if (gemInfo.IsValid()) { - return AZ::Success(AZStd::move(gemInfo)); + return AZ::Success(AZStd::move(gemInfo)); } else { @@ -428,42 +472,143 @@ namespace O3DE::ProjectManager } } - AZ::Outcome> PythonBindings::GetGems() + AZ::Outcome, AZStd::string> PythonBindings::GetEngineGemInfos() { QVector gems; - bool result = ExecuteWithLock([&] { - // external gems - for (auto path : m_registration.attr("get_gems")()) + auto result = ExecuteWithLockErrorHandling([&] { - gems.push_back(GemInfoFromPath(path)); - } - - // gems from the engine - for (auto path : m_registration.attr("get_engine_gems")()) - { - gems.push_back(GemInfoFromPath(path)); - } - }); - - if (!result) + for (auto path : m_manifest.attr("get_engine_gems")()) + { + gems.push_back(GemInfoFromPath(path)); + } + }); + if (!result.IsSuccess()) { - return AZ::Failure(); - } - else - { - return AZ::Success(AZStd::move(gems)); + return AZ::Failure(result.GetError().c_str()); } + + std::sort(gems.begin(), gems.end()); + return AZ::Success(AZStd::move(gems)); } - AZ::Outcome PythonBindings::CreateProject(const QString& projectTemplatePath, const ProjectInfo& projectInfo) + AZ::Outcome, AZStd::string> PythonBindings::GetAllGemInfos(const QString& projectPath) + { + QVector gems; + + auto result = ExecuteWithLockErrorHandling([&] + { + pybind11::str pyProjectPath = projectPath.toStdString(); + for (auto path : m_manifest.attr("get_all_gems")(pyProjectPath)) + { + gems.push_back(GemInfoFromPath(path)); + } + }); + if (!result.IsSuccess()) + { + return AZ::Failure(result.GetError().c_str()); + } + + std::sort(gems.begin(), gems.end()); + return AZ::Success(AZStd::move(gems)); + } + + AZ::Outcome, AZStd::string> PythonBindings::GetEnabledGemNames(const QString& projectPath) + { + // Retrieve the path to the cmake file that lists the enabled gems. + pybind11::str enabledGemsFilename; + auto result = ExecuteWithLockErrorHandling([&] + { + const pybind11::str pyProjectPath = projectPath.toStdString(); + enabledGemsFilename = m_cmake.attr("get_enabled_gem_cmake_file")( + pybind11::none(), // project_name + pyProjectPath); // project_path + }); + if (!result.IsSuccess()) + { + return AZ::Failure(result.GetError().c_str()); + } + + // Retrieve the actual list of names from the cmake file. + QVector gemNames; + result = ExecuteWithLockErrorHandling([&] + { + const auto pyGemNames = m_cmake.attr("get_enabled_gems")(enabledGemsFilename); + for (auto gemName : pyGemNames) + { + gemNames.push_back(Py_To_String(gemName)); + } + }); + if (!result.IsSuccess()) + { + return AZ::Failure(result.GetError().c_str()); + } + + return AZ::Success(AZStd::move(gemNames)); + } + + bool PythonBindings::AddProject(const QString& path) + { + bool registrationResult = false; + bool result = ExecuteWithLock( + [&] + { + pybind11::str projectPath = path.toStdString(); + auto pythonRegistrationResult = m_register.attr("register")(pybind11::none(), projectPath); + + // Returns an exit code so boolify it then invert result + registrationResult = !pythonRegistrationResult.cast(); + }); + + return result && registrationResult; + } + + bool PythonBindings::RemoveProject(const QString& path) + { + bool registrationResult = false; + bool result = ExecuteWithLock( + [&] + { + pybind11::str projectPath = path.toStdString(); + auto pythonRegistrationResult = m_register.attr("register")( + pybind11::none(), // engine_path + projectPath, // project_path + pybind11::none(), // gem_path + pybind11::none(), // external_subdir_path + pybind11::none(), // template_path + pybind11::none(), // restricted_path + pybind11::none(), // repo_uri + pybind11::none(), // default_engines_folder + pybind11::none(), // default_projects_folder + pybind11::none(), // default_gems_folder + pybind11::none(), // default_templates_folder + pybind11::none(), // default_restricted_folder + pybind11::none(), // external_subdir_engine_path + pybind11::none(), // external_subdir_project_path + true, // remove + false // force + ); + + // Returns an exit code so boolify it then invert result + registrationResult = !pythonRegistrationResult.cast(); + }); + + return result && registrationResult; + } + + AZ::Outcome PythonBindings::CreateProject(const QString& projectTemplatePath, const ProjectInfo& projectInfo) { ProjectInfo createdProjectInfo; bool result = ExecuteWithLock([&] { - pybind11::str projectPath = projectInfo.m_path.toStdString(); + pybind11::str projectName = projectInfo.m_projectName.toStdString(); pybind11::str templatePath = projectTemplatePath.toStdString(); - auto createProjectResult = m_engineTemplate.attr("create_project")(projectPath, templatePath); + + auto createProjectResult = m_engineTemplate.attr("create_project")( + projectPath, + projectName, + templatePath + ); if (createProjectResult.cast() == 0) { createdProjectInfo = ProjectInfoFromPath(projectPath); @@ -476,16 +621,16 @@ namespace O3DE::ProjectManager } else { - return AZ::Success(AZStd::move(createdProjectInfo)); + return AZ::Success(AZStd::move(createdProjectInfo)); } } - AZ::Outcome PythonBindings::GetProject(const QString& path) + AZ::Outcome PythonBindings::GetProject(const QString& path) { ProjectInfo projectInfo = ProjectInfoFromPath(pybind11::str(path.toStdString())); if (projectInfo.IsValid()) { - return AZ::Success(AZStd::move(projectInfo)); + return AZ::Success(AZStd::move(projectInfo)); } else { @@ -496,30 +641,21 @@ namespace O3DE::ProjectManager GemInfo PythonBindings::GemInfoFromPath(pybind11::handle path) { GemInfo gemInfo; - gemInfo.m_path = Py_To_String(path); + gemInfo.m_path = Py_To_String(path); - auto data = m_registration.attr("get_gem_data")(pybind11::none(), path); + auto data = m_manifest.attr("get_gem_json_data")(pybind11::none(), path); if (pybind11::isinstance(data)) { try { // required - gemInfo.m_name = Py_To_String(data["Name"]); - gemInfo.m_uuid = AZ::Uuid(Py_To_String(data["Uuid"])); + gemInfo.m_name = Py_To_String(data["gem_name"]); // optional - gemInfo.m_displayName = Py_To_String_Optional(data, "DisplayName", gemInfo.m_name); - gemInfo.m_summary = Py_To_String_Optional(data, "Summary", ""); - gemInfo.m_version = Py_To_String_Optional(data, "Version", ""); + gemInfo.m_displayName = Py_To_String_Optional(data, "DisplayName", gemInfo.m_name); + gemInfo.m_summary = Py_To_String_Optional(data, "Summary", ""); + gemInfo.m_version = Py_To_String_Optional(data, "Version", ""); - if (data.contains("Dependencies")) - { - for (auto dependency : data["Dependencies"]) - { - const AZ::Uuid uuid = Py_To_String(dependency["Uuid"]); - gemInfo.m_dependingGemUuids.push_back(uuid.ToString().c_str()); - } - } if (data.contains("Tags")) { for (auto tag : data["Tags"]) @@ -543,13 +679,13 @@ namespace O3DE::ProjectManager projectInfo.m_path = Py_To_String(path); projectInfo.m_isNew = false; - auto projectData = m_registration.attr("get_project_data")(pybind11::none(), path); + auto projectData = m_manifest.attr("get_project_json_data")(pybind11::none(), path); if (pybind11::isinstance(projectData)) { try { - projectInfo.m_projectName = Py_To_String(projectData["project_name"]); - projectInfo.m_displayName = Py_To_String_Optional(projectData,"display_name", projectInfo.m_projectName); + projectInfo.m_projectName = Py_To_String(projectData["project_name"]); + projectInfo.m_displayName = Py_To_String_Optional(projectData,"display_name", projectInfo.m_projectName); } catch ([[maybe_unused]] const std::exception& e) { @@ -560,19 +696,19 @@ namespace O3DE::ProjectManager return projectInfo; } - AZ::Outcome> PythonBindings::GetProjects() + AZ::Outcome> PythonBindings::GetProjects() { QVector projects; bool result = ExecuteWithLock([&] { - // external projects - for (auto path : m_registration.attr("get_projects")()) + // external projects + for (auto path : m_manifest.attr("get_projects")()) { projects.push_back(ProjectInfoFromPath(path)); } - // projects from the engine - for (auto path : m_registration.attr("get_engine_projects")()) + // projects from the engine + for (auto path : m_manifest.attr("get_engine_projects")()) { projects.push_back(ProjectInfoFromPath(path)); } @@ -584,47 +720,43 @@ namespace O3DE::ProjectManager } else { - return AZ::Success(AZStd::move(projects)); + return AZ::Success(AZStd::move(projects)); } } - bool PythonBindings::AddGemToProject(const QString& gemPath, const QString& projectPath) + AZ::Outcome PythonBindings::AddGemToProject(const QString& gemPath, const QString& projectPath) { - bool result = ExecuteWithLock([&] { - pybind11::str pyGemPath = gemPath.toStdString(); - pybind11::str pyProjectPath = projectPath.toStdString(); + return ExecuteWithLockErrorHandling([&] + { + pybind11::str pyGemPath = gemPath.toStdString(); + pybind11::str pyProjectPath = projectPath.toStdString(); - m_registration.attr("add_gem_to_project")( - pybind11::none(), // gem_name - pyGemPath, - pybind11::none(), // gem_target - pybind11::none(), // project_name - pyProjectPath + m_enableGemProject.attr("enable_gem_in_project")( + pybind11::none(), // gem name not needed as path is provided + pyGemPath, + pybind11::none(), // project name not needed as path is provided + pyProjectPath ); - }); - - return result; + }); } - bool PythonBindings::RemoveGemFromProject(const QString& gemPath, const QString& projectPath) + AZ::Outcome PythonBindings::RemoveGemFromProject(const QString& gemPath, const QString& projectPath) { - bool result = ExecuteWithLock([&] { - pybind11::str pyGemPath = gemPath.toStdString(); - pybind11::str pyProjectPath = projectPath.toStdString(); + return ExecuteWithLockErrorHandling([&] + { + pybind11::str pyGemPath = gemPath.toStdString(); + pybind11::str pyProjectPath = projectPath.toStdString(); - m_registration.attr("remove_gem_to_project")( - pybind11::none(), // gem_name - pyGemPath, - pybind11::none(), // gem_target - pybind11::none(), // project_name - pyProjectPath + m_disableGemProject.attr("disable_gem_in_project")( + pybind11::none(), // gem name not needed as path is provided + pyGemPath, + pybind11::none(), // project name not needed as path is provided + pyProjectPath ); - }); - - return result; + }); } - bool PythonBindings::UpdateProject([[maybe_unused]] const ProjectInfo& projectInfo) + bool PythonBindings::UpdateProject([[maybe_unused]] const ProjectInfo& projectInfo) { return false; } @@ -632,18 +764,18 @@ namespace O3DE::ProjectManager ProjectTemplateInfo PythonBindings::ProjectTemplateInfoFromPath(pybind11::handle path) { ProjectTemplateInfo templateInfo; - templateInfo.m_path = Py_To_String(path); + templateInfo.m_path = Py_To_String(path); - auto data = m_registration.attr("get_template_data")(pybind11::none(), path); + auto data = m_manifest.attr("get_template_json_data")(pybind11::none(), path); if (pybind11::isinstance(data)) { try { // required - templateInfo.m_displayName = Py_To_String(data["display_name"]); - templateInfo.m_name = Py_To_String(data["template_name"]); - templateInfo.m_summary = Py_To_String(data["summary"]); - + templateInfo.m_displayName = Py_To_String(data["display_name"]); + templateInfo.m_name = Py_To_String(data["template_name"]); + templateInfo.m_summary = Py_To_String(data["summary"]); + // optional if (data.contains("canonical_tags")) { @@ -669,12 +801,12 @@ namespace O3DE::ProjectManager return templateInfo; } - AZ::Outcome> PythonBindings::GetProjectTemplates() + AZ::Outcome> PythonBindings::GetProjectTemplates() { QVector templates; bool result = ExecuteWithLock([&] { - for (auto path : m_registration.attr("get_project_templates")()) + for (auto path : m_manifest.attr("get_project_templates")()) { templates.push_back(ProjectTemplateInfoFromPath(path)); } @@ -686,7 +818,7 @@ namespace O3DE::ProjectManager } else { - return AZ::Success(AZStd::move(templates)); + return AZ::Success(AZStd::move(templates)); } } } diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.h b/Code/Tools/ProjectManager/Source/PythonBindings.h index 892e13a65b..278aa2d5d7 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.h +++ b/Code/Tools/ProjectManager/Source/PythonBindings.h @@ -12,7 +12,7 @@ #pragma once #include -#include +#include #include // Qt defines slots, which interferes with the use here. @@ -25,7 +25,7 @@ namespace O3DE::ProjectManager { - class PythonBindings + class PythonBindings : public PythonBindingsInterface::Registrar { public: @@ -39,16 +39,20 @@ namespace O3DE::ProjectManager bool SetEngineInfo(const EngineInfo& engineInfo) override; // Gem - AZ::Outcome GetGem(const QString& path) override; - AZ::Outcome> GetGems() override; + AZ::Outcome GetGemInfo(const QString& path) override; + AZ::Outcome, AZStd::string> GetEngineGemInfos() override; + AZ::Outcome, AZStd::string> GetAllGemInfos(const QString& projectPath) override; + AZ::Outcome, AZStd::string> GetEnabledGemNames(const QString& projectPath) override; // Project AZ::Outcome CreateProject(const QString& projectTemplatePath, const ProjectInfo& projectInfo) override; AZ::Outcome GetProject(const QString& path) override; AZ::Outcome> GetProjects() override; + bool AddProject(const QString& path) override; + bool RemoveProject(const QString& path) override; bool UpdateProject(const ProjectInfo& projectInfo) override; - bool AddGemToProject(const QString& gemPath, const QString& projectPath) override; - bool RemoveGemFromProject(const QString& gemPath, const QString& projectPath) override; + AZ::Outcome AddGemToProject(const QString& gemPath, const QString& projectPath) override; + AZ::Outcome RemoveGemFromProject(const QString& gemPath, const QString& projectPath) override; // ProjectTemplate AZ::Outcome> GetProjectTemplates() override; @@ -56,16 +60,23 @@ namespace O3DE::ProjectManager private: AZ_DISABLE_COPY_MOVE(PythonBindings); + AZ::Outcome ExecuteWithLockErrorHandling(AZStd::function executionCallback); bool ExecuteWithLock(AZStd::function executionCallback); GemInfo GemInfoFromPath(pybind11::handle path); ProjectInfo ProjectInfoFromPath(pybind11::handle path); ProjectTemplateInfo ProjectTemplateInfoFromPath(pybind11::handle path); + bool RegisterThisEngine(); bool StartPython(); bool StopPython(); + AZ::IO::FixedMaxPath m_enginePath; pybind11::handle m_engineTemplate; AZStd::recursive_mutex m_lock; - pybind11::handle m_registration; + pybind11::handle m_cmake; + pybind11::handle m_register; + pybind11::handle m_manifest; + pybind11::handle m_enableGemProject; + pybind11::handle m_disableGemProject; }; } diff --git a/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h b/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h index b5c8f1a76a..09d9187dbd 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h +++ b/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h @@ -57,13 +57,27 @@ namespace O3DE::ProjectManager * @param path the absolute path to the Gem * @return an outcome with GemInfo on success */ - virtual AZ::Outcome GetGem(const QString& path) = 0; + virtual AZ::Outcome GetGemInfo(const QString& path) = 0; /** - * Get info about all known Gems - * @return an outcome with GemInfos on success + * Get all available gem infos. This concatenates gems registered by the engine and the project. + * @param path The absolute path to the project. + * @return A list of gem infos. */ - virtual AZ::Outcome> GetGems() = 0; + virtual AZ::Outcome, AZStd::string> GetAllGemInfos(const QString& projectPath) = 0; + + /** + * Get engine gem infos. + * @return A list of all registered gem infos. + */ + virtual AZ::Outcome, AZStd::string> GetEngineGemInfos() = 0; + + /** + * Get a list of all enabled gem names for a given project. + * @param[in] projectPath Absolute file path to the project. + * @return A list of gem names of all the enabled gems for a given project or a error message on failure. + */ + virtual AZ::Outcome, AZStd::string> GetEnabledGemNames(const QString& projectPath) = 0; // Projects @@ -88,6 +102,20 @@ namespace O3DE::ProjectManager * @return an outcome with ProjectInfos on success */ virtual AZ::Outcome> GetProjects() = 0; + + /** + * Adds existing project on disk + * @param path the absolute path to the project + * @return true on success, false on failure + */ + virtual bool AddProject(const QString& path) = 0; + + /** + * Adds existing project on disk + * @param path the absolute path to the project + * @return true on success, false on failure + */ + virtual bool RemoveProject(const QString& path) = 0; /** * Update a project @@ -100,17 +128,17 @@ namespace O3DE::ProjectManager * Add a gem to a project * @param gemPath the absolute path to the gem * @param projectPath the absolute path to the project - * @return true on success, false on failure + * @return An outcome with the success flag as well as an error message in case of a failure. */ - virtual bool AddGemToProject(const QString& gemPath, const QString& projectPath) = 0; + virtual AZ::Outcome AddGemToProject(const QString& gemPath, const QString& projectPath) = 0; /** * Remove gem to a project * @param gemPath the absolute path to the gem * @param projectPath the absolute path to the project - * @return true on success, false on failure + * @return An outcome with the success flag as well as an error message in case of a failure. */ - virtual bool RemoveGemFromProject(const QString& gemPath, const QString& projectPath) = 0; + virtual AZ::Outcome RemoveGemFromProject(const QString& gemPath, const QString& projectPath) = 0; // Project Templates diff --git a/Code/Tools/ProjectManager/Source/ScreenDefs.h b/Code/Tools/ProjectManager/Source/ScreenDefs.h index 46d243f677..198f1b5d03 100644 --- a/Code/Tools/ProjectManager/Source/ScreenDefs.h +++ b/Code/Tools/ProjectManager/Source/ScreenDefs.h @@ -11,9 +11,13 @@ */ #pragma once +#include +#include +#include + namespace O3DE::ProjectManager { - enum ProjectManagerScreen + enum class ProjectManagerScreen { Invalid = -1, Empty, @@ -22,7 +26,24 @@ namespace O3DE::ProjectManager GemCatalog, Projects, UpdateProject, - ProjectSettings, + UpdateProjectSettings, EngineSettings }; + + static QHash s_ProjectManagerStringNames = { + { "Empty", ProjectManagerScreen::Empty}, + { "CreateProject", ProjectManagerScreen::CreateProject}, + { "NewProjectSettings", ProjectManagerScreen::NewProjectSettings}, + { "GemCatalog", ProjectManagerScreen::GemCatalog}, + { "Projects", ProjectManagerScreen::Projects}, + { "UpdateProject", ProjectManagerScreen::UpdateProject}, + { "UpdateProjectSettings", ProjectManagerScreen::UpdateProjectSettings}, + { "EngineSettings", ProjectManagerScreen::EngineSettings} + }; + + // need to define qHash for ProjectManagerScreen when using scoped enums + inline uint qHash(ProjectManagerScreen key, uint seed) + { + return ::qHash(static_cast(key), seed); + } } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/ScreenFactory.cpp b/Code/Tools/ProjectManager/Source/ScreenFactory.cpp index b2b4376e14..a85f44080d 100644 --- a/Code/Tools/ProjectManager/Source/ScreenFactory.cpp +++ b/Code/Tools/ProjectManager/Source/ScreenFactory.cpp @@ -16,7 +16,7 @@ #include #include #include -#include +#include #include namespace O3DE::ProjectManager @@ -42,8 +42,8 @@ namespace O3DE::ProjectManager case (ProjectManagerScreen::UpdateProject): newScreen = new UpdateProjectCtrl(parent); break; - case (ProjectManagerScreen::ProjectSettings): - newScreen = new ProjectSettingsScreen(parent); + case (ProjectManagerScreen::UpdateProjectSettings): + newScreen = new UpdateProjectSettingsScreen(parent); break; case (ProjectManagerScreen::EngineSettings): newScreen = new EngineSettingsScreen(parent); diff --git a/Code/Tools/ProjectManager/Source/ScreensCtrl.cpp b/Code/Tools/ProjectManager/Source/ScreensCtrl.cpp index 7d31d02f6c..52fcbf354a 100644 --- a/Code/Tools/ProjectManager/Source/ScreensCtrl.cpp +++ b/Code/Tools/ProjectManager/Source/ScreensCtrl.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -136,6 +137,7 @@ namespace O3DE::ProjectManager { shouldRestoreCurrentScreen = true; } + int tabIndex = GetScreenTabIndex(screen); // Delete old screen if it exists to start fresh DeleteScreen(screen); @@ -144,11 +146,19 @@ namespace O3DE::ProjectManager ScreenWidget* newScreen = BuildScreen(this, screen); if (newScreen->IsTab()) { - m_tabWidget->addTab(newScreen, newScreen->GetTabText()); + if (tabIndex > -1) + { + m_tabWidget->insertTab(tabIndex, newScreen, newScreen->GetTabText()); + } + else + { + m_tabWidget->addTab(newScreen, newScreen->GetTabText()); + } if (shouldRestoreCurrentScreen) { m_tabWidget->setCurrentWidget(newScreen); m_screenStack->setCurrentWidget(m_tabWidget); + newScreen->NotifyCurrentScreen(); } } else @@ -157,6 +167,7 @@ namespace O3DE::ProjectManager if (shouldRestoreCurrentScreen) { m_screenStack->setCurrentWidget(newScreen); + newScreen->NotifyCurrentScreen(); } } @@ -219,4 +230,19 @@ namespace O3DE::ProjectManager screen->NotifyCurrentScreen(); } } + + int ScreensCtrl::GetScreenTabIndex(ProjectManagerScreen screen) + { + const auto iter = m_screenMap.find(screen); + if (iter != m_screenMap.end()) + { + ScreenWidget* screenWidget = iter.value(); + if (screenWidget->IsTab()) + { + return m_tabWidget->indexOf(screenWidget); + } + } + + return -1; + } } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/ScreensCtrl.h b/Code/Tools/ProjectManager/Source/ScreensCtrl.h index 935fc78e25..3b51ed529a 100644 --- a/Code/Tools/ProjectManager/Source/ScreensCtrl.h +++ b/Code/Tools/ProjectManager/Source/ScreensCtrl.h @@ -51,6 +51,8 @@ namespace O3DE::ProjectManager void TabChanged(int index); private: + int GetScreenTabIndex(ProjectManagerScreen screen); + QStackedWidget* m_screenStack; QHash m_screenMap; QStack m_screenVisitOrder; diff --git a/Code/Tools/ProjectManager/Source/TagWidget.cpp b/Code/Tools/ProjectManager/Source/TagWidget.cpp index 3e80944204..628b682b95 100644 --- a/Code/Tools/ProjectManager/Source/TagWidget.cpp +++ b/Code/Tools/ProjectManager/Source/TagWidget.cpp @@ -18,9 +18,9 @@ namespace O3DE::ProjectManager TagWidget::TagWidget(const QString& text, QWidget* parent) : QLabel(text, parent) { - setFixedHeight(35); + setFixedHeight(24); setMargin(5); - setStyleSheet("font-size: 12pt; background-color: #333333; border-radius: 4px;"); + setStyleSheet("font-size: 12px; background-color: #333333; border-radius: 3px;"); } TagContainerWidget::TagContainerWidget(QWidget* parent) @@ -35,7 +35,7 @@ namespace O3DE::ProjectManager void TagContainerWidget::Update(const QStringList& tags) { QWidget* parentWidget = qobject_cast(parent()); - int width = 250; + int width = 200; if (parentWidget) { width = parentWidget->width(); diff --git a/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.cpp b/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.cpp index b3180966ce..a383a0f93b 100644 --- a/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.cpp +++ b/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.cpp @@ -10,15 +10,20 @@ * */ -#include -#include +#include #include -#include +#include +#include +#include +#include +#include #include -#include -#include #include +#include +#include +#include +#include namespace O3DE::ProjectManager { @@ -26,31 +31,57 @@ namespace O3DE::ProjectManager : ScreenWidget(parent) { QVBoxLayout* vLayout = new QVBoxLayout(); - setLayout(vLayout); + vLayout->setContentsMargins(0, 0, 0, 0); - m_screensCtrl = new ScreensCtrl(); - vLayout->addWidget(m_screensCtrl); + m_header = new ScreenHeader(this); + m_header->setTitle(tr("")); + m_header->setSubTitle(tr("Edit Project Settings:")); + connect(m_header->backButton(), &QPushButton::clicked, this, &UpdateProjectCtrl::HandleBackButton); + vLayout->addWidget(m_header); + + m_updateSettingsScreen = new UpdateProjectSettingsScreen(); + m_gemCatalogScreen = new GemCatalogScreen(); + + m_stack = new QStackedWidget(this); + m_stack->setObjectName("body"); + m_stack->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding)); + vLayout->addWidget(m_stack); + + QFrame* topBarFrameWidget = new QFrame(this); + topBarFrameWidget->setObjectName("projectSettingsTopFrame"); + QHBoxLayout* topBarHLayout = new QHBoxLayout(); + topBarHLayout->setContentsMargins(0, 0, 0, 0); + topBarFrameWidget->setLayout(topBarHLayout); + + QTabWidget* tabWidget = new QTabWidget(); + tabWidget->setObjectName("projectSettingsTab"); + tabWidget->tabBar()->setObjectName("projectSettingsTabBar"); + tabWidget->addTab(m_updateSettingsScreen, tr("General")); + + QPushButton* gemsButton = new QPushButton(tr("Add More Gems"), this); + topBarHLayout->addWidget(gemsButton); + tabWidget->setCornerWidget(gemsButton); + + topBarHLayout->addWidget(tabWidget); + + m_stack->addWidget(topBarFrameWidget); + m_stack->addWidget(m_gemCatalogScreen); QDialogButtonBox* backNextButtons = new QDialogButtonBox(); + backNextButtons->setObjectName("footer"); vLayout->addWidget(backNextButtons); m_backButton = backNextButtons->addButton(tr("Back"), QDialogButtonBox::RejectRole); + m_backButton->setProperty("secondary", true); m_nextButton = backNextButtons->addButton(tr("Next"), QDialogButtonBox::ApplyRole); - connect(m_backButton, &QPushButton::pressed, this, &UpdateProjectCtrl::HandleBackButton); - connect(m_nextButton, &QPushButton::pressed, this, &UpdateProjectCtrl::HandleNextButton); + connect(gemsButton, &QPushButton::clicked, this, &UpdateProjectCtrl::HandleGemsButton); + connect(m_backButton, &QPushButton::clicked, this, &UpdateProjectCtrl::HandleBackButton); + connect(m_nextButton, &QPushButton::clicked, this, &UpdateProjectCtrl::HandleNextButton); connect(reinterpret_cast(parent), &ScreensCtrl::NotifyCurrentProject, this, &UpdateProjectCtrl::UpdateCurrentProject); - m_screensOrder = - { - ProjectManagerScreen::ProjectSettings, - ProjectManagerScreen::GemCatalog - }; - m_screensCtrl->BuildScreens(m_screensOrder); - m_screensCtrl->ForceChangeToScreen(ProjectManagerScreen::ProjectSettings, false); - - UpdateNextButtonText(); - + Update(); + setLayout(vLayout); } ProjectManagerScreen UpdateProjectCtrl::GetScreenEnum() @@ -58,63 +89,80 @@ namespace O3DE::ProjectManager return ProjectManagerScreen::UpdateProject; } + void UpdateProjectCtrl::NotifyCurrentScreen() + { + m_stack->setCurrentIndex(ScreenOrder::Settings); + Update(); + } + + void UpdateProjectCtrl::HandleGemsButton() + { + // The next page is the gem catalog. Gather the available gems that will be shown in the gem catalog. + m_gemCatalogScreen->ReinitForProject(m_projectInfo.m_path, /*isNewProject=*/false); + + m_stack->setCurrentWidget(m_gemCatalogScreen); + Update(); + } + void UpdateProjectCtrl::HandleBackButton() { - if (!m_screensCtrl->GotoPreviousScreen()) + if (m_stack->currentIndex() > 0) { - emit GotoPreviousScreenRequest(); + m_stack->setCurrentIndex(m_stack->currentIndex() - 1); + Update(); } else { - UpdateNextButtonText(); + emit GotoPreviousScreenRequest(); } } + void UpdateProjectCtrl::HandleNextButton() { - ScreenWidget* currentScreen = m_screensCtrl->GetCurrentScreen(); - ProjectManagerScreen screenEnum = currentScreen->GetScreenEnum(); - auto screenOrderIter = m_screensOrder.begin(); - for (; screenOrderIter != m_screensOrder.end(); ++screenOrderIter) + if (m_stack->currentIndex() == ScreenOrder::Settings) { - if (*screenOrderIter == screenEnum) + if (m_updateSettingsScreen) { - ++screenOrderIter; - break; - } - } - - if (screenEnum == ProjectManagerScreen::ProjectSettings) - { - auto projectScreen = reinterpret_cast(currentScreen); - if (projectScreen) - { - if (!projectScreen->Validate()) + if (!m_updateSettingsScreen->Validate()) { QMessageBox::critical(this, tr("Invalid project settings"), tr("Invalid project settings")); return; } - m_projectInfo = projectScreen->GetProjectInfo(); + ProjectInfo newProjectSettings = m_updateSettingsScreen->GetProjectInfo(); + + // Update project if settings changed + if (m_projectInfo != newProjectSettings) + { + bool result = PythonBindingsInterface::Get()->UpdateProject(newProjectSettings); + if (!result) + { + QMessageBox::critical(this, tr("Project update failed"), tr("Failed to update project.")); + return; + } + } + + // Check if project path has changed and move it + if (newProjectSettings.m_path != m_projectInfo.m_path) + { + if (!ProjectUtils::MoveProject(m_projectInfo.m_path, newProjectSettings.m_path)) + { + QMessageBox::critical(this, tr("Project move failed"), tr("Failed to move project.")); + return; + } + } + + m_projectInfo = newProjectSettings; } } - if (screenOrderIter != m_screensOrder.end()) + if (m_stack->currentIndex() == ScreenOrder::Gems && m_gemCatalogScreen) { - m_screensCtrl->ChangeToScreen(*screenOrderIter); - UpdateNextButtonText(); - } - else - { - auto result = PythonBindingsInterface::Get()->UpdateProject(m_projectInfo); - if (result) - { - emit ChangeScreenRequest(ProjectManagerScreen::Projects); - } - else - { - QMessageBox::critical(this, tr("Project update failed"), tr("Failed to update project.")); - } + // Enable or disable the gems that got adjusted in the gem catalog and apply them to the given project. + m_gemCatalogScreen->EnableDisableGemsForProject(m_projectInfo.m_path); } + + emit ChangeScreenRequest(ProjectManagerScreen::Projects); } void UpdateProjectCtrl::UpdateCurrentProject(const QString& projectPath) @@ -124,16 +172,28 @@ namespace O3DE::ProjectManager { m_projectInfo = projectResult.GetValue(); } + + Update(); + UpdateSettingsScreen(); } - void UpdateProjectCtrl::UpdateNextButtonText() + void UpdateProjectCtrl::Update() { - QString nextButtonText = tr("Continue"); - if (m_screensCtrl->GetCurrentScreen()->GetScreenEnum() == ProjectManagerScreen::GemCatalog) + if (m_stack->currentIndex() == ScreenOrder::Gems) { - nextButtonText = tr("Update Project"); + m_header->setSubTitle(QString(tr("Add More Gems to \"%1\"")).arg(m_projectInfo.m_projectName)); + m_nextButton->setText(tr("Confirm")); } - m_nextButton->setText(nextButtonText); + else + { + m_header->setSubTitle(QString(tr("Edit Project Settings: \"%1\"")).arg(m_projectInfo.m_projectName)); + m_nextButton->setText(tr("Save")); + } + } + + void UpdateProjectCtrl::UpdateSettingsScreen() + { + m_updateSettingsScreen->SetProjectInfo(m_projectInfo); } } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.h b/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.h index ee871e7bb2..231bfb8f19 100644 --- a/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.h +++ b/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.h @@ -12,40 +12,57 @@ #pragma once #if !defined(Q_MOC_RUN) -#include "ProjectInfo.h" +#include #include -#include -#include #endif +QT_FORWARD_DECLARE_CLASS(QStackedWidget) +QT_FORWARD_DECLARE_CLASS(QTabWidget) +QT_FORWARD_DECLARE_CLASS(QPushButton) +QT_FORWARD_DECLARE_CLASS(QFrame) namespace O3DE::ProjectManager { - class UpdateProjectCtrl - : public ScreenWidget + QT_FORWARD_DECLARE_CLASS(ScreenHeader) + QT_FORWARD_DECLARE_CLASS(UpdateProjectSettingsScreen) + QT_FORWARD_DECLARE_CLASS(GemCatalogScreen) + + class UpdateProjectCtrl : public ScreenWidget { public: explicit UpdateProjectCtrl(QWidget* parent = nullptr); ~UpdateProjectCtrl() = default; ProjectManagerScreen GetScreenEnum() override; + protected: + void NotifyCurrentScreen() override; protected slots: void HandleBackButton(); void HandleNextButton(); + void HandleGemsButton(); void UpdateCurrentProject(const QString& projectPath); private: - void UpdateNextButtonText(); + void Update(); + void UpdateSettingsScreen(); - ScreensCtrl* m_screensCtrl; - QPushButton* m_backButton; - QPushButton* m_nextButton; + enum ScreenOrder + { + Settings, + Gems + }; + + ScreenHeader* m_header = nullptr; + QStackedWidget* m_stack = nullptr; + UpdateProjectSettingsScreen* m_updateSettingsScreen = nullptr; + GemCatalogScreen* m_gemCatalogScreen = nullptr; + + QPushButton* m_backButton = nullptr; + QPushButton* m_nextButton = nullptr; QVector m_screensOrder; ProjectInfo m_projectInfo; - - ProjectManagerScreen m_screenEnum; }; } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/UpdateProjectSettingsScreen.cpp b/Code/Tools/ProjectManager/Source/UpdateProjectSettingsScreen.cpp new file mode 100644 index 0000000000..c29be3c7fd --- /dev/null +++ b/Code/Tools/ProjectManager/Source/UpdateProjectSettingsScreen.cpp @@ -0,0 +1,51 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include +#include +#include + +#include +#include + +namespace O3DE::ProjectManager +{ + UpdateProjectSettingsScreen::UpdateProjectSettingsScreen(QWidget* parent) + : ProjectSettingsScreen(parent) + { + } + + ProjectManagerScreen UpdateProjectSettingsScreen::GetScreenEnum() + { + return ProjectManagerScreen::UpdateProjectSettings; + } + + void UpdateProjectSettingsScreen::SetProjectInfo(const ProjectInfo& projectInfo) + { + m_projectName->lineEdit()->setText(projectInfo.m_projectName); + m_projectPath->lineEdit()->setText(projectInfo.m_path); + } + + bool UpdateProjectSettingsScreen::ValidateProjectPath() + { + bool projectPathIsValid = true; + if (m_projectPath->lineEdit()->text().isEmpty()) + { + projectPathIsValid = false; + m_projectPath->setErrorLabelText(tr("Please provide a valid location.")); + } + + m_projectPath->setErrorLabelVisible(!projectPathIsValid); + return projectPathIsValid; + } + +} // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/UpdateProjectSettingsScreen.h b/Code/Tools/ProjectManager/Source/UpdateProjectSettingsScreen.h new file mode 100644 index 0000000000..95bbceb9c6 --- /dev/null +++ b/Code/Tools/ProjectManager/Source/UpdateProjectSettingsScreen.h @@ -0,0 +1,34 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ +#pragma once + +#if !defined(Q_MOC_RUN) +#include +#endif + +namespace O3DE::ProjectManager +{ + class UpdateProjectSettingsScreen + : public ProjectSettingsScreen + { + public: + explicit UpdateProjectSettingsScreen(QWidget* parent = nullptr); + ~UpdateProjectSettingsScreen() = default; + ProjectManagerScreen GetScreenEnum() override; + + void SetProjectInfo(const ProjectInfo& projectInfo); + + protected: + bool ValidateProjectPath() override; + }; + +} // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/main.cpp b/Code/Tools/ProjectManager/Source/main.cpp index cbeacbaf65..c597b8a729 100644 --- a/Code/Tools/ProjectManager/Source/main.cpp +++ b/Code/Tools/ProjectManager/Source/main.cpp @@ -15,13 +15,17 @@ #include #include #include +#include #include +#include #include #include #include +using namespace O3DE::ProjectManager; + int main(int argc, char* argv[]) { QApplication::setOrganizationName("O3DE"); @@ -51,7 +55,29 @@ int main(int argc, char* argv[]) AzQtComponents::StyleManager styleManager(&app); styleManager.initialize(&app, engineRootPath); - O3DE::ProjectManager::ProjectManagerWindow window(nullptr, engineRootPath); + // Get the initial start screen if one is provided via command line + constexpr char optionPrefix[] = "--"; + AZ::CommandLine commandLine(optionPrefix); + commandLine.Parse(argc, argv); + + ProjectManagerScreen startScreen = ProjectManagerScreen::Projects; + if(commandLine.HasSwitch("screen")) + { + QString screenOption = commandLine.GetSwitchValue("screen", 0).c_str(); + ProjectManagerScreen screen = ProjectUtils::GetProjectManagerScreen(screenOption); + if (screen != ProjectManagerScreen::Invalid) + { + startScreen = screen; + } + } + + AZ::IO::FixedMaxPath projectPath; + if (commandLine.HasSwitch("project-path")) + { + projectPath = commandLine.GetSwitchValue("project-path", 0).c_str(); + } + + ProjectManagerWindow window(nullptr, engineRootPath, projectPath, startScreen); window.show(); // somethings is preventing us from moving the window to the center of the diff --git a/Code/Tools/ProjectManager/project_manager_files.cmake b/Code/Tools/ProjectManager/project_manager_files.cmake index 223465f3c8..a7a36f26ab 100644 --- a/Code/Tools/ProjectManager/project_manager_files.cmake +++ b/Code/Tools/ProjectManager/project_manager_files.cmake @@ -36,6 +36,10 @@ set(FILES Source/PythonBindingsInterface.h Source/ProjectInfo.h Source/ProjectInfo.cpp + Source/ProjectUtils.h + Source/ProjectUtils.cpp + Source/UpdateProjectSettingsScreen.h + Source/UpdateProjectSettingsScreen.cpp Source/NewProjectSettingsScreen.h Source/NewProjectSettingsScreen.cpp Source/CreateProjectCtrl.h @@ -46,7 +50,6 @@ set(FILES Source/ProjectsScreen.cpp Source/ProjectSettingsScreen.h Source/ProjectSettingsScreen.cpp - Source/ProjectSettingsScreen.ui Source/EngineSettingsScreen.h Source/EngineSettingsScreen.cpp Source/ProjectButtonWidget.h diff --git a/Code/Tools/SceneAPI/SDKWrapper/AssImpSceneWrapper.cpp b/Code/Tools/SceneAPI/SDKWrapper/AssImpSceneWrapper.cpp index 2cda1e68ae..336cc1b172 100644 --- a/Code/Tools/SceneAPI/SDKWrapper/AssImpSceneWrapper.cpp +++ b/Code/Tools/SceneAPI/SDKWrapper/AssImpSceneWrapper.cpp @@ -17,6 +17,13 @@ #include #include +#if AZ_TRAIT_COMPILER_SUPPORT_CSIGNAL +#include +#include +#include +#include +#endif // AZ_TRAIT_COMPILER_SUPPORT_CSIGNAL + namespace AZ { namespace AssImpSDKWrapper @@ -34,10 +41,31 @@ namespace AZ { } +#if AZ_TRAIT_COMPILER_SUPPORT_CSIGNAL + void signal_handler([[maybe_unused]] int signal) + { + AZ_TracePrintf( + SceneAPI::Utilities::ErrorWindow, + "Failed to import scene with Asset Importer library. An %s has occured in the library, this scene file cannot be parsed by the library.", + signal == SIGABRT ? "assert" : "unknown error"); + } +#endif // AZ_TRAIT_COMPILER_SUPPORT_CSIGNAL + bool AssImpSceneWrapper::LoadSceneFromFile(const char* fileName) { AZ_TracePrintf(SceneAPI::Utilities::LogWindow, "AssImpSceneWrapper::LoadSceneFromFile %s", fileName); AZ_TraceContext("Filename", fileName); + +#if AZ_TRAIT_COMPILER_SUPPORT_CSIGNAL + // Turn off the abort popup because it can disrupt automation. + // AssImp calls abort when asserts are enabled, and an assert is encountered. +#ifdef _WRITE_ABORT_MSG + _set_abort_behavior(0, _WRITE_ABORT_MSG); +#endif // #ifdef _WRITE_ABORT_MSG + // Instead, capture any calls to abort with a signal handler, and report them. + auto previous_handler = std::signal(SIGABRT, signal_handler); +#endif // AZ_TRAIT_COMPILER_SUPPORT_CSIGNAL + // aiProcess_JoinIdenticalVertices is not enabled because O3DE has a mesh optimizer that also does this, // this flag is disabled to keep AssImp output similar to FBX SDK to reduce downstream bugs for the initial AssImp release. // There's currently a minimum of properties and flags set to maximize compatibility with the existing node graph. @@ -49,6 +77,15 @@ namespace AZ | aiProcess_LimitBoneWeights //Limits the number of bones that can affect a vertex to a maximum value //dropping the least important and re-normalizing | aiProcess_GenNormals); //Generate normals for meshes + +#if AZ_TRAIT_COMPILER_SUPPORT_CSIGNAL + // Reset abort behavior for anything else that may call abort. + std::signal(SIGABRT, previous_handler); +#ifdef _WRITE_ABORT_MSG + _set_abort_behavior(1, _WRITE_ABORT_MSG); +#endif // #ifdef _WRITE_ABORT_MSG +#endif // AZ_TRAIT_COMPILER_SUPPORT_CSIGNAL + if (!m_assImpScene) { AZ_TracePrintf(SceneAPI::Utilities::ErrorWindow, "Failed to import Asset Importer Scene. Error returned: %s", m_importer.GetErrorString()); diff --git a/Code/Tools/SceneAPI/SceneUI/RowWidgets/TransformRowHandler.cpp b/Code/Tools/SceneAPI/SceneUI/RowWidgets/TransformRowHandler.cpp index 322aa9ac51..640c092070 100644 --- a/Code/Tools/SceneAPI/SceneUI/RowWidgets/TransformRowHandler.cpp +++ b/Code/Tools/SceneAPI/SceneUI/RowWidgets/TransformRowHandler.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include namespace AZ @@ -58,10 +59,11 @@ namespace AZ } else { - AzToolsFramework::Vector3PropertyHandler handler; - handler.ConsumeAttribute(widget->GetTranslationWidget(), attrib, attrValue, debugName); - handler.ConsumeAttribute(widget->GetRotationWidget(), attrib, attrValue, debugName); - handler.ConsumeAttribute(widget->GetScaleWidget(), attrib, attrValue, debugName); + AzToolsFramework::Vector3PropertyHandler vector3Handler; + vector3Handler.ConsumeAttribute(widget->GetTranslationWidget(), attrib, attrValue, debugName); + vector3Handler.ConsumeAttribute(widget->GetRotationWidget(), attrib, attrValue, debugName); + AzToolsFramework::doublePropertySpinboxHandler spinboxHandler; + spinboxHandler.ConsumeAttribute(widget->GetScaleWidget(), attrib, attrValue, debugName); } } diff --git a/Code/Tools/SceneAPI/SceneUI/RowWidgets/TransformRowWidget.cpp b/Code/Tools/SceneAPI/SceneUI/RowWidgets/TransformRowWidget.cpp index 10e0fd2a68..e8ecaa0c27 100644 --- a/Code/Tools/SceneAPI/SceneUI/RowWidgets/TransformRowWidget.cpp +++ b/Code/Tools/SceneAPI/SceneUI/RowWidgets/TransformRowWidget.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -47,7 +48,7 @@ namespace AZ ExpandedTransform::ExpandedTransform() : m_translation(0, 0, 0) , m_rotation(0, 0, 0) - , m_scale(1, 1, 1) + , m_scale(1) { } @@ -60,14 +61,14 @@ namespace AZ { m_translation = transform.GetTranslation(); m_rotation = transform.GetEulerDegrees(); - m_scale = transform.GetScale(); + m_scale = transform.GetUniformScale(); } void ExpandedTransform::GetTransform(AZ::Transform& transform) const { transform = Transform::CreateTranslation(m_translation); transform *= AZ::ConvertEulerDegreesToTransform(m_rotation); - transform.MultiplyByScale(m_scale); + transform.MultiplyByUniformScale(m_scale); } const AZ::Vector3& ExpandedTransform::GetTranslation() const @@ -90,12 +91,12 @@ namespace AZ m_rotation = rotation; } - const AZ::Vector3& ExpandedTransform::GetScale() const + const float ExpandedTransform::GetScale() const { return m_scale; } - void ExpandedTransform::SetScale(const AZ::Vector3& scale) + void ExpandedTransform::SetScale(const float scale) { m_scale = scale; } @@ -131,7 +132,7 @@ namespace AZ m_rotationWidget->setMaximum(360); m_rotationWidget->setSuffix(" degrees"); - m_scaleWidget = new AzQtComponents::VectorInput(this, 3); + m_scaleWidget = new AzToolsFramework::PropertyDoubleSpinCtrl(this); m_scaleWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); m_scaleWidget->setMinimum(0); m_scaleWidget->setMaximum(10000); @@ -191,13 +192,10 @@ namespace AZ AzToolsFramework::PropertyEditorGUIMessages::Bus::Broadcast(&AzToolsFramework::PropertyEditorGUIMessages::RequestWrite, this); }); - QObject::connect(m_scaleWidget, &AzQtComponents::VectorInput::valueChanged, this, [this] + QObject::connect(m_scaleWidget, &AzToolsFramework::PropertyDoubleSpinCtrl::valueChanged, this, [this] { - AzQtComponents::VectorInput* widget = this->GetScaleWidget(); - AZ::Vector3 scale; - - PopulateVector3(widget, scale); - + AzToolsFramework::PropertyDoubleSpinCtrl* widget = this->GetScaleWidget(); + float scale = aznumeric_cast(widget->value()); m_transform.SetScale(scale); AzToolsFramework::PropertyEditorGUIMessages::Bus::Broadcast(&AzToolsFramework::PropertyEditorGUIMessages::RequestWrite, this); }); @@ -224,9 +222,7 @@ namespace AZ m_rotationWidget->setValuebyIndex(m_transform.GetRotation().GetY(), 1); m_rotationWidget->setValuebyIndex(m_transform.GetRotation().GetZ(), 2); - m_scaleWidget->setValuebyIndex(m_transform.GetScale().GetX(), 0); - m_scaleWidget->setValuebyIndex(m_transform.GetScale().GetY(), 1); - m_scaleWidget->setValuebyIndex(m_transform.GetScale().GetZ(), 2); + m_scaleWidget->setValue(m_transform.GetScale()); blockSignals(false); } @@ -251,7 +247,7 @@ namespace AZ return m_rotationWidget; } - AzQtComponents::VectorInput* TransformRowWidget::GetScaleWidget() + AzToolsFramework::PropertyDoubleSpinCtrl* TransformRowWidget::GetScaleWidget() { return m_scaleWidget; } diff --git a/Code/Tools/SceneAPI/SceneUI/RowWidgets/TransformRowWidget.h b/Code/Tools/SceneAPI/SceneUI/RowWidgets/TransformRowWidget.h index dc3286f80e..3977d26c7c 100644 --- a/Code/Tools/SceneAPI/SceneUI/RowWidgets/TransformRowWidget.h +++ b/Code/Tools/SceneAPI/SceneUI/RowWidgets/TransformRowWidget.h @@ -21,6 +21,7 @@ #include #include #include + #endif namespace AzQtComponents @@ -28,6 +29,11 @@ namespace AzQtComponents class VectorInput; } +namespace AzToolsFramework +{ + class PropertyDoubleSpinCtrl; +} + namespace AZ { namespace SceneAPI @@ -51,14 +57,14 @@ namespace AZ const AZ::Vector3& GetRotation() const; void SetRotation(const AZ::Vector3& translation); - const AZ::Vector3& GetScale() const; - void SetScale(const AZ::Vector3& scale); + const float GetScale() const; + void SetScale(const float scale); private: AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING AZ::Vector3 m_translation; AZ::Vector3 m_rotation; - AZ::Vector3 m_scale; + float m_scale; AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING }; @@ -78,7 +84,7 @@ namespace AZ AzQtComponents::VectorInput* GetTranslationWidget(); AzQtComponents::VectorInput* GetRotationWidget(); - AzQtComponents::VectorInput* GetScaleWidget(); + AzToolsFramework::PropertyDoubleSpinCtrl* GetScaleWidget(); protected: ExpandedTransform m_transform; @@ -87,7 +93,7 @@ namespace AZ AzQtComponents::VectorInput* m_translationWidget; AzQtComponents::VectorInput* m_rotationWidget; - AzQtComponents::VectorInput* m_scaleWidget; + AzToolsFramework::PropertyDoubleSpinCtrl* m_scaleWidget; }; } // namespace SceneUI } // namespace SceneAPI diff --git a/Code/Tools/SceneAPI/SceneUI/Tests/RowWidgets/TransformRowWidgetTests.cpp b/Code/Tools/SceneAPI/SceneUI/Tests/RowWidgets/TransformRowWidgetTests.cpp index 05082f29fb..cda6582e63 100644 --- a/Code/Tools/SceneAPI/SceneUI/Tests/RowWidgets/TransformRowWidgetTests.cpp +++ b/Code/Tools/SceneAPI/SceneUI/Tests/RowWidgets/TransformRowWidgetTests.cpp @@ -30,7 +30,7 @@ namespace AZ Vector3 m_translation = Vector3(10.0f, 20.0f, 30.0f); Vector3 m_rotation = Vector3(30.0f, 45.0f, 60.0f); - Vector3 m_scale = Vector3(2.0f, 3.0f, 4.0f); + float m_scale = 3.0f; }; TEST_F(TransformRowWidgetTest, GetTranslation_TranslationInMatrix_TranslationCanBeRetrievedDirectly) @@ -83,26 +83,22 @@ namespace AZ TEST_F(TransformRowWidgetTest, GetScale_ScaleInMatrix_ScaleCanBeRetrievedDirectly) { - m_transform = Transform::CreateScale(m_scale); + m_transform = Transform::CreateUniformScale(m_scale); m_expanded.SetTransform(m_transform); - const Vector3& returned = m_expanded.GetScale(); - EXPECT_NEAR(m_scale.GetX(), returned.GetX(), 0.1f); - EXPECT_NEAR(m_scale.GetY(), returned.GetY(), 0.1f); - EXPECT_NEAR(m_scale.GetZ(), returned.GetZ(), 0.1f); + const float returned = m_expanded.GetScale(); + EXPECT_NEAR(m_scale, returned, 0.1f); } TEST_F(TransformRowWidgetTest, GetScale_ScaleInMatrix_ScaleCanBeRetrievedFromTransform) { - m_transform = Transform::CreateScale(m_scale); + m_transform = Transform::CreateUniformScale(m_scale); m_expanded.SetTransform(m_transform); Transform rebuild; m_expanded.GetTransform(rebuild); - Vector3 returned = rebuild.GetScale(); - EXPECT_NEAR(m_scale.GetX(), returned.GetX(), 0.1f); - EXPECT_NEAR(m_scale.GetY(), returned.GetY(), 0.1f); - EXPECT_NEAR(m_scale.GetZ(), returned.GetZ(), 0.1f); + float returned = rebuild.GetUniformScale(); + EXPECT_NEAR(m_scale, returned, 0.1f); } TEST_F(TransformRowWidgetTest, GetTransform_RotateAndTranslateInMatrix_ReconstructedTransformMatchesOriginal) @@ -121,7 +117,7 @@ namespace AZ { Quaternion quaternion = AZ::ConvertEulerDegreesToQuaternion(m_rotation); m_transform = Transform::CreateFromQuaternionAndTranslation(quaternion, m_translation); - m_transform.MultiplyByScale(m_scale); + m_transform.MultiplyByUniformScale(m_scale); m_expanded.SetTransform(m_transform); Transform rebuild; diff --git a/Code/Tools/SerializeContextTools/SliceConverter.cpp b/Code/Tools/SerializeContextTools/SliceConverter.cpp index 3fbf7cb25c..d06534e303 100644 --- a/Code/Tools/SerializeContextTools/SliceConverter.cpp +++ b/Code/Tools/SerializeContextTools/SliceConverter.cpp @@ -244,7 +244,7 @@ namespace AZ } else { - return SavePrefab(templateId); + return SavePrefab(outputPath, templateId); } } @@ -318,7 +318,7 @@ namespace AZ nestedPrefabPath.ReplaceExtension("prefab"); auto prefabLoaderInterface = AZ::Interface::Get(); - nestedPrefabPath = prefabLoaderInterface->GetRelativePathToProject(nestedPrefabPath); + nestedPrefabPath = prefabLoaderInterface->GenerateRelativePath(nestedPrefabPath); AzToolsFramework::Prefab::TemplateId nestedTemplateId = prefabSystemComponentInterface->GetTemplateIdFromFilePath(nestedPrefabPath); @@ -439,17 +439,31 @@ namespace AZ AZ::Debug::Trace::Instance().Output("", "\n"); } - bool SliceConverter::SavePrefab(AzToolsFramework::Prefab::TemplateId templateId) + bool SliceConverter::SavePrefab(AZ::IO::PathView outputPath, AzToolsFramework::Prefab::TemplateId templateId) { auto prefabLoaderInterface = AZ::Interface::Get(); - if (!prefabLoaderInterface->SaveTemplate(templateId)) + AZStd::string out; + if (prefabLoaderInterface->SaveTemplateToString(templateId, out)) { - AZ_Printf("Convert-Slice", " Could not save prefab - internal error (Json write operation failure).\n"); - return false; + IO::SystemFile outputFile; + if (!outputFile.Open( + AZStd::string(outputPath.Native()).c_str(), + IO::SystemFile::OpenMode::SF_OPEN_CREATE | + IO::SystemFile::OpenMode::SF_OPEN_CREATE_PATH | + IO::SystemFile::OpenMode::SF_OPEN_WRITE_ONLY)) + { + AZ_Error("Convert-Slice", false, " Unable to create output file '%.*s'.", AZ_STRING_ARG(outputPath.Native())); + return false; + } + + outputFile.Write(out.data(), out.size()); + outputFile.Close(); + return true; } - return true; + AZ_Printf("Convert-Slice", " Could not save prefab - internal error (Json write operation failure).\n"); + return false; } bool SliceConverter::ConnectToAssetProcessor() diff --git a/Code/Tools/SerializeContextTools/SliceConverter.h b/Code/Tools/SerializeContextTools/SliceConverter.h index a977095f02..bec893ff56 100644 --- a/Code/Tools/SerializeContextTools/SliceConverter.h +++ b/Code/Tools/SerializeContextTools/SliceConverter.h @@ -56,7 +56,7 @@ namespace AZ AZ::SliceComponent::SliceInstance& instance, AZ::Data::Asset& sliceAsset, AzToolsFramework::Prefab::TemplateReference nestedTemplate, AzToolsFramework::Prefab::Instance* topLevelInstance); static void PrintPrefab(AzToolsFramework::Prefab::TemplateId templateId); - static bool SavePrefab(AzToolsFramework::Prefab::TemplateId templateId); + static bool SavePrefab(AZ::IO::PathView outputPath, AzToolsFramework::Prefab::TemplateId templateId); }; } // namespace SerializeContextTools } // namespace AZ diff --git a/Gems/AWSClientAuth/Code/CMakeLists.txt b/Gems/AWSClientAuth/Code/CMakeLists.txt index e9f2a4ed84..3f85b6453e 100644 --- a/Gems/AWSClientAuth/Code/CMakeLists.txt +++ b/Gems/AWSClientAuth/Code/CMakeLists.txt @@ -29,6 +29,9 @@ ly_add_target( Gem::HttpRequestor 3rdParty::AWSNativeSDK::AWSClientAuth 3rdParty::AWSNativeSDK::Core + RUNTIME_DEPENDENCIES + Gem::AWSCore + Gem::HttpRequestor ) ly_add_target( @@ -44,13 +47,23 @@ ly_add_target( AZ::AzCore AZ::AzFramework Gem::AWSCore - Gem::HttpRequestor 3rdParty::AWSNativeSDK::AWSClientAuth 3rdParty::AWSNativeSDK::Core PUBLIC Gem::AWSClientAuth.Static + RUNTIME_DEPENDENCIES + Gem::AWSCore + Gem::HttpRequestor ) +# Load the "Gem::AWSClientAuth" module in all types of applications. +ly_create_alias(NAME AWSClientAuth.Servers NAMESPACE Gem TARGETS Gem::AWSClientAuth) +ly_create_alias(NAME AWSClientAuth.Clients NAMESPACE Gem TARGETS Gem::AWSClientAuth) +if (PAL_TRAIT_BUILD_HOST_TOOLS) + ly_create_alias(NAME AWSClientAuth.Tools NAMESPACE Gem TARGETS Gem::AWSClientAuth) + ly_create_alias(NAME AWSClientAuth.Builders NAMESPACE Gem TARGETS Gem::AWSClientAuth) +endif() + ################################################################################ # Tests ################################################################################ @@ -71,10 +84,14 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) 3rdParty::AWSNativeSDK::AWSClientAuth AZ::AzCore AZ::AzFramework - Gem::AWSCore - Gem::AWSClientAuth.Static AZ::AWSNativeSDKInit + Gem::AWSClientAuth.Static + Gem::AWSCore Gem::HttpRequestor + RUNTIME_DEPENDENCIES + Gem::AWSCore + AZ::AWSNativeSDKInit + Gem::HttpRequestor ) ly_add_googletest( NAME Gem::AWSClientAuth.Tests diff --git a/Gems/AWSCore/Code/CMakeLists.txt b/Gems/AWSCore/Code/CMakeLists.txt index a58b02d1d4..7edb22124f 100644 --- a/Gems/AWSCore/Code/CMakeLists.txt +++ b/Gems/AWSCore/Code/CMakeLists.txt @@ -45,6 +45,10 @@ ly_add_target( Gem::AWSCore.Static ) +# clients and servers will use the above Gem::AWSCore module. +ly_create_alias(NAME AWSCore.Servers NAMESPACE Gem TARGETS Gem::AWSCore) +ly_create_alias(NAME AWSCore.Clients NAMESPACE Gem TARGETS Gem::AWSCore) + if (PAL_TRAIT_BUILD_HOST_TOOLS) ly_add_target( NAME AWSCore.Editor.Static STATIC @@ -75,14 +79,12 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS) INCLUDE_DIRECTORIES PRIVATE Include/Private - COMPILE_DEFINITIONS - PRIVATE - AWSCORE_EDITOR BUILD_DEPENDENCIES PRIVATE AZ::AzCore - Gem::AWSCore.Static Gem::AWSCore.Editor.Static + RUNTIME_DEPENDENCIES + Gem::AWSCore ) ly_add_target( @@ -99,6 +101,11 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS) Gem::AWSCore.Editor.Static ) ly_add_dependencies(AWSCore.Editor AWSCore.ResourceMappingTool) + + # Builders and Tools (such as the Editor use AWSCore.Editor) use the .Editor module above. + ly_create_alias(NAME AWSCore.Tools NAMESPACE Gem TARGETS Gem::AWSCore.Editor) + ly_create_alias(NAME AWSCore.Builders NAMESPACE Gem TARGETS Gem::AWSCore.Editor) + endif() ################################################################################ diff --git a/Gems/AWSCore/Code/Include/Private/AWSCoreEditorModule.h b/Gems/AWSCore/Code/Include/Private/AWSCoreEditorModule.h index 91a1af00d9..45a2c1f9f7 100644 --- a/Gems/AWSCore/Code/Include/Private/AWSCoreEditorModule.h +++ b/Gems/AWSCore/Code/Include/Private/AWSCoreEditorModule.h @@ -11,15 +11,15 @@ #pragma once -#include +#include namespace AWSCore { class AWSCoreEditorModule - : public AWSCoreModule + :public AZ::Module { public: - AZ_RTTI(AWSCoreEditorModule, "{C1C9B898-848B-4C2F-A7AA-69642D12BCB5}", AWSCoreModule); + AZ_RTTI(AWSCoreEditorModule, "{C1C9B898-848B-4C2F-A7AA-69642D12BCB5}", AZ::Module); AZ_CLASS_ALLOCATOR(AWSCoreEditorModule, AZ::SystemAllocator, 0); AWSCoreEditorModule(); diff --git a/Gems/AWSCore/Code/Include/Private/AWSCoreInternalBus.h b/Gems/AWSCore/Code/Include/Private/AWSCoreInternalBus.h index 738d41c796..27487ed481 100644 --- a/Gems/AWSCore/Code/Include/Private/AWSCoreInternalBus.h +++ b/Gems/AWSCore/Code/Include/Private/AWSCoreInternalBus.h @@ -38,6 +38,11 @@ namespace AWSCore //! @return The path of AWS resource mapping config file virtual AZStd::string GetResourceMappingConfigFilePath() const = 0; + //! GetResourceMappingConfigFolderPath + //! Get the path of AWS resource mapping config folder + //! @return The path of AWS resource mapping config folder + virtual AZStd::string GetResourceMappingConfigFolderPath() const = 0; + //! ReloadConfiguration //! Reload AWSCore configuration without restarting application virtual void ReloadConfiguration() = 0; diff --git a/Gems/AWSCore/Code/Include/Private/Configuration/AWSCoreConfiguration.h b/Gems/AWSCore/Code/Include/Private/Configuration/AWSCoreConfiguration.h index bd30af3ce7..92082617b7 100644 --- a/Gems/AWSCore/Code/Include/Private/Configuration/AWSCoreConfiguration.h +++ b/Gems/AWSCore/Code/Include/Private/Configuration/AWSCoreConfiguration.h @@ -53,6 +53,7 @@ namespace AWSCore // AWSCoreInternalRequestBus interface implementation AZStd::string GetResourceMappingConfigFilePath() const override; + AZStd::string GetResourceMappingConfigFolderPath() const override; AZStd::string GetProfileName() const override; void ReloadConfiguration() override; diff --git a/Gems/AWSCore/Code/Include/Private/Editor/AWSCoreEditorManager.h b/Gems/AWSCore/Code/Include/Private/Editor/AWSCoreEditorManager.h index 721cd6dd6a..98467727ea 100644 --- a/Gems/AWSCore/Code/Include/Private/Editor/AWSCoreEditorManager.h +++ b/Gems/AWSCore/Code/Include/Private/Editor/AWSCoreEditorManager.h @@ -18,7 +18,7 @@ namespace AWSCore class AWSCoreEditorManager { public: - static constexpr const char CLOUD_SERVICES_MENU_TEXT[] = "&Cloud services"; + static constexpr const char AWS_MENU_TEXT[] = "&AWS"; AWSCoreEditorManager(); virtual ~AWSCoreEditorManager(); diff --git a/Gems/AWSCore/Code/Include/Private/Editor/Constants/AWSCoreEditorMenuLinks.h b/Gems/AWSCore/Code/Include/Private/Editor/Constants/AWSCoreEditorMenuLinks.h new file mode 100644 index 0000000000..46acfbd4a3 --- /dev/null +++ b/Gems/AWSCore/Code/Include/Private/Editor/Constants/AWSCoreEditorMenuLinks.h @@ -0,0 +1,53 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +namespace AWSCore +{ + static constexpr const char NewToAWSUrl[] = "https://docs.o3de.org/docs/user-guide/gems/reference/aws/"; + + static constexpr const char AWSAndScriptCanvasUrl[] = "https://docs.o3de.org/docs/user-guide/components/reference/aws/"; + static constexpr const char AWSAndComponentsUrl[] = "https://docs.o3de.org/docs/user-guide/components/reference/aws/"; + static constexpr const char CallAWSResourcesUrl[] = "https://docs.o3de.org/docs/user-guide/components/reference/aws/"; + + static constexpr const char AWSCredentialConfigurationUrl[] = + "https://docs.o3de.org/docs/user-guide/gems/reference/aws/aws-core/configuring-credentials/"; + + static constexpr const char AWSClientAuthGemOverviewUrl[] = + "https://docs.o3de.org/docs/user-guide/gems/reference/aws/aws-client-auth/"; + static constexpr const char AWSClientAuthCDKAndResourcesUrl[] = + "https://docs.o3de.org/docs/user-guide/gems/reference/aws/aws-client-auth/"; + static constexpr const char AWSClientAuthScriptCanvasAndLuaUrl[] = + "https://docs.o3de.org/docs/user-guide/gems/reference/aws/aws-client-auth/"; + static constexpr const char AWSClientAuth3rdPartyAuthProviderUrl[] = + "https://docs.o3de.org/docs/user-guide/gems/reference/aws/aws-client-auth/"; + static constexpr const char AWSClientAuthCustomAuthProviderUrl[] = + "https://docs.o3de.org/docs/user-guide/gems/reference/aws/aws-client-auth/"; + static constexpr const char AWSClientAuthPlatformSpecificUrl[] = + "https://docs.o3de.org/docs/user-guide/gems/reference/aws/aws-client-auth/"; + static constexpr const char AWSClientAuthAPIReferenceUrl[] = + "https://docs.o3de.org/docs/user-guide/gems/reference/aws/aws-client-auth/"; + + static constexpr const char AWSMetricsGemOverviewUrl[] = + "https://docs.o3de.org/docs/user-guide/gems/reference/aws/aws-metrics/"; + static constexpr const char AWSMetricsSetupGemUrl[] = + "https://docs.o3de.org/docs/user-guide/gems/reference/aws/aws-metrics/"; + static constexpr const char AWSMetricsScriptingUrl[] = + "https://docs.o3de.org/docs/user-guide/gems/reference/aws/aws-metrics/"; + static constexpr const char AWSMetricsAPIReferenceUrl[] = + "https://docs.o3de.org/docs/user-guide/gems/reference/aws/aws-metrics/"; + static constexpr const char AWSMetricsAdvancedTopicsUrl[] = + "https://docs.o3de.org/docs/user-guide/gems/reference/aws/aws-metrics/"; + static constexpr const char AWSMetricsSettingsUrl[] = + "https://docs.o3de.org/docs/user-guide/gems/reference/aws/aws-metrics/"; +} // namespace AWSCore diff --git a/Gems/AWSCore/Code/Include/Private/Editor/Constants/AWSCoreEditorMenuNames.h b/Gems/AWSCore/Code/Include/Private/Editor/Constants/AWSCoreEditorMenuNames.h new file mode 100644 index 0000000000..a9a8198e52 --- /dev/null +++ b/Gems/AWSCore/Code/Include/Private/Editor/Constants/AWSCoreEditorMenuNames.h @@ -0,0 +1,44 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +namespace AWSCore +{ + static constexpr const char NewToAWSActionText[] = "Getting started with AWS?"; + + static constexpr const char AWSAndO3DEGlobalDocsText[] = "AWS & O3DE global docs"; + static constexpr const char AWSAndScriptCanvasActionText[] = "AWS && ScriptCanvas"; + static constexpr const char AWSAndComponentsActionText[] = "AWS & Components"; + static constexpr const char CallAWSResourcesActionText[] = "Call AWS resources"; + + static constexpr const char AWSCredentialConfigurationActionText[] = "AWS credential configuration"; + + static constexpr const char AWSResourceMappingToolActionText[] = "AWS Resource Mapping Tool..."; + + static constexpr const char AWSClientAuthActionText[] = "Client Auth"; + static constexpr const char AWSClientAuthGemOverviewActionText[] = "Gem Overview"; + static constexpr const char AWSClientAuthCDKAndResourcesActionText[] = "CDK Application and Resource Mappings"; + static constexpr const char AWSClientAuthScriptCanvasAndLuaActionText[] = "Script Canvas and Lua"; + static constexpr const char AWSClientAuth3rdPartyAuthProviderActionText[] = "3rd Party developer Authentication Provider support"; + static constexpr const char AWSClientAuthCustomAuthProviderActionText[] = "Custom developer Authentication Provider support"; + static constexpr const char AWSClientAuthPlatformSpecificActionText[] = "Platform specific Callouts"; + static constexpr const char AWSClientAuthAPIReferenceActionText[] = "API Reference"; + + static constexpr const char AWSMetricsActionText[] = "Metrics"; + static constexpr const char AWSMetricsGemOverviewActionText[] = "Metrics Overview"; + static constexpr const char AWSMetricsSetupGemActionText[] = "Setup Metrics Gem"; + static constexpr const char AWSMetricsScriptingActionText[] = "Scripting with AWS Metrics"; + static constexpr const char AWSMetricsAPIReferenceActionText[] = "C++ API with AWS Metrics Gem"; + static constexpr const char AWSMetricsAdvancedTopicsActionText[] = "Advanced topics"; + static constexpr const char AWSMetricsSettingsActionText[] = "Metrics Settings"; +} // namespace AWSCore diff --git a/Gems/AWSCore/Code/Include/Private/Editor/UI/AWSCoreEditorMenu.h b/Gems/AWSCore/Code/Include/Private/Editor/UI/AWSCoreEditorMenu.h index 19f241e368..c892f86b66 100644 --- a/Gems/AWSCore/Code/Include/Private/Editor/UI/AWSCoreEditorMenu.h +++ b/Gems/AWSCore/Code/Include/Private/Editor/UI/AWSCoreEditorMenu.h @@ -35,29 +35,23 @@ namespace AWSCore static constexpr const char AWSResourceMappingToolIsRunningText[] = "Resource Mapping Tool is running..."; static constexpr const char AWSResourceMappingToolLogWarningText[] = "Failed to launch Resource Mapping Tool, please check logs for details."; - static constexpr const char AWSResourceMappingToolActionText[] = "AWS Resource Mapping Tool..."; - static constexpr const char CredentialConfigurationActionText[] = "Credential Configuration"; - static constexpr const char CredentialConfigurationUrl[] = "https://docs.aws.amazon.com/sdk-for-cpp/v1/developer-guide/credentials.html"; - static constexpr const char NewToAWSActionText[] = "New to AWS?"; - static constexpr const char NewToAWSUrl[] = "https://o3deorg.netlify.app/docs/user-guide/gems/reference/aws"; - static constexpr const char AWSAndScriptCanvasActionText[] = "AWS && ScriptCanvas"; - static constexpr const char AWSAndScriptCanvasUrl[] = "https://o3deorg.netlify.app/docs/user-guide/gems/reference/aws"; - static constexpr const char AWSClientAuthActionText[] = "Client Auth"; - static constexpr const char AWSMetricsActionText[] = "Metrics"; AWSCoreEditorMenu(const QString& text); ~AWSCoreEditorMenu(); private: + QAction* AddExternalLinkAction(const AZStd::string& name, const AZStd::string& url, const AZStd::string& icon = ""); + void InitializeResourceMappingToolAction(); void InitializeAWSDocActions(); + void InitializeAWSGlobalDocsSubMenu(); void InitializeAWSFeatureGemActions(); // AWSCoreEditorRequestBus interface implementation void SetAWSClientAuthEnabled() override; void SetAWSMetricsEnabled() override; - void SetAWSFeatureActionsEnabled(const AZStd::string actionText); + QMenu* SetAWSFeatureSubMenu(const AZStd::string& menuText); // To improve experience, use process watcher to keep track of ongoing tool process AZStd::unique_ptr m_resourceMappingToolWatcher; diff --git a/Gems/AWSCore/Code/Source/AWSCoreEditorModule.cpp b/Gems/AWSCore/Code/Source/AWSCoreEditorModule.cpp index d8df1695c2..69e45bfd68 100644 --- a/Gems/AWSCore/Code/Source/AWSCoreEditorModule.cpp +++ b/Gems/AWSCore/Code/Source/AWSCoreEditorModule.cpp @@ -15,7 +15,6 @@ namespace AWSCore { AWSCoreEditorModule::AWSCoreEditorModule() - : AWSCoreModule() { // Push results of [MyComponent]::CreateDescriptor() into m_descriptors here. m_descriptors.insert(m_descriptors.end(), { @@ -28,10 +27,9 @@ namespace AWSCore */ AZ::ComponentTypeList AWSCoreEditorModule::GetRequiredSystemComponents() const { - AZ::ComponentTypeList requiredComponents = AWSCoreModule::GetRequiredSystemComponents(); - requiredComponents.push_back(azrtti_typeid()); - - return requiredComponents; + return AZ::ComponentTypeList{ + azrtti_typeid() + }; } } diff --git a/Gems/AWSCore/Code/Source/AWSCoreModule.cpp b/Gems/AWSCore/Code/Source/AWSCoreModule.cpp index 19a3ad4383..a80ca62b89 100644 --- a/Gems/AWSCore/Code/Source/AWSCoreModule.cpp +++ b/Gems/AWSCore/Code/Source/AWSCoreModule.cpp @@ -40,9 +40,7 @@ namespace AWSCore } -#if !defined(AWSCORE_EDITOR) // DO NOT MODIFY THIS LINE UNLESS YOU RENAME THE GEM // The first parameter should be GemName_GemIdLower // The second should be the fully qualified name of the class above AZ_DECLARE_MODULE_CLASS(Gem_AWSCore, AWSCore::AWSCoreModule) -#endif diff --git a/Gems/AWSCore/Code/Source/Configuration/AWSCoreConfiguration.cpp b/Gems/AWSCore/Code/Source/Configuration/AWSCoreConfiguration.cpp index 3c0f48c058..b22749dbaa 100644 --- a/Gems/AWSCore/Code/Source/Configuration/AWSCoreConfiguration.cpp +++ b/Gems/AWSCore/Code/Source/Configuration/AWSCoreConfiguration.cpp @@ -58,6 +58,19 @@ namespace AWSCore return configFilePath; } + AZStd::string AWSCoreConfiguration::GetResourceMappingConfigFolderPath() const + { + if (m_sourceProjectFolder.empty()) + { + AZ_Warning(AWSCoreConfigurationName, false, ProjectSourceFolderNotFoundErrorMessage); + return ""; + } + AZStd::string configFolderPath = AZStd::string::format( + "%s/%s", m_sourceProjectFolder.c_str(), AWSCoreResourceMappingConfigFolderName); + AzFramework::StringFunc::Path::Normalize(configFolderPath); + return configFolderPath; + } + void AWSCoreConfiguration::InitConfig() { InitSourceProjectFolderPath(); @@ -123,7 +136,7 @@ namespace AWSCore auto profileNamePath = AZStd::string::format("%s%s", AZ::SettingsRegistryMergeUtils::OrganizationRootKey, AWSCoreProfileNameKey); m_settingsRegistry.Remove(profileNamePath); - m_profileName.clear(); + m_profileName = AWSCoreDefaultProfileName; auto resourceMappingConfigFileNamePath = AZStd::string::format("%s%s", AZ::SettingsRegistryMergeUtils::OrganizationRootKey, AWSCoreResourceMappingConfigFileNameKey); diff --git a/Gems/AWSCore/Code/Source/Editor/AWSCoreEditorManager.cpp b/Gems/AWSCore/Code/Source/Editor/AWSCoreEditorManager.cpp index 1e3e44255a..89956d61b8 100644 --- a/Gems/AWSCore/Code/Source/Editor/AWSCoreEditorManager.cpp +++ b/Gems/AWSCore/Code/Source/Editor/AWSCoreEditorManager.cpp @@ -16,7 +16,7 @@ namespace AWSCore { AWSCoreEditorManager::AWSCoreEditorManager() - : m_awsCoreEditorMenu(new AWSCoreEditorMenu(CLOUD_SERVICES_MENU_TEXT)) + : m_awsCoreEditorMenu(new AWSCoreEditorMenu(AWS_MENU_TEXT)) { } diff --git a/Gems/AWSCore/Code/Source/Editor/UI/AWSCoreEditorMenu.cpp b/Gems/AWSCore/Code/Source/Editor/UI/AWSCoreEditorMenu.cpp index 754cc32751..c319788547 100644 --- a/Gems/AWSCore/Code/Source/Editor/UI/AWSCoreEditorMenu.cpp +++ b/Gems/AWSCore/Code/Source/Editor/UI/AWSCoreEditorMenu.cpp @@ -13,10 +13,13 @@ #include #include #include +#include #include #include #include +#include +#include #include #include @@ -36,8 +39,8 @@ namespace AWSCore : QMenu(text) , m_resourceMappingToolWatcher(nullptr) { - InitializeResourceMappingToolAction(); InitializeAWSDocActions(); + InitializeResourceMappingToolAction(); this->addSeparator(); InitializeAWSFeatureGemActions(); @@ -58,6 +61,21 @@ namespace AWSCore this->clear(); } + QAction* AWSCoreEditorMenu::AddExternalLinkAction( + const AZStd::string& name, const AZStd::string& url, const AZStd::string& icon) + { + QAction* linkAction = new QAction(QObject::tr(name.c_str())); + QObject::connect(linkAction, &QAction::triggered, this, + [url]() { + QDesktopServices::openUrl(QUrl(url.c_str())); + }); + if (!icon.empty()) + { + linkAction->setIcon(QIcon(icon.c_str())); + } + return linkAction; + } + void AWSCoreEditorMenu::InitializeResourceMappingToolAction() { #ifdef AWSCORE_EDITOR_RESOURCE_MAPPING_TOOL_ENABLED @@ -103,21 +121,21 @@ namespace AWSCore void AWSCoreEditorMenu::InitializeAWSDocActions() { - QAction* credentialConfiguration = new QAction(QObject::tr(CredentialConfigurationActionText)); - QObject::connect(credentialConfiguration, &QAction::triggered, this, []() { - QDesktopServices::openUrl(QUrl(CredentialConfigurationUrl)); - }); - this->addAction(credentialConfiguration); + this->addAction(AddExternalLinkAction(NewToAWSActionText, NewToAWSUrl, ":/Notifications/link.svg")); - QAction* newToAWS = new QAction(QObject::tr(NewToAWSActionText)); - QObject::connect(newToAWS, &QAction::triggered, this, []() { - QDesktopServices::openUrl(QUrl(NewToAWSUrl)); }); - this->addAction(newToAWS); + InitializeAWSGlobalDocsSubMenu(); - QAction* awsAndScriptCanvas = new QAction(QObject::tr(AWSAndScriptCanvasActionText)); - QObject::connect(awsAndScriptCanvas, &QAction::triggered, this, []() { - QDesktopServices::openUrl(QUrl(AWSAndScriptCanvasUrl)); }); - this->addAction(awsAndScriptCanvas); + this->addAction(AddExternalLinkAction( + AWSCredentialConfigurationActionText, AWSCredentialConfigurationUrl, ":/Notifications/link.svg")); + } + + void AWSCoreEditorMenu::InitializeAWSGlobalDocsSubMenu() + { + QMenu* globalDocsMenu = this->addMenu(QObject::tr(AWSAndO3DEGlobalDocsText)); + + globalDocsMenu->addAction(AddExternalLinkAction(AWSAndScriptCanvasActionText, AWSAndScriptCanvasUrl, ":/Notifications/link.svg")); + globalDocsMenu->addAction(AddExternalLinkAction(AWSAndComponentsActionText, AWSAndComponentsUrl, ":/Notifications/link.svg")); + globalDocsMenu->addAction(AddExternalLinkAction(CallAWSResourcesActionText, CallAWSResourcesUrl, ":/Notifications/link.svg")); } void AWSCoreEditorMenu::InitializeAWSFeatureGemActions() @@ -135,25 +153,67 @@ namespace AWSCore void AWSCoreEditorMenu::SetAWSClientAuthEnabled() { - SetAWSFeatureActionsEnabled(AWSClientAuthActionText); + // TODO: instead of creating submenu in core editor, aws feature gem should return submenu component directly + QMenu* subMenu = SetAWSFeatureSubMenu(AWSClientAuthActionText); + + subMenu->addAction(AddExternalLinkAction( + AWSClientAuthGemOverviewActionText, AWSClientAuthGemOverviewUrl, ":/Notifications/link.svg")); + subMenu->addAction(AddExternalLinkAction( + AWSClientAuthCDKAndResourcesActionText, AWSClientAuthCDKAndResourcesUrl, ":/Notifications/link.svg")); + subMenu->addAction(AddExternalLinkAction( + AWSClientAuthScriptCanvasAndLuaActionText, AWSClientAuthScriptCanvasAndLuaUrl, ":/Notifications/link.svg")); + subMenu->addAction(AddExternalLinkAction( + AWSClientAuth3rdPartyAuthProviderActionText, AWSClientAuth3rdPartyAuthProviderUrl, ":/Notifications/link.svg")); + subMenu->addAction(AddExternalLinkAction( + AWSClientAuthCustomAuthProviderActionText, AWSClientAuthCustomAuthProviderUrl, ":/Notifications/link.svg")); + subMenu->addAction(AddExternalLinkAction( + AWSClientAuthPlatformSpecificActionText, AWSClientAuthPlatformSpecificUrl, ":/Notifications/link.svg")); + subMenu->addAction(AddExternalLinkAction( + AWSClientAuthAPIReferenceActionText, AWSClientAuthAPIReferenceUrl, ":/Notifications/link.svg")); } void AWSCoreEditorMenu::SetAWSMetricsEnabled() { - SetAWSFeatureActionsEnabled(AWSMetricsActionText); + // TODO: instead of creating submenu in core editor, aws feature gem should return submenu component directly + QMenu* subMenu = SetAWSFeatureSubMenu(AWSMetricsActionText); + + subMenu->addAction(AddExternalLinkAction( + AWSMetricsGemOverviewActionText, AWSMetricsGemOverviewUrl, ":/Notifications/link.svg")); + subMenu->addAction(AddExternalLinkAction( + AWSMetricsSetupGemActionText, AWSMetricsSetupGemUrl, ":/Notifications/link.svg")); + subMenu->addAction(AddExternalLinkAction( + AWSMetricsScriptingActionText, AWSMetricsScriptingUrl, ":/Notifications/link.svg")); + subMenu->addAction(AddExternalLinkAction( + AWSMetricsAPIReferenceActionText, AWSMetricsAPIReferenceUrl, ":/Notifications/link.svg")); + subMenu->addAction(AddExternalLinkAction( + AWSMetricsAdvancedTopicsActionText, AWSMetricsAdvancedTopicsUrl, ":/Notifications/link.svg")); + + AZStd::string priorAlias = AZ::IO::FileIOBase::GetInstance()->GetAlias("@devroot@"); + AZStd::string configFilePath = priorAlias + "\\Gems\\AWSMetrics\\Code\\" + AZ::SettingsRegistryInterface::RegistryFolder; + AzFramework::StringFunc::Path::Normalize(configFilePath); + + QAction* settingsAction = new QAction(QObject::tr(AWSMetricsSettingsActionText)); + QObject::connect(settingsAction, &QAction::triggered, this, + [configFilePath](){ + QDesktopServices::openUrl(QUrl::fromLocalFile(configFilePath.c_str())); + }); + subMenu->addAction(settingsAction); } - void AWSCoreEditorMenu::SetAWSFeatureActionsEnabled(const AZStd::string actionText) + QMenu* AWSCoreEditorMenu::SetAWSFeatureSubMenu(const AZStd::string& menuText) { auto actionList = this->actions(); for (QList::iterator itr = actionList.begin(); itr != actionList.end(); itr++) { - if (QString::compare((*itr)->text(), actionText.c_str()) == 0) + if (QString::compare((*itr)->text(), menuText.c_str()) == 0) { - (*itr)->setIcon(QIcon(QString(":/Notifications/checkmark.svg"))); - (*itr)->setEnabled(true); - break; + QMenu* subMenu = new QMenu(QObject::tr(menuText.c_str())); + subMenu->setIcon(QIcon(QString(":/Notifications/checkmark.svg"))); + this->insertMenu(*itr, subMenu); + this->removeAction(*itr); + return subMenu; } } + return nullptr; } } // namespace AWSCore diff --git a/Gems/AWSCore/Code/Source/Editor/UI/AWSCoreResourceMappingToolAction.cpp b/Gems/AWSCore/Code/Source/Editor/UI/AWSCoreResourceMappingToolAction.cpp index fb46a8a700..18d437a66c 100644 --- a/Gems/AWSCore/Code/Source/Editor/UI/AWSCoreResourceMappingToolAction.cpp +++ b/Gems/AWSCore/Code/Source/Editor/UI/AWSCoreResourceMappingToolAction.cpp @@ -14,6 +14,7 @@ #include #include +#include #include namespace AWSCore @@ -108,17 +109,24 @@ namespace AWSCore { return ""; } + + AZStd::string profileName = "default"; + AWSCoreInternalRequestBus::BroadcastResult(profileName, &AWSCoreInternalRequests::GetProfileName); + + AZStd::string configPath = ""; + AWSCoreInternalRequestBus::BroadcastResult(configPath, &AWSCoreInternalRequests::GetResourceMappingConfigFolderPath); + if (m_isDebug) { return AZStd::string::format( - "%s debug %s --binaries_path %s --debug", - m_enginePythonEntryPath.c_str(), m_toolScriptPath.c_str(), m_toolQtBinDirectoryPath.c_str()); + "%s debug %s --binaries_path %s --debug --profile %s --config_path %s", m_enginePythonEntryPath.c_str(), + m_toolScriptPath.c_str(), m_toolQtBinDirectoryPath.c_str(), profileName.c_str(), configPath.c_str()); } else { return AZStd::string::format( - "%s %s --binaries_path %s", - m_enginePythonEntryPath.c_str(), m_toolScriptPath.c_str(), m_toolQtBinDirectoryPath.c_str()); + "%s %s --binaries_path %s --profile %s --config_path %s", m_enginePythonEntryPath.c_str(), + m_toolScriptPath.c_str(), m_toolQtBinDirectoryPath.c_str(), profileName.c_str(), configPath.c_str()); } } diff --git a/Gems/AWSCore/Code/Tests/AWSCoreEditorSystemComponentTest.cpp b/Gems/AWSCore/Code/Tests/AWSCoreEditorSystemComponentTest.cpp index e9c249f1c8..ff78d8e252 100644 --- a/Gems/AWSCore/Code/Tests/AWSCoreEditorSystemComponentTest.cpp +++ b/Gems/AWSCore/Code/Tests/AWSCoreEditorSystemComponentTest.cpp @@ -85,7 +85,7 @@ TEST_F(AWSCoreEditorSystemComponentTest, NotifyMainWindowInitialized_HaveDummyMe testMenuBar->addMenu("dummy menu"); AzToolsFramework::EditorEvents::Bus::Broadcast(&AzToolsFramework::EditorEvents::NotifyMainWindowInitialized, &testMainWindow); EXPECT_TRUE(testMenuBar->actions().size() == 2); - EXPECT_TRUE(QString::compare(testMenuBar->actions()[1]->text(), AWSCoreEditorManager::CLOUD_SERVICES_MENU_TEXT) == 0); + EXPECT_TRUE(QString::compare(testMenuBar->actions()[1]->text(), AWSCoreEditorManager::AWS_MENU_TEXT) == 0); } TEST_F(AWSCoreEditorSystemComponentTest, NotifyMainWindowInitialized_HaveHelpMenuInMenuBar_ExpectedMenuGetsAddedAtFront) @@ -95,5 +95,5 @@ TEST_F(AWSCoreEditorSystemComponentTest, NotifyMainWindowInitialized_HaveHelpMen testMenuBar->addMenu(AWSCoreEditorSystemComponent::EDITOR_HELP_MENU_TEXT); AzToolsFramework::EditorEvents::Bus::Broadcast(&AzToolsFramework::EditorEvents::NotifyMainWindowInitialized, &testMainWindow); EXPECT_TRUE(testMenuBar->actions().size() == 2); - EXPECT_TRUE(QString::compare(testMenuBar->actions()[0]->text(), AWSCoreEditorManager::CLOUD_SERVICES_MENU_TEXT) == 0); + EXPECT_TRUE(QString::compare(testMenuBar->actions()[0]->text(), AWSCoreEditorManager::AWS_MENU_TEXT) == 0); } diff --git a/Gems/AWSCore/Code/Tests/Credential/AWSDefaultCredentialHandlerTest.cpp b/Gems/AWSCore/Code/Tests/Credential/AWSDefaultCredentialHandlerTest.cpp index 297e56fc33..7f00adaae7 100644 --- a/Gems/AWSCore/Code/Tests/Credential/AWSDefaultCredentialHandlerTest.cpp +++ b/Gems/AWSCore/Code/Tests/Credential/AWSDefaultCredentialHandlerTest.cpp @@ -34,7 +34,8 @@ public: MOCK_METHOD0(GetAWSCredentials, Aws::Auth::AWSCredentials()); }; -class AWSDefaultCredentialHandlerMock : public AWSDefaultCredentialHandler +class AWSDefaultCredentialHandlerMock + : public AWSDefaultCredentialHandler { public: void SetupMocks( @@ -76,6 +77,7 @@ public: // AWSCoreInternalRequestBus interface implementation AZStd::string GetProfileName() const override { return m_profileName; } AZStd::string GetResourceMappingConfigFilePath() const override { return ""; } + AZStd::string GetResourceMappingConfigFolderPath() const override { return ""; } void ReloadConfiguration() override {} std::shared_ptr m_environmentCredentialsProviderMock; diff --git a/Gems/AWSCore/Code/Tests/Editor/UI/AWSCoreEditorMenuTest.cpp b/Gems/AWSCore/Code/Tests/Editor/UI/AWSCoreEditorMenuTest.cpp index 7a578d2bbe..bde2a43993 100644 --- a/Gems/AWSCore/Code/Tests/Editor/UI/AWSCoreEditorMenuTest.cpp +++ b/Gems/AWSCore/Code/Tests/Editor/UI/AWSCoreEditorMenuTest.cpp @@ -14,6 +14,7 @@ #include #include +#include #include #include #include @@ -35,6 +36,7 @@ class AWSCoreEditorMenuTest { AWSCoreEditorUIFixture::SetUp(); AWSCoreFixture::SetUp(); + m_localFileIO->SetAlias("@devroot@", "dummy engine root"); } void TearDown() override @@ -77,12 +79,12 @@ TEST_F(AWSCoreEditorMenuTest, AWSCoreEditorMenu_BroadcastFeatureGemsAreEnabled_C QList actualActions = testMenu.actions(); for (QList::iterator itr = actualActions.begin(); itr != actualActions.end(); itr++) { - if (QString::compare((*itr)->text(), AWSCoreEditorMenu::AWSClientAuthActionText) == 0) + if (QString::compare((*itr)->text(), AWSClientAuthActionText) == 0) { EXPECT_TRUE((*itr)->isEnabled()); } - if (QString::compare((*itr)->text(), AWSCoreEditorMenu::AWSMetricsActionText) == 0) + if (QString::compare((*itr)->text(), AWSMetricsActionText) == 0) { EXPECT_TRUE((*itr)->isEnabled()); } diff --git a/Gems/AWSCore/Code/Tests/ResourceMapping/AWSResourceMappingManagerTest.cpp b/Gems/AWSCore/Code/Tests/ResourceMapping/AWSResourceMappingManagerTest.cpp index 3adebc9a24..b46a840d40 100644 --- a/Gems/AWSCore/Code/Tests/ResourceMapping/AWSResourceMappingManagerTest.cpp +++ b/Gems/AWSCore/Code/Tests/ResourceMapping/AWSResourceMappingManagerTest.cpp @@ -119,6 +119,7 @@ public: // AWSCoreInternalRequestBus interface implementation AZStd::string GetProfileName() const override { return ""; } AZStd::string GetResourceMappingConfigFilePath() const override { return m_normalizedConfigFilePath; } + AZStd::string GetResourceMappingConfigFolderPath() const override { return m_normalizedConfigFolderPath; } void ReloadConfiguration() override { m_reloadConfigurationCounter++; } AZStd::unique_ptr m_resourceMappingManager; diff --git a/Gems/AWSCore/Code/Tools/ResourceMappingTool/manager/configuration_manager.py b/Gems/AWSCore/Code/Tools/ResourceMappingTool/manager/configuration_manager.py index ee305c750a..b679921196 100755 --- a/Gems/AWSCore/Code/Tools/ResourceMappingTool/manager/configuration_manager.py +++ b/Gems/AWSCore/Code/Tools/ResourceMappingTool/manager/configuration_manager.py @@ -48,11 +48,14 @@ class ConfigurationManager(object): def configuration(self, new_configuration: ConfigurationManager) -> None: self._configuration = new_configuration - def setup(self) -> None: + def setup(self, config_path: str) -> None: logger.info("Setting up default configuration ...") - # TODO: remove config directory and files default setup once integrating with user input try: - self._configuration.config_directory = file_utils.get_current_directory_path() + normalized_config_path: str = file_utils.normalize_file_path(config_path); + if normalized_config_path: + self._configuration.config_directory = normalized_config_path + else: + self._configuration.config_directory = file_utils.get_current_directory_path() self._configuration.config_files = \ file_utils.find_files_with_suffix_under_directory(self._configuration.config_directory, constants.RESOURCE_MAPPING_CONFIG_FILE_NAME_SUFFIX) diff --git a/Gems/AWSCore/Code/Tools/ResourceMappingTool/resource_mapping_tool.py b/Gems/AWSCore/Code/Tools/ResourceMappingTool/resource_mapping_tool.py index 0ee7455e6c..e99bf5d441 100755 --- a/Gems/AWSCore/Code/Tools/ResourceMappingTool/resource_mapping_tool.py +++ b/Gems/AWSCore/Code/Tools/ResourceMappingTool/resource_mapping_tool.py @@ -13,13 +13,16 @@ from argparse import (ArgumentParser, Namespace) import logging import sys +from utils import aws_utils from utils import environment_utils from utils import file_utils # arguments setup argument_parser: ArgumentParser = ArgumentParser() argument_parser.add_argument('--binaries_path', help='Path to QT Binaries necessary for PySide.') +argument_parser.add_argument('--config_path', help='Path to resource mapping config directory.') argument_parser.add_argument('--debug', action='store_true', help='Execute on debug mode to enable DEBUG logging level') +argument_parser.add_argument('--profile', default='default', help='Named AWS profile to use for querying AWS resources') arguments: Namespace = argument_parser.parse_args() # logging setup @@ -70,9 +73,12 @@ if __name__ == "__main__": except FileNotFoundError: logger.warning("Failed to load style sheet for resource mapping tool") + logger.info("Initializing boto3 default session ...") + aws_utils.setup_default_session(arguments.profile) + logger.info("Initializing configuration manager ...") configuration_manager: ConfigurationManager = ConfigurationManager() - configuration_manager.setup() + configuration_manager.setup(arguments.config_path) logger.info("Initializing thread manager ...") thread_manager: ThreadManager = ThreadManager() diff --git a/Gems/AWSCore/Code/Tools/ResourceMappingTool/tests/unit/manager/test_configuration_manager.py b/Gems/AWSCore/Code/Tools/ResourceMappingTool/tests/unit/manager/test_configuration_manager.py index a9dcf97af9..552f9fff01 100755 --- a/Gems/AWSCore/Code/Tools/ResourceMappingTool/tests/unit/manager/test_configuration_manager.py +++ b/Gems/AWSCore/Code/Tools/ResourceMappingTool/tests/unit/manager/test_configuration_manager.py @@ -43,7 +43,7 @@ class TestConfigurationManager(TestCase): mock_find_files_with_suffix_under_directory: MagicMock, mock_get_default_account_id: MagicMock, mock_get_default_region: MagicMock) -> None: - TestConfigurationManager._expected_configuration_manager.setup() + TestConfigurationManager._expected_configuration_manager.setup("") mock_get_current_directory_path.assert_called_once() mock_check_path_exists.assert_called_once_with(TestConfigurationManager._expected_directory_path) mock_find_files_with_suffix_under_directory.assert_called_once_with( @@ -58,3 +58,29 @@ class TestConfigurationManager(TestCase): TestConfigurationManager._expected_account_id assert TestConfigurationManager._expected_configuration_manager.configuration.region == \ TestConfigurationManager._expected_region + + @patch("utils.aws_utils.get_default_region", return_value=_expected_region) + @patch("utils.aws_utils.get_default_account_id", return_value=_expected_account_id) + @patch("utils.file_utils.find_files_with_suffix_under_directory", return_value=_expected_config_files) + @patch("utils.file_utils.check_path_exists", return_value=True) + @patch("utils.file_utils.normalize_file_path", return_value=_expected_directory_path) + def test_setup_get_configuration_setup_with_path_as_expected(self, mock_normalize_file_path: MagicMock, + mock_check_path_exists: MagicMock, + mock_find_files_with_suffix_under_directory: MagicMock, + mock_get_default_account_id: MagicMock, + mock_get_default_region: MagicMock) -> None: + TestConfigurationManager._expected_configuration_manager.setup(TestConfigurationManager._expected_directory_path) + mock_normalize_file_path.assert_called_once() + mock_check_path_exists.assert_called_once_with(TestConfigurationManager._expected_directory_path) + mock_find_files_with_suffix_under_directory.assert_called_once_with( + TestConfigurationManager._expected_directory_path, constants.RESOURCE_MAPPING_CONFIG_FILE_NAME_SUFFIX) + mock_get_default_account_id.assert_called_once() + mock_get_default_region.assert_called_once() + assert TestConfigurationManager._expected_configuration_manager.configuration.config_directory == \ + TestConfigurationManager._expected_directory_path + assert TestConfigurationManager._expected_configuration_manager.configuration.config_files == \ + TestConfigurationManager._expected_config_files + assert TestConfigurationManager._expected_configuration_manager.configuration.account_id == \ + TestConfigurationManager._expected_account_id + assert TestConfigurationManager._expected_configuration_manager.configuration.region == \ + TestConfigurationManager._expected_region diff --git a/Gems/AWSCore/Code/Tools/ResourceMappingTool/tests/unit/manager/test_view_manager.py b/Gems/AWSCore/Code/Tools/ResourceMappingTool/tests/unit/manager/test_view_manager.py index eb8304182f..e5c84c6579 100755 --- a/Gems/AWSCore/Code/Tools/ResourceMappingTool/tests/unit/manager/test_view_manager.py +++ b/Gems/AWSCore/Code/Tools/ResourceMappingTool/tests/unit/manager/test_view_manager.py @@ -36,7 +36,7 @@ class TestViewManager(TestCase): main_window_patcher: patch = patch("manager.view_manager.QMainWindow") cls._mock_main_window = main_window_patcher.start() - window_icon_patcher: patch = patch("manager.view_manager.QPixmap") + window_icon_patcher: patch = patch("manager.view_manager.QIcon") window_icon_patcher.start() stacked_pages_patcher: patch = patch("manager.view_manager.QStackedWidget") diff --git a/Gems/AWSCore/Code/Tools/ResourceMappingTool/tests/unit/utils/test_aws_utils.py b/Gems/AWSCore/Code/Tools/ResourceMappingTool/tests/unit/utils/test_aws_utils.py index 7244431e76..51bd6ade23 100755 --- a/Gems/AWSCore/Code/Tools/ResourceMappingTool/tests/unit/utils/test_aws_utils.py +++ b/Gems/AWSCore/Code/Tools/ResourceMappingTool/tests/unit/utils/test_aws_utils.py @@ -37,13 +37,12 @@ class TestAWSUtils(TestCase): .build() def setUp(self) -> None: - client_patcher: patch = patch("boto3.client") - self.addCleanup(client_patcher.stop) - self._mock_client: MagicMock = client_patcher.start() - session_patcher: patch = patch("boto3.session.Session") self.addCleanup(session_patcher.stop) self._mock_session: MagicMock = session_patcher.start() + self._mock_client: MagicMock = self._mock_session.return_value.client + + aws_utils.setup_default_session("default") def test_get_default_account_id_return_expected_account_id(self) -> None: mocked_sts_client: MagicMock = self._mock_client.return_value diff --git a/Gems/AWSCore/Code/Tools/ResourceMappingTool/utils/aws_utils.py b/Gems/AWSCore/Code/Tools/ResourceMappingTool/utils/aws_utils.py index 329d3ff44e..b0c3c9c1b3 100755 --- a/Gems/AWSCore/Code/Tools/ResourceMappingTool/utils/aws_utils.py +++ b/Gems/AWSCore/Code/Tools/ResourceMappingTool/utils/aws_utils.py @@ -26,6 +26,8 @@ aws account, region, resources, etc. _PAGINATION_MAX_ITEMS: int = 10 _PAGINATION_PAGE_SIZE: int = 10 +default_session: boto3.session.Session = None + class AWSConstants(object): CLOUDFORMATION_SERVICE_NAME: str = "cloudformation" @@ -53,15 +55,20 @@ def _close_client_connection(client: BaseClient) -> None: def _initialize_boto3_aws_client(service: str, region: str = "") -> BaseClient: if region: - boto3_client: BaseClient = boto3.client(service, region_name=region) + boto3_client: BaseClient = default_session.client(service, region_name=region) else: - boto3_client: BaseClient = boto3.client(service) + boto3_client: BaseClient = default_session.client(service) boto3_client.meta.events.register( f"after-call.{service}.*", lambda **kwargs: _close_client_connection(boto3_client) ) return boto3_client +def setup_default_session(profile: str) -> None: + global default_session + default_session = boto3.session.Session(profile_name=profile) + + def get_default_account_id() -> str: sts_client: BaseClient = _initialize_boto3_aws_client(AWSConstants.STS_SERVICE_NAME) try: @@ -72,7 +79,7 @@ def get_default_account_id() -> str: def get_default_region() -> str: - region: str = boto3.session.Session().region_name + region: str = default_session.region_name if region: return region diff --git a/Gems/AWSCore/Code/awscore_editor_files.cmake b/Gems/AWSCore/Code/awscore_editor_files.cmake index 652f0455e1..13bfbb6102 100644 --- a/Gems/AWSCore/Code/awscore_editor_files.cmake +++ b/Gems/AWSCore/Code/awscore_editor_files.cmake @@ -12,6 +12,8 @@ set(FILES Include/Private/AWSCoreEditorSystemComponent.h Include/Private/Editor/AWSCoreEditorManager.h + Include/Private/Editor/Constants/AWSCoreEditorMenuLinks.h + Include/Private/Editor/Constants/AWSCoreEditorMenuNames.h Include/Private/Editor/UI/AWSCoreEditorMenu.h Include/Private/Editor/UI/AWSCoreResourceMappingToolAction.h Source/AWSCoreEditorSystemComponent.cpp diff --git a/Gems/AWSCore/Code/awscore_editor_shared_files.cmake b/Gems/AWSCore/Code/awscore_editor_shared_files.cmake index 61ff9c3bf2..42cebe8dc4 100644 --- a/Gems/AWSCore/Code/awscore_editor_shared_files.cmake +++ b/Gems/AWSCore/Code/awscore_editor_shared_files.cmake @@ -11,7 +11,5 @@ set(FILES Include/Private/AWSCoreEditorModule.h - Include/Private/AWSCoreModule.h Source/AWSCoreEditorModule.cpp - Source/AWSCoreModule.cpp ) diff --git a/Gems/AWSMetrics/Code/CMakeLists.txt b/Gems/AWSMetrics/Code/CMakeLists.txt index ffa9ac0408..3e7118cb58 100644 --- a/Gems/AWSMetrics/Code/CMakeLists.txt +++ b/Gems/AWSMetrics/Code/CMakeLists.txt @@ -23,6 +23,7 @@ ly_add_target( PRIVATE AZ::AzCore AZ::AzFramework + PUBLIC Gem::AWSCore ) @@ -40,10 +41,19 @@ ly_add_target( PRIVATE AZ::AzCore AZ::AzFramework - Gem::AWSCore Gem::AWSMetrics.Static + RUNTIME_DEPENDENCIES + Gem::AWSCore ) +# Load the "Gem::AWSMetrics" module in all types of applications. +ly_create_alias(NAME AWSMetrics.Servers NAMESPACE Gem TARGETS Gem::AWSMetrics) +ly_create_alias(NAME AWSMetrics.Clients NAMESPACE Gem TARGETS Gem::AWSMetrics) +if (PAL_TRAIT_BUILD_HOST_TOOLS) + ly_create_alias(NAME AWSMetrics.Tools NAMESPACE Gem TARGETS Gem::AWSMetrics) + ly_create_alias(NAME AWSMetrics.Builders NAMESPACE Gem TARGETS Gem::AWSMetrics) +endif() + ################################################################################ # Tests ################################################################################ @@ -63,8 +73,9 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) AZ::AzTest AZ::AzCore AZ::AzFramework - Gem::AWSCore Gem::AWSMetrics.Static + RUNTIME_DEPENDENCIES + Gem::AWSCore ) ly_add_googletest( NAME Gem::AWSMetrics.Tests diff --git a/Gems/AWSMetrics/cdk/aws_metrics/batch_processing.py b/Gems/AWSMetrics/cdk/aws_metrics/batch_processing.py index fe85615d8d..22f6ad6903 100755 --- a/Gems/AWSMetrics/cdk/aws_metrics/batch_processing.py +++ b/Gems/AWSMetrics/cdk/aws_metrics/batch_processing.py @@ -111,8 +111,7 @@ class BatchProcessing: self._events_firehose_delivery_stream = kinesisfirehose.CfnDeliveryStream( self._stack, - id='EventsFirehoseDeliveryStream', - delivery_stream_name=f'{self._stack.stack_name}-EventsFirehoseDeliveryStream', + id=f'{self._stack.stack_name}-EventsFirehoseDeliveryStream', delivery_stream_type='KinesisStreamAsSource', kinesis_stream_source_configuration=kinesisfirehose.CfnDeliveryStream.KinesisStreamSourceConfigurationProperty( kinesis_stream_arn=self._input_stream_arn, @@ -327,7 +326,7 @@ class BatchProcessing: @property def delivery_stream_name(self) -> kinesisfirehose.CfnDeliveryStream.delivery_stream_name: - return self._events_firehose_delivery_stream.delivery_stream_name + return self._events_firehose_delivery_stream.ref @property def delivery_stream_role_arn(self) -> iam.Role.role_arn: diff --git a/Gems/AWSMetrics/cdk/aws_metrics/data_ingestion.py b/Gems/AWSMetrics/cdk/aws_metrics/data_ingestion.py index a3ab99fccf..fbd332a577 100755 --- a/Gems/AWSMetrics/cdk/aws_metrics/data_ingestion.py +++ b/Gems/AWSMetrics/cdk/aws_metrics/data_ingestion.py @@ -73,14 +73,14 @@ class DataIngestion: api_id_output = core.CfnOutput( self._stack, - id='RestApiId', + id='RESTApiId', description='Service API Id for the analytics pipeline', export_name=f"{application_name}:RestApiId", value=self._rest_api.rest_api_id) stage_output = core.CfnOutput( self._stack, - id='DeploymentStage', + id='RESTApiStage', description='Stage for the REST API deployment', export_name=f"{application_name}:DeploymentStage", value=self._rest_api.deployment_stage.stage_name) diff --git a/Gems/Achievements/Code/CMakeLists.txt b/Gems/Achievements/Code/CMakeLists.txt index b49409bd5e..4b2aa07dab 100644 --- a/Gems/Achievements/Code/CMakeLists.txt +++ b/Gems/Achievements/Code/CMakeLists.txt @@ -44,3 +44,6 @@ ly_add_target( PRIVATE Gem::Achievements.Static ) + +# we'll load the above "Gem::Achievements" module in clients only. +ly_create_alias(NAME Achievements.Clients NAMESPACE Gem TARGETS Gem::Achievements) diff --git a/Gems/AssetMemoryAnalyzer/Code/CMakeLists.txt b/Gems/AssetMemoryAnalyzer/Code/CMakeLists.txt index df97feaa3f..bdf76eca60 100644 --- a/Gems/AssetMemoryAnalyzer/Code/CMakeLists.txt +++ b/Gems/AssetMemoryAnalyzer/Code/CMakeLists.txt @@ -43,6 +43,10 @@ ly_add_target( Gem::ImGui ) +# AssetMemoryAnalyzer is available in clients and servers. +ly_create_alias(NAME AssetMemoryAnalyzer.Clients NAMESPACE Gem TARGETS Gem::AssetMemoryAnalyzer) +ly_create_alias(NAME AssetMemoryAnalyzer.Servers NAMESPACE Gem TARGETS Gem::AssetMemoryAnalyzer) + ################################################################################ # Tests ################################################################################ @@ -65,3 +69,4 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) NAME Gem::AssetMemoryAnalyzer.Tests ) endif() + diff --git a/Gems/AssetValidation/Code/CMakeLists.txt b/Gems/AssetValidation/Code/CMakeLists.txt index 983ddd9e9d..f62baf57f5 100644 --- a/Gems/AssetValidation/Code/CMakeLists.txt +++ b/Gems/AssetValidation/Code/CMakeLists.txt @@ -65,3 +65,8 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) NAME Gem::AssetValidation.Tests ) endif() + +# AssetValidation should be active in all clients plus tools +ly_create_alias(NAME AssetValidation.Clients NAMESPACE Gem TARGETS Gem::AssetValidation) +ly_create_alias(NAME AssetValidation.Tools NAMESPACE Gem TARGETS Gem::AssetValidation) + diff --git a/Gems/Atom/Asset/ImageProcessingAtom/gem.json b/Gems/Atom/Asset/ImageProcessingAtom/gem.json new file mode 100644 index 0000000000..86256bff9d --- /dev/null +++ b/Gems/Atom/Asset/ImageProcessingAtom/gem.json @@ -0,0 +1,10 @@ +{ + "gem_name": "ImageProcessingAtom", + "display_name": "Atom Image Processing", + "summary": "", + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + ] +} diff --git a/Gems/Atom/Asset/Shader/gem.json b/Gems/Atom/Asset/Shader/gem.json new file mode 100644 index 0000000000..71c741f436 --- /dev/null +++ b/Gems/Atom/Asset/Shader/gem.json @@ -0,0 +1,10 @@ +{ + "gem_name": "AtomShader", + "display_name": "Atom Shader Builder", + "summary": "", + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + ] +} diff --git a/Gems/Atom/Bootstrap/gem.json b/Gems/Atom/Bootstrap/gem.json new file mode 100644 index 0000000000..8aa5cade6e --- /dev/null +++ b/Gems/Atom/Bootstrap/gem.json @@ -0,0 +1,10 @@ +{ + "gem_name": "Atom_Bootstrap", + "display_name": "Atom Bootstrap", + "summary": "", + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + ] +} diff --git a/Gems/Atom/Component/DebugCamera/gem.json b/Gems/Atom/Component/DebugCamera/gem.json new file mode 100644 index 0000000000..06d39d1fc0 --- /dev/null +++ b/Gems/Atom/Component/DebugCamera/gem.json @@ -0,0 +1,10 @@ +{ + "gem_name": "Atom_Component_DebugCamera", + "display_name": "Atom Debug Camera Component", + "summary": "", + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + ] +} diff --git a/Gems/Atom/Feature/Common/Assets/Materials/ReflectionProbe/ReflectionProbeVisualization.materialtype b/Gems/Atom/Feature/Common/Assets/Materials/ReflectionProbe/ReflectionProbeVisualization.materialtype index 214fc02660..9a2edc9fca 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/ReflectionProbe/ReflectionProbeVisualization.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/ReflectionProbe/ReflectionProbeVisualization.materialtype @@ -469,7 +469,7 @@ "max": 1.0, "connection": { "type": "ShaderInput", - "id": "m_depthFactor" + "id": "m_heightmapScale" } }, { @@ -479,18 +479,7 @@ "type": "Image", "connection": { "type": "ShaderInput", - "id": "m_depthMap" - } - }, - { - "id": "invert", - "displayName": "Invert", - "description": "Invert to depthmap if the texture is heightmap", - "type": "Bool", - "defaultValue": true, - "connection": { - "type": "ShaderInput", - "id": "m_depthInverted" + "id": "m_heightmap" } }, { diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR.materialtype b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR.materialtype index 4d13663aae..ff0c4c59da 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR.materialtype @@ -13,11 +13,6 @@ "displayName": "Metallic", "description": "Properties for configuring whether the surface is metallic or not." }, - { - "id": "anisotropy", - "displayName": "Anisotropic Material Response", - "description": "How much is this material response anisotropic." - }, { "id": "roughness", "displayName": "Roughness", @@ -28,46 +23,11 @@ "displayName": "Specular Reflectance f0", "description": "The constant f0 represents the specular reflectance at normal incidence (Fresnel 0 Angle). Used to adjust reflectance of non-metal surfaces." }, - { - "id": "clearCoat", - "displayName": "Clear Coat", - "description": "Properties for configuring gloss clear coat" - }, { "id": "normal", "displayName": "Normal", "description": "Properties related to configuring surface normal." }, - { - "id": "opacity", - "displayName": "Opacity", - "description": "Properties for configuring the materials transparency." - }, - { - "id": "uv", - "displayName": "UVs", - "description": "Properties for configuring UV transforms." - }, - { - "id": "occlusion", - "displayName": "Occlusion", - "description": "Properties for baked textures that represent geometric occlusion of light." - }, - { - "id": "emissive", - "displayName": "Emissive", - "description": "Properties to add light emission, independent of other lights in the scene." - }, - { - "id": "parallax", - "displayName": "Parallax Mapping", - "description": "Properties for parallax effect produced by depthmap." - }, - { - "id": "subsurfaceScattering", - "displayName": "Subsurface Scattering", - "description": "Properties for configuring subsurface scattering effects." - }, { "id": "detailLayerGroup", "displayName": "Detail Layer", @@ -78,6 +38,46 @@ "displayName": "Detail Layer UV", "description": "Properties for modifying detail layer UV." }, + { + "id": "anisotropy", + "displayName": "Anisotropic Material Response", + "description": "How much is this material response anisotropic." + }, + { + "id": "occlusion", + "displayName": "Occlusion", + "description": "Properties for baked textures that represent geometric occlusion of light." + }, + { + "id": "emissive", + "displayName": "Emissive", + "description": "Properties to add light emission, independent of other lights in the scene." + }, + { + "id": "subsurfaceScattering", + "displayName": "Subsurface Scattering", + "description": "Properties for configuring subsurface scattering effects." + }, + { + "id": "clearCoat", + "displayName": "Clear Coat", + "description": "Properties for configuring gloss clear coat" + }, + { + "id": "parallax", + "displayName": "Displacement", + "description": "Properties for parallax effect produced by a height map." + }, + { + "id": "opacity", + "displayName": "Opacity", + "description": "Properties for configuring the materials transparency." + }, + { + "id": "uv", + "displayName": "UVs", + "description": "Properties for configuring UV transforms." + }, { // Note: this property group is used in the DiffuseGlobalIllumination pass and not by the main forward shader "id": "irradiance", @@ -86,7 +86,7 @@ }, { "id": "general", - "displayName": "General", + "displayName": "General Settings", "description": "General settings." } ], @@ -197,7 +197,7 @@ }, { "id": "textureMap", - "displayName": "Texture Map", + "displayName": "Texture", "description": "Base color texture map", "type": "Image", "connection": { @@ -208,14 +208,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map.", + "description": "Whether to use the texture.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Base color texture map UV set", + "description": "Base color map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -227,7 +227,7 @@ { "id": "textureBlendMode", "displayName": "Texture Blend Mode", - "description": "Selects the equation to use when combining Color, Factor, and Texture Map.", + "description": "Selects the equation to use when combining Color, Factor, and Texture.", "type": "Enum", "enumValues": [ "Multiply", "LinearLight", "Lerp", "Overlay" ], "defaultValue": "Multiply", @@ -253,7 +253,7 @@ }, { "id": "textureMap", - "displayName": "Texture Map", + "displayName": "Texture", "description": "", "type": "Image", "connection": { @@ -264,14 +264,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map, or just default to the Factor value.", + "description": "Whether to use the texture, or just default to the Factor value.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Metallic texture map UV set", + "description": "Metallic map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -284,8 +284,8 @@ "roughness": [ { "id": "textureMap", - "displayName": "Texture Map", - "description": "Texture map for defining surface roughness.", + "displayName": "Texture", + "description": "Texture for defining surface roughness.", "type": "Image", "connection": { "type": "ShaderInput", @@ -295,14 +295,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map, or just default to the Factor value.", + "description": "Whether to use the texture, or just default to the Factor value.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Roughness texture map UV set", + "description": "Roughness map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -315,7 +315,7 @@ // Note that "factor" is mutually exclusive with "lowerBound"/"upperBound". These are swapped by a lua functor. "id": "lowerBound", "displayName": "Lower Bound", - "description": "The roughness value that corresponds to black in the texture map.", + "description": "The roughness value that corresponds to black in the texture.", "type": "Float", "defaultValue": 0.0, "min": 0.0, @@ -329,7 +329,7 @@ // Note that "factor" is mutually exclusive with "lowerBound"/"upperBound". These are swapped by a lua functor. "id": "upperBound", "displayName": "Upper Bound", - "description": "The roughness value that corresponds to white in the texture map.", + "description": "The roughness value that corresponds to white in the texture.", "type": "Float", "defaultValue": 1.0, "min": 0.0, @@ -409,8 +409,8 @@ }, { "id": "textureMap", - "displayName": "Texture Map", - "description": "Texture map for defining surface reflectance.", + "displayName": "Texture", + "description": "Texture for defining surface reflectance.", "type": "Image", "connection": { "type": "ShaderInput", @@ -420,14 +420,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map, or just default to the Factor value.", + "description": "Whether to use the texture, or just default to the Factor value.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Specular reflection texture map UV set", + "description": "Specular reflection map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -472,7 +472,7 @@ { "id": "influenceMap", "displayName": " Influence Map", - "description": "Strength factor texture map", + "description": "Strength factor texture", "type": "Image", "connection": { "type": "ShaderInput", @@ -482,14 +482,14 @@ { "id": "useInfluenceMap", "displayName": " Use Texture", - "description": "Whether to use the texture map, or just default to the Factor value.", + "description": "Whether to use the texture, or just default to the Factor value.", "type": "Bool", "defaultValue": true }, { "id": "influenceMapUv", "displayName": " UV", - "description": "Strength factor texture map UV set", + "description": "Strength factor map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -514,7 +514,7 @@ { "id": "roughnessMap", "displayName": " Roughness Map", - "description": "Roughness texture map", + "description": "Texture for defining surface roughness", "type": "Image", "connection": { "type": "ShaderInput", @@ -524,14 +524,14 @@ { "id": "useRoughnessMap", "displayName": " Use Texture", - "description": "Whether to use the texture map, or just default to the roughness value.", + "description": "Whether to use the texture, or just default to the roughness value.", "type": "Bool", "defaultValue": true }, { "id": "roughnessMapUv", "displayName": " UV", - "description": "Roughness texture map UV set", + "description": "Roughness map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -573,7 +573,7 @@ { "id": "normalMapUv", "displayName": " UV", - "description": "Normal texture map UV set", + "description": "Normal map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -586,8 +586,8 @@ "normal": [ { "id": "textureMap", - "displayName": "Texture Map", - "description": "Texture map for defining surface normal direction.", + "displayName": "Texture", + "description": "Texture for defining surface normal direction.", "type": "Image", "connection": { "type": "ShaderInput", @@ -597,14 +597,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map, or just rely on vertex normals.", + "description": "Whether to use the texture, or just rely on vertex normals.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Normal texture map UV set", + "description": "Normal map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -653,7 +653,7 @@ { "id": "mode", "displayName": "Opacity Mode", - "description": "Opacity mode for this texture.", + "description": "Indicates the general approach how transparency is to be applied.", "type": "Enum", "enumValues": [ "Opaque", "Cutout", "Blended", "TintedTransparent" ], "defaultValue": "Opaque", @@ -665,7 +665,7 @@ { "id": "alphaSource", "displayName": "Alpha Source", - "description": "Source texture of alpha value.", + "description": "Indicates whether to get the opacity texture from the Base Color map (Packed) or from a separate greyscale texture (Split).", "type": "Enum", "enumValues": [ "Packed", "Split", "None" ], "defaultValue": "Packed", @@ -676,8 +676,8 @@ }, { "id": "textureMap", - "displayName": "Texture Map", - "description": "Texture map for defining surface opacity.", + "displayName": "Texture", + "description": "Texture for defining surface opacity.", "type": "Image", "connection": { "type": "ShaderInput", @@ -687,7 +687,7 @@ { "id": "textureMapUv", "displayName": "UV", - "description": "Opacity texture map UV set", + "description": "Opacity map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -782,7 +782,7 @@ { "id": "diffuseTextureMap", "displayName": "Diffuse AO", - "description": "Texture map for defining occlusion area for diffuse ambient lighting.", + "description": "Texture for defining occlusion area for diffuse ambient lighting.", "type": "Image", "connection": { "type": "ShaderInput", @@ -792,14 +792,14 @@ { "id": "diffuseUseTexture", "displayName": " Use Texture", - "description": "Whether to use the Diffuse AO texture map.", + "description": "Whether to use the Diffuse AO map.", "type": "Bool", "defaultValue": true }, { "id": "diffuseTextureMapUv", "displayName": " UV", - "description": "Diffuse AO texture map UV set.", + "description": "Diffuse AO map UV set.", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -824,7 +824,7 @@ { "id": "specularTextureMap", "displayName": "Specular Cavity", - "description": "Texture map for defining occlusion area for specular lighting.", + "description": "Texture for defining occlusion area for specular lighting.", "type": "Image", "connection": { "type": "ShaderInput", @@ -834,14 +834,14 @@ { "id": "specularUseTexture", "displayName": " Use Texture", - "description": "Whether to use the Specular Cavity texture map.", + "description": "Whether to use the Specular Cavity map.", "type": "Bool", "defaultValue": true }, { "id": "specularTextureMapUv", "displayName": " UV", - "description": "Specular Cavity texture map UV set.", + "description": "Specular Cavity map UV set.", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -904,8 +904,8 @@ }, { "id": "textureMap", - "displayName": "Texture Map", - "description": "Texture map for defining emissive area.", + "displayName": "Texture", + "description": "Texture for defining emissive area.", "type": "Image", "connection": { "type": "ShaderInput", @@ -915,14 +915,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map.", + "description": "Whether to use the texture.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Emissive texture map UV set", + "description": "Emissive map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -935,25 +935,25 @@ "parallax": [ { "id": "textureMap", - "displayName": "Texture Map", - "description": "Depthmap to create parallax effect.", + "displayName": "Height Map", + "description": "Displacement height map to create parallax effect.", "type": "Image", "connection": { "type": "ShaderInput", - "id": "m_depthMap" + "id": "m_heightmap" } }, { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map.", + "description": "Whether to use the height map.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Depth texture map UV set", + "description": "Height map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -964,15 +964,15 @@ }, { "id": "factor", - "displayName": "Heightmap Scale", - "description": "The total height of the heightmap in local model units.", + "displayName": "Height Map Scale", + "description": "The total height of the height map in local model units.", "type": "Float", "defaultValue": 0.05, "min": 0.0, "softMax": 0.1, "connection": { "type": "ShaderInput", - "id": "m_depthFactor" + "id": "m_heightmapScale" } }, { @@ -985,18 +985,7 @@ "softMax": 0.1, "connection": { "type": "ShaderInput", - "id": "m_depthOffset" - } - }, - { - "id": "invert", - "displayName": "Invert", - "description": "Invert to depthmap if the texture is heightmap", - "type": "Bool", - "defaultValue": true, - "connection": { - "type": "ShaderInput", - "id": "m_depthInverted" + "id": "m_heightmapOffset" } }, { @@ -1037,7 +1026,7 @@ { "id": "showClipping", "displayName": "Show Clipping", - "description": "Highlight areas where the heightmap is clipped by the mesh surface.", + "description": "Highlight areas where the height map is clipped by the mesh surface.", "type": "Bool", "defaultValue": false, "connection": { @@ -1074,7 +1063,7 @@ { "id": "influenceMap", "displayName": " Influence Map", - "description": "Use texture map to control the strength of subsurface scattering", + "description": "Texture for controlling the strength of subsurface scattering", "type": "Image", "connection": { "type": "ShaderInput", @@ -1084,7 +1073,7 @@ { "id": "useInfluenceMap", "displayName": " Use Influence Map", - "description": "Whether to use the texture map as influence mask.", + "description": "Whether to use the influence map.", "type": "Bool", "defaultValue": true }, @@ -1153,7 +1142,7 @@ { "id": "thicknessMap", "displayName": " Thickness Map", - "description": "Use a greyscale texture for per pixel thickness", + "description": "Texture for controlling per pixel thickness", "type": "Image", "connection": { "type": "ShaderInput", @@ -1182,7 +1171,7 @@ { "id": "transmissionTint", "displayName": " Transmission Tint", - "description": "Color of the volume light travelling through", + "description": "Color of the volume light traveling through", "type": "Color", "defaultValue": [ 1.0, 0.8, 0.6 ] }, @@ -1257,7 +1246,7 @@ { "id": "enableDetailMaskTexture", "displayName": " Use Texture", - "description": "Enable detail mask texture", + "description": "Enable detail blend mask", "type": "Bool", "defaultValue": true }, @@ -1276,7 +1265,7 @@ { "id": "textureMapUv", "displayName": "Detail Map UVs", - "description": "Which UV set to use for detail map texture sampling", + "description": "Which UV set to use for detail map sampling", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -1294,8 +1283,8 @@ }, { "id": "baseColorDetailMap", - "displayName": " Texture Map", - "description": "Detailed Base Color Texture map", + "displayName": " Texture", + "description": "Detailed Base Color Texture", "type": "Image", "connection": { "type": "ShaderInput", @@ -1318,7 +1307,7 @@ { "id": "enableNormals", "displayName": "Enable Normal", - "description": "Enable detail normal texture to be used for fine detail normal such as scratches and small dents", + "description": "Enable detail normal map to be used for fine detail normal such as scratches and small dents", "type": "Bool", "defaultValue": false }, @@ -1337,8 +1326,8 @@ }, { "id": "normalDetailMap", - "displayName": " Texture Map", - "description": "Detailed Normal Texture map", + "displayName": " Texture", + "description": "Detailed Normal map", "type": "Image", "connection": { "type": "ShaderInput", @@ -1647,7 +1636,7 @@ { "type": "Lua", "args": { - "file": "StandardPBR_SubsurfaceState.lua" + "file": "EnhancedPBR_SubsurfaceState.lua" } }, { diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_Common.azsli b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_Common.azsli index 34af9229c2..b6d6439268 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_Common.azsli +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_Common.azsli @@ -108,7 +108,7 @@ ShaderResourceGroup MaterialSrg : SRG_PerMaterial // Callback function for ParallaxMapping.azsli DepthResult GetDepth(float2 uv, float2 uv_ddx, float2 uv_ddy) { - return SampleDepthOrHeightMap(MaterialSrg::m_depthInverted, MaterialSrg::m_depthMap, MaterialSrg::m_sampler, uv, uv_ddx, uv_ddy); + return SampleDepthFromHeightmap(MaterialSrg::m_heightmap, MaterialSrg::m_sampler, uv, uv_ddx, uv_ddy); } COMMON_OPTIONS_PARALLAX() @@ -116,7 +116,7 @@ COMMON_OPTIONS_PARALLAX() bool ShouldHandleParallax() { // Parallax mapping's non uniform uv transformations break screen space subsurface scattering, disable it when subsurface scattering is enabled. - return !o_enableSubsurfaceScattering && o_parallax_feature_enabled && o_useDepthMap; + return !o_enableSubsurfaceScattering && o_parallax_feature_enabled && o_useHeightmap; } bool ShouldHandleParallaxInDepthShaders() diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_DepthPass_WithPS.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_DepthPass_WithPS.azsl index 644473fef9..d70e3b899a 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_DepthPass_WithPS.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_DepthPass_WithPS.azsl @@ -84,7 +84,7 @@ PSDepthOutput MainPS(VSDepthOutput IN, bool isFrontFace : SV_IsFrontFace) float3x3 uvMatrix = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3(); float3x3 uvMatrixInverse = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrixInverse : CreateIdentity3x3(); - GetParallaxInput(IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex], MaterialSrg::m_depthFactor, MaterialSrg::m_depthOffset, + GetParallaxInput(IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex], MaterialSrg::m_heightmapScale, MaterialSrg::m_heightmapOffset, ObjectSrg::GetWorldMatrix(), uvMatrix, uvMatrixInverse, IN.m_uv[MaterialSrg::m_parallaxUvIndex], IN.m_worldPosition, OUT.m_depth); } diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_ForwardPass.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_ForwardPass.azsl index a4fcccb5f5..a8b4075d51 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_ForwardPass.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_ForwardPass.azsl @@ -141,7 +141,7 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float float3x3 uvMatrix = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3(); float3x3 uvMatrixInverse = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrixInverse : CreateIdentity3x3(); - GetParallaxInput(IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex], MaterialSrg::m_depthFactor, MaterialSrg::m_depthOffset, + GetParallaxInput(IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex], MaterialSrg::m_heightmapScale, MaterialSrg::m_heightmapOffset, ObjectSrg::GetWorldMatrix(), uvMatrix, uvMatrixInverse, IN.m_uv[MaterialSrg::m_parallaxUvIndex], IN.m_worldPosition, depth, IN.m_position.w, displacementIsClipped); diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_Shadowmap_WithPS.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_Shadowmap_WithPS.azsl index 7f6be252e2..6d3d4f2ea5 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_Shadowmap_WithPS.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_Shadowmap_WithPS.azsl @@ -88,7 +88,7 @@ PSDepthOutput MainPS(VertexOutput IN, bool isFrontFace : SV_IsFrontFace) float3x3 uvMatrix = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3(); float3x3 uvMatrixInverse = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrixInverse : CreateIdentity3x3(); - GetParallaxInput(IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex], MaterialSrg::m_depthFactor, MaterialSrg::m_depthOffset, + GetParallaxInput(IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex], MaterialSrg::m_heightmapScale, MaterialSrg::m_heightmapOffset, ObjectSrg::GetWorldMatrix(), uvMatrix, uvMatrixInverse, IN.m_uv[MaterialSrg::m_parallaxUvIndex], IN.m_worldPosition, OUT.m_depth); diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_SubsurfaceState.lua b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_SubsurfaceState.lua similarity index 100% rename from Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_SubsurfaceState.lua rename to Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR_SubsurfaceState.lua diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialInputs/ParallaxInput.azsli b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialInputs/ParallaxInput.azsli index ffd7c18045..84d4bfcc02 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialInputs/ParallaxInput.azsli +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/MaterialInputs/ParallaxInput.azsli @@ -22,15 +22,14 @@ // You can optionally provide a prefix for the set of inputs which corresponds to a prefix string supplied by the .materialtype file. This is common for multi-layered material types. #define COMMON_SRG_INPUTS_PARALLAX(prefix) \ -Texture2D prefix##m_depthMap; \ -float prefix##m_depthFactor; \ -float prefix##m_depthOffset; \ -bool prefix##m_depthInverted; +Texture2D prefix##m_heightmap; \ +float prefix##m_heightmapScale; \ +float prefix##m_heightmapOffset; #define COMMON_OPTIONS_PARALLAX(prefix) \ -option bool prefix##o_useDepthMap; +option bool prefix##o_useHeightmap; -void GetParallaxInput(float3 normal, float3 tangent, float3 bitangent, float depthFactor, float depthOffset, +void GetParallaxInput(float3 normal, float3 tangent, float3 bitangent, float heightmapScale, float heightmapOffset, float4x4 objectWorldMatrix, float3x3 uvMatrix, float3x3 uvMatrixInverse, inout float2 uv, inout float3 worldPosition, inout float depthNDC, inout float depthCS, out bool isClipped) { @@ -48,8 +47,8 @@ void GetParallaxInput(float3 normal, float3 tangent, float3 bitangent, float dep dirToCamera = ViewSrg::m_worldPosition.xyz - worldPosition; } - ParallaxOffset tangentOffset = GetParallaxOffset( depthFactor, - depthOffset, + ParallaxOffset tangentOffset = GetParallaxOffset( heightmapScale, + -heightmapOffset, uv, dirToCamera, tangent, @@ -62,7 +61,7 @@ void GetParallaxInput(float3 normal, float3 tangent, float3 bitangent, float dep if(o_parallax_enablePixelDepthOffset) { - PixelDepthOffset pdo = CalcPixelDepthOffset(depthFactor, + PixelDepthOffset pdo = CalcPixelDepthOffset(heightmapScale, tangentOffset.m_offsetTS, worldPosition, tangent, @@ -81,19 +80,19 @@ void GetParallaxInput(float3 normal, float3 tangent, float3 bitangent, float dep } } -void GetParallaxInput(float3 normal, float3 tangent, float3 bitangent, float depthFactor, float depthOffset, +void GetParallaxInput(float3 normal, float3 tangent, float3 bitangent, float heightmapScale, float heightmapOffset, float4x4 objectWorldMatrix, float3x3 uvMatrix, float3x3 uvMatrixInverse, inout float2 uv, inout float3 worldPosition, inout float depthNDC, inout float depthCS) { bool isClipped; - GetParallaxInput(normal, tangent, bitangent, depthFactor, depthOffset, objectWorldMatrix, uvMatrix, uvMatrixInverse, uv, worldPosition, depthNDC, depthCS, isClipped); + GetParallaxInput(normal, tangent, bitangent, heightmapScale, heightmapOffset, objectWorldMatrix, uvMatrix, uvMatrixInverse, uv, worldPosition, depthNDC, depthCS, isClipped); } -void GetParallaxInput(float3 normal, float3 tangent, float3 bitangent, float depthFactor, float depthOffset, +void GetParallaxInput(float3 normal, float3 tangent, float3 bitangent, float heightmapScale, float heightmapOffset, float4x4 objectWorldMatrix, float3x3 uvMatrix, float3x3 uvMatrixInverse, inout float2 uv, inout float3 worldPosition, inout float depthNDC) { float depthCS; - GetParallaxInput(normal, tangent, bitangent, depthFactor, depthOffset, objectWorldMatrix, uvMatrix, uvMatrixInverse, uv, worldPosition, depthNDC, depthCS); + GetParallaxInput(normal, tangent, bitangent, heightmapScale, heightmapOffset, objectWorldMatrix, uvMatrix, uvMatrixInverse, uv, worldPosition, depthNDC, depthCS); } diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.materialtype b/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.materialtype index f8c49d579c..dfe2fad60f 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.materialtype @@ -23,6 +23,16 @@ "displayName": "Normal", "description": "Properties related to configuring surface normal." }, + { + "id": "detailLayerGroup", + "displayName": "Detail Layer", + "description": "Properties for Fine Details Layer." + }, + { + "id": "detailUV", + "displayName": "Detail Layer UV", + "description": "Properties for modifying detail layer UV." + }, { "id": "occlusion", "displayName": "Occlusion", @@ -38,19 +48,9 @@ "displayName": "Wrinkle Layers", "description": "Properties for wrinkle maps to support morph animation, using vertex color blend weights." }, - { - "id": "detailLayerGroup", - "displayName": "Detail Layer", - "description": "Properties for Fine Details Layer." - }, - { - "id": "detailUV", - "displayName": "Detail Layer UV", - "description": "Properties for modifying detail layer UV." - }, { "id": "general", - "displayName": "General", + "displayName": "General Settings", "description": "General settings." } ], @@ -150,7 +150,7 @@ }, { "id": "textureMap", - "displayName": "Texture Map", + "displayName": "Texture", "description": "Base color texture map", "type": "Image", "connection": { @@ -161,14 +161,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map.", + "description": "Whether to use the texture.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Base color texture map UV set", + "description": "Base color map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Unwrapped", @@ -180,7 +180,7 @@ { "id": "textureBlendMode", "displayName": "Texture Blend Mode", - "description": "Selects the equation to use when combining Color, Factor, and Texture Map.", + "description": "Selects the equation to use when combining Color, Factor, and Texture.", "type": "Enum", "enumValues": [ "Multiply", "LinearLight", "Lerp", "Overlay" ], "defaultValue": "Multiply", @@ -193,8 +193,8 @@ "roughness": [ { "id": "textureMap", - "displayName": "Texture Map", - "description": "Texture map for defining surface roughness.", + "displayName": "Texture", + "description": "Texture for defining surface roughness.", "type": "Image", "connection": { "type": "ShaderInput", @@ -204,14 +204,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map, or just default to the Factor value.", + "description": "Whether to use the texture, or just default to the Factor value.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Roughness texture map UV set", + "description": "Roughness map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Unwrapped", @@ -224,7 +224,7 @@ // Note that "factor" is mutually exclusive with "lowerBound"/"upperBound". These are swapped by a lua functor. "id": "lowerBound", "displayName": "Lower Bound", - "description": "The roughness value that corresponds to black in the texture map.", + "description": "The roughness value that corresponds to black in the texture.", "type": "Float", "defaultValue": 0.0, "min": 0.0, @@ -238,7 +238,7 @@ // Note that "factor" is mutually exclusive with "lowerBound"/"upperBound". These are swapped by a lua functor. "id": "upperBound", "displayName": "Upper Bound", - "description": "The roughness value that corresponds to white in the texture map.", + "description": "The roughness value that corresponds to white in the texture.", "type": "Float", "defaultValue": 1.0, "min": 0.0, @@ -279,8 +279,8 @@ }, { "id": "textureMap", - "displayName": "Texture Map", - "description": "Texture map for defining surface reflectance.", + "displayName": "Texture", + "description": "Texture for defining surface reflectance.", "type": "Image", "connection": { "type": "ShaderInput", @@ -290,14 +290,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map, or just default to the Factor value.", + "description": "Whether to use the texture, or just default to the Factor value.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Specular reflection texture map UV set", + "description": "Specular reflection map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Unwrapped", @@ -321,8 +321,8 @@ "normal": [ { "id": "textureMap", - "displayName": "Texture Map", - "description": "Texture map for defining surface normal direction.", + "displayName": "Texture", + "description": "Texture for defining surface normal direction.", "type": "Image", "connection": { "type": "ShaderInput", @@ -332,14 +332,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map, or just rely on vertex normals.", + "description": "Whether to use the texture, or just rely on vertex normals.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Normal texture map UV set", + "description": "Normal map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Unwrapped", @@ -388,7 +388,7 @@ { "id": "diffuseTextureMap", "displayName": "Diffuse AO", - "description": "Texture map for defining occlusion area for diffuse ambient lighting.", + "description": "Texture for defining occlusion area for diffuse ambient lighting.", "type": "Image", "connection": { "type": "ShaderInput", @@ -398,14 +398,14 @@ { "id": "diffuseUseTexture", "displayName": " Use Texture", - "description": "Whether to use the Diffuse AO texture map.", + "description": "Whether to use the Diffuse AO map.", "type": "Bool", "defaultValue": true }, { "id": "diffuseTextureMapUv", "displayName": " UV", - "description": "Diffuse AO texture map UV set.", + "description": "Diffuse AO map UV set.", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -430,7 +430,7 @@ { "id": "specularTextureMap", "displayName": "Specular Cavity", - "description": "Texture map for defining occlusion area for specular lighting.", + "description": "Texture for defining occlusion area for specular lighting.", "type": "Image", "connection": { "type": "ShaderInput", @@ -440,14 +440,14 @@ { "id": "specularUseTexture", "displayName": " Use Texture", - "description": "Whether to use the Specular Cavity texture map.", + "description": "Whether to use the Specular Cavity map.", "type": "Bool", "defaultValue": true }, { "id": "specularTextureMapUv", "displayName": " UV", - "description": "Specular Cavity texture map UV set.", + "description": "Specular Cavity map UV set.", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -498,7 +498,7 @@ { "id": "influenceMap", "displayName": " Influence Map", - "description": "Use texture map to control the strength of subsurface scattering", + "description": "Texture for controlling the strength of subsurface scattering", "type": "Image", "connection": { "type": "ShaderInput", @@ -508,7 +508,7 @@ { "id": "useInfluenceMap", "displayName": " Use Influence Map", - "description": "Whether to use the texture map as influence mask.", + "description": "Whether to use the influence map.", "type": "Bool", "defaultValue": true }, @@ -577,7 +577,7 @@ { "id": "thicknessMap", "displayName": " Thickness Map", - "description": "Use a greyscale texture for per pixel thickness", + "description": "Texture for controlling per pixel thickness", "type": "Image", "connection": { "type": "ShaderInput", @@ -606,7 +606,7 @@ { "id": "transmissionTint", "displayName": " Transmission Tint", - "description": "Color of the volume light travelling through", + "description": "Color of the volume light traveling through", "type": "Color", "defaultValue": [ 1.0, 0.8, 0.6 ] }, @@ -792,7 +792,7 @@ { "id": "enableDetailMaskTexture", "displayName": " Use Texture", - "description": "Enable detail mask texture", + "description": "Enable detail blend mask", "type": "Bool", "defaultValue": true }, @@ -811,7 +811,7 @@ { "id": "textureMapUv", "displayName": "Detail Map UVs", - "description": "Which UV set to use for detail map texture sampling", + "description": "Which UV set to use for detail map sampling", "type": "Enum", "enumIsUv": true, "defaultValue": "Unwrapped", @@ -829,8 +829,8 @@ }, { "id": "baseColorDetailMap", - "displayName": " Texture Map", - "description": "Detailed Base Color Texture map", + "displayName": " Texture", + "description": "Detailed Base Color Texture", "type": "Image", "connection": { "type": "ShaderInput", @@ -853,7 +853,7 @@ { "id": "enableNormals", "displayName": "Enable Normal", - "description": "Enable detail normal texture to be used for fine detail normal such as scratches and small dents", + "description": "Enable detail normal map to be used for fine detail normal such as scratches and small dents", "type": "Bool", "defaultValue": false }, @@ -872,8 +872,8 @@ }, { "id": "normalDetailMap", - "displayName": " Texture Map", - "description": "Detailed Normal Texture map", + "displayName": " Texture", + "description": "Detailed Normal map", "type": "Image", "connection": { "type": "ShaderInput", @@ -1080,7 +1080,7 @@ { "type": "Lua", "args": { - "file": "StandardPBR_SubsurfaceState.lua" + "file": "EnhancedPBR_SubsurfaceState.lua" } }, { diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR.materialtype b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR.materialtype index ec1298ae77..c07eac3d47 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR.materialtype @@ -26,7 +26,7 @@ }, { "id": "general", - "displayName": "General", + "displayName": "General Settings", "description": "General settings." }, //############################################################################################## @@ -57,11 +57,6 @@ "displayName": "Layer 1: Normal", "description": "Properties related to configuring surface normal." }, - { - "id": "layer1_clearCoat", - "displayName": "Layer 1: Clear Coat", - "description": "Properties for configuring gloss clear coat" - }, { "id": "layer1_occlusion", "displayName": "Layer 1: Occlusion", @@ -72,6 +67,11 @@ "displayName": "Layer 1: Emissive", "description": "Properties to add light emission, independent of other lights in the scene." }, + { + "id": "layer1_clearCoat", + "displayName": "Layer 1: Clear Coat", + "description": "Properties for configuring gloss clear coat" + }, { "id": "layer1_parallax", "displayName": "Layer 1: Displacement", @@ -110,11 +110,6 @@ "displayName": "Layer 2: Normal", "description": "Properties related to configuring surface normal." }, - { - "id": "layer2_clearCoat", - "displayName": "Layer 2: Clear Coat", - "description": "Properties for configuring gloss clear coat" - }, { "id": "layer2_occlusion", "displayName": "Layer 2: Occlusion", @@ -125,6 +120,11 @@ "displayName": "Layer 2: Emissive", "description": "Properties to add light emission, independent of other lights in the scene." }, + { + "id": "layer2_clearCoat", + "displayName": "Layer 2: Clear Coat", + "description": "Properties for configuring gloss clear coat" + }, { "id": "layer2_parallax", "displayName": "Layer 2: Displacement", @@ -163,11 +163,6 @@ "displayName": "Layer 3: Normal", "description": "Properties related to configuring surface normal." }, - { - "id": "layer3_clearCoat", - "displayName": "Layer 3: Clear Coat", - "description": "Properties for configuring gloss clear coat" - }, { "id": "layer3_occlusion", "displayName": "Layer 3: Occlusion", @@ -178,6 +173,11 @@ "displayName": "Layer 3: Emissive", "description": "Properties to add light emission, independent of other lights in the scene." }, + { + "id": "layer3_clearCoat", + "displayName": "Layer 3: Clear Coat", + "description": "Properties for configuring gloss clear coat" + }, { "id": "layer3_parallax", "displayName": "Layer 3: Displacement", @@ -428,7 +428,7 @@ { "id": "showClipping", "displayName": "Show Clipping", - "description": "Highlight areas where the heightmap is clipped by the mesh surface.", + "description": "Highlight areas where the height map is clipped by the mesh surface.", "type": "Bool", "defaultValue": false, "connection": { @@ -550,7 +550,7 @@ }, { "id": "textureMap", - "displayName": "Texture Map", + "displayName": "Texture", "description": "Base color texture map", "type": "Image", "connection": { @@ -561,14 +561,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map.", + "description": "Whether to use the texture.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Base color texture map UV set", + "description": "Base color map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -580,7 +580,7 @@ { "id": "textureBlendMode", "displayName": "Texture Blend Mode", - "description": "Selects the equation to use when combining Color, Factor, and Texture Map.", + "description": "Selects the equation to use when combining Color, Factor, and Texture.", "type": "Enum", "enumValues": [ "Multiply", "LinearLight", "Lerp", "Overlay" ], "defaultValue": "Multiply", @@ -606,7 +606,7 @@ }, { "id": "textureMap", - "displayName": "Texture Map", + "displayName": "Texture", "description": "", "type": "Image", "connection": { @@ -617,14 +617,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map, or just default to the Factor value.", + "description": "Whether to use the texture, or just default to the Factor value.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Metallic texture map UV set", + "description": "Metallic map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -637,8 +637,8 @@ "layer1_roughness": [ { "id": "textureMap", - "displayName": "Texture Map", - "description": "Texture map for defining surface roughness.", + "displayName": "Texture", + "description": "Texture for defining surface roughness.", "type": "Image", "connection": { "type": "ShaderInput", @@ -648,14 +648,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map, or just default to the Factor value.", + "description": "Whether to use the texture, or just default to the Factor value.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Roughness texture map UV set", + "description": "Roughness map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -668,7 +668,7 @@ // Note that "factor" is mutually exclusive with "lowerBound"/"upperBound". These are swapped by a lua functor. "id": "lowerBound", "displayName": "Lower Bound", - "description": "The roughness value that corresponds to black in the texture map.", + "description": "The roughness value that corresponds to black in the texture.", "type": "Float", "defaultValue": 0.0, "min": 0.0, @@ -682,7 +682,7 @@ // Note that "factor" is mutually exclusive with "lowerBound"/"upperBound". These are swapped by a lua functor. "id": "upperBound", "displayName": "Upper Bound", - "description": "The roughness value that corresponds to white in the texture map.", + "description": "The roughness value that corresponds to white in the texture.", "type": "Float", "defaultValue": 1.0, "min": 0.0, @@ -723,8 +723,8 @@ }, { "id": "textureMap", - "displayName": "Texture Map", - "description": "Texture map for defining surface reflectance.", + "displayName": "Texture", + "description": "Texture for defining surface reflectance.", "type": "Image", "connection": { "type": "ShaderInput", @@ -734,14 +734,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map, or just default to the Factor value.", + "description": "Whether to use the texture, or just default to the Factor value.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Specular reflection texture map UV set", + "description": "Specular reflection map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -754,8 +754,8 @@ "layer1_normal": [ { "id": "textureMap", - "displayName": "Texture Map", - "description": "Texture map for defining surface normal direction.", + "displayName": "Texture", + "description": "Texture for defining surface normal direction.", "type": "Image", "connection": { "type": "ShaderInput", @@ -765,14 +765,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map, or just rely on vertex normals.", + "description": "Whether to use the texture, or just rely on vertex normals.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Normal texture map UV set", + "description": "Normal map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -841,7 +841,7 @@ { "id": "influenceMap", "displayName": " Influence Map", - "description": "Strength factor texture map", + "description": "Strength factor texture", "type": "Image", "connection": { "type": "ShaderInput", @@ -851,14 +851,14 @@ { "id": "useInfluenceMap", "displayName": " Use Texture", - "description": "Whether to use the texture map, or just default to the Factor value.", + "description": "Whether to use the texture, or just default to the Factor value.", "type": "Bool", "defaultValue": true }, { "id": "influenceMapUv", "displayName": " UV", - "description": "Strength factor texture map UV set", + "description": "Strength factor map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -883,7 +883,7 @@ { "id": "roughnessMap", "displayName": " Roughness Map", - "description": "Roughness texture map", + "description": "Texture for defining surface roughness", "type": "Image", "connection": { "type": "ShaderInput", @@ -893,14 +893,14 @@ { "id": "useRoughnessMap", "displayName": " Use Texture", - "description": "Whether to use the texture map, or just default to the roughness value.", + "description": "Whether to use the texture, or just default to the roughness value.", "type": "Bool", "defaultValue": true }, { "id": "roughnessMapUv", "displayName": " UV", - "description": "Roughness texture map UV set", + "description": "Roughness map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -942,7 +942,7 @@ { "id": "normalMapUv", "displayName": " UV", - "description": "Normal texture map UV set", + "description": "Normal map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -956,7 +956,7 @@ { "id": "diffuseTextureMap", "displayName": "Diffuse AO", - "description": "Texture map for defining occlusion area for diffuse ambient lighting.", + "description": "Texture for defining occlusion area for diffuse ambient lighting.", "type": "Image", "connection": { "type": "ShaderInput", @@ -966,14 +966,14 @@ { "id": "diffuseUseTexture", "displayName": " Use Texture", - "description": "Whether to use the Diffuse AO texture map.", + "description": "Whether to use the Diffuse AO map.", "type": "Bool", "defaultValue": true }, { "id": "diffuseTextureMapUv", "displayName": " UV", - "description": "Diffuse AO texture map UV set.", + "description": "Diffuse AO map UV set.", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -998,7 +998,7 @@ { "id": "specularTextureMap", "displayName": "Specular Cavity", - "description": "Texture map for defining occlusion area for specular lighting.", + "description": "Texture for defining occlusion area for specular lighting.", "type": "Image", "connection": { "type": "ShaderInput", @@ -1008,14 +1008,14 @@ { "id": "specularUseTexture", "displayName": " Use Texture", - "description": "Whether to use the Specular Cavity texture map.", + "description": "Whether to use the Specular Cavity map.", "type": "Bool", "defaultValue": true }, { "id": "specularTextureMapUv", "displayName": " UV", - "description": "Specular Cavity texture map UV set.", + "description": "Specular Cavity map UV set.", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -1078,8 +1078,8 @@ }, { "id": "textureMap", - "displayName": "Texture Map", - "description": "Texture map for defining emissive area.", + "displayName": "Texture", + "description": "Texture for defining emissive area.", "type": "Image", "connection": { "type": "ShaderInput", @@ -1089,14 +1089,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map.", + "description": "Whether to use the texture.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Emissive texture map UV set", + "description": "Emissive map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -1109,43 +1109,32 @@ "layer1_parallax": [ { "id": "textureMap", - "displayName": "Texture Map", - "description": "Displacement texture map, which can be used for layer blending and/or a parallax effect.", + "displayName": "Height Map", + "description": "Displacement height map, which can be used for layer blending and/or a parallax effect.", "type": "Image", "connection": { "type": "ShaderInput", - "id": "m_layer1_m_depthMap" + "id": "m_layer1_m_heightmap" } }, { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map.", + "description": "Whether to use the height map.", "type": "Bool", "defaultValue": true }, - { - "id": "invert", - "displayName": "Invert", - "description": "Invert the displacement map", - "type": "Bool", - "defaultValue": true, - "connection": { - "type": "ShaderInput", - "id": "m_layer1_m_depthInverted" - } - }, { "id": "factor", "displayName": "Scale", - "description": "The total height of the displacement texture map in local model units.", + "description": "The total height of the height map in local model units.", "type": "Float", "defaultValue": 0.05, "min": 0.0, "softMax": 0.1, "connection": { "type": "ShaderInput", - "id": "m_layer1_m_depthFactor" + "id": "m_layer1_m_heightmapScale" } }, { @@ -1158,7 +1147,7 @@ "softMax": 0.1, "connection": { "type": "ShaderInput", - "id": "m_layer1_m_depthOffset" + "id": "m_layer1_m_heightmapOffset" } } ], @@ -1256,7 +1245,7 @@ }, { "id": "textureMap", - "displayName": "Texture Map", + "displayName": "Texture", "description": "Base color texture map", "type": "Image", "connection": { @@ -1267,14 +1256,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map.", + "description": "Whether to use the texture.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Base color texture map UV set", + "description": "Base color map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -1286,7 +1275,7 @@ { "id": "textureBlendMode", "displayName": "Texture Blend Mode", - "description": "Selects the equation to use when combining Color, Factor, and Texture Map.", + "description": "Selects the equation to use when combining Color, Factor, and Texture.", "type": "Enum", "enumValues": [ "Multiply", "LinearLight", "Lerp", "Overlay" ], "defaultValue": "Multiply", @@ -1312,7 +1301,7 @@ }, { "id": "textureMap", - "displayName": "Texture Map", + "displayName": "Texture", "description": "", "type": "Image", "connection": { @@ -1323,14 +1312,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map, or just default to the Factor value.", + "description": "Whether to use the texture, or just default to the Factor value.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Metallic texture map UV set", + "description": "Metallic map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -1343,8 +1332,8 @@ "layer2_roughness": [ { "id": "textureMap", - "displayName": "Texture Map", - "description": "Texture map for defining surface roughness.", + "displayName": "Texture", + "description": "Texture for defining surface roughness.", "type": "Image", "connection": { "type": "ShaderInput", @@ -1354,14 +1343,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map, or just default to the Factor value.", + "description": "Whether to use the texture, or just default to the Factor value.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Roughness texture map UV set", + "description": "Roughness map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -1374,7 +1363,7 @@ // Note that "factor" is mutually exclusive with "lowerBound"/"upperBound". These are swapped by a lua functor. "id": "lowerBound", "displayName": "Lower Bound", - "description": "The roughness value that corresponds to black in the texture map.", + "description": "The roughness value that corresponds to black in the texture.", "type": "Float", "defaultValue": 0.0, "min": 0.0, @@ -1388,7 +1377,7 @@ // Note that "factor" is mutually exclusive with "lowerBound"/"upperBound". These are swapped by a lua functor. "id": "upperBound", "displayName": "Upper Bound", - "description": "The roughness value that corresponds to white in the texture map.", + "description": "The roughness value that corresponds to white in the texture.", "type": "Float", "defaultValue": 1.0, "min": 0.0, @@ -1429,8 +1418,8 @@ }, { "id": "textureMap", - "displayName": "Texture Map", - "description": "Texture map for defining surface reflectance.", + "displayName": "Texture", + "description": "Texture for defining surface reflectance.", "type": "Image", "connection": { "type": "ShaderInput", @@ -1440,14 +1429,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map, or just default to the Factor value.", + "description": "Whether to use the texture, or just default to the Factor value.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Specular reflection texture map UV set", + "description": "Specular reflection map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -1460,8 +1449,8 @@ "layer2_normal": [ { "id": "textureMap", - "displayName": "Texture Map", - "description": "Texture map for defining surface normal direction.", + "displayName": "Texture", + "description": "Texture for defining surface normal direction.", "type": "Image", "connection": { "type": "ShaderInput", @@ -1471,14 +1460,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map, or just rely on vertex normals.", + "description": "Whether to use the texture, or just rely on vertex normals.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Normal texture map UV set", + "description": "Normal map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -1547,7 +1536,7 @@ { "id": "influenceMap", "displayName": " Influence Map", - "description": "Strength factor texture map", + "description": "Strength factor texture", "type": "Image", "connection": { "type": "ShaderInput", @@ -1557,14 +1546,14 @@ { "id": "useInfluenceMap", "displayName": " Use Texture", - "description": "Whether to use the texture map, or just default to the Factor value.", + "description": "Whether to use the texture, or just default to the Factor value.", "type": "Bool", "defaultValue": true }, { "id": "influenceMapUv", "displayName": " UV", - "description": "Strength factor texture map UV set", + "description": "Strength factor map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -1589,7 +1578,7 @@ { "id": "roughnessMap", "displayName": " Roughness Map", - "description": "Roughness texture map", + "description": "Texture for defining surface roughness", "type": "Image", "connection": { "type": "ShaderInput", @@ -1599,14 +1588,14 @@ { "id": "useRoughnessMap", "displayName": " Use Texture", - "description": "Whether to use the texture map, or just default to the roughness value.", + "description": "Whether to use the texture, or just default to the roughness value.", "type": "Bool", "defaultValue": true }, { "id": "roughnessMapUv", "displayName": " UV", - "description": "Roughness texture map UV set", + "description": "Roughness map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -1648,7 +1637,7 @@ { "id": "normalMapUv", "displayName": " UV", - "description": "Normal texture map UV set", + "description": "Normal map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -1662,7 +1651,7 @@ { "id": "diffuseTextureMap", "displayName": "Diffuse AO", - "description": "Texture map for defining occlusion area for diffuse ambient lighting.", + "description": "Texture for defining occlusion area for diffuse ambient lighting.", "type": "Image", "connection": { "type": "ShaderInput", @@ -1672,14 +1661,14 @@ { "id": "diffuseUseTexture", "displayName": " Use Texture", - "description": "Whether to use the Diffuse AO texture map.", + "description": "Whether to use the Diffuse AO map.", "type": "Bool", "defaultValue": true }, { "id": "diffuseTextureMapUv", "displayName": " UV", - "description": "Diffuse AO texture map UV set.", + "description": "Diffuse AO map UV set.", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -1704,7 +1693,7 @@ { "id": "specularTextureMap", "displayName": "Specular Cavity", - "description": "Texture map for defining occlusion area for specular lighting.", + "description": "Texture for defining occlusion area for specular lighting.", "type": "Image", "connection": { "type": "ShaderInput", @@ -1714,14 +1703,14 @@ { "id": "specularUseTexture", "displayName": " Use Texture", - "description": "Whether to use the Specular Cavity texture map.", + "description": "Whether to use the Specular Cavity map.", "type": "Bool", "defaultValue": true }, { "id": "specularTextureMapUv", "displayName": " UV", - "description": "Specular Cavity texture map UV set.", + "description": "Specular Cavity map UV set.", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -1784,8 +1773,8 @@ }, { "id": "textureMap", - "displayName": "Texture Map", - "description": "Texture map for defining emissive area.", + "displayName": "Texture", + "description": "Texture for defining emissive area.", "type": "Image", "connection": { "type": "ShaderInput", @@ -1795,14 +1784,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map.", + "description": "Whether to use the texture.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Emissive texture map UV set", + "description": "Emissive map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -1815,43 +1804,32 @@ "layer2_parallax": [ { "id": "textureMap", - "displayName": "Texture Map", - "description": "Displacement texture map, which can be used for layer blending and/or a parallax effect.", + "displayName": "Height Map", + "description": "Displacement height map, which can be used for layer blending and/or a parallax effect.", "type": "Image", "connection": { "type": "ShaderInput", - "id": "m_layer2_m_depthMap" + "id": "m_layer2_m_heightmap" } }, { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map.", + "description": "Whether to use the height map.", "type": "Bool", "defaultValue": true }, - { - "id": "invert", - "displayName": "Invert", - "description": "Invert the displacement map", - "type": "Bool", - "defaultValue": true, - "connection": { - "type": "ShaderInput", - "id": "m_layer2_m_depthInverted" - } - }, { "id": "factor", "displayName": "Scale", - "description": "The total height of the displacement texture map in local model units.", + "description": "The total height of the height map in local model units.", "type": "Float", "defaultValue": 0.05, "min": 0.0, "softMax": 0.1, "connection": { "type": "ShaderInput", - "id": "m_layer2_m_depthFactor" + "id": "m_layer2_m_heightmapScale" } }, { @@ -1864,7 +1842,7 @@ "softMax": 0.1, "connection": { "type": "ShaderInput", - "id": "m_layer2_m_depthOffset" + "id": "m_layer2_m_heightmapOffset" } } ], @@ -1962,7 +1940,7 @@ }, { "id": "textureMap", - "displayName": "Texture Map", + "displayName": "Texture", "description": "Base color texture map", "type": "Image", "connection": { @@ -1973,14 +1951,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map.", + "description": "Whether to use the texture.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Base color texture map UV set", + "description": "Base color map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -1992,7 +1970,7 @@ { "id": "textureBlendMode", "displayName": "Texture Blend Mode", - "description": "Selects the equation to use when combining Color, Factor, and Texture Map.", + "description": "Selects the equation to use when combining Color, Factor, and Texture.", "type": "Enum", "enumValues": [ "Multiply", "LinearLight", "Lerp", "Overlay" ], "defaultValue": "Multiply", @@ -2018,7 +1996,7 @@ }, { "id": "textureMap", - "displayName": "Texture Map", + "displayName": "Texture", "description": "", "type": "Image", "connection": { @@ -2029,14 +2007,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map, or just default to the Factor value.", + "description": "Whether to use the texture, or just default to the Factor value.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Metallic texture map UV set", + "description": "Metallic map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -2049,8 +2027,8 @@ "layer3_roughness": [ { "id": "textureMap", - "displayName": "Texture Map", - "description": "Texture map for defining surface roughness.", + "displayName": "Texture", + "description": "Texture for defining surface roughness.", "type": "Image", "connection": { "type": "ShaderInput", @@ -2060,14 +2038,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map, or just default to the Factor value.", + "description": "Whether to use the texture, or just default to the Factor value.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Roughness texture map UV set", + "description": "Roughness map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -2080,7 +2058,7 @@ // Note that "factor" is mutually exclusive with "lowerBound"/"upperBound". These are swapped by a lua functor. "id": "lowerBound", "displayName": "Lower Bound", - "description": "The roughness value that corresponds to black in the texture map.", + "description": "The roughness value that corresponds to black in the texture.", "type": "Float", "defaultValue": 0.0, "min": 0.0, @@ -2094,7 +2072,7 @@ // Note that "factor" is mutually exclusive with "lowerBound"/"upperBound". These are swapped by a lua functor. "id": "upperBound", "displayName": "Upper Bound", - "description": "The roughness value that corresponds to white in the texture map.", + "description": "The roughness value that corresponds to white in the texture.", "type": "Float", "defaultValue": 1.0, "min": 0.0, @@ -2135,8 +2113,8 @@ }, { "id": "textureMap", - "displayName": "Texture Map", - "description": "Texture map for defining surface reflectance.", + "displayName": "Texture", + "description": "Texture for defining surface reflectance.", "type": "Image", "connection": { "type": "ShaderInput", @@ -2146,14 +2124,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map, or just default to the Factor value.", + "description": "Whether to use the texture, or just default to the Factor value.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Specular reflection texture map UV set", + "description": "Specular reflection map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -2166,8 +2144,8 @@ "layer3_normal": [ { "id": "textureMap", - "displayName": "Texture Map", - "description": "Texture map for defining surface normal direction.", + "displayName": "Texture", + "description": "Texture for defining surface normal direction.", "type": "Image", "connection": { "type": "ShaderInput", @@ -2177,14 +2155,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map, or just rely on vertex normals.", + "description": "Whether to use the texture, or just rely on vertex normals.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Normal texture map UV set", + "description": "Normal map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -2253,7 +2231,7 @@ { "id": "influenceMap", "displayName": " Influence Map", - "description": "Strength factor texture map", + "description": "Strength factor texture", "type": "Image", "connection": { "type": "ShaderInput", @@ -2263,14 +2241,14 @@ { "id": "useInfluenceMap", "displayName": " Use Texture", - "description": "Whether to use the texture map, or just default to the Factor value.", + "description": "Whether to use the texture, or just default to the Factor value.", "type": "Bool", "defaultValue": true }, { "id": "influenceMapUv", "displayName": " UV", - "description": "Strength factor texture map UV set", + "description": "Strength factor map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -2295,7 +2273,7 @@ { "id": "roughnessMap", "displayName": " Roughness Map", - "description": "Roughness texture map", + "description": "Texture for defining surface roughness", "type": "Image", "connection": { "type": "ShaderInput", @@ -2305,14 +2283,14 @@ { "id": "useRoughnessMap", "displayName": " Use Texture", - "description": "Whether to use the texture map, or just default to the roughness value.", + "description": "Whether to use the texture, or just default to the roughness value.", "type": "Bool", "defaultValue": true }, { "id": "roughnessMapUv", "displayName": " UV", - "description": "Roughness texture map UV set", + "description": "Roughness map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -2354,7 +2332,7 @@ { "id": "normalMapUv", "displayName": " UV", - "description": "Normal texture map UV set", + "description": "Normal map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -2368,7 +2346,7 @@ { "id": "diffuseTextureMap", "displayName": "Diffuse AO", - "description": "Texture map for defining occlusion area for diffuse ambient lighting.", + "description": "Texture for defining occlusion area for diffuse ambient lighting.", "type": "Image", "connection": { "type": "ShaderInput", @@ -2378,14 +2356,14 @@ { "id": "diffuseUseTexture", "displayName": " Use Texture", - "description": "Whether to use the Diffuse AO texture map.", + "description": "Whether to use the Diffuse AO map.", "type": "Bool", "defaultValue": true }, { "id": "diffuseTextureMapUv", "displayName": " UV", - "description": "Diffuse AO texture map UV set.", + "description": "Diffuse AO map UV set.", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -2410,7 +2388,7 @@ { "id": "specularTextureMap", "displayName": "Specular Cavity", - "description": "Texture map for defining occlusion area for specular lighting.", + "description": "Texture for defining occlusion area for specular lighting.", "type": "Image", "connection": { "type": "ShaderInput", @@ -2420,14 +2398,14 @@ { "id": "specularUseTexture", "displayName": " Use Texture", - "description": "Whether to use the Specular Cavity texture map.", + "description": "Whether to use the Specular Cavity map.", "type": "Bool", "defaultValue": true }, { "id": "specularTextureMapUv", "displayName": " UV", - "description": "Specular Cavity texture map UV set.", + "description": "Specular Cavity map UV set.", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -2490,8 +2468,8 @@ }, { "id": "textureMap", - "displayName": "Texture Map", - "description": "Texture map for defining emissive area.", + "displayName": "Texture", + "description": "Texture for defining emissive area.", "type": "Image", "connection": { "type": "ShaderInput", @@ -2501,14 +2479,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map.", + "description": "Whether to use the texture.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Emissive texture map UV set", + "description": "Emissive map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -2521,43 +2499,32 @@ "layer3_parallax": [ { "id": "textureMap", - "displayName": "Texture Map", - "description": "Displacement texture map, which can be used for layer blending and/or a parallax effect.", + "displayName": "Height Map", + "description": "Displacement height map, which can be used for layer blending and/or a parallax effect.", "type": "Image", "connection": { "type": "ShaderInput", - "id": "m_layer3_m_depthMap" + "id": "m_layer3_m_heightmap" } }, { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map.", + "description": "Whether to use the height map.", "type": "Bool", "defaultValue": true }, - { - "id": "invert", - "displayName": "Invert", - "description": "Invert the displacement map", - "type": "Bool", - "defaultValue": true, - "connection": { - "type": "ShaderInput", - "id": "m_layer3_m_depthInverted" - } - }, { "id": "factor", "displayName": "Scale", - "description": "The total height of the displacement texture map in local model units.", + "description": "The total height of the height map in local model units.", "type": "Float", "defaultValue": 0.05, "min": 0.0, "softMax": 0.1, "connection": { "type": "ShaderInput", - "id": "m_layer3_m_depthFactor" + "id": "m_layer3_m_heightmapScale" } }, { @@ -2570,7 +2537,7 @@ "softMax": 0.1, "connection": { "type": "ShaderInput", - "id": "m_layer3_m_depthOffset" + "id": "m_layer3_m_heightmapOffset" } } ], @@ -2837,8 +2804,8 @@ "args": { "textureProperty": "layer1_parallax.textureMap", "useTextureProperty": "layer1_parallax.useTexture", - "dependentProperties": ["layer1_parallax.factor", "layer1_parallax.invert"], - "shaderOption": "o_layer1_o_useDepthMap" + "dependentProperties": ["layer1_parallax.factor"], + "shaderOption": "o_layer1_o_useHeightmap" } }, { @@ -2974,8 +2941,8 @@ "args": { "textureProperty": "layer2_parallax.textureMap", "useTextureProperty": "layer2_parallax.useTexture", - "dependentProperties": ["layer2_parallax.factor", "layer2_parallax.invert"], - "shaderOption": "o_layer2_o_useDepthMap" + "dependentProperties": ["layer2_parallax.factor"], + "shaderOption": "o_layer2_o_useHeightmap" } }, { @@ -3111,8 +3078,8 @@ "args": { "textureProperty": "layer3_parallax.textureMap", "useTextureProperty": "layer3_parallax.useTexture", - "dependentProperties": ["layer3_parallax.factor", "layer3_parallax.invert"], - "shaderOption": "o_layer3_o_useDepthMap" + "dependentProperties": ["layer3_parallax.factor"], + "shaderOption": "o_layer3_o_useHeightmap" } }, { diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_Common.azsli b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_Common.azsli index c20a90c00b..1750da5020 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_Common.azsli +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_Common.azsli @@ -379,7 +379,7 @@ float3 GetLayerDepthValues(float2 uv, float2 uv_ddx, float2 uv_ddy) // layer1 { - if(o_layer1_o_useDepthMap) + if(o_layer1_o_useHeightmap) { float2 layerUv = uv; if(MaterialSrg::m_parallaxUvIndex == 0) @@ -387,16 +387,16 @@ float3 GetLayerDepthValues(float2 uv, float2 uv_ddx, float2 uv_ddy) layerUv = mul(MaterialSrg::m_layer1_m_uvMatrix, float3(uv, 1.0)).xy; } - layerDepthValues.r = SampleDepthOrHeightMap(MaterialSrg::m_layer1_m_depthInverted, MaterialSrg::m_layer1_m_depthMap, MaterialSrg::m_sampler, layerUv, uv_ddx, uv_ddy).m_depth; - layerDepthValues.r *= MaterialSrg::m_layer1_m_depthFactor; + layerDepthValues.r = SampleDepthFromHeightmap(MaterialSrg::m_layer1_m_heightmap, MaterialSrg::m_sampler, layerUv, uv_ddx, uv_ddy).m_depth; + layerDepthValues.r *= MaterialSrg::m_layer1_m_heightmapScale; } - layerDepthValues.r -= MaterialSrg::m_layer1_m_depthOffset; + layerDepthValues.r -= MaterialSrg::m_layer1_m_heightmapOffset; } if(o_layer2_enabled) { - if(o_layer2_o_useDepthMap) + if(o_layer2_o_useHeightmap) { float2 layerUv = uv; if(MaterialSrg::m_parallaxUvIndex == 0) @@ -404,17 +404,17 @@ float3 GetLayerDepthValues(float2 uv, float2 uv_ddx, float2 uv_ddy) layerUv = mul(MaterialSrg::m_layer2_m_uvMatrix, float3(uv, 1.0)).xy; } - layerDepthValues.g = SampleDepthOrHeightMap(MaterialSrg::m_layer2_m_depthInverted, MaterialSrg::m_layer2_m_depthMap, MaterialSrg::m_sampler, layerUv, uv_ddx, uv_ddy).m_depth; - layerDepthValues.g *= MaterialSrg::m_layer2_m_depthFactor; + layerDepthValues.g = SampleDepthFromHeightmap(MaterialSrg::m_layer2_m_heightmap, MaterialSrg::m_sampler, layerUv, uv_ddx, uv_ddy).m_depth; + layerDepthValues.g *= MaterialSrg::m_layer2_m_heightmapScale; } - layerDepthValues.g -= MaterialSrg::m_layer2_m_depthOffset; + layerDepthValues.g -= MaterialSrg::m_layer2_m_heightmapOffset; } if(o_layer3_enabled) { - if(o_layer3_o_useDepthMap) + if(o_layer3_o_useHeightmap) { float2 layerUv = uv; if(MaterialSrg::m_parallaxUvIndex == 0) @@ -422,11 +422,11 @@ float3 GetLayerDepthValues(float2 uv, float2 uv_ddx, float2 uv_ddy) layerUv = mul(MaterialSrg::m_layer3_m_uvMatrix, float3(uv, 1.0)).xy; } - layerDepthValues.b = SampleDepthOrHeightMap(MaterialSrg::m_layer3_m_depthInverted, MaterialSrg::m_layer3_m_depthMap, MaterialSrg::m_sampler, layerUv, uv_ddx, uv_ddy).m_depth; - layerDepthValues.b *= MaterialSrg::m_layer3_m_depthFactor; + layerDepthValues.b = SampleDepthFromHeightmap(MaterialSrg::m_layer3_m_heightmap, MaterialSrg::m_sampler, layerUv, uv_ddx, uv_ddy).m_depth; + layerDepthValues.b *= MaterialSrg::m_layer3_m_heightmapScale; } - layerDepthValues.b -= MaterialSrg::m_layer3_m_depthOffset; + layerDepthValues.b -= MaterialSrg::m_layer3_m_heightmapOffset; } @@ -448,13 +448,13 @@ float3 ApplyBlendMaskToDepthValues(float3 blendMaskValues, float3 layerDepthValu if(o_layer2_enabled) { - float dropoffRange = MaterialSrg::m_layer2_m_depthOffset - zeroMaskDisplacement; + float dropoffRange = MaterialSrg::m_layer2_m_heightmapOffset - zeroMaskDisplacement; layerDepthValues.g += dropoffRange * (1-blendMaskValues.r); } if(o_layer3_enabled) { - float dropoffRange = MaterialSrg::m_layer3_m_depthOffset - zeroMaskDisplacement; + float dropoffRange = MaterialSrg::m_layer3_m_heightmapOffset - zeroMaskDisplacement; layerDepthValues.b += dropoffRange * (1-blendMaskValues.g); } } diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype index ca3e5e1ce4..2d94f66edf 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype @@ -23,26 +23,11 @@ "displayName": "Specular Reflectance f0", "description": "The constant f0 represents the specular reflectance at normal incidence (Fresnel 0 Angle). Used to adjust reflectance of non-metal surfaces." }, - { - "id": "clearCoat", - "displayName": "Clear Coat", - "description": "Properties for configuring gloss clear coat" - }, { "id": "normal", "displayName": "Normal", "description": "Properties related to configuring surface normal." }, - { - "id": "opacity", - "displayName": "Opacity", - "description": "Properties for configuring the materials transparency." - }, - { - "id": "uv", - "displayName": "UVs", - "description": "Properties for configuring UV transforms." - }, { "id": "occlusion", "displayName": "Occlusion", @@ -53,15 +38,25 @@ "displayName": "Emissive", "description": "Properties to add light emission, independent of other lights in the scene." }, + { + "id": "clearCoat", + "displayName": "Clear Coat", + "description": "Properties for configuring gloss clear coat" + }, { "id": "parallax", - "displayName": "Parallax Mapping", - "description": "Properties for parallax effect produced by depthmap." + "displayName": "Displacement", + "description": "Properties for parallax effect produced by a height map." }, { - "id": "subsurfaceScattering", - "displayName": "Subsurface Scattering", - "description": "Properties for configuring subsurface scattering effects." + "id": "opacity", + "displayName": "Opacity", + "description": "Properties for configuring the materials transparency." + }, + { + "id": "uv", + "displayName": "UVs", + "description": "Properties for configuring UV transforms." }, { // Note: this property group is used in the DiffuseGlobalIllumination pass, it is not read by the StandardPBR shader @@ -71,7 +66,7 @@ }, { "id": "general", - "displayName": "General", + "displayName": "General Settings", "description": "General settings." } ], @@ -182,7 +177,7 @@ }, { "id": "textureMap", - "displayName": "Texture Map", + "displayName": "Texture", "description": "Base color texture map", "type": "Image", "connection": { @@ -193,14 +188,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map.", + "description": "Whether to use the texture.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Base color texture map UV set", + "description": "Base color map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -212,7 +207,7 @@ { "id": "textureBlendMode", "displayName": "Texture Blend Mode", - "description": "Selects the equation to use when combining Color, Factor, and Texture Map.", + "description": "Selects the equation to use when combining Color, Factor, and Texture.", "type": "Enum", "enumValues": [ "Multiply", "LinearLight", "Lerp", "Overlay" ], "defaultValue": "Multiply", @@ -238,7 +233,7 @@ }, { "id": "textureMap", - "displayName": "Texture Map", + "displayName": "Texture", "description": "", "type": "Image", "connection": { @@ -249,14 +244,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map, or just default to the Factor value.", + "description": "Whether to use the texture, or just default to the Factor value.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Metallic texture map UV set", + "description": "Metallic map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -269,8 +264,8 @@ "roughness": [ { "id": "textureMap", - "displayName": "Texture Map", - "description": "Texture map for defining surface roughness.", + "displayName": "Texture", + "description": "Texture for defining surface roughness.", "type": "Image", "connection": { "type": "ShaderInput", @@ -280,14 +275,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map, or just default to the Factor value.", + "description": "Whether to use the texture, or just default to the Factor value.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Roughness texture map UV set", + "description": "Roughness map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -300,7 +295,7 @@ // Note that "factor" is mutually exclusive with "lowerBound"/"upperBound". These are swapped by a lua functor. "id": "lowerBound", "displayName": "Lower Bound", - "description": "The roughness value that corresponds to black in the texture map.", + "description": "The roughness value that corresponds to black in the texture.", "type": "Float", "defaultValue": 0.0, "min": 0.0, @@ -314,7 +309,7 @@ // Note that "factor" is mutually exclusive with "lowerBound"/"upperBound". These are swapped by a lua functor. "id": "upperBound", "displayName": "Upper Bound", - "description": "The roughness value that corresponds to white in the texture map.", + "description": "The roughness value that corresponds to white in the texture.", "type": "Float", "defaultValue": 1.0, "min": 0.0, @@ -355,8 +350,8 @@ }, { "id": "textureMap", - "displayName": "Texture Map", - "description": "Texture map for defining surface reflectance.", + "displayName": "Texture", + "description": "Texture for defining surface reflectance.", "type": "Image", "connection": { "type": "ShaderInput", @@ -366,14 +361,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map, or just default to the Factor value.", + "description": "Whether to use the texture, or just default to the Factor value.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Specular reflection texture map UV set", + "description": "Specular reflection map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -418,7 +413,7 @@ { "id": "influenceMap", "displayName": " Influence Map", - "description": "Strength factor texture map", + "description": "Strength factor texture", "type": "Image", "connection": { "type": "ShaderInput", @@ -428,14 +423,14 @@ { "id": "useInfluenceMap", "displayName": " Use Texture", - "description": "Whether to use the texture map, or just default to the Factor value.", + "description": "Whether to use the texture, or just default to the Factor value.", "type": "Bool", "defaultValue": true }, { "id": "influenceMapUv", "displayName": " UV", - "description": "Strength factor texture map UV set", + "description": "Strength factor map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -460,7 +455,7 @@ { "id": "roughnessMap", "displayName": " Roughness Map", - "description": "Roughness texture map", + "description": "Texture for defining surface roughness", "type": "Image", "connection": { "type": "ShaderInput", @@ -470,14 +465,14 @@ { "id": "useRoughnessMap", "displayName": " Use Texture", - "description": "Whether to use the texture map, or just default to the roughness value.", + "description": "Whether to use the texture, or just default to the roughness value.", "type": "Bool", "defaultValue": true }, { "id": "roughnessMapUv", "displayName": " UV", - "description": "Roughness texture map UV set", + "description": "Roughness map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -519,7 +514,7 @@ { "id": "normalMapUv", "displayName": " UV", - "description": "Normal texture map UV set", + "description": "Normal map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -532,8 +527,8 @@ "normal": [ { "id": "textureMap", - "displayName": "Texture Map", - "description": "Texture map for defining surface normal direction.", + "displayName": "Texture", + "description": "Texture for defining surface normal direction.", "type": "Image", "connection": { "type": "ShaderInput", @@ -543,14 +538,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map, or just rely on vertex normals.", + "description": "Whether to use the texture, or just rely on vertex normals.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Normal texture map UV set", + "description": "Normal map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -599,7 +594,7 @@ { "id": "mode", "displayName": "Opacity Mode", - "description": "Opacity mode for this texture.", + "description": "Indicates the general approach how transparency is to be applied.", "type": "Enum", "enumValues": [ "Opaque", "Cutout", "Blended", "TintedTransparent" ], "defaultValue": "Opaque", @@ -611,7 +606,7 @@ { "id": "alphaSource", "displayName": "Alpha Source", - "description": "Source texture of alpha value.", + "description": "Indicates whether to get the opacity texture from the Base Color map (Packed) or from a separate greyscale texture (Split).", "type": "Enum", "enumValues": [ "Packed", "Split", "None" ], "defaultValue": "Packed", @@ -622,8 +617,8 @@ }, { "id": "textureMap", - "displayName": "Texture Map", - "description": "Texture map for defining surface opacity.", + "displayName": "Texture", + "description": "Texture for defining surface opacity.", "type": "Image", "connection": { "type": "ShaderInput", @@ -633,7 +628,7 @@ { "id": "textureMapUv", "displayName": "UV", - "description": "Opacity texture map UV set", + "description": "Opacity map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -728,7 +723,7 @@ { "id": "diffuseTextureMap", "displayName": "Diffuse AO", - "description": "Texture map for defining occlusion area for diffuse ambient lighting.", + "description": "Texture for defining occlusion area for diffuse ambient lighting.", "type": "Image", "connection": { "type": "ShaderInput", @@ -738,14 +733,14 @@ { "id": "diffuseUseTexture", "displayName": " Use Texture", - "description": "Whether to use the Diffuse AO texture map.", + "description": "Whether to use the Diffuse AO map.", "type": "Bool", "defaultValue": true }, { "id": "diffuseTextureMapUv", "displayName": " UV", - "description": "Diffuse AO texture map UV set.", + "description": "Diffuse AO map UV set.", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -770,7 +765,7 @@ { "id": "specularTextureMap", "displayName": "Specular Cavity", - "description": "Texture map for defining occlusion area for specular lighting.", + "description": "Texture for defining occlusion area for specular lighting.", "type": "Image", "connection": { "type": "ShaderInput", @@ -780,14 +775,14 @@ { "id": "specularUseTexture", "displayName": " Use Texture", - "description": "Whether to use the Specular Cavity texture map.", + "description": "Whether to use the Specular Cavity map.", "type": "Bool", "defaultValue": true }, { "id": "specularTextureMapUv", "displayName": " UV", - "description": "Specular Cavity texture map UV set.", + "description": "Specular Cavity map UV set.", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -850,8 +845,8 @@ }, { "id": "textureMap", - "displayName": "Texture Map", - "description": "Texture map for defining emissive area.", + "displayName": "Texture", + "description": "Texture for defining emissive area.", "type": "Image", "connection": { "type": "ShaderInput", @@ -861,14 +856,14 @@ { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map.", + "description": "Whether to use the texture.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Emissive texture map UV set", + "description": "Emissive map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -881,25 +876,25 @@ "parallax": [ { "id": "textureMap", - "displayName": "Texture Map", - "description": "Depthmap to create parallax effect.", + "displayName": "Height Map", + "description": "Displacement height map to create parallax effect.", "type": "Image", "connection": { "type": "ShaderInput", - "id": "m_depthMap" + "id": "m_heightmap" } }, { "id": "useTexture", "displayName": "Use Texture", - "description": "Whether to use the texture map.", + "description": "Whether to use the height map.", "type": "Bool", "defaultValue": true }, { "id": "textureMapUv", "displayName": "UV", - "description": "Depth texture map UV set", + "description": "Height map UV set", "type": "Enum", "enumIsUv": true, "defaultValue": "Tiled", @@ -910,15 +905,15 @@ }, { "id": "factor", - "displayName": "Heightmap Scale", - "description": "The total height of the heightmap in local model units.", + "displayName": "Height Map Scale", + "description": "The total height of the height map in local model units.", "type": "Float", "defaultValue": 0.05, "min": 0.0, "softMax": 0.1, "connection": { "type": "ShaderInput", - "id": "m_depthFactor" + "id": "m_heightmapScale" } }, { @@ -931,18 +926,7 @@ "softMax": 0.1, "connection": { "type": "ShaderInput", - "id": "m_depthOffset" - } - }, - { - "id": "invert", - "displayName": "Invert", - "description": "Invert to depthmap if the texture is heightmap", - "type": "Bool", - "defaultValue": true, - "connection": { - "type": "ShaderInput", - "id": "m_depthInverted" + "id": "m_heightmapOffset" } }, { @@ -983,7 +967,7 @@ { "id": "showClipping", "displayName": "Show Clipping", - "description": "Highlight areas where the heightmap is clipped by the mesh surface.", + "description": "Highlight areas where the height map is clipped by the mesh surface.", "type": "Bool", "defaultValue": false, "connection": { @@ -992,183 +976,6 @@ } } ], - "subsurfaceScattering": [ - { - "id": "enableSubsurfaceScattering", - "displayName": "Subsurface Scattering", - "description": "Enable subsurface scattering feature, this will disable metallic and parallax mapping property due to incompatibility", - "type": "Bool", - "defaultValue": false, - "connection": { - "type": "ShaderOption", - "id": "o_enableSubsurfaceScattering" - } - }, - { - "id": "subsurfaceScatterFactor", - "displayName": " Factor", - "description": "Strength factor for scaling percentage of subsurface scattering effect applied", - "type": "float", - "defaultValue": 1.0, - "min": 0.0, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "id": "m_subsurfaceScatteringFactor" - } - }, - { - "id": "influenceMap", - "displayName": " Influence Map", - "description": "Use texture map to control the strength of subsurface scattering", - "type": "Image", - "connection": { - "type": "ShaderInput", - "id": "m_subsurfaceScatteringInfluenceMap" - } - }, - { - "id": "useInfluenceMap", - "displayName": " Use Influence Map", - "description": "Whether to use the texture map as influence mask.", - "type": "Bool", - "defaultValue": true - }, - { - "id": "influenceMapUv", - "displayName": " UV", - "description": "Influence map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "id": "m_subsurfaceScatteringInfluenceMapUvIndex" - } - }, - { - "id": "scatterColor", - "displayName": " Scatter color", - "description": "Color of volume light traveled through", - "type": "Color", - "defaultValue": [ 1.0, 0.27, 0.13 ] - }, - { - "id": "scatterDistance", - "displayName": " Scatter distance", - "description": "How far light traveled inside the volume", - "type": "float", - "defaultValue": 8, - "min": 0.0, - "softMax": 20.0 - }, - { - "id": "quality", - "displayName": " Quality", - "description": "How much percent of sample will be used for each pixel, more samples improve quality and reduce artifacts, especially when the scatter distance is relatively large, but slow down computation time, 1.0 = full set 200 samples per pixel", - "type": "float", - "defaultValue": 0.4, - "min": 0.2, - "max": 1.0, - "connection": { - "type": "ShaderInput", - "id": "m_subsurfaceScatteringQuality" - } - }, - { - "id": "transmissionMode", - "displayName": "Transmission", - "description": "Algorithm used for calculating transmission", - "type": "Enum", - "enumValues": [ "None", "ThickObject", "ThinObject" ], - "defaultValue": "None", - "connection": { - "type": "ShaderOption", - "id": "o_transmission_mode" - } - }, - { - "id": "thickness", - "displayName": " Thickness", - "description": "Normalized global thickness, the maxima between this value (multiplied by thickness map if enabled) and thickness from shadow map (if applicable) will be used as final thickness of pixel", - "type": "float", - "defaultValue": 0.5, - "min": 0.0, - "max": 1.0 - }, - { - "id": "thicknessMap", - "displayName": " Thickness Map", - "description": "Use a greyscale texture for per pixel thickness", - "type": "Image", - "connection": { - "type": "ShaderInput", - "id": "m_transmissionThicknessMap" - } - }, - { - "id": "useThicknessMap", - "displayName": " Use Thickness Map", - "description": "Whether to use the thickness map", - "type": "Bool", - "defaultValue": true - }, - { - "id": "thicknessMapUv", - "displayName": " UV", - "description": "Thickness map UV set", - "type": "Enum", - "enumIsUv": true, - "defaultValue": "Tiled", - "connection": { - "type": "ShaderInput", - "id": "m_transmissionThicknessMapUvIndex" - } - }, - { - "id": "transmissionTint", - "displayName": " Transmission Tint", - "description": "Color of the volume light travelling through", - "type": "Color", - "defaultValue": [ 1.0, 0.8, 0.6 ] - }, - { - "id": "transmissionPower", - "displayName": " Power", - "description": "How much transmitted light scatter radially ", - "type": "float", - "defaultValue": 6.0, - "min": 0.0, - "softMax": 20.0 - }, - { - "id": "transmissionDistortion", - "displayName": " Distortion", - "description": "How much light direction distorted towards surface normal", - "type": "float", - "defaultValue": 0.1, - "min": 0.0, - "max": 1.0 - }, - { - "id": "transmissionAttenuation", - "displayName": " Attenuation", - "description": "How fast transmitted light fade with thickness", - "type": "float", - "defaultValue": 4.0, - "min": 0.0, - "softMax": 20.0 - }, - { - "id": "transmissionScale", - "displayName": " Scale", - "description": "Strength of transmission", - "type": "float", - "defaultValue": 3.0, - "min": 0.0, - "softMax": 20.0 - } - ], "irradiance": [ // Note: this property group is used in the DiffuseGlobalIllumination pass and not by the main forward shader { @@ -1272,25 +1079,6 @@ "nitMinMax": [0.001, 100000.0] } }, - { - // Preprocess & build parameter set for subsurface scattering and translucency - "type": "HandleSubsurfaceScatteringParameters", - "args": { - "mode": "subsurfaceScattering.transmissionMode", - "scale": "subsurfaceScattering.transmissionScale", - "power": "subsurfaceScattering.transmissionPower", - "distortion": "subsurfaceScattering.transmissionDistortion", - "attenuation": "subsurfaceScattering.transmissionAttenuation", - "tintColor": "subsurfaceScattering.transmissionTint", - "thickness": "subsurfaceScattering.thickness", - "enabled": "subsurfaceScattering.enableSubsurfaceScattering", - "scatterDistanceColor": "subsurfaceScattering.scatterColor", - "scatterDistanceIntensity": "subsurfaceScattering.scatterDistance", - "scatterDistanceShaderInput": "m_scatterDistance", - "parametersShaderInput": "m_transmissionParams", - "tintThickenssShaderInput": "m_transmissionTintThickness" - } - }, { "type": "UseTexture", "args": { @@ -1375,12 +1163,6 @@ "file": "StandardPBR_Roughness.lua" } }, - { - "type": "Lua", - "args": { - "file": "StandardPBR_SubsurfaceState.lua" - } - }, { "type": "Lua", "args": { diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_Common.azsli b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_Common.azsli index 5723a6cd1e..f339642a4d 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_Common.azsli +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_Common.azsli @@ -73,31 +73,12 @@ ShaderResourceGroup MaterialSrg : SRG_PerMaterial MagFilter = Linear; MipFilter = Linear; }; - - // Parameters for subsurface scattering - float m_subsurfaceScatteringFactor; - float m_subsurfaceScatteringQuality; - float3 m_scatterDistance; - Texture2D m_subsurfaceScatteringInfluenceMap; - uint m_subsurfaceScatteringInfluenceMapUvIndex; - - // Parameters for transmission - - // Elements of m_transmissionParams: - // Thick object mode: (attenuation coefficient, power, distortion, scale) - // Thin object mode: (float3 scatter distance, scale) - float4 m_transmissionParams; - - // (float3 TintColor, thickness) - float4 m_transmissionTintThickness; - Texture2D m_transmissionThicknessMap; - uint m_transmissionThicknessMapUvIndex; } // Callback function for ParallaxMapping.azsli DepthResult GetDepth(float2 uv, float2 uv_ddx, float2 uv_ddy) { - return SampleDepthOrHeightMap(MaterialSrg::m_depthInverted, MaterialSrg::m_depthMap, MaterialSrg::m_sampler, uv, uv_ddx, uv_ddy); + return SampleDepthFromHeightmap(MaterialSrg::m_heightmap, MaterialSrg::m_sampler, uv, uv_ddx, uv_ddy); } @@ -106,7 +87,7 @@ COMMON_OPTIONS_PARALLAX() bool ShouldHandleParallax() { // Parallax mapping's non uniform uv transformations break screen space subsurface scattering, disable it when subsurface scattering is enabled. - return !o_enableSubsurfaceScattering && o_parallax_feature_enabled && o_useDepthMap; + return !o_enableSubsurfaceScattering && o_parallax_feature_enabled && o_useHeightmap; } bool ShouldHandleParallaxInDepthShaders() diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_DepthPass_WithPS.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_DepthPass_WithPS.azsl index afc93f060e..cc2b4ce659 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_DepthPass_WithPS.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_DepthPass_WithPS.azsl @@ -86,7 +86,7 @@ PSDepthOutput MainPS(VSDepthOutput IN, bool isFrontFace : SV_IsFrontFace) float3x3 uvMatrix = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3(); float3x3 uvMatrixInverse = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrixInverse : CreateIdentity3x3(); - GetParallaxInput(IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex], MaterialSrg::m_depthFactor, MaterialSrg::m_depthOffset, + GetParallaxInput(IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex], MaterialSrg::m_heightmapScale, MaterialSrg::m_heightmapOffset, ObjectSrg::GetWorldMatrix(), uvMatrix, uvMatrixInverse, IN.m_uv[MaterialSrg::m_parallaxUvIndex], IN.m_worldPosition, OUT.m_depth); } diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.azsl index 10fa3814f3..b221ee0a33 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ForwardPass.azsl @@ -47,13 +47,6 @@ COMMON_OPTIONS_EMISSIVE() // Alpha #include "MaterialInputs/AlphaInput.azsli" -// Subsurface -#include "MaterialInputs/SubsurfaceInput.azsli" - -// Transmission -#include "MaterialInputs/TransmissionInput.azsli" - - // ---------- Vertex Shader ---------- struct VSInput @@ -113,7 +106,7 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float float3 tangents[UvSetCount] = { IN.m_tangent.xyz, IN.m_tangent.xyz }; float3 bitangents[UvSetCount] = { IN.m_bitangent.xyz, IN.m_bitangent.xyz }; - if ((o_parallax_feature_enabled && !o_enableSubsurfaceScattering) || o_normal_useTexture || (o_clearCoat_enabled && o_clearCoat_normal_useTexture)) + if (ShouldHandleParallax() || o_normal_useTexture || (o_clearCoat_enabled && o_clearCoat_normal_useTexture)) { PrepareGeneratedTangent(IN.m_normal, IN.m_worldPosition, isFrontFace, IN.m_uv, UvSetCount, tangents, bitangents); } @@ -124,13 +117,12 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float bool displacementIsClipped = false; - // Parallax mapping's non uniform uv transformations break screen space subsurface scattering, disable it when subsurface scatteirng is enabled if(ShouldHandleParallax()) { float3x3 uvMatrix = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3(); float3x3 uvMatrixInverse = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrixInverse : CreateIdentity3x3(); - GetParallaxInput(IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex], MaterialSrg::m_depthFactor, MaterialSrg::m_depthOffset, + GetParallaxInput(IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex], MaterialSrg::m_heightmapScale, MaterialSrg::m_heightmapOffset, ObjectSrg::GetWorldMatrix(), uvMatrix, uvMatrixInverse, IN.m_uv[MaterialSrg::m_parallaxUvIndex], IN.m_worldPosition, depthNDC, IN.m_position.w, displacementIsClipped); @@ -174,12 +166,8 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float // ------- Metallic ------- - float metallic = 0; - if(!o_enableSubsurfaceScattering) // If subsurface scattering is enabled skip texture lookup for metallic, as this quantity won't be used anyway - { - float2 metallicUv = IN.m_uv[MaterialSrg::m_metallicMapUvIndex]; - metallic = GetMetallicInput(MaterialSrg::m_metallicMap, MaterialSrg::m_sampler, metallicUv, MaterialSrg::m_metallicFactor, o_metallic_useTexture); - } + float2 metallicUv = IN.m_uv[MaterialSrg::m_metallicMapUvIndex]; + float metallic = GetMetallicInput(MaterialSrg::m_metallicMap, MaterialSrg::m_sampler, metallicUv, MaterialSrg::m_metallicFactor, o_metallic_useTexture); // ------- Specular ------- @@ -195,11 +183,6 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float MaterialSrg::m_roughnessLowerBound, MaterialSrg::m_roughnessUpperBound, o_roughness_useTexture); surface.CalculateRoughnessA(); - // ------- Subsurface ------- - - float surfaceScatteringFactor = 0.0f; - surface.transmission.InitializeToZero(); - // ------- Lighting Data ------- LightingData lightingData; @@ -271,7 +254,7 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float ApplyIBL(surface, lightingData); // Finalize Lighting - lightingData.FinalizeLighting(surface.transmission.tint); + lightingData.FinalizeLighting(); if (o_opacity_mode == OpacityMode::Blended || o_opacity_mode == OpacityMode::TintedTransparent) @@ -312,13 +295,6 @@ PbrLightingOutput ForwardPassPS_Common(VSOutput IN, bool isFrontFace, out float lightingOutput.m_diffuseColor.rgb += lightingOutput.m_specularColor.rgb; // add specular lightingOutput.m_specularColor.rgb = baseColor * (1.0 - lightingOutput.m_diffuseColor.w); } - else - { - // Pack factor and quality, drawback: because of precision limit of float16 cannot represent exact 1, maximum representable value is 0.9961 - uint factorAndQuality = dot(round(float2(saturate(surfaceScatteringFactor), MaterialSrg::m_subsurfaceScatteringQuality) * 255), float2(256, 1)); - lightingOutput.m_diffuseColor.w = factorAndQuality * (o_enableSubsurfaceScattering ? 1.0 : -1.0); - lightingOutput.m_scatterDistance = MaterialSrg::m_scatterDistance; - } return lightingOutput; } diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ParallaxState.lua b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ParallaxState.lua index 53d6334f28..771726aea7 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ParallaxState.lua +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_ParallaxState.lua @@ -17,7 +17,7 @@ function GetMaterialPropertyDependencies() end function GetShaderOptionDependencies() - return {"o_parallax_feature_enabled", "o_useDepthMap"} + return {"o_parallax_feature_enabled", "o_useHeightmap"} end function Process(context) @@ -25,7 +25,7 @@ function Process(context) local useTexture = context:GetMaterialPropertyValue_bool("parallax.useTexture") local enable = textureMap ~= nil and useTexture context:SetShaderOptionValue_bool("o_parallax_feature_enabled", enable) - context:SetShaderOptionValue_bool("o_useDepthMap", enable) + context:SetShaderOptionValue_bool("o_useHeightmap", enable) end function ProcessEditor(context) diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_Shadowmap_WithPS.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_Shadowmap_WithPS.azsl index 533df3bb92..8b6fee849e 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_Shadowmap_WithPS.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR_Shadowmap_WithPS.azsl @@ -88,7 +88,7 @@ PSDepthOutput MainPS(VertexOutput IN, bool isFrontFace : SV_IsFrontFace) float3x3 uvMatrix = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrix : CreateIdentity3x3(); float3x3 uvMatrixInverse = MaterialSrg::m_parallaxUvIndex == 0 ? MaterialSrg::m_uvMatrixInverse : CreateIdentity3x3(); - GetParallaxInput(IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex], MaterialSrg::m_depthFactor, MaterialSrg::m_depthOffset, + GetParallaxInput(IN.m_normal, tangents[MaterialSrg::m_parallaxUvIndex], bitangents[MaterialSrg::m_parallaxUvIndex], MaterialSrg::m_heightmapScale, MaterialSrg::m_heightmapOffset, ObjectSrg::GetWorldMatrix(), uvMatrix, uvMatrixInverse, IN.m_uv[MaterialSrg::m_parallaxUvIndex], IN.m_worldPosition, OUT.m_depth); diff --git a/Gems/Atom/Feature/Common/Assets/Passes/LowEndPipeline.pass b/Gems/Atom/Feature/Common/Assets/Passes/LowEndPipeline.pass index b19569fb9d..38a4524aa2 100644 --- a/Gems/Atom/Feature/Common/Assets/Passes/LowEndPipeline.pass +++ b/Gems/Atom/Feature/Common/Assets/Passes/LowEndPipeline.pass @@ -243,6 +243,13 @@ "Attachment": "LightListRemapped" } }, + { + "LocalSlot": "InputLinearDepth", + "AttachmentRef": { + "Pass": "DepthPrePass", + "Attachment": "DepthLinear" + } + }, { "LocalSlot": "DepthStencil", "AttachmentRef": { diff --git a/Gems/Atom/Feature/Common/Assets/Passes/MainPipeline.pass b/Gems/Atom/Feature/Common/Assets/Passes/MainPipeline.pass index ee943f6d39..af7408b48c 100644 --- a/Gems/Atom/Feature/Common/Assets/Passes/MainPipeline.pass +++ b/Gems/Atom/Feature/Common/Assets/Passes/MainPipeline.pass @@ -251,6 +251,13 @@ "Attachment": "LightListRemapped" } }, + { + "LocalSlot": "InputLinearDepth", + "AttachmentRef": { + "Pass": "DepthPrePass", + "Attachment": "DepthLinear" + } + }, { "LocalSlot": "DepthStencil", "AttachmentRef": { diff --git a/Gems/Atom/Feature/Common/Assets/Passes/Transparent.pass b/Gems/Atom/Feature/Common/Assets/Passes/Transparent.pass index 415aa2fec0..3bb184a1f8 100644 --- a/Gems/Atom/Feature/Common/Assets/Passes/Transparent.pass +++ b/Gems/Atom/Feature/Common/Assets/Passes/Transparent.pass @@ -67,6 +67,12 @@ "ShaderInputName": "m_lightListRemapped", "ScopeAttachmentUsage": "Shader" }, + { + "Name": "InputLinearDepth", + "SlotType": "Input", + "ShaderInputName": "m_linearDepthTexture", + "ScopeAttachmentUsage": "Shader" + }, // Input/Outputs { "Name": "DepthStencil", diff --git a/Gems/Atom/Feature/Common/Assets/Passes/TransparentParent.pass b/Gems/Atom/Feature/Common/Assets/Passes/TransparentParent.pass index 32517484f3..b278f2bcb4 100644 --- a/Gems/Atom/Feature/Common/Assets/Passes/TransparentParent.pass +++ b/Gems/Atom/Feature/Common/Assets/Passes/TransparentParent.pass @@ -32,6 +32,10 @@ "Name": "LightListRemapped", "SlotType": "Input" }, + { + "Name": "InputLinearDepth", + "SlotType": "Input" + }, // Input/Outputs... { "Name": "DepthStencil", @@ -91,6 +95,13 @@ "Attachment": "LightListRemapped" } }, + { + "LocalSlot": "InputLinearDepth", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "InputLinearDepth" + } + }, // Input/Outputs... { "LocalSlot": "DepthStencil", diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lighting/LightingData.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lighting/LightingData.azsli index 37cae0acb1..e37aa55c09 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lighting/LightingData.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lighting/LightingData.azsli @@ -54,6 +54,7 @@ class LightingData void Init(float3 positionWS, float3 normal, float roughnessLinear); void CalculateMultiscatterCompensation(float3 specularF0, bool enabled); + void FinalizeLighting(); void FinalizeLighting(float3 transmissionTint); }; @@ -80,10 +81,15 @@ void LightingData::CalculateMultiscatterCompensation(float3 specularF0, bool ena multiScatterCompensation = GetMultiScatterCompensation(specularF0, brdf, enabled); } -void LightingData::FinalizeLighting(float3 transmissionTint) +void LightingData::FinalizeLighting() { specularLighting *= specularOcclusion; specularLighting += emissiveLighting; +} + +void LightingData::FinalizeLighting(float3 transmissionTint) +{ + FinalizeLighting(); // Transmitted light if(o_transmission_mode != TransmissionMode::None) diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lighting/StandardLighting.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lighting/StandardLighting.azsli index 45aabeede4..f690163a58 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lighting/StandardLighting.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lighting/StandardLighting.azsli @@ -75,7 +75,6 @@ struct PbrLightingOutput float4 m_albedo; float4 m_specularF0; float4 m_normal; - float3 m_scatterDistance; }; diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Surfaces/StandardSurface.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Surfaces/StandardSurface.azsli index 1a74a68e96..bc4e41d2c2 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Surfaces/StandardSurface.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Surfaces/StandardSurface.azsli @@ -20,7 +20,7 @@ class Surface { ClearCoatSurfaceData clearCoat; - TransmissionSurfaceData transmission; + TransmissionSurfaceData transmission; // This is not actually used for Standard PBR, but must be present for common lighting code to compile // ------- BasePbrSurfaceData ------- diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/TransparentPassSrg.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/TransparentPassSrg.azsli index 14cff21739..d9367f9d03 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/TransparentPassSrg.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/TransparentPassSrg.azsli @@ -35,4 +35,5 @@ ShaderResourceGroup PassSrg : SRG_PerPass Texture2D m_tileLightData; StructuredBuffer m_lightListRemapped; + Texture2D m_linearDepthTexture; } diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/ParallaxMapping.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/ParallaxMapping.azsli index 8b1efc8eea..ff2a37d29b 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/ParallaxMapping.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/ParallaxMapping.azsli @@ -58,7 +58,7 @@ DepthResult DepthResultAbsolute(float depth) //! The client shader must define this function. //! This allows the client shader to implement special depth map sampling, for example procedurally generating or blending depth maps. -//! In simple cases though, the implementation of GetDepth() can simply call SampleDepthOrHeightMap(). +//! In simple cases though, the implementation of GetDepth() can simply call SampleDepthFromHeightmap(). //! @param uv the UV coordinates to use for sampling //! @param uv_ddx will be set to ddx_fine(uv) //! @param uv_ddy will be set to ddy_fine(uv) @@ -66,13 +66,12 @@ DepthResult DepthResultAbsolute(float depth) DepthResult GetDepth(float2 uv, float2 uv_ddx, float2 uv_ddy); //! Convenience function that can be used to implement GetDepth(). -//! @param isHeightmap indicates whether to sample the map is a height map rather than a depth map. //! @return see struct DepthResult. In this case it will always contain a Code::Normalized result. -DepthResult SampleDepthOrHeightMap(bool isHeightmap, Texture2D map, sampler mapSampler, float2 uv, float2 uv_ddx, float2 uv_ddy) +DepthResult SampleDepthFromHeightmap(Texture2D map, sampler mapSampler, float2 uv, float2 uv_ddx, float2 uv_ddy) { DepthResult result; result.m_resultCode = DepthResultCode_Normalized; - result.m_depth = abs((isHeightmap * 1.0) - map.SampleGrad(mapSampler, uv, uv_ddx, uv_ddy).r); + result.m_depth = 1.0 - map.SampleGrad(mapSampler, uv, uv_ddx, uv_ddy).r; return result; } @@ -169,20 +168,20 @@ ParallaxOffset AdvancedParallaxMapping(float depthFactor, float depthOffset, flo float2 ddx_uv = ddx_fine(uv); float2 ddy_uv = ddy_fine(uv); - float depthSearchStart = -depthOffset; + float depthSearchStart = depthOffset; float depthSearchEnd = depthSearchStart + depthFactor; float inverseDepthFactor = 1.0 / depthFactor; // This is the relative position at which we begin searching for intersection. // It is adjusted according to the depthOffset, raising or lowering the whole surface by depthOffset units. - float3 parallaxOffset = dirToCameraTS.xyz * dirToCameraZInverse * depthOffset; + float3 parallaxOffset = -dirToCameraTS.xyz * dirToCameraZInverse * depthOffset; // Get an initial heightmap sample to start the intersection search, starting at our initial parallaxOffset position. float currentSample = GetNormalizedDepth(depthSearchStart, depthSearchEnd, inverseDepthFactor, uv + parallaxOffset.xy, ddx_uv, ddy_uv); float prevSample; - // Note that when depthOffset < 0, we could actually narrow the search so that instead of going through the entire [depthSearchStart,depthSearchEnd] range + // Note that when depthOffset > 0, we could actually narrow the search so that instead of going through the entire [depthSearchStart,depthSearchEnd] range // of the heightmap, we could go through the range [0,depthSearchEnd]. This would give more accurate results and fewer artifacts // in case where the magnitude of depthOffset is significant. But for the sake of simplicity we currently search the whole range in all cases. @@ -271,7 +270,7 @@ ParallaxOffset AdvancedParallaxMapping(float depthFactor, float depthOffset, flo } // Even though we do a bunch of clamping above when calling GetClampedDepth(), there are still cases where the parallax offset - // can be noticeably above the surface and still needs to be clamped here. The main case is when depthFactor==0 and depthOffset>1. + // can be noticeably above the surface and still needs to be clamped here. The main case is when depthFactor==0 and depthOffset<1. if(parallaxOffset.z > 0.0) { parallaxOffset = float3(0,0,0); @@ -371,13 +370,13 @@ ParallaxOffset CalculateParallaxOffset(float depthFactor, float depthOffset, flo // @param dirToCameraTS - normalized direction to the camera, in tangent space. // @param dirToLightTS - normalized direction to a light source, in tangent space, for self-shadowing (if enabled via o_parallax_shadow). ParallaxOffset GetParallaxOffset( float depthFactor, - float depthOffset, - float2 uv, - float3 dirToCameraWS, - float3 tangentWS, - float3 bitangentWS, - float3 normalWS, - float3x3 uvMatrix) + float depthOffset, + float2 uv, + float3 dirToCameraWS, + float3 tangentWS, + float3 bitangentWS, + float3 normalWS, + float3x3 uvMatrix) { // Tangent space eye vector float3 dirToCameraTS = normalize(WorldSpaceToTangent(dirToCameraWS, normalWS, tangentWS, bitangentWS)); diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/RayTracing/RayTracingMaterialSrg.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/RayTracing/RayTracingMaterialSrg.azsli new file mode 100644 index 0000000000..40faba24f9 --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/RayTracing/RayTracingMaterialSrg.azsli @@ -0,0 +1,48 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#include + +ShaderResourceGroup RayTracingMaterialSrg : SRG_RayTracingMaterial +{ + Sampler LinearSampler + { + AddressU = Wrap; + AddressV = Wrap; + MinFilter = Linear; + MagFilter = Linear; + MipFilter = Linear; + MaxAnisotropy = 16; + }; + + // material info structured buffer + struct MaterialInfo + { + float4 m_baseColor; + float m_metallicFactor; + float m_roughnessFactor; + uint m_textureFlags; + uint m_textureStartIndex; + }; + + // hit shaders can retrieve the MaterialInfo for a mesh hit using: RayTracingMaterialSrg::m_materialInfo[InstanceIndex()] + StructuredBuffer m_materialInfo; + + // texture flag bits indicating if optional textures are present + #define TEXTURE_FLAG_BASECOLOR 1 + #define TEXTURE_FLAG_NORMAL 2 + #define TEXTURE_FLAG_METALLIC 4 + #define TEXTURE_FLAG_ROUGHNESS 8 + + // unbounded array of Material textures + Texture2D m_materialTextures[]; +} \ No newline at end of file diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/RayTracing/RayTracingMaterialUtils.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/RayTracing/RayTracingMaterialUtils.azsli new file mode 100644 index 0000000000..d6dd77f4fa --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/RayTracing/RayTracingMaterialUtils.azsli @@ -0,0 +1,69 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +struct TextureData +{ + float4 m_baseColor; + float3 m_normal; + float m_metallic; + float m_roughness; +}; + +TextureData GetHitTextureData(RayTracingMaterialSrg::MaterialInfo materialInfo, float2 uv) +{ + TextureData textureData = (TextureData)0; + + uint textureIndex = materialInfo.m_textureStartIndex; + + // base color + if (materialInfo.m_textureFlags & TEXTURE_FLAG_BASECOLOR) + { + textureData.m_baseColor = RayTracingMaterialSrg::m_materialTextures[textureIndex++].SampleLevel(RayTracingMaterialSrg::LinearSampler, uv, 0); + } + else + { + textureData.m_baseColor = materialInfo.m_baseColor; + } + + // normal + if (materialInfo.m_textureFlags & TEXTURE_FLAG_NORMAL) + { + textureData.m_normal = RayTracingMaterialSrg::m_materialTextures[textureIndex++].SampleLevel(RayTracingMaterialSrg::LinearSampler, uv, 0); + } + else + { + textureData.m_normal = float3(0.0f, 0.0f, 1.0f); + } + + // metallic + if (materialInfo.m_textureFlags & TEXTURE_FLAG_METALLIC) + { + textureData.m_metallic = RayTracingMaterialSrg::m_materialTextures[textureIndex++].SampleLevel(RayTracingMaterialSrg::LinearSampler, uv, 0); + } + else + { + textureData.m_metallic = materialInfo.m_metallicFactor; + } + + // roughness + if (materialInfo.m_textureFlags & TEXTURE_FLAG_ROUGHNESS) + { + textureData.m_roughness = RayTracingMaterialSrg::m_materialTextures[textureIndex++].SampleLevel(RayTracingMaterialSrg::LinearSampler, uv, 0); + } + else + { + textureData.m_roughness = materialInfo.m_roughnessFactor; + } + + return textureData; +} + \ No newline at end of file diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/RayTracing/RayTracingSceneSrg.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/RayTracing/RayTracingSceneSrg.azsli index fdf7ba92de..b8c97ef421 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/RayTracing/RayTracingSceneSrg.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/RayTracing/RayTracingSceneSrg.azsli @@ -136,18 +136,35 @@ ShaderResourceGroup RayTracingSceneSrg : SRG_RayTracingScene uint m_indexOffset; uint m_positionOffset; uint m_normalOffset; + uint m_tangentOffset; + uint m_bitangentOffset; + uint m_uvOffset; + float m_padding0[2]; + float4 m_irradianceColor; float3x3 m_worldInvTranspose; + float m_padding1; + + uint m_bufferFlags; + uint m_bufferStartIndex; }; - + + // hit shaders can retrieve the MeshInfo for a mesh hit using: RayTracingSceneSrg::m_meshInfo[InstanceIndex()] StructuredBuffer m_meshInfo; - // unbounded array of Index, VertexPosition, and VertexNormal buffers - // each mesh has three entries in this array starting at its InstanceIndex() * BUFFER_COUNT_PER_MESH - #define BUFFER_COUNT_PER_MESH 3 - #define MESH_INDEX_BUFFER_OFFSET 0 - #define MESH_POSITION_BUFFER_OFFSET 1 - #define MESH_NORMAL_BUFFER_OFFSET 2 - + // buffer array index offsets for buffers that are always present for each mesh + #define MESH_INDEX_BUFFER_OFFSET 0 + #define MESH_POSITION_BUFFER_OFFSET 1 + #define MESH_NORMAL_BUFFER_OFFSET 2 + #define MESH_TANGENT_BUFFER_OFFSET 3 + #define MESH_BITANGENT_BUFFER_OFFSET 4 + + // buffer flag bits indicating if optional buffers are present + #define MESH_BUFFER_FLAG_UV 1 + + // Unbounded array of mesh stream buffers: + // - Index, Position, Normal, Tangent, and Bitangent stream buffers are always present + // - Optional stream buffers such as UV are indicated in the MeshInfo.m_bufferFlags field + // - Buffers for a particular mesh start at MeshInfo.m_bufferStartIndex ByteAddressBuffer m_meshBuffers[]; } \ No newline at end of file diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/RayTracing/RayTracingSceneUtils.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/RayTracing/RayTracingSceneUtils.azsli new file mode 100644 index 0000000000..f858b9a11a --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/RayTracing/RayTracingSceneUtils.azsli @@ -0,0 +1,126 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +// returns the normalized camera view ray into the scene for this raytracing dispatch thread +float3 GetViewRayDirection(float4x4 viewProjectionInverseMatrix) +{ + float2 pixel = ((float2)DispatchRaysIndex().xy + float2(0.5f, 0.5f)) / (float2)DispatchRaysDimensions(); + float2 ndc = pixel * float2(2.0f, -2.0f) + float2(-1.0f, 1.0f); + return normalize(mul(viewProjectionInverseMatrix, float4(ndc, 0.0f, 1.0f)).xyz); +} + +// returns the vertex indices for the primitive hit by the ray +// Note: usable only in a raytracing Hit shader +uint3 GetHitIndices(RayTracingSceneSrg::MeshInfo meshInfo) +{ + // compute the array index of the index buffer for this mesh in the m_meshBuffers unbounded array + uint meshIndexBufferArrayIndex = meshInfo.m_bufferStartIndex + MESH_INDEX_BUFFER_OFFSET; + + // compute the offset into the index buffer for this primitve of the mesh + uint offsetBytes = meshInfo.m_indexOffset + (PrimitiveIndex() * 12); + + // load the indices for this primitive from the index buffer + return RayTracingSceneSrg::m_meshBuffers[meshIndexBufferArrayIndex].Load3(offsetBytes); +} + +// returns the interpolated vertex data for the primitive hit by the ray +// Note: usable only in a raytracing hit shader +struct VertexData +{ + float3 m_position; + float3 m_normal; + float3 m_tangent; + float3 m_bitangent; + float2 m_uv; +}; + +VertexData GetHitInterpolatedVertexData(RayTracingSceneSrg::MeshInfo meshInfo, float2 builtInBarycentrics) +{ + // retrieve the poly indices + uint3 indices = GetHitIndices(meshInfo); + + // compute barycentrics + float3 barycentrics = float3((1.0f - builtInBarycentrics.x - builtInBarycentrics.y), builtInBarycentrics.x, builtInBarycentrics.y); + + // compute the vertex data using barycentric interpolation + VertexData vertexData = (VertexData)0; + for (uint i = 0; i < 3; ++i) + { + // position + { + // array index of the position buffer for this mesh in the m_meshBuffers unbounded array + uint meshVertexPositionArrayIndex = meshInfo.m_bufferStartIndex + MESH_POSITION_BUFFER_OFFSET; + + // offset into the position buffer for this vertex + uint positionOffset = meshInfo.m_positionOffset + (indices[i] * 12); + + // load the position data + vertexData.m_position += asfloat(RayTracingSceneSrg::m_meshBuffers[meshVertexPositionArrayIndex].Load3(positionOffset)) * barycentrics[i]; + } + + // normal + { + // array index of the normal buffer for this mesh in the m_meshBuffers unbounded array + uint meshVertexNormalArrayIndex = meshInfo.m_bufferStartIndex + MESH_NORMAL_BUFFER_OFFSET; + + // offset into the normal buffer for this vertex + uint normalOffset = meshInfo.m_normalOffset + (indices[i] * 12); + + // load the normal data + vertexData.m_normal += asfloat(RayTracingSceneSrg::m_meshBuffers[meshVertexNormalArrayIndex].Load3(normalOffset)) * barycentrics[i]; + } + + // tangent + { + // array index of the tangent buffer for this mesh in the m_meshBuffers unbounded array + uint meshVertexTangentArrayIndex = meshInfo.m_bufferStartIndex + MESH_TANGENT_BUFFER_OFFSET; + + // offset into the tangent buffer for this vertex + uint tangentOffset = meshInfo.m_tangentOffset + (indices[i] * 12); + + // load the tangent data + vertexData.m_tangent += asfloat(RayTracingSceneSrg::m_meshBuffers[meshVertexTangentArrayIndex].Load3(tangentOffset)) * barycentrics[i]; + } + + // bitangent + { + // array index of the bitangent buffer for this mesh in the m_meshBuffers unbounded array + uint meshVertexBitangentArrayIndex = meshInfo.m_bufferStartIndex + MESH_BITANGENT_BUFFER_OFFSET; + + // offset into the bitangent buffer for this vertex + uint bitangentOffset = meshInfo.m_bitangentOffset + (indices[i] * 12); + + // load the bitangent data + vertexData.m_bitangent += asfloat(RayTracingSceneSrg::m_meshBuffers[meshVertexBitangentArrayIndex].Load3(bitangentOffset)) * barycentrics[i]; + } + + // optional streams begin after MESH_BITANGENT_BUFFER_OFFSET + uint optionalBufferOffset = MESH_BITANGENT_BUFFER_OFFSET + 1; + + // UV + if (meshInfo.m_bufferFlags & MESH_BUFFER_FLAG_UV) + { + // array index of the UV buffer for this mesh in the m_meshBuffers unbounded array + uint meshVertexUVArrayIndex = meshInfo.m_bufferStartIndex + optionalBufferOffset++; + + // offset into the UV buffer for this vertex + uint uvOffset = meshInfo.m_uvOffset + (indices[i] * 8); + + // load the UV data + vertexData.m_uv += asfloat(RayTracingSceneSrg::m_meshBuffers[meshVertexUVArrayIndex].Load2(uvOffset)) * barycentrics[i]; + } + } + + vertexData.m_normal = normalize(vertexData.m_normal); + + return vertexData; +} \ No newline at end of file diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/SrgSemantics.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/SrgSemantics.azsli index c134a9d293..6d1b05d797 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/SrgSemantics.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/SrgSemantics.azsli @@ -64,3 +64,8 @@ ShaderResourceGroupSemantic SRG_RayTracingScene { FrequencyId = 1; }; + +ShaderResourceGroupSemantic SRG_RayTracingMaterial +{ + FrequencyId = 2; +}; diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracing.azshader b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracing.azshader index 804b534277..746ae357ee 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 b3bee56bfd..841c5defe3 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 d9b2f17848..39eacb4ade 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 cf39160d3f..597b239218 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 c4a83a26dc..0f42cd04d6 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 fb5b740f04..49bc94767a 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 b7bdabeb4e..d9d30c99f7 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 1eeeffa8be..e2f2a13ca3 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/diffuseprobegridraytracingcommon_raytracingglobalsrg.azsrg b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingcommon_raytracingglobalsrg.azsrg index c9fa0f4e1c..35d7050294 100644 --- a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingcommon_raytracingglobalsrg.azsrg +++ b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingcommon_raytracingglobalsrg.azsrg @@ -44,8 +44,14 @@ "field": "element", "typeName": "ShaderResourceGroupLayout", "typeId": "{1F92C651-9B83-4379-AB5C-5201F1B2C278}", - "version": 6, + "version": 7, "Objects": [ + { + "field": "m_name", + "typeName": "Name", + "typeId": "{3D2B920C-9EFD-40D5-AAE0-DF131C3D4931}", + "value": "" + }, { "field": "m_staticSamplers", "typeName": "AZStd::vector", @@ -3338,8 +3344,14 @@ "field": "element", "typeName": "ShaderResourceGroupLayout", "typeId": "{1F92C651-9B83-4379-AB5C-5201F1B2C278}", - "version": 6, + "version": 7, "Objects": [ + { + "field": "m_name", + "typeName": "Name", + "typeId": "{3D2B920C-9EFD-40D5-AAE0-DF131C3D4931}", + "value": "" + }, { "field": "m_staticSamplers", "typeName": "AZStd::vector", @@ -6632,8 +6644,14 @@ "field": "element", "typeName": "ShaderResourceGroupLayout", "typeId": "{1F92C651-9B83-4379-AB5C-5201F1B2C278}", - "version": 6, + "version": 7, "Objects": [ + { + "field": "m_name", + "typeName": "Name", + "typeId": "{3D2B920C-9EFD-40D5-AAE0-DF131C3D4931}", + "value": "" + }, { "field": "m_staticSamplers", "typeName": "AZStd::vector", diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingmiss.azshader b/Gems/Atom/Feature/Common/Assets/Shaders/DiffuseGlobalIllumination/diffuseprobegridraytracingmiss.azshader index 4f0cea1bfe..77a0f94f97 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 839efa7278..c144350c7f 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 5f08825487..c369381fe9 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 a5b0e842ef..13fd579d9b 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/Textures/sampleEnvironment/PaperMill_E_3k.exr b/Gems/Atom/Feature/Common/Assets/Textures/sampleEnvironment/PaperMill_E_3k.exr deleted file mode 100644 index 0fcbcc4746..0000000000 --- a/Gems/Atom/Feature/Common/Assets/Textures/sampleEnvironment/PaperMill_E_3k.exr +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bc9981393c88c6d30a0a5a6837e6f6246a9f550042b3c4be39dd34e479b90569 -size 16793931 diff --git a/Gems/Atom/Feature/Common/Assets/Textures/sampleEnvironment/PaperMill_E_3k.exr.assetinfo b/Gems/Atom/Feature/Common/Assets/Textures/sampleEnvironment/PaperMill_E_3k.exr.assetinfo deleted file mode 100644 index 16cb0dd668..0000000000 --- a/Gems/Atom/Feature/Common/Assets/Textures/sampleEnvironment/PaperMill_E_3k.exr.assetinfo +++ /dev/null @@ -1,69 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Gems/Atom/Feature/Common/Assets/Textures/sampleEnvironment/exampleBrdf_lut.dds b/Gems/Atom/Feature/Common/Assets/Textures/sampleEnvironment/exampleBrdf_lut.dds deleted file mode 100644 index f2b2ce550d..0000000000 --- a/Gems/Atom/Feature/Common/Assets/Textures/sampleEnvironment/exampleBrdf_lut.dds +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:440bcb1579d4ad667c040bda914ed3121980526a94f23879a0c482c799fd5132 -size 1310848 diff --git a/Gems/Atom/Feature/Common/Assets/Textures/sampleEnvironment/exampleDiffuseHDR_cm.dds b/Gems/Atom/Feature/Common/Assets/Textures/sampleEnvironment/exampleDiffuseHDR_cm.dds deleted file mode 100644 index 9585c25dd1..0000000000 --- a/Gems/Atom/Feature/Common/Assets/Textures/sampleEnvironment/exampleDiffuseHDR_cm.dds +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cf4b22481726214c062ac27fb2d9d8a49e760a76d8fff03330ff9c5f9275a8da -size 1966208 diff --git a/Gems/Atom/Feature/Common/Assets/Textures/sampleEnvironment/exampleSpecularHDR_cm.dds b/Gems/Atom/Feature/Common/Assets/Textures/sampleEnvironment/exampleSpecularHDR_cm.dds deleted file mode 100644 index a35fac45fb..0000000000 --- a/Gems/Atom/Feature/Common/Assets/Textures/sampleEnvironment/exampleSpecularHDR_cm.dds +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8b4c99faffc34988c268613948f2004f40e9bd51de915461a4cb74edc5e8bae6 -size 134217920 diff --git a/Gems/Atom/Feature/Common/Assets/Textures/sampleEnvironment/example_iblskyboxcm.dds b/Gems/Atom/Feature/Common/Assets/Textures/sampleEnvironment/example_iblskyboxcm.dds deleted file mode 100644 index a89dfdbd3d..0000000000 --- a/Gems/Atom/Feature/Common/Assets/Textures/sampleEnvironment/example_iblskyboxcm.dds +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f7edea42ded8143764654f12c19e0c9b74c74afb21f435ebb59c0a4d203892a3 -size 536871104 diff --git a/Gems/Atom/Feature/Common/Assets/Textures/sampleEnvironment/papermill_license.txt b/Gems/Atom/Feature/Common/Assets/Textures/sampleEnvironment/papermill_license.txt deleted file mode 100644 index 83cfe08ab9..0000000000 --- a/Gems/Atom/Feature/Common/Assets/Textures/sampleEnvironment/papermill_license.txt +++ /dev/null @@ -1,10 +0,0 @@ -The papermill 'Image base lighting' (IBL) images are modified from the following: - -http://www.hdrlabs.com/sibl/archive.html -'Papermill Ruins E' - -All sIBL-sets on this page, including the images within, are licensed under the Creative Commons Attribution-Noncommercial-Share Alike 3.0 License. - -Creative Commons License: http://creativecommons.org/licenses/by-nc-sa/3.0/us/ - -Remember: Do what you want with them, but always mention where you got them from... \ No newline at end of file diff --git a/Gems/Atom/Feature/Common/Assets/atom_feature_common_asset_files.cmake b/Gems/Atom/Feature/Common/Assets/atom_feature_common_asset_files.cmake index 841cdf67c8..359c0b9b20 100644 --- a/Gems/Atom/Feature/Common/Assets/atom_feature_common_asset_files.cmake +++ b/Gems/Atom/Feature/Common/Assets/atom_feature_common_asset_files.cmake @@ -23,6 +23,7 @@ set(FILES Materials/Types/EnhancedPBR_ForwardPass_EDS.shader Materials/Types/EnhancedPBR_Shadowmap_WithPS.azsl Materials/Types/EnhancedPBR_Shadowmap_WithPS.shader + Materials/Types/EnhancedPBR_SubsurfaceState.lua Materials/Types/Skin.azsl Materials/Types/Skin.materialtype Materials/Types/Skin.shader @@ -61,7 +62,6 @@ set(FILES Materials/Types/StandardPBR_ShaderEnable.lua Materials/Types/StandardPBR_Shadowmap_WithPS.azsl Materials/Types/StandardPBR_Shadowmap_WithPS.shader - Materials/Types/StandardPBR_SubsurfaceState.lua Materials/Types/MaterialInputs/AlphaInput.azsli Materials/Types/MaterialInputs/BaseColorInput.azsli Materials/Types/MaterialInputs/ClearCoatInput.azsli diff --git a/Gems/Atom/Feature/Common/Code/Source/CommonSystemComponent.cpp b/Gems/Atom/Feature/Common/Code/Source/CommonSystemComponent.cpp index 16f20d7178..00c55cbade 100644 --- a/Gems/Atom/Feature/Common/Code/Source/CommonSystemComponent.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/CommonSystemComponent.cpp @@ -89,6 +89,8 @@ #include #include +#include +#include #include #include #include @@ -128,6 +130,7 @@ namespace AZ SMAAFeatureProcessor::Reflect(context); PostProcessFeatureProcessor::Reflect(context); ImGuiPassData::Reflect(context); + RayTracingPassData::Reflect(context); LightingPreset::Reflect(context); ModelPreset::Reflect(context); @@ -271,6 +274,9 @@ namespace AZ passSystem->AddPassCreator(Name("ReflectionScreenSpaceBlurChildPass"), &Render::ReflectionScreenSpaceBlurChildPass::Create); passSystem->AddPassCreator(Name("ReflectionCopyFrameBufferPass"), &Render::ReflectionCopyFrameBufferPass::Create); + // Add RayTracing pas + passSystem->AddPassCreator(Name("RayTracingPass"), &Render::RayTracingPass::Create); + // setup handler for load pass template mappings m_loadTemplatesHandler = RPI::PassSystemInterface::OnReadyLoadTemplatesEvent::Handler([this]() { this->LoadPassTemplateMappings(); }); RPI::PassSystemInterface::Get()->ConnectEvent(m_loadTemplatesHandler); diff --git a/Gems/Atom/Feature/Common/Code/Source/FrameCaptureSystemComponent.cpp b/Gems/Atom/Feature/Common/Code/Source/FrameCaptureSystemComponent.cpp index 90499be8b8..0d9e46a418 100644 --- a/Gems/Atom/Feature/Common/Code/Source/FrameCaptureSystemComponent.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/FrameCaptureSystemComponent.cpp @@ -16,11 +16,14 @@ #include #include #include +#include #include #include #include +#include +#include #include #include @@ -55,6 +58,43 @@ namespace AZ FrameCaptureOutputResult PngFrameCaptureOutput( const AZStd::string& outputFilePath, const AZ::RPI::AttachmentReadback::ReadbackResult& readbackResult) { + AZStd::shared_ptr> buffer = readbackResult.m_dataBuffer; + + // convert bgra to rgba by swapping channels + const int numChannels = AZ::RHI::GetFormatComponentCount(readbackResult.m_imageDescriptor.m_format); + if (readbackResult.m_imageDescriptor.m_format == RHI::Format::B8G8R8A8_UNORM) + { + buffer = AZStd::make_shared>(readbackResult.m_dataBuffer->size()); + AZStd::copy(readbackResult.m_dataBuffer->begin(), readbackResult.m_dataBuffer->end(), buffer->begin()); + + AZ::JobCompletion jobCompletion; + const int numThreads = 8; + const int numPixelsPerThread = buffer->size() / numChannels / numThreads; + for (int i = 0; i < numThreads; ++i) + { + int startPixel = i * numPixelsPerThread; + + AZ::Job* job = AZ::CreateJobFunction( + [&, startPixel, numPixelsPerThread]() + { + for (int pixelOffset = 0; pixelOffset < numPixelsPerThread; ++pixelOffset) + { + if (startPixel * numChannels + numChannels < buffer->size()) + { + AZStd::swap( + buffer->data()[(startPixel + pixelOffset) * numChannels], + buffer->data()[(startPixel + pixelOffset) * numChannels + 2] + ); + } + } + }, true, nullptr); + + job->SetDependent(&jobCompletion); + job->Start(); + } + jobCompletion.StartAndWaitForCompletion(); + } + using namespace OIIO; AZStd::unique_ptr out = ImageOutput::create(outputFilePath.c_str()); if (out) @@ -62,13 +102,13 @@ namespace AZ ImageSpec spec( readbackResult.m_imageDescriptor.m_size.m_width, readbackResult.m_imageDescriptor.m_size.m_height, - AZ::RHI::GetFormatComponentCount(readbackResult.m_imageDescriptor.m_format) + numChannels ); spec.attribute("png:compressionLevel", r_pngCompressionLevel); if (out->open(outputFilePath.c_str(), spec)) { - out->write_image(TypeDesc::UINT8, readbackResult.m_dataBuffer->data()); + out->write_image(TypeDesc::UINT8, buffer->data()); out->close(); return FrameCaptureOutputResult{FrameCaptureResult::Success, AZStd::nullopt}; } @@ -250,11 +290,7 @@ namespace AZ bool FrameCaptureSystemComponent::CaptureScreenshot(const AZStd::string& filePath) { - AzFramework::NativeWindowHandle windowHandle = nullptr; - AzFramework::WindowSystemRequestBus::BroadcastResult( - windowHandle, - &AzFramework::WindowSystemRequestBus::Events::GetDefaultWindowHandle); - + AzFramework::NativeWindowHandle windowHandle = AZ::RPI::ViewportContextRequests::Get()->GetDefaultViewportContext()->GetWindowHandle(); if (windowHandle) { return CaptureScreenshotForWindow(filePath, windowHandle); @@ -460,13 +496,23 @@ namespace AZ #if defined(OPEN_IMAGE_IO_ENABLED) else if (extension == "png") { - AZStd::string folderPath; - AzFramework::StringFunc::Path::GetFolderPath(m_outputFilePath.c_str(), folderPath); - AZ::IO::SystemFile::CreateDir(folderPath.c_str()); + if (readbackResult.m_imageDescriptor.m_format == RHI::Format::R8G8B8A8_UNORM || + readbackResult.m_imageDescriptor.m_format == RHI::Format::B8G8R8A8_UNORM) + { + AZStd::string folderPath; + AzFramework::StringFunc::Path::GetFolderPath(m_outputFilePath.c_str(), folderPath); + AZ::IO::SystemFile::CreateDir(folderPath.c_str()); - const auto frameCaptureResult = PngFrameCaptureOutput(m_outputFilePath, readbackResult); - m_result = frameCaptureResult.m_result; - m_latestCaptureInfo = frameCaptureResult.m_errorMessage.value_or(""); + const auto frameCaptureResult = PngFrameCaptureOutput(m_outputFilePath, readbackResult); + m_result = frameCaptureResult.m_result; + m_latestCaptureInfo = frameCaptureResult.m_errorMessage.value_or(""); + } + else + { + m_latestCaptureInfo = AZStd::string::format( + "Can't save image with format %s to a png file", RHI::ToString(readbackResult.m_imageDescriptor.m_format)); + m_result = FrameCaptureResult::UnsupportedFormat; + } } #endif else diff --git a/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignment.cpp b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignment.cpp index ffb5469aef..029a81ec49 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignment.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignment.cpp @@ -12,7 +12,9 @@ #include #include +#include #include +#include namespace AZ { @@ -22,6 +24,11 @@ namespace AZ { MaterialAssignmentId::Reflect(context); + if (auto jsonContext = azrtti_cast(context)) + { + jsonContext->Serializer()->HandlesType(); + } + if (auto serializeContext = azrtti_cast(context)) { serializeContext->RegisterGenericType(); diff --git a/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignmentSerializer.cpp b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignmentSerializer.cpp new file mode 100644 index 0000000000..a895c04b95 --- /dev/null +++ b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignmentSerializer.cpp @@ -0,0 +1,214 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include +#include + +namespace AZ +{ + namespace Render + { + AZ_CLASS_ALLOCATOR_IMPL(JsonMaterialAssignmentSerializer, AZ::SystemAllocator, 0); + + JsonSerializationResult::Result JsonMaterialAssignmentSerializer::Load( + void* outputValue, [[maybe_unused]] const Uuid& outputValueTypeId, const rapidjson::Value& inputValue, + JsonDeserializerContext& context) + { + namespace JSR = JsonSerializationResult; + + AZ_Assert( + azrtti_typeid() == outputValueTypeId, + "Unable to deserialize MaterialAssignment from json because the provided type is %s.", + outputValueTypeId.ToString().c_str()); + + AZ::Render::MaterialAssignment* materialAssignment = reinterpret_cast(outputValue); + AZ_Assert(materialAssignment, "Output value for JsonMaterialAssignmentSerializer can't be null."); + + JSR::ResultCode result(JSR::Tasks::ReadField); + { + result.Combine(ContinueLoadingFromJsonObjectField( + &materialAssignment->m_materialAsset, azrtti_typeidm_materialAsset)>(), inputValue, + "MaterialAsset", context)); + } + + if (inputValue.HasMember("PropertyOverrides") && inputValue["PropertyOverrides"].IsObject()) + { + // Attempt to load material property override values for a subset of types + for (const auto& inputPropertyPair : inputValue["PropertyOverrides"].GetObject()) + { + const AZ::Name propertyName(inputPropertyPair.name.GetString()); + if (!propertyName.IsEmpty()) + { + AZStd::any propertyValue; + if (LoadAny(propertyValue, inputPropertyPair.value, context, result) || + LoadAny(propertyValue, inputPropertyPair.value, context, result) || + LoadAny(propertyValue, inputPropertyPair.value, context, result) || + LoadAny(propertyValue, inputPropertyPair.value, context, result) || + LoadAny(propertyValue, inputPropertyPair.value, context, result) || + LoadAny(propertyValue, inputPropertyPair.value, context, result) || + LoadAny(propertyValue, inputPropertyPair.value, context, result) || + LoadAny(propertyValue, inputPropertyPair.value, context, result) || + LoadAny(propertyValue, inputPropertyPair.value, context, result) || + LoadAny(propertyValue, inputPropertyPair.value, context, result) || + LoadAny(propertyValue, inputPropertyPair.value, context, result) || + LoadAny(propertyValue, inputPropertyPair.value, context, result) || + LoadAny(propertyValue, inputPropertyPair.value, context, result) || + LoadAny(propertyValue, inputPropertyPair.value, context, result) || + LoadAny(propertyValue, inputPropertyPair.value, context, result) || + LoadAny(propertyValue, inputPropertyPair.value, context, result) || + LoadAny(propertyValue, inputPropertyPair.value, context, result) || + LoadAny>(propertyValue, inputPropertyPair.value, context, result) || + LoadAny>(propertyValue, inputPropertyPair.value, context, result)) + { + materialAssignment->m_propertyOverrides[propertyName] = propertyValue; + } + } + } + } + + return context.Report( + result, + result.GetProcessing() != JSR::Processing::Halted ? "Succesfully loaded MaterialAssignment information." + : "Failed to load MaterialAssignment information."); + } + + JsonSerializationResult::Result JsonMaterialAssignmentSerializer::Store( + rapidjson::Value& outputValue, const void* inputValue, const void* defaultValue, [[maybe_unused]] const Uuid& valueTypeId, + JsonSerializerContext& context) + { + namespace JSR = AZ::JsonSerializationResult; + + AZ_Assert( + azrtti_typeid() == valueTypeId, + "Unable to Serialize MaterialAssignment because the provided type is %s.", valueTypeId.ToString().c_str()); + + const AZ::Render::MaterialAssignment* materialAssignment = reinterpret_cast(inputValue); + AZ_Assert(materialAssignment, "Input value for JsonMaterialAssignmentSerializer can't be null."); + const AZ::Render::MaterialAssignment* defaultMaterialAssignmentInstance = + reinterpret_cast(defaultValue); + + outputValue.SetObject(); + + JSR::ResultCode result(JSR::Tasks::WriteValue); + { + AZ::ScopedContextPath subPathMaterialAsset(context, "m_materialAsset"); + const AZ::Data::Asset* materialAsset = &materialAssignment->m_materialAsset; + const AZ::Data::Asset* defaultmaterialAsset = + defaultMaterialAssignmentInstance ? &defaultMaterialAssignmentInstance->m_materialAsset : nullptr; + + result.Combine(ContinueStoringToJsonObjectField( + outputValue, "MaterialAsset", materialAsset, defaultmaterialAsset, + azrtti_typeidm_materialAsset)>(), context)); + } + + { + AZ::ScopedContextPath subPathPropertyOverrides(context, "m_propertyOverrides"); + if (!materialAssignment->m_propertyOverrides.empty()) + { + rapidjson::Value outputPropertyValueContainer; + outputPropertyValueContainer.SetObject(); + + // Attempt to extract and store material property override values for a subset of types + for (const auto& propertyPair : materialAssignment->m_propertyOverrides) + { + const AZ::Name& propertyName = propertyPair.first; + const AZStd::any& propertyValue = propertyPair.second; + if (!propertyName.IsEmpty() && !propertyValue.empty()) + { + rapidjson::Value outputPropertyValue; + if (StoreAny(propertyValue, outputPropertyValue, context, result) || + StoreAny(propertyValue, outputPropertyValue, context, result) || + StoreAny(propertyValue, outputPropertyValue, context, result) || + StoreAny(propertyValue, outputPropertyValue, context, result) || + StoreAny(propertyValue, outputPropertyValue, context, result) || + StoreAny(propertyValue, outputPropertyValue, context, result) || + StoreAny(propertyValue, outputPropertyValue, context, result) || + StoreAny(propertyValue, outputPropertyValue, context, result) || + StoreAny(propertyValue, outputPropertyValue, context, result) || + StoreAny(propertyValue, outputPropertyValue, context, result) || + StoreAny(propertyValue, outputPropertyValue, context, result) || + StoreAny(propertyValue, outputPropertyValue, context, result) || + StoreAny(propertyValue, outputPropertyValue, context, result) || + StoreAny(propertyValue, outputPropertyValue, context, result) || + StoreAny(propertyValue, outputPropertyValue, context, result) || + StoreAny(propertyValue, outputPropertyValue, context, result) || + StoreAny(propertyValue, outputPropertyValue, context, result) || + StoreAny>(propertyValue, outputPropertyValue, context, result) || + StoreAny>( + propertyValue, outputPropertyValue, context, result)) + { + outputPropertyValueContainer.AddMember( + rapidjson::Value::StringRefType(propertyName.GetCStr()), outputPropertyValue, + context.GetJsonAllocator()); + } + } + } + + if (outputPropertyValueContainer.MemberCount() > 0) + { + outputValue.AddMember("PropertyOverrides", outputPropertyValueContainer, context.GetJsonAllocator()); + } + } + } + + return context.Report( + result, + result.GetProcessing() != JSR::Processing::Halted ? "Successfully stored MaterialAssignment information." + : "Failed to store MaterialAssignment information."); + } + + template + bool JsonMaterialAssignmentSerializer::LoadAny( + AZStd::any& propertyValue, const rapidjson::Value& inputPropertyValue, AZ::JsonDeserializerContext& context, + AZ::JsonSerializationResult::ResultCode& result) + { + if (inputPropertyValue.IsObject() && inputPropertyValue.HasMember("Value") && inputPropertyValue.HasMember("$type")) + { + // Requiring explicit type info to differentiate be=tween colors versus vectors and numeric types + const AZ::Uuid baseTypeId = azrtti_typeid(); + AZ::Uuid typeId = AZ::Uuid::CreateNull(); + result.Combine(LoadTypeId(typeId, inputPropertyValue, context, &baseTypeId)); + + if (typeId == azrtti_typeid()) + { + T value = {}; + result.Combine(ContinueLoadingFromJsonObjectField(&value, azrtti_typeid(), inputPropertyValue, "Value", context)); + propertyValue = value; + return true; + } + } + return false; + } + + template + bool JsonMaterialAssignmentSerializer::StoreAny( + const AZStd::any& propertyValue, rapidjson::Value& outputPropertyValue, AZ::JsonSerializerContext& context, + AZ::JsonSerializationResult::ResultCode& result) + { + if (propertyValue.is()) + { + outputPropertyValue.SetObject(); + + // Storing explicit type info to differentiate be=tween colors versus vectors and numeric types + rapidjson::Value typeValue; + result.Combine(StoreTypeId(typeValue, azrtti_typeid(), context)); + outputPropertyValue.AddMember("$type", typeValue, context.GetJsonAllocator()); + + T value = AZStd::any_cast(propertyValue); + result.Combine( + ContinueStoringToJsonObjectField(outputPropertyValue, "Value", &value, nullptr, azrtti_typeid(), context)); + return true; + } + return false; + } + } // namespace Render +} // namespace AZ diff --git a/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignmentSerializer.h b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignmentSerializer.h new file mode 100644 index 0000000000..14db019668 --- /dev/null +++ b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignmentSerializer.h @@ -0,0 +1,50 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include +#include +#include + +namespace AZ +{ + namespace Render + { + // Custom JSON serializer for material assignment objects containing AZStd::any property overrides, + // which aren't supported by the system + class JsonMaterialAssignmentSerializer : public BaseJsonSerializer + { + public: + AZ_RTTI(JsonMaterialAssignmentSerializer, "{3D33653E-4582-483F-91F5-BBCC347C3DF0}", BaseJsonSerializer); + AZ_CLASS_ALLOCATOR_DECL; + + JsonSerializationResult::Result Load( + void* outputValue, const Uuid& outputValueTypeId, const rapidjson::Value& inputValue, + JsonDeserializerContext& context) override; + + JsonSerializationResult::Result Store( + rapidjson::Value& outputValue, const void* inputValue, const void* defaultValue, const Uuid& valueTypeId, + JsonSerializerContext& context) override; + + private: + template + bool LoadAny( + AZStd::any& propertyValue, const rapidjson::Value& inputPropertyValue, AZ::JsonDeserializerContext& context, + AZ::JsonSerializationResult::ResultCode& result); + template + bool StoreAny( + const AZStd::any& propertyValue, rapidjson::Value& outputPropertyValue, AZ::JsonSerializerContext& context, + AZ::JsonSerializationResult::ResultCode& result); + }; + } // namespace Render +} // namespace AZ diff --git a/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp index 117b177f05..4aaa0be132 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp @@ -710,27 +710,53 @@ namespace AZ uint32_t rayTracingLod = aznumeric_cast(modelLods.size() - 1); const Data::Instance& modelLod = modelLods[rayTracingLod]; - // setup a stream layout and shader input contract for the position and normal streams + // setup a stream layout and shader input contract for the vertex streams static const char* PositionSemantic = "POSITION"; static const char* NormalSemantic = "NORMAL"; - static const RHI::Format StreamFormat = RHI::Format::R32G32B32_FLOAT; + static const char* TangentSemantic = "TANGENT"; + static const char* BitangentSemantic = "BITANGENT"; + static const char* UVSemantic = "UV"; + static const RHI::Format PositionStreamFormat = RHI::Format::R32G32B32_FLOAT; + static const RHI::Format NormalStreamFormat = RHI::Format::R32G32B32_FLOAT; + static const RHI::Format TangentStreamFormat = RHI::Format::R32G32B32_FLOAT; + static const RHI::Format BitangentStreamFormat = RHI::Format::R32G32B32_FLOAT; + static const RHI::Format UVStreamFormat = RHI::Format::R32G32_FLOAT; RHI::InputStreamLayoutBuilder layoutBuilder; - layoutBuilder.AddBuffer()->Channel(PositionSemantic, StreamFormat); - layoutBuilder.AddBuffer()->Channel(NormalSemantic, StreamFormat); + layoutBuilder.AddBuffer()->Channel(PositionSemantic, PositionStreamFormat); + layoutBuilder.AddBuffer()->Channel(NormalSemantic, NormalStreamFormat); + layoutBuilder.AddBuffer()->Channel(UVSemantic, UVStreamFormat); + layoutBuilder.AddBuffer()->Channel(TangentSemantic, TangentStreamFormat); + layoutBuilder.AddBuffer()->Channel(BitangentSemantic, BitangentStreamFormat); RHI::InputStreamLayout inputStreamLayout = layoutBuilder.End(); RPI::ShaderInputContract::StreamChannelInfo positionStreamChannelInfo; positionStreamChannelInfo.m_semantic = RHI::ShaderSemantic(AZ::Name(PositionSemantic)); - positionStreamChannelInfo.m_componentCount = RHI::GetFormatComponentCount(StreamFormat); + positionStreamChannelInfo.m_componentCount = RHI::GetFormatComponentCount(PositionStreamFormat); RPI::ShaderInputContract::StreamChannelInfo normalStreamChannelInfo; normalStreamChannelInfo.m_semantic = RHI::ShaderSemantic(AZ::Name(NormalSemantic)); - normalStreamChannelInfo.m_componentCount = RHI::GetFormatComponentCount(StreamFormat); + normalStreamChannelInfo.m_componentCount = RHI::GetFormatComponentCount(NormalStreamFormat); + + RPI::ShaderInputContract::StreamChannelInfo tangentStreamChannelInfo; + tangentStreamChannelInfo.m_semantic = RHI::ShaderSemantic(AZ::Name(TangentSemantic)); + tangentStreamChannelInfo.m_componentCount = RHI::GetFormatComponentCount(TangentStreamFormat); + + RPI::ShaderInputContract::StreamChannelInfo bitangentStreamChannelInfo; + bitangentStreamChannelInfo.m_semantic = RHI::ShaderSemantic(AZ::Name(BitangentSemantic)); + bitangentStreamChannelInfo.m_componentCount = RHI::GetFormatComponentCount(BitangentStreamFormat); + + RPI::ShaderInputContract::StreamChannelInfo uvStreamChannelInfo; + uvStreamChannelInfo.m_semantic = RHI::ShaderSemantic(AZ::Name(UVSemantic)); + uvStreamChannelInfo.m_componentCount = RHI::GetFormatComponentCount(UVStreamFormat); + uvStreamChannelInfo.m_isOptional = true; RPI::ShaderInputContract shaderInputContract; shaderInputContract.m_streamChannels.emplace_back(positionStreamChannelInfo); shaderInputContract.m_streamChannels.emplace_back(normalStreamChannelInfo); + shaderInputContract.m_streamChannels.emplace_back(tangentStreamChannelInfo); + shaderInputContract.m_streamChannels.emplace_back(bitangentStreamChannelInfo); + shaderInputContract.m_streamChannels.emplace_back(uvStreamChannelInfo); // setup the raytracing data for each sub-mesh const size_t meshCount = modelLod->GetMeshes().size(); @@ -739,26 +765,6 @@ namespace AZ { const RPI::ModelLod::Mesh& mesh = modelLod->GetMeshes()[meshIndex]; - // retrieve vertex/index buffers - RPI::ModelLod::StreamBufferViewList streamBufferViews; - [[maybe_unused]] bool result = modelLod->GetStreamsForMesh(inputStreamLayout, streamBufferViews, nullptr, shaderInputContract, meshIndex); - AZ_Assert(result, "Failed to retrieve mesh stream buffer views"); - - // note that the element count is the size of the entire buffer, even though this mesh may only - // occupy a portion of the vertex buffer. This is necessary since we are accessing it using - // a ByteAddressBuffer in the raytracing shaders and passing the byte offset to the shader in a constant buffer. - uint32_t vertexBufferByteCount = const_cast(streamBufferViews[0].GetBuffer())->GetDescriptor().m_byteCount; - RHI::BufferViewDescriptor vertexBufferDescriptor = RHI::BufferViewDescriptor::CreateRaw(0, vertexBufferByteCount); - - const RHI::IndexBufferView& indexBufferView = mesh.m_indexBufferView; - uint32_t indexElementSize = indexBufferView.GetIndexFormat() == RHI::IndexFormat::Uint16 ? 2 : 4; - uint32_t indexElementCount = (uint32_t)indexBufferView.GetBuffer()->GetDescriptor().m_byteCount / indexElementSize; - RHI::BufferViewDescriptor indexBufferDescriptor; - indexBufferDescriptor.m_elementOffset = 0; - indexBufferDescriptor.m_elementCount = indexElementCount; - indexBufferDescriptor.m_elementSize = indexElementSize; - indexBufferDescriptor.m_elementFormat = indexBufferView.GetIndexFormat() == RHI::IndexFormat::Uint16 ? RHI::Format::R16_UINT : RHI::Format::R32_UINT; - // retrieve the material Data::Instance material = mesh.m_material; @@ -769,31 +775,163 @@ namespace AZ material = materialAssignment.m_materialInstance; } - AZ::Color irradianceColor(1.0f, 1.0f, 1.0f, 1.0f); + // retrieve vertex/index buffers + RPI::ModelLod::StreamBufferViewList streamBufferViews; + [[maybe_unused]] bool result = modelLod->GetStreamsForMesh( + inputStreamLayout, + streamBufferViews, + nullptr, + shaderInputContract, + meshIndex, + materialAssignment.m_matModUvOverrides, + material->GetAsset()->GetMaterialTypeAsset()->GetUvNameMap()); + AZ_Assert(result, "Failed to retrieve mesh stream buffer views"); + + // note that the element count is the size of the entire buffer, even though this mesh may only + // occupy a portion of the vertex buffer. This is necessary since we are accessing it using + // a ByteAddressBuffer in the raytracing shaders and passing the byte offset to the shader in a constant buffer. + uint32_t positionBufferByteCount = const_cast(streamBufferViews[0].GetBuffer())->GetDescriptor().m_byteCount; + RHI::BufferViewDescriptor positionBufferDescriptor = RHI::BufferViewDescriptor::CreateRaw(0, positionBufferByteCount); + + uint32_t normalBufferByteCount = const_cast(streamBufferViews[1].GetBuffer())->GetDescriptor().m_byteCount; + RHI::BufferViewDescriptor normalBufferDescriptor = RHI::BufferViewDescriptor::CreateRaw(0, normalBufferByteCount); + + uint32_t tangentBufferByteCount = const_cast(streamBufferViews[2].GetBuffer())->GetDescriptor().m_byteCount; + RHI::BufferViewDescriptor tangentBufferDescriptor = RHI::BufferViewDescriptor::CreateRaw(0, tangentBufferByteCount); + + uint32_t bitangentBufferByteCount = const_cast(streamBufferViews[3].GetBuffer())->GetDescriptor().m_byteCount; + RHI::BufferViewDescriptor bitangentBufferDescriptor = RHI::BufferViewDescriptor::CreateRaw(0, bitangentBufferByteCount); + + uint32_t uvBufferByteCount = const_cast(streamBufferViews[4].GetBuffer())->GetDescriptor().m_byteCount; + RHI::BufferViewDescriptor uvBufferDescriptor = RHI::BufferViewDescriptor::CreateRaw(0, uvBufferByteCount); + + const RHI::IndexBufferView& indexBufferView = mesh.m_indexBufferView; + uint32_t indexElementSize = indexBufferView.GetIndexFormat() == RHI::IndexFormat::Uint16 ? 2 : 4; + uint32_t indexElementCount = (uint32_t)indexBufferView.GetBuffer()->GetDescriptor().m_byteCount / indexElementSize; + RHI::BufferViewDescriptor indexBufferDescriptor; + indexBufferDescriptor.m_elementOffset = 0; + indexBufferDescriptor.m_elementCount = indexElementCount; + indexBufferDescriptor.m_elementSize = indexElementSize; + indexBufferDescriptor.m_elementFormat = indexBufferView.GetIndexFormat() == RHI::IndexFormat::Uint16 ? RHI::Format::R16_UINT : RHI::Format::R32_UINT; + + // set the SubMesh data to pass to the RayTracingFeatureProcessor, starting with vertex/index data + RayTracingFeatureProcessor::SubMesh subMesh; + subMesh.m_positionFormat = PositionStreamFormat; + subMesh.m_positionVertexBufferView = streamBufferViews[0]; + subMesh.m_positionShaderBufferView = const_cast(streamBufferViews[0].GetBuffer())->GetBufferView(positionBufferDescriptor); + + subMesh.m_normalFormat = NormalStreamFormat; + subMesh.m_normalVertexBufferView = streamBufferViews[1]; + subMesh.m_normalShaderBufferView = const_cast(streamBufferViews[1].GetBuffer())->GetBufferView(normalBufferDescriptor); + + subMesh.m_tangentFormat = TangentStreamFormat; + subMesh.m_tangentVertexBufferView = streamBufferViews[2]; + subMesh.m_tangentShaderBufferView = const_cast(streamBufferViews[2].GetBuffer())->GetBufferView(tangentBufferDescriptor); + + subMesh.m_bitangentFormat = BitangentStreamFormat; + subMesh.m_bitangentVertexBufferView = streamBufferViews[3]; + subMesh.m_bitangentShaderBufferView = const_cast(streamBufferViews[3].GetBuffer())->GetBufferView(bitangentBufferDescriptor); + + if (uvBufferByteCount > 0) + { + subMesh.m_bufferFlags |= RayTracingSubMeshBufferFlags::UV; + subMesh.m_uvFormat = UVStreamFormat; + subMesh.m_uvVertexBufferView = streamBufferViews[4]; + subMesh.m_uvShaderBufferView = const_cast(streamBufferViews[4].GetBuffer())->GetBufferView(uvBufferDescriptor); + } + + subMesh.m_indexBufferView = mesh.m_indexBufferView; + subMesh.m_indexShaderBufferView = const_cast(mesh.m_indexBufferView.GetBuffer())->GetBufferView(indexBufferDescriptor); + + // add material data if (material) { + // irradiance color RPI::MaterialPropertyIndex propertyIndex = material->FindPropertyIndex(AZ::Name("irradiance.color")); if (propertyIndex.IsValid()) { - irradianceColor = material->GetPropertyValue(propertyIndex); + subMesh.m_irradianceColor = material->GetPropertyValue(propertyIndex); } propertyIndex = material->FindPropertyIndex(AZ::Name("irradiance.factor")); if (propertyIndex.IsValid()) { - irradianceColor *= material->GetPropertyValue(propertyIndex); + subMesh.m_irradianceColor *= material->GetPropertyValue(propertyIndex); + } + + // base color + propertyIndex = material->FindPropertyIndex(AZ::Name("baseColor.color")); + if (propertyIndex.IsValid()) + { + subMesh.m_baseColor = material->GetPropertyValue(propertyIndex); + } + + propertyIndex = material->FindPropertyIndex(AZ::Name("baseColor.factor")); + if (propertyIndex.IsValid()) + { + subMesh.m_baseColor *= material->GetPropertyValue(propertyIndex); + } + + // metallic + propertyIndex = material->FindPropertyIndex(AZ::Name("metallic.factor")); + if (propertyIndex.IsValid()) + { + subMesh.m_metallicFactor = material->GetPropertyValue(propertyIndex); + } + + // roughness + propertyIndex = material->FindPropertyIndex(AZ::Name("roughness.factor")); + if (propertyIndex.IsValid()) + { + subMesh.m_roughnessFactor = material->GetPropertyValue(propertyIndex); + } + + // textures + propertyIndex = material->FindPropertyIndex(AZ::Name("baseColor.textureMap")); + if (propertyIndex.IsValid()) + { + Data::Instance image = material->GetPropertyValue>(propertyIndex); + if (image.get()) + { + subMesh.m_textureFlags |= RayTracingSubMeshTextureFlags::BaseColor; + subMesh.m_baseColorImageView = image->GetImageView(); + } + } + + propertyIndex = material->FindPropertyIndex(AZ::Name("normal.textureMap")); + if (propertyIndex.IsValid()) + { + Data::Instance image = material->GetPropertyValue>(propertyIndex); + if (image.get()) + { + subMesh.m_textureFlags |= RayTracingSubMeshTextureFlags::Normal; + subMesh.m_normalImageView = image->GetImageView(); + } + } + + propertyIndex = material->FindPropertyIndex(AZ::Name("metallic.textureMap")); + if (propertyIndex.IsValid()) + { + Data::Instance image = material->GetPropertyValue>(propertyIndex); + if (image.get()) + { + subMesh.m_textureFlags |= RayTracingSubMeshTextureFlags::Metallic; + subMesh.m_metallicImageView = image->GetImageView(); + } + } + + propertyIndex = material->FindPropertyIndex(AZ::Name("roughness.textureMap")); + if (propertyIndex.IsValid()) + { + Data::Instance image = material->GetPropertyValue>(propertyIndex); + if (image.get()) + { + subMesh.m_textureFlags |= RayTracingSubMeshTextureFlags::Roughness; + subMesh.m_roughnessImageView = image->GetImageView(); + } } } - RayTracingFeatureProcessor::SubMesh subMesh; - subMesh.m_vertexFormat = StreamFormat; - subMesh.m_positionVertexBufferView = streamBufferViews[0]; - subMesh.m_positionShaderBufferView = const_cast(streamBufferViews[0].GetBuffer())->GetBufferView(vertexBufferDescriptor); - subMesh.m_normalVertexBufferView = streamBufferViews[1]; - subMesh.m_normalShaderBufferView = const_cast(streamBufferViews[1].GetBuffer())->GetBufferView(vertexBufferDescriptor); - subMesh.m_indexBufferView = mesh.m_indexBufferView; - subMesh.m_indexShaderBufferView = const_cast(mesh.m_indexBufferView.GetBuffer())->GetBufferView(indexBufferDescriptor); - subMesh.m_irradianceColor = irradianceColor; subMeshes.push_back(subMesh); } diff --git a/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingAccelerationStructurePass.cpp b/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingAccelerationStructurePass.cpp index 2bb2fa2ac2..92cd41b4e8 100644 --- a/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingAccelerationStructurePass.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingAccelerationStructurePass.cpp @@ -115,11 +115,11 @@ namespace AZ } } - // update and compile the RayTracingSceneSrg + // update and compile the RayTracingSceneSrg and RayTracingMaterialSrg // Note: the timing of this update is very important, it needs to be updated after the TLAS is allocated so it can // be set on the RayTracingSceneSrg for this frame, and the ray tracing mesh data in the RayTracingSceneSrg must // exactly match the TLAS. Any mismatch in this data may result in a TDR. - rayTracingFeatureProcessor->UpdateRayTracingSceneSrg(); + rayTracingFeatureProcessor->UpdateRayTracingSrgs(); } } diff --git a/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingFeatureProcessor.cpp index 7c13daea3b..10c7c2d378 100644 --- a/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingFeatureProcessor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingFeatureProcessor.cpp @@ -71,6 +71,13 @@ namespace AZ AZ_Assert(rayTracingSceneSrgAsset.IsReady(), "Failed to load RayTracingSceneSrg asset"); m_rayTracingSceneSrg = RPI::ShaderResourceGroup::Create(rayTracingSceneSrgAsset); + + // load the RayTracingMaterialSrg asset + Data::Asset rayTracingMaterialSrgAsset = + RPI::AssetUtils::LoadAssetByProductPath("shaderlib/atom/features/raytracing/raytracingmaterialsrg_raytracingmaterialsrg.azsrg", RPI::AssetUtils::TraceLevel::Error); + AZ_Assert(rayTracingMaterialSrgAsset.IsReady(), "Failed to load RayTracingMaterialSrg asset"); + + m_rayTracingMaterialSrg = RPI::ShaderResourceGroup::Create(rayTracingMaterialSrgAsset); } void RayTracingFeatureProcessor::SetMesh(const ObjectId objectId, const SubMeshVector& subMeshes) @@ -104,7 +111,7 @@ namespace AZ RHI::RayTracingBlasDescriptor blasDescriptor; blasDescriptor.Build() ->Geometry() - ->VertexFormat(subMesh.m_vertexFormat) + ->VertexFormat(subMesh.m_positionFormat) ->VertexBuffer(subMesh.m_positionVertexBufferView) ->IndexBuffer(subMesh.m_indexBufferView) ; @@ -124,6 +131,7 @@ namespace AZ m_subMeshCount += aznumeric_cast(subMeshes.size()); m_meshInfoBufferNeedsUpdate = true; + m_materialInfoBufferNeedsUpdate = true; } void RayTracingFeatureProcessor::RemoveMesh(const ObjectId objectId) @@ -142,6 +150,7 @@ namespace AZ } m_meshInfoBufferNeedsUpdate = true; + m_materialInfoBufferNeedsUpdate = true; } void RayTracingFeatureProcessor::SetMeshTransform(const ObjectId objectId, const AZ::Transform transform, const AZ::Vector3 nonUniformScale) @@ -162,14 +171,14 @@ namespace AZ m_meshInfoBufferNeedsUpdate = true; } - void RayTracingFeatureProcessor::UpdateRayTracingSceneSrg() + void RayTracingFeatureProcessor::UpdateRayTracingSrgs() { if (!m_tlas->GetTlasBuffer()) { return; } - if (m_rayTracingSceneSrg->IsQueuedForCompile()) + if (m_rayTracingSceneSrg->IsQueuedForCompile() || m_rayTracingMaterialSrg->IsQueuedForCompile()) { //[GFX TODO][ATOM-14792] AtomSampleViewer: Reset scene and feature processors before switching to sample return; @@ -178,7 +187,148 @@ namespace AZ // update the mesh info buffer with the latest ray tracing enabled meshes UpdateMeshInfoBuffer(); + // update the material info buffer with the latest ray tracing enabled meshes + UpdateMaterialInfoBuffer(); + // update the RayTracingSceneSrg + UpdateRayTracingSceneSrg(); + + // update the RayTracingMaterialSrg + UpdateRayTracingMaterialSrg(); + } + + void RayTracingFeatureProcessor::UpdateMeshInfoBuffer() + { + if (m_meshInfoBufferNeedsUpdate && (m_subMeshCount > 0)) + { + TransformServiceFeatureProcessor* transformFeatureProcessor = GetParentScene()->GetFeatureProcessor(); + + AZStd::vector meshInfos; + meshInfos.reserve(m_subMeshCount); + + uint32_t newMeshByteCount = m_subMeshCount * sizeof(MeshInfo); + + if (m_meshInfoBuffer == nullptr) + { + AZStd::string uuidString = AZ::Uuid::CreateRandom().ToString(); + + // allocate the MeshInfo structured buffer + RPI::CommonBufferDescriptor desc; + desc.m_poolType = RPI::CommonBufferPoolType::ReadOnly; + desc.m_bufferName = AZStd::string::format("RayTracingMeshInfo_%s", uuidString.c_str()); + desc.m_byteCount = newMeshByteCount; + desc.m_elementSize = sizeof(MeshInfo); + m_meshInfoBuffer = RPI::BufferSystemInterface::Get()->CreateBufferFromCommonPool(desc); + } + else if (m_meshInfoBuffer->GetBufferSize() < newMeshByteCount) + { + // resize for the new sub-mesh count + m_meshInfoBuffer->Resize(newMeshByteCount); + } + + // keep track of the start index of the buffers for each mesh, this is put into the MeshInfo + // entry for each mesh so it knows where to find the start of its buffers in the unbounded array + uint32_t bufferStartIndex = 0; + + for (const auto& mesh : m_meshes) + { + AZ::Transform meshTransform = transformFeatureProcessor->GetTransformForId(TransformServiceFeatureProcessorInterface::ObjectId(mesh.first)); + AZ::Transform noScaleTransform = meshTransform; + noScaleTransform.ExtractUniformScale(); + AZ::Matrix3x3 rotationMatrix = Matrix3x3::CreateFromTransform(noScaleTransform); + rotationMatrix = rotationMatrix.GetInverseFull().GetTranspose(); + + const RayTracingFeatureProcessor::SubMeshVector& subMeshes = mesh.second.m_subMeshes; + for (const auto& subMesh : subMeshes) + { + MeshInfo meshInfo; + meshInfo.m_indexOffset = subMesh.m_indexBufferView.GetByteOffset(); + meshInfo.m_positionOffset = subMesh.m_positionVertexBufferView.GetByteOffset(); + meshInfo.m_normalOffset = subMesh.m_normalVertexBufferView.GetByteOffset(); + meshInfo.m_tangentOffset = subMesh.m_tangentVertexBufferView.GetByteOffset(); + meshInfo.m_bitangentOffset = subMesh.m_bitangentVertexBufferView.GetByteOffset(); + + if (RHI::CheckBitsAll(subMesh.m_bufferFlags, RayTracingSubMeshBufferFlags::UV)) + { + meshInfo.m_uvOffset = subMesh.m_uvVertexBufferView.GetByteOffset(); + } + + subMesh.m_irradianceColor.StoreToFloat4(meshInfo.m_irradianceColor.data()); + rotationMatrix.StoreToRowMajorFloat9(meshInfo.m_worldInvTranspose.data()); + meshInfo.m_bufferFlags = subMesh.m_bufferFlags; + meshInfo.m_bufferStartIndex = bufferStartIndex; + + // add the count of buffers present in this subMesh to the start index for the next subMesh + // note that the Index, Position, Normal, Tangent, and Bitangent buffers are always counted since they are guaranteed + static const uint32_t RayTracingSubMeshFixedStreamCount = 5; + bufferStartIndex += (RayTracingSubMeshFixedStreamCount + RHI::CountBitsSet(aznumeric_cast(meshInfo.m_bufferFlags))); + + meshInfos.emplace_back(meshInfo); + } + } + + m_meshInfoBuffer->UpdateData(meshInfos.data(), newMeshByteCount); + m_meshInfoBufferNeedsUpdate = false; + } + } + + void RayTracingFeatureProcessor::UpdateMaterialInfoBuffer() + { + if (m_materialInfoBufferNeedsUpdate && (m_subMeshCount > 0)) + { + AZStd::vector materialInfos; + materialInfos.reserve(m_subMeshCount); + + uint32_t newMaterialByteCount = m_subMeshCount * sizeof(MaterialInfo); + + if (m_materialInfoBuffer == nullptr) + { + AZStd::string uuidString = AZ::Uuid::CreateRandom().ToString(); + + // allocate the MaterialInfo structured buffer + RPI::CommonBufferDescriptor desc; + desc.m_poolType = RPI::CommonBufferPoolType::ReadOnly; + desc.m_bufferName = AZStd::string::format("RayTracingMaterialInfo_%s", uuidString.c_str()); + desc.m_byteCount = newMaterialByteCount; + desc.m_elementSize = sizeof(MaterialInfo); + m_materialInfoBuffer = RPI::BufferSystemInterface::Get()->CreateBufferFromCommonPool(desc); + } + else if (m_materialInfoBuffer->GetBufferSize() < newMaterialByteCount) + { + // resize for the new sub-mesh count + m_materialInfoBuffer->Resize(newMaterialByteCount); + } + + // keep track of the start index of the textures for each mesh, this is put into the MaterialInfo + // entry for each mesh so it knows where to find the start of its textures in the unbounded array + uint32_t textureStartIndex = 0; + + for (const auto& mesh : m_meshes) + { + const RayTracingFeatureProcessor::SubMeshVector& subMeshes = mesh.second.m_subMeshes; + for (const auto& subMesh : subMeshes) + { + MaterialInfo materialInfo; + subMesh.m_baseColor.StoreToFloat4(materialInfo.m_baseColor.data()); + materialInfo.m_metallicFactor = subMesh.m_metallicFactor; + materialInfo.m_roughnessFactor = subMesh.m_roughnessFactor; + materialInfo.m_textureFlags = subMesh.m_textureFlags; + materialInfo.m_textureStartIndex = textureStartIndex; + + // add the count of textures present in this subMesh to the start index for the next subMesh + textureStartIndex += RHI::CountBitsSet(aznumeric_cast(materialInfo.m_textureFlags)); + + materialInfos.emplace_back(materialInfo); + } + } + + m_materialInfoBuffer->UpdateData(materialInfos.data(), newMaterialByteCount); + m_materialInfoBufferNeedsUpdate = false; + } + } + + void RayTracingFeatureProcessor::UpdateRayTracingSceneSrg() + { const RHI::ShaderResourceGroupLayout* srgLayout = m_rayTracingSceneSrg->GetLayout(); RHI::ShaderInputImageIndex imageIndex; RHI::ShaderInputBufferIndex bufferIndex; @@ -272,11 +422,18 @@ namespace AZ const SubMeshVector& subMeshes = mesh.second.m_subMeshes; for (const auto& subMesh : subMeshes) { - // add the index, position, and normal buffers for this sub-mesh to the mesh buffer list, this will - // go into the shader as an unbounded array in the Srg + // add the stream buffers for this sub-mesh to the mesh buffer list, + // this is sent to the shader as an unbounded array in the Srg meshBuffers.push_back(subMesh.m_indexShaderBufferView.get()); meshBuffers.push_back(subMesh.m_positionShaderBufferView.get()); meshBuffers.push_back(subMesh.m_normalShaderBufferView.get()); + meshBuffers.push_back(subMesh.m_tangentShaderBufferView.get()); + meshBuffers.push_back(subMesh.m_bitangentShaderBufferView.get()); + + if (RHI::CheckBitsAll(subMesh.m_bufferFlags, RayTracingSubMeshBufferFlags::UV)) + { + meshBuffers.push_back(subMesh.m_uvShaderBufferView.get()); + } } } @@ -287,58 +444,53 @@ namespace AZ m_rayTracingSceneSrg->Compile(); } - void RayTracingFeatureProcessor::UpdateMeshInfoBuffer() + void RayTracingFeatureProcessor::UpdateRayTracingMaterialSrg() { - if (m_meshInfoBufferNeedsUpdate && (m_subMeshCount > 0)) + const RHI::ShaderResourceGroupLayout* srgLayout = m_rayTracingMaterialSrg->GetLayout(); + RHI::ShaderInputImageIndex imageIndex; + RHI::ShaderInputBufferIndex bufferIndex; + RHI::ShaderInputConstantIndex constantIndex; + + bufferIndex = srgLayout->FindShaderInputBufferIndex(AZ::Name("m_materialInfo")); + m_rayTracingMaterialSrg->SetBufferView(bufferIndex, m_materialInfoBuffer->GetBufferView()); + + if (m_subMeshCount) { - TransformServiceFeatureProcessor* transformFeatureProcessor = GetParentScene()->GetFeatureProcessor(); - - AZStd::vector meshInfos; - meshInfos.reserve(m_subMeshCount); - - uint32_t newMeshByteCount = m_subMeshCount * sizeof(MeshInfo); - - if (m_meshInfoBuffer == nullptr) - { - // allocate the MeshInfo structured buffer - RPI::CommonBufferDescriptor desc; - desc.m_poolType = RPI::CommonBufferPoolType::ReadOnly; - desc.m_bufferName = "RayTracingMeshInfo"; - desc.m_byteCount = newMeshByteCount; - desc.m_elementSize = sizeof(MeshInfo); - m_meshInfoBuffer = RPI::BufferSystemInterface::Get()->CreateBufferFromCommonPool(desc); - } - else if (m_meshInfoBuffer->GetBufferSize() < newMeshByteCount) - { - // resize for the new sub-mesh count - m_meshInfoBuffer->Resize(newMeshByteCount); - } - + AZStd::vector materialTextures; for (const auto& mesh : m_meshes) { - AZ::Transform meshTransform = transformFeatureProcessor->GetTransformForId(TransformServiceFeatureProcessorInterface::ObjectId(mesh.first)); - AZ::Transform noScaleTransform = meshTransform; - noScaleTransform.ExtractUniformScale(); - AZ::Matrix3x3 rotationMatrix = Matrix3x3::CreateFromTransform(noScaleTransform); - rotationMatrix = rotationMatrix.GetInverseFull().GetTranspose(); - - const RayTracingFeatureProcessor::SubMeshVector& subMeshes = mesh.second.m_subMeshes; + const SubMeshVector& subMeshes = mesh.second.m_subMeshes; for (const auto& subMesh : subMeshes) { - MeshInfo meshInfo; - meshInfo.m_indexOffset = subMesh.m_indexBufferView.GetByteOffset(); - meshInfo.m_positionOffset = subMesh.m_positionVertexBufferView.GetByteOffset(); - meshInfo.m_normalOffset = subMesh.m_normalVertexBufferView.GetByteOffset(); - subMesh.m_irradianceColor.StoreToFloat4(meshInfo.m_irradianceColor.data()); - rotationMatrix.StoreToRowMajorFloat9(meshInfo.m_worldInvTranspose.data()); + // add the baseColor, normal, metallic, and roughness images for this sub-mesh to the material texture list, + // this is sent to the shader as an unbounded array in the Srg + if (RHI::CheckBitsAll(subMesh.m_textureFlags, RayTracingSubMeshTextureFlags::BaseColor)) + { + materialTextures.push_back(subMesh.m_baseColorImageView.get()); + } - meshInfos.emplace_back(meshInfo); + if (RHI::CheckBitsAll(subMesh.m_textureFlags, RayTracingSubMeshTextureFlags::Normal)) + { + materialTextures.push_back(subMesh.m_normalImageView.get()); + } + + if (RHI::CheckBitsAll(subMesh.m_textureFlags, RayTracingSubMeshTextureFlags::Metallic)) + { + materialTextures.push_back(subMesh.m_metallicImageView.get()); + } + + if (RHI::CheckBitsAll(subMesh.m_textureFlags, RayTracingSubMeshTextureFlags::Roughness)) + { + materialTextures.push_back(subMesh.m_roughnessImageView.get()); + } } } - m_meshInfoBuffer->UpdateData(meshInfos.data(), newMeshByteCount); - m_meshInfoBufferNeedsUpdate = false; + RHI::ShaderInputImageUnboundedArrayIndex textureUnboundedArrayIndex = srgLayout->FindShaderInputImageUnboundedArrayIndex(AZ::Name("m_materialTextures")); + m_rayTracingMaterialSrg->SetImageViewUnboundedArray(textureUnboundedArrayIndex, materialTextures); } + + m_rayTracingMaterialSrg->Compile(); } } } diff --git a/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingFeatureProcessor.h b/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingFeatureProcessor.h index f317f1c096..90a9383ae6 100644 --- a/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingFeatureProcessor.h +++ b/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingFeatureProcessor.h @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -23,6 +24,28 @@ namespace AZ { namespace Render { + static const uint32_t RayTracingGlobalSrgBindingSlot = 0; + static const uint32_t RayTracingSceneSrgBindingSlot = 1; + static const uint32_t RayTracingMaterialSrgBindingSlot = 2; + + enum class RayTracingSubMeshBufferFlags : uint32_t + { + None = 0, + + UV = AZ_BIT(0) + }; + AZ_DEFINE_ENUM_BITWISE_OPERATORS(AZ::Render::RayTracingSubMeshBufferFlags); + + enum class RayTracingSubMeshTextureFlags : uint32_t + { + None = 0, + BaseColor = AZ_BIT(0), + Normal = AZ_BIT(1), + Metallic = AZ_BIT(2), + Roughness = AZ_BIT(3) + }; + AZ_DEFINE_ENUM_BITWISE_OPERATORS(AZ::Render::RayTracingSubMeshTextureFlags); + //! This feature processor manages ray tracing data for a Scene class RayTracingFeatureProcessor : public RPI::FeatureProcessor @@ -42,20 +65,53 @@ namespace AZ //! Contains data for a single sub-mesh struct SubMesh { - // vertex/index buffer data - RHI::Format m_vertexFormat = RHI::Format::Unknown; + // vertex streams + RHI::Format m_positionFormat = RHI::Format::Unknown; RHI::StreamBufferView m_positionVertexBufferView; RHI::Ptr m_positionShaderBufferView; + + RHI::Format m_normalFormat = RHI::Format::Unknown; RHI::StreamBufferView m_normalVertexBufferView; RHI::Ptr m_normalShaderBufferView; + + RHI::Format m_tangentFormat = RHI::Format::Unknown; + RHI::StreamBufferView m_tangentVertexBufferView; + RHI::Ptr m_tangentShaderBufferView; + + RHI::Format m_bitangentFormat = RHI::Format::Unknown; + RHI::StreamBufferView m_bitangentVertexBufferView; + RHI::Ptr m_bitangentShaderBufferView; + + RHI::Format m_uvFormat = RHI::Format::Unknown; + RHI::StreamBufferView m_uvVertexBufferView; + RHI::Ptr m_uvShaderBufferView; + + // index buffer RHI::IndexBufferView m_indexBufferView; RHI::Ptr m_indexShaderBufferView; + // vertex buffer usage flags + RayTracingSubMeshBufferFlags m_bufferFlags = RayTracingSubMeshBufferFlags::None; + // color of the bounced light from this sub-mesh - AZ::Color m_irradianceColor; + AZ::Color m_irradianceColor = AZ::Color(1.0f); // ray tracing Blas RHI::Ptr m_blas; + + // material data + AZ::Color m_baseColor = AZ::Color(0.0f); + float m_metallicFactor = 0.0f; + float m_roughnessFactor = 0.0f; + + // material texture usage flags + RayTracingSubMeshTextureFlags m_textureFlags = RayTracingSubMeshTextureFlags::None; + + // material textures + RHI::Ptr m_baseColorImageView; + RHI::Ptr m_normalImageView; + RHI::Ptr m_metallicImageView; + RHI::Ptr m_roughnessImageView; }; using SubMeshVector = AZStd::vector; @@ -98,6 +154,9 @@ namespace AZ //! Retrieves the RayTracingSceneSrg Data::Instance GetRayTracingSceneSrg() const { return m_rayTracingSceneSrg; } + //! Retrieves the RayTracingMaterialSrg + Data::Instance GetRayTracingMaterialSrg() const { return m_rayTracingMaterialSrg; } + //! Retrieves the RayTracingTlas const RHI::Ptr& GetTlas() const { return m_tlas; } RHI::Ptr& GetTlas() { return m_tlas; } @@ -118,14 +177,20 @@ namespace AZ //! Retrieves the GPU buffer containing information for all ray tracing meshes. const Data::Instance GetMeshInfoBuffer() const { return m_meshInfoBuffer; } - //! Updates the RayTracingSceneSrg, called after the TLAS is allocated in the RayTracingAccelerationStructurePass - void UpdateRayTracingSceneSrg(); + //! Retrieves the GPU buffer containing information for all ray tracing materials. + const Data::Instance GetMaterialInfoBuffer() const { return m_materialInfoBuffer; } + + //! Updates the RayTracingSceneSrg and RayTracingMaterialSrg, called after the TLAS is allocated in the RayTracingAccelerationStructurePass + void UpdateRayTracingSrgs(); private: AZ_DISABLE_COPY_MOVE(RayTracingFeatureProcessor); void UpdateMeshInfoBuffer(); + void UpdateMaterialInfoBuffer(); + void UpdateRayTracingSceneSrg(); + void UpdateRayTracingMaterialSrg(); // flag indicating if RayTracing is enabled, currently based on device support bool m_rayTracingEnabled = false; @@ -143,6 +208,9 @@ namespace AZ // ray tracing scene Srg Data::Instance m_rayTracingSceneSrg; + // ray tracing material Srg + Data::Instance m_rayTracingMaterialSrg; + // current revision number of ray tracing data uint32_t m_revision = 0; @@ -158,18 +226,43 @@ namespace AZ // structure for data in the m_meshInfoBuffer, shaders that use the buffer must match this type struct MeshInfo { - uint32_t m_indexOffset; - uint32_t m_positionOffset; - uint32_t m_normalOffset; + uint32_t m_indexOffset; + uint32_t m_positionOffset; + uint32_t m_normalOffset; + uint32_t m_tangentOffset; + uint32_t m_bitangentOffset; + uint32_t m_uvOffset; + float m_padding0[2]; + AZStd::array m_irradianceColor; // float4 AZStd::array m_worldInvTranspose; // float3x3 + float m_padding1; + + RayTracingSubMeshBufferFlags m_bufferFlags = RayTracingSubMeshBufferFlags::None; + uint32_t m_bufferStartIndex = 0; }; // buffer containing a MeshInfo for each sub-mesh Data::Instance m_meshInfoBuffer; - // flag indicating we need to update the mesh info GPU buffer + // structure for data in the m_materialInfoBuffer, shaders that use the buffer must match this type + struct MaterialInfo + { + AZStd::array m_baseColor; // float4 + float m_metallicFactor = 0.0f; + float m_roughnessFactor = 0.0f; + RayTracingSubMeshTextureFlags m_textureFlags = RayTracingSubMeshTextureFlags::None; + uint32_t m_textureStartIndex = 0; + }; + + // buffer containing a MaterialInfo for each sub-mesh + Data::Instance m_materialInfoBuffer; + + // flag indicating we need to update the meshInfo buffer bool m_meshInfoBufferNeedsUpdate = false; + + // flag indicating we need to update the materialInfo buffer + bool m_materialInfoBufferNeedsUpdate = false; }; } } diff --git a/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingPass.cpp b/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingPass.cpp new file mode 100644 index 0000000000..988870cc0e --- /dev/null +++ b/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingPass.cpp @@ -0,0 +1,362 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace AZ +{ + namespace Render + { + RPI::Ptr RayTracingPass::Create(const RPI::PassDescriptor& descriptor) + { + RPI::Ptr pass = aznew RayTracingPass(descriptor); + return pass; + } + + RayTracingPass::RayTracingPass(const RPI::PassDescriptor& descriptor) + : RenderPass(descriptor) + , m_passDescriptor(descriptor) + { + RHI::Ptr device = RHI::RHISystemInterface::Get()->GetDevice(); + if (device->GetFeatures().m_rayTracing == false) + { + // raytracing is not supported on this platform + SetEnabled(false); + return; + } + + Init(); + } + + RayTracingPass::~RayTracingPass() + { + RPI::ShaderReloadNotificationBus::MultiHandler::BusDisconnect(); + } + + void RayTracingPass::Init() + { + RHI::Ptr device = RHI::RHISystemInterface::Get()->GetDevice(); + + m_passData = RPI::PassUtils::GetPassData(m_passDescriptor); + if (m_passData == nullptr) + { + AZ_Error("PassSystem", false, "RayTracingPass [%s]: Invalid RayTracingPassData", GetPathName().GetCStr()); + return; + } + + // ray generation shader + m_rayGenerationShader = LoadShader(m_passData->m_rayGenerationShaderAssetReference); + if (m_rayGenerationShader == nullptr) + { + AZ_Error("PassSystem", false, "RayTracingPass [%s]: Failed to load RayGeneration shader [%s]", GetPathName().GetCStr(), m_passData->m_rayGenerationShaderAssetReference.m_filePath.data()); + return; + } + + auto shaderVariant = m_rayGenerationShader->GetVariant(RPI::ShaderAsset::RootShaderVariantStableId); + RHI::PipelineStateDescriptorForRayTracing rayGenerationShaderDescriptor; + shaderVariant.ConfigurePipelineState(rayGenerationShaderDescriptor); + + // closest hit shader + m_closestHitShader = LoadShader(m_passData->m_closestHitShaderAssetReference); + if (m_closestHitShader == nullptr) + { + AZ_Error("PassSystem", false, "RayTracingPass [%s]: Failed to load ClosestHit shader [%s]", GetPathName().GetCStr(), m_passData->m_closestHitShaderAssetReference.m_filePath.data()); + return; + } + + shaderVariant = m_closestHitShader->GetVariant(RPI::ShaderAsset::RootShaderVariantStableId); + RHI::PipelineStateDescriptorForRayTracing closestHitShaderDescriptor; + shaderVariant.ConfigurePipelineState(closestHitShaderDescriptor); + + // miss shader + m_missShader = LoadShader(m_passData->m_missShaderAssetReference); + if (m_missShader == nullptr) + { + AZ_Error("PassSystem", false, "RayTracingPass [%s]: Failed to load Miss shader [%s]", GetPathName().GetCStr(), m_passData->m_missShaderAssetReference.m_filePath.data()); + return; + } + + shaderVariant = m_missShader->GetVariant(RPI::ShaderAsset::RootShaderVariantStableId); + RHI::PipelineStateDescriptorForRayTracing missShaderDescriptor; + shaderVariant.ConfigurePipelineState(missShaderDescriptor); + + // retrieve global pipeline state + m_globalPipelineState = m_rayGenerationShader->AcquirePipelineState(rayGenerationShaderDescriptor); + AZ_Assert(m_globalPipelineState, "Failed to acquire ray tracing global pipeline state"); + + // create global srg + Data::Asset globalSrgAsset = m_rayGenerationShader->FindShaderResourceGroupAsset(RayTracingGlobalSrgBindingSlot); + AZ_Error("PassSystem", globalSrgAsset.GetId().IsValid(), "RayTracingPass [%s] Failed to find RayTracingGlobalSrg asset", GetPathName().GetCStr()); + AZ_Error("PassSystem", globalSrgAsset.IsReady(), "RayTracingPass [%s] asset is not loaded for shader", GetPathName().GetCStr()); + + m_shaderResourceGroup = RPI::ShaderResourceGroup::Create(globalSrgAsset); + AZ_Assert(m_shaderResourceGroup, "RayTracingPass [%s]: Failed to create RayTracingGlobalSrg", GetPathName().GetCStr()); + RPI::PassUtils::BindDataMappingsToSrg(m_passDescriptor, m_shaderResourceGroup.get()); + + // check to see if the shader requires the View and RayTracingMaterial Srgs + Data::Asset viewSrgAsset = m_rayGenerationShader->FindShaderResourceGroupAsset(RPI::SrgBindingSlot::View); + m_requiresViewSrg = viewSrgAsset.GetId().IsValid(); + + Data::Asset rayTracingMaterialSrgAsset = m_rayGenerationShader->FindShaderResourceGroupAsset(RayTracingMaterialSrgBindingSlot); + m_requiresRayTracingMaterialSrg = rayTracingMaterialSrgAsset.GetId().IsValid(); + + // build the ray tracing pipeline state descriptor + RHI::RayTracingPipelineStateDescriptor descriptor; + descriptor.Build() + ->PipelineState(m_globalPipelineState.get()) + ->MaxPayloadSize(m_passData->m_maxPayloadSize) + ->MaxAttributeSize(m_passData->m_maxAttributeSize) + ->MaxRecursionDepth(m_passData->m_maxRecursionDepth) + ->ShaderLibrary(rayGenerationShaderDescriptor) + ->RayGenerationShaderName(AZ::Name(m_passData->m_rayGenerationShaderName.c_str())) + ->ShaderLibrary(missShaderDescriptor) + ->MissShaderName(AZ::Name(m_passData->m_missShaderName.c_str())) + ->ShaderLibrary(closestHitShaderDescriptor) + ->ClosestHitShaderName(AZ::Name(m_passData->m_closestHitShaderName.c_str())) + ->HitGroup(AZ::Name("HitGroup")) + ->ClosestHitShaderName(AZ::Name(m_passData->m_closestHitShaderName.c_str())); + + // create the ray tracing pipeline state object + m_rayTracingPipelineState = RHI::Factory::Get().CreateRayTracingPipelineState(); + m_rayTracingPipelineState->Init(*device.get(), &descriptor); + + // make sure the shader table rebuilds if we're hotreloading + m_rayTracingRevision = 0; + + RPI::ShaderReloadNotificationBus::MultiHandler::BusDisconnect(); + RPI::ShaderReloadNotificationBus::MultiHandler::BusConnect(m_passData->m_rayGenerationShaderAssetReference.m_assetId); + RPI::ShaderReloadNotificationBus::MultiHandler::BusConnect(m_passData->m_closestHitShaderAssetReference.m_assetId); + RPI::ShaderReloadNotificationBus::MultiHandler::BusConnect(m_passData->m_missShaderAssetReference.m_assetId); + } + + Data::Instance RayTracingPass::LoadShader(const RPI::AssetReference& shaderAssetReference) + { + Data::Asset shaderAsset; + if (shaderAssetReference.m_assetId.IsValid()) + { + shaderAsset = RPI::FindShaderAsset(shaderAssetReference.m_assetId, shaderAssetReference.m_filePath); + } + + if (!shaderAsset.GetId().IsValid()) + { + AZ_Error("PassSystem", false, "RayTracingPass [%s]: Failed to load shader asset [%s]", GetPathName().GetCStr(), shaderAssetReference.m_filePath.data()); + return nullptr; + } + + return RPI::Shader::FindOrCreate(shaderAsset); + } + + void RayTracingPass::FrameBeginInternal(FramePrepareParams params) + { + RPI::Scene* scene = m_pipeline->GetScene(); + RayTracingFeatureProcessor* rayTracingFeatureProcessor = scene->GetFeatureProcessor(); + if (!rayTracingFeatureProcessor) + { + return; + } + + if (!m_rayTracingShaderTable) + { + RHI::Ptr device = RHI::RHISystemInterface::Get()->GetDevice(); + RHI::RayTracingBufferPools& rayTracingBufferPools = rayTracingFeatureProcessor->GetBufferPools(); + + m_rayTracingShaderTable = RHI::Factory::Get().CreateRayTracingShaderTable(); + m_rayTracingShaderTable->Init(*device.get(), rayTracingBufferPools); + } + + RPI::RenderPass::FrameBeginInternal(params); + } + + void RayTracingPass::SetupFrameGraphDependencies(RHI::FrameGraphInterface frameGraph) + { + RPI::Scene* scene = m_pipeline->GetScene(); + RayTracingFeatureProcessor* rayTracingFeatureProcessor = scene->GetFeatureProcessor(); + AZ_Assert(rayTracingFeatureProcessor, "RayTracingPass requires the RayTracingFeatureProcessor"); + + RPI::RenderPass::SetupFrameGraphDependencies(frameGraph); + frameGraph.SetEstimatedItemCount(1); + + // TLAS + { + const RHI::Ptr& rayTracingTlasBuffer = rayTracingFeatureProcessor->GetTlas()->GetTlasBuffer(); + if (rayTracingTlasBuffer) + { + AZ::RHI::AttachmentId tlasAttachmentId = rayTracingFeatureProcessor->GetTlasAttachmentId(); + if (frameGraph.GetAttachmentDatabase().IsAttachmentValid(tlasAttachmentId) == false) + { + [[maybe_unused]] RHI::ResultCode result = frameGraph.GetAttachmentDatabase().ImportBuffer(tlasAttachmentId, rayTracingTlasBuffer); + AZ_Assert(result == RHI::ResultCode::Success, "Failed to import ray tracing TLAS buffer with error %d", result); + } + + uint32_t tlasBufferByteCount = aznumeric_cast(rayTracingFeatureProcessor->GetTlas()->GetTlasBuffer()->GetDescriptor().m_byteCount); + RHI::BufferViewDescriptor tlasBufferViewDescriptor = RHI::BufferViewDescriptor::CreateRaw(0, tlasBufferByteCount); + + RHI::BufferScopeAttachmentDescriptor desc; + desc.m_attachmentId = tlasAttachmentId; + desc.m_bufferViewDescriptor = tlasBufferViewDescriptor; + desc.m_loadStoreAction.m_loadAction = AZ::RHI::AttachmentLoadAction::Load; + + frameGraph.UseShaderAttachment(desc, RHI::ScopeAttachmentAccess::ReadWrite); + } + } + } + + void RayTracingPass::CompileResources(const RHI::FrameGraphCompileContext& context) + { + RPI::Scene* scene = m_pipeline->GetScene(); + RayTracingFeatureProcessor* rayTracingFeatureProcessor = scene->GetFeatureProcessor(); + AZ_Assert(rayTracingFeatureProcessor, "RayTracingPass requires the RayTracingFeatureProcessor"); + + if (m_shaderResourceGroup != nullptr) + { + BindPassSrg(context, m_shaderResourceGroup); + m_shaderResourceGroup->Compile(); + } + + uint32_t rayTracingRevision = rayTracingFeatureProcessor->GetRevision(); + if (m_rayTracingRevision != rayTracingRevision) + { + // scene changed, need to rebuild the shader table + m_rayTracingRevision = rayTracingRevision; + + AZStd::shared_ptr descriptor = AZStd::make_shared(); + + if (rayTracingFeatureProcessor->GetSubMeshCount()) + { + // build the ray tracing shader table descriptor + RHI::RayTracingShaderTableDescriptor* descriptorBuild = descriptor->Build(AZ::Name("RayTracingShaderTable"), m_rayTracingPipelineState) + ->RayGenerationRecord(AZ::Name(m_passData->m_rayGenerationShaderName.c_str())) + ->MissRecord(AZ::Name(m_passData->m_missShaderName.c_str())); + + // add a hit group for each mesh to the shader table + for (uint32_t i = 0; i < rayTracingFeatureProcessor->GetSubMeshCount(); ++i) + { + descriptorBuild->HitGroupRecord(AZ::Name("HitGroup")); + } + } + + m_rayTracingShaderTable->Build(descriptor); + } + } + + void RayTracingPass::BuildCommandListInternal(const RHI::FrameGraphExecuteContext& context) + { + RPI::Scene* scene = m_pipeline->GetScene(); + RayTracingFeatureProcessor* rayTracingFeatureProcessor = scene->GetFeatureProcessor(); + AZ_Assert(rayTracingFeatureProcessor, "RayTracingPass requires the RayTracingFeatureProcessor"); + + if (!rayTracingFeatureProcessor || + !rayTracingFeatureProcessor->GetTlas()->GetTlasBuffer() || + !rayTracingFeatureProcessor->GetSubMeshCount() || + !m_rayTracingShaderTable) + { + return; + } + + RHI::DispatchRaysItem dispatchRaysItem; + + // calculate thread counts if this is a full screen raytracing pass + if (m_passData->m_makeFullscreenPass) + { + RPI::PassAttachment* outputAttachment = nullptr; + + if (GetOutputCount() > 0) + { + outputAttachment = GetOutputBinding(0).m_attachment.get(); + } + else if (GetInputOutputCount() > 0) + { + outputAttachment = GetInputOutputBinding(0).m_attachment.get(); + } + + AZ_Assert(outputAttachment != nullptr, "[RayTracingPass '%s']: A fullscreen RayTracing pass must have a valid output or input/output.", GetPathName().GetCStr()); + AZ_Assert(outputAttachment->GetAttachmentType() == RHI::AttachmentType::Image, "[RayTracingPass '%s']: The output of a fullscreen RayTracing pass must be an image.", GetPathName().GetCStr()); + + RHI::Size imageSize = outputAttachment->m_descriptor.m_image.m_size; + + dispatchRaysItem.m_width = imageSize.m_width; + dispatchRaysItem.m_height = imageSize.m_height; + dispatchRaysItem.m_depth = imageSize.m_depth; + } + else + { + dispatchRaysItem.m_width = m_passData->m_threadCountX; + dispatchRaysItem.m_height = m_passData->m_threadCountY; + dispatchRaysItem.m_depth = m_passData->m_threadCountZ; + } + + // bind RayTracingGlobal, RayTracingScene, and View Srgs + // [GFX TODO][ATOM-15610] Add RenderPass::SetSrgsForRayTracingDispatch + AZStd::vector shaderResourceGroups = + { + m_shaderResourceGroup->GetRHIShaderResourceGroup(), + rayTracingFeatureProcessor->GetRayTracingSceneSrg()->GetRHIShaderResourceGroup() + }; + + if (m_requiresViewSrg) + { + const AZStd::vector& views = m_pipeline->GetViews(m_passData->m_pipelineViewTag); + if (views.size() > 0) + { + shaderResourceGroups.push_back(views[0]->GetRHIShaderResourceGroup()); + } + } + + if (m_requiresRayTracingMaterialSrg) + { + shaderResourceGroups.push_back(rayTracingFeatureProcessor->GetRayTracingMaterialSrg()->GetRHIShaderResourceGroup()); + } + + dispatchRaysItem.m_shaderResourceGroupCount = aznumeric_cast(shaderResourceGroups.size()); + dispatchRaysItem.m_shaderResourceGroups = shaderResourceGroups.data(); + dispatchRaysItem.m_rayTracingPipelineState = m_rayTracingPipelineState.get(); + dispatchRaysItem.m_rayTracingShaderTable = m_rayTracingShaderTable.get(); + dispatchRaysItem.m_globalPipelineState = m_globalPipelineState.get(); + + // submit the DispatchRays item + context.GetCommandList()->Submit(dispatchRaysItem); + } + + void RayTracingPass::OnShaderReinitialized([[maybe_unused]] const RPI::Shader& shader) + { + Init(); + } + + void RayTracingPass::OnShaderAssetReinitialized([[maybe_unused]] const Data::Asset& shaderAsset) + { + Init(); + } + + void RayTracingPass::OnShaderVariantReinitialized([[maybe_unused]] const RPI::Shader& shader, [[maybe_unused]] const RPI::ShaderVariantId& shaderVariantId, [[maybe_unused]] RPI::ShaderVariantStableId shaderVariantStableId) + { + Init(); + } + } // namespace RPI +} // namespace AZ diff --git a/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingPass.h b/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingPass.h new file mode 100644 index 0000000000..6ad082e894 --- /dev/null +++ b/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingPass.h @@ -0,0 +1,82 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace AZ +{ + namespace Render + { + struct RayTracingPassData; + + //! This pass executes a raytracing shader as specified in the PassData. + class RayTracingPass + : public RPI::RenderPass + , private RPI::ShaderReloadNotificationBus::MultiHandler + { + AZ_RPI_PASS(RayTracingPass); + + public: + AZ_RTTI(RayTracingPass, "{7A68A36E-956A-4258-93FE-38686042C4D9}", RPI::RenderPass); + AZ_CLASS_ALLOCATOR(RayTracingPass, SystemAllocator, 0); + virtual ~RayTracingPass(); + + //! Creates a RayTracingPass + static RPI::Ptr Create(const RPI::PassDescriptor& descriptor); + + protected: + RayTracingPass(const RPI::PassDescriptor& descriptor); + + // Pass overrides + void FrameBeginInternal(FramePrepareParams params) override; + + // Scope producer functions + void SetupFrameGraphDependencies(RHI::FrameGraphInterface frameGraph) override; + void CompileResources(const RHI::FrameGraphCompileContext& context) override; + void BuildCommandListInternal(const RHI::FrameGraphExecuteContext& context) override; + + // ShaderReloadNotificationBus::Handler overrides + void OnShaderReinitialized(const RPI::Shader& shader) override; + void OnShaderAssetReinitialized(const Data::Asset& shaderAsset) override; + void OnShaderVariantReinitialized(const RPI::Shader& shader, const RPI::ShaderVariantId& shaderVariantId, RPI::ShaderVariantStableId shaderVariantStableId) override; + + // load the raytracing shaders and setup pipeline states + void Init(); + + // helper for loading a shader from a shader asset reference + Data::Instance LoadShader(const RPI::AssetReference& shaderAssetReference); + + // pass data + RPI::PassDescriptor m_passDescriptor; + const RayTracingPassData* m_passData = nullptr; + + // revision number of the ray tracing TLAS when the shader table was built + uint32_t m_rayTracingRevision = 0; + + // raytracing shaders, pipeline states, and shader table + Data::Instance m_rayGenerationShader; + Data::Instance m_missShader; + Data::Instance m_closestHitShader; + RHI::Ptr m_rayTracingPipelineState; + RHI::ConstPtr m_globalPipelineState; + RHI::Ptr m_rayTracingShaderTable; + bool m_requiresViewSrg = false; + bool m_requiresRayTracingMaterialSrg = false; + }; + } // namespace RPI +} // namespace AZ diff --git a/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingPassData.h b/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingPassData.h new file mode 100644 index 0000000000..bc15a8372c --- /dev/null +++ b/Gems/Atom/Feature/Common/Code/Source/RayTracing/RayTracingPassData.h @@ -0,0 +1,73 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ +#pragma once + +#include +#include + +namespace AZ +{ + namespace Render + { + //! Custom data for the RayTracingPass, specified in the PassRequest. + struct RayTracingPassData + : public RPI::RenderPassData + { + AZ_RTTI(RayTracingPassData, "{26C2E2FD-D30A-4142-82A3-0167BC94B3EE}", RPI::RenderPassData); + AZ_CLASS_ALLOCATOR(RayTracingPassData, SystemAllocator, 0); + + RayTracingPassData() = default; + virtual ~RayTracingPassData() = default; + + static void Reflect(ReflectContext* context) + { + if (auto* serializeContext = azrtti_cast(context)) + { + serializeContext->Class() + ->Version(1) + ->Field("RayGenerationShaderAsset", &RayTracingPassData::m_rayGenerationShaderAssetReference) + ->Field("RayGenerationShaderName", &RayTracingPassData::m_rayGenerationShaderName) + ->Field("ClosestHitShaderAsset", &RayTracingPassData::m_closestHitShaderAssetReference) + ->Field("ClosestHitShaderName", &RayTracingPassData::m_closestHitShaderName) + ->Field("MissShaderAsset", &RayTracingPassData::m_missShaderAssetReference) + ->Field("MissShaderName", &RayTracingPassData::m_missShaderName) + ->Field("MaxPayloadSize", &RayTracingPassData::m_maxPayloadSize) + ->Field("MaxAttributeSize", &RayTracingPassData::m_maxAttributeSize) + ->Field("MaxRecursionDepth", &RayTracingPassData::m_maxRecursionDepth) + ->Field("Thread Count X", &RayTracingPassData::m_threadCountX) + ->Field("Thread Count Y", &RayTracingPassData::m_threadCountY) + ->Field("Thread Count Z", &RayTracingPassData::m_threadCountZ) + ->Field("Make Fullscreen Pass", &RayTracingPassData::m_makeFullscreenPass) + ; + } + } + + RPI::AssetReference m_rayGenerationShaderAssetReference; + AZStd::string m_rayGenerationShaderName; + RPI::AssetReference m_closestHitShaderAssetReference; + AZStd::string m_closestHitShaderName; + RPI::AssetReference m_missShaderAssetReference; + AZStd::string m_missShaderName; + + uint32_t m_maxPayloadSize = 64; + uint32_t m_maxAttributeSize = 32; + uint32_t m_maxRecursionDepth = 1; + + uint32_t m_threadCountX = 1; + uint32_t m_threadCountY = 1; + uint32_t m_threadCountZ = 1; + + bool m_makeFullscreenPass = false; + }; + } // namespace RPI +} // namespace AZ + diff --git a/Gems/Atom/Feature/Common/Code/atom_feature_common_files.cmake b/Gems/Atom/Feature/Common/Code/atom_feature_common_files.cmake index 4f98046adb..76b71e5fae 100644 --- a/Gems/Atom/Feature/Common/Code/atom_feature_common_files.cmake +++ b/Gems/Atom/Feature/Common/Code/atom_feature_common_files.cmake @@ -250,6 +250,9 @@ set(FILES Source/RayTracing/RayTracingFeatureProcessor.cpp Source/RayTracing/RayTracingAccelerationStructurePass.cpp Source/RayTracing/RayTracingAccelerationStructurePass.h + Source/RayTracing/RayTracingPass.cpp + Source/RayTracing/RayTracingPass.h + Source/RayTracing/RayTracingPassData.h Source/ReflectionProbe/ReflectionProbeFeatureProcessor.cpp Source/ReflectionProbe/ReflectionProbe.cpp Source/ReflectionScreenSpace/ReflectionScreenSpaceBlurPass.cpp diff --git a/Gems/Atom/Feature/Common/Code/atom_feature_common_staticlibrary_files.cmake b/Gems/Atom/Feature/Common/Code/atom_feature_common_staticlibrary_files.cmake index 553f307409..285659ab82 100644 --- a/Gems/Atom/Feature/Common/Code/atom_feature_common_staticlibrary_files.cmake +++ b/Gems/Atom/Feature/Common/Code/atom_feature_common_staticlibrary_files.cmake @@ -16,6 +16,8 @@ set(FILES Include/Atom/Feature/Utils/ModelPreset.h Source/Material/MaterialAssignment.cpp Source/Material/MaterialAssignmentId.cpp + Source/Material/MaterialAssignmentSerializer.cpp + Source/Material/MaterialAssignmentSerializer.h Source/Utils/LightingPreset.cpp Source/Utils/ModelPreset.cpp ) diff --git a/Gems/Atom/Feature/Common/gem.json b/Gems/Atom/Feature/Common/gem.json new file mode 100644 index 0000000000..6980863b4c --- /dev/null +++ b/Gems/Atom/Feature/Common/gem.json @@ -0,0 +1,10 @@ +{ + "gem_name": "Atom_Feature_Common", + "display_name": "Atom Feature Common", + "summary": "", + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + ] +} diff --git a/Gems/Atom/RHI/DX12/gem.json b/Gems/Atom/RHI/DX12/gem.json new file mode 100644 index 0000000000..683ccfb43a --- /dev/null +++ b/Gems/Atom/RHI/DX12/gem.json @@ -0,0 +1,10 @@ +{ + "gem_name": "Atom_RHI_DX12", + "display_name": "Atom RHI DX12", + "summary": "", + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + ] +} diff --git a/Gems/Atom/RHI/Metal/gem.json b/Gems/Atom/RHI/Metal/gem.json new file mode 100644 index 0000000000..3e1726e8fa --- /dev/null +++ b/Gems/Atom/RHI/Metal/gem.json @@ -0,0 +1,10 @@ +{ + "gem_name": "Atom_RHI_Metal", + "display_name": "Atom RHI Metal", + "summary": "", + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + ] +} diff --git a/Gems/Atom/RHI/Null/gem.json b/Gems/Atom/RHI/Null/gem.json new file mode 100644 index 0000000000..4fa5f1e480 --- /dev/null +++ b/Gems/Atom/RHI/Null/gem.json @@ -0,0 +1,10 @@ +{ + "gem_name": "Atom_RHI_Null", + "display_name": "Atom RHI Null", + "summary": "", + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + ] +} diff --git a/Gems/Atom/RHI/Vulkan/External/glad/2.0.0-beta/include/glad/license.txt b/Gems/Atom/RHI/Vulkan/External/glad/2.0.0-beta/include/glad/license.txt new file mode 100644 index 0000000000..158138973a --- /dev/null +++ b/Gems/Atom/RHI/Vulkan/External/glad/2.0.0-beta/include/glad/license.txt @@ -0,0 +1,209 @@ +vulkan.h was generated using a code generator from https://github.com/Dav1dde/glad + +/* +** Copyright (c) 2014-2020 The Khronos Group Inc. +** +** SPDX-License-Identifier: Apache-2.0 +*/ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Gems/Atom/RHI/Vulkan/gem.json b/Gems/Atom/RHI/Vulkan/gem.json new file mode 100644 index 0000000000..1f2fcd7f30 --- /dev/null +++ b/Gems/Atom/RHI/Vulkan/gem.json @@ -0,0 +1,10 @@ +{ + "gem_name": "Atom_RHI_Vulkan", + "display_name": "Atom RHI Vulkan", + "summary": "", + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + ] +} diff --git a/Gems/Atom/RHI/gem.json b/Gems/Atom/RHI/gem.json new file mode 100644 index 0000000000..eb67e40a4a --- /dev/null +++ b/Gems/Atom/RHI/gem.json @@ -0,0 +1,10 @@ +{ + "gem_name": "Atom_RHI", + "display_name": "Atom RHI", + "summary": "", + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + ] +} diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialSourceData.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialSourceData.h index 9baf80bae0..c8b406f94b 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialSourceData.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialSourceData.h @@ -32,7 +32,7 @@ namespace AZ //! In the source data, properties and UV names are loaded separately. //! However, treating UV names as a special property group can greatly simplify the editor code. //! See MaterialInspector::AddUvNamesGroup() for more details. - static constexpr const char UvGroupName[] = "UvNames"; + static constexpr const char UvGroupName[] = "uvSets"; class MaterialAsset; diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/DynamicDraw/DynamicDrawContext.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/DynamicDraw/DynamicDrawContext.h index 2dd0865688..81b23068a1 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/DynamicDraw/DynamicDrawContext.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/DynamicDraw/DynamicDrawContext.h @@ -43,7 +43,7 @@ namespace AZ { PrimitiveType = AZ_BIT(0), DepthState = AZ_BIT(1), - EnableStencil = AZ_BIT(2), + StencilState = AZ_BIT(2), FaceCullMode = AZ_BIT(3), BlendMode = AZ_BIT(4) }; @@ -110,8 +110,8 @@ namespace AZ //! Set DepthState if DrawStateOptions::DepthState option is enabled void SetDepthState(RHI::DepthState depthState); - //! Enable/disable stencil if DrawStateOptions::EnableStencil option is enabled - void SetEnableStencil(bool enable); + //! Set StencilState if DrawStateOptions::StencilState option is enabled + void SetStencilState(RHI::StencilState stencilState); //! Set CullMode if DrawStateOptions::FaceCullMode option is enabled void SetCullMode(RHI::CullMode cullMode); //! Set TargetBlendState for target 0 if DrawStateOptions::BlendMode option is enabled @@ -188,7 +188,7 @@ namespace AZ // states available for change RHI::CullMode m_cullMode; RHI::DepthState m_depthState; - bool m_enableStencil; + RHI::StencilState m_stencilState; RHI::PrimitiveTopology m_topology; RHI::TargetBlendState m_blendState0; diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Base.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Base.h index c376112290..52b75c4157 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Base.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Base.h @@ -59,7 +59,10 @@ namespace AZ static constexpr uint32_t Draw = 0; static constexpr uint32_t Object = 1; static constexpr uint32_t Material = 2; + static constexpr uint32_t SubPass = 3; static constexpr uint32_t Pass = 4; + static constexpr uint32_t View = 5; + static constexpr uint32_t Scene = 6; }; } } diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/ModelKdTree.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/ModelKdTree.h index 9cd33eda6c..832681d7f1 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/ModelKdTree.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/ModelKdTree.h @@ -35,7 +35,15 @@ namespace AZ ModelKdTree() = default; bool Build(const ModelAsset* model); - bool RayIntersection(const AZ::Vector3& raySrc, const AZ::Vector3& rayDir, float& distance, AZ::Vector3& normal) const; + //! Return if a ray intersected the model. + //! @param raySrc The starting point of the ray. + //! @param rayDir The direction and length of the ray (magnitude is encoded in the direction). + //! @param[out] The normalized distance of the intersection (in the range 0.0-1.0) - to calculate the actual + //! distance, multiply distanceNormalized by the magnitude of rayDir. + //! @param[out] The surface normal of the intersection with the model. + //! @return Return true if there was an intersection with the model, false otherwise. + bool RayIntersection( + const AZ::Vector3& raySrc, const AZ::Vector3& rayDir, float& distanceNormalized, AZ::Vector3& normal) const; void GetPenetratedBoxes(const AZ::Vector3& raySrc, const AZ::Vector3& rayDir, AZStd::vector& outBoxes); enum ESplitAxis @@ -53,8 +61,14 @@ namespace AZ private: void BuildRecursively(ModelKdTreeNode* pNode, const AZ::Aabb& boundbox, AZStd::vector& indices); - bool RayIntersectionRecursively(ModelKdTreeNode* pNode, const AZ::Vector3& raySrc, const AZ::Vector3& rayDir, float& distance, AZ::Vector3& normal) const; - void GetPenetratedBoxesRecursively(ModelKdTreeNode* pNode, const AZ::Vector3& raySrc, const AZ::Vector3& rayDir, AZStd::vector& outBoxes); + bool RayIntersectionRecursively( + ModelKdTreeNode* pNode, + const AZ::Vector3& raySrc, + const AZ::Vector3& rayDir, + float& distanceNormalized, + AZ::Vector3& normal) const; + void GetPenetratedBoxesRecursively( + ModelKdTreeNode* pNode, const AZ::Vector3& raySrc, const AZ::Vector3& rayDir, AZStd::vector& outBoxes); void ConstructMeshList(const ModelAsset* model, const AZ::Transform& matParent); static const int s_MinimumVertexSizeInLeafNode = 3 * 10; diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/DynamicDraw/DynamicDrawContext.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/DynamicDraw/DynamicDrawContext.cpp index 4e4a7a5e71..00c2122825 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/DynamicDraw/DynamicDrawContext.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/DynamicDraw/DynamicDrawContext.cpp @@ -30,25 +30,7 @@ namespace AZ constexpr const char* PerContextSrgName = "PerContextSrg"; constexpr const char* PerDrawSrgName = "PerDrawSrg"; }; - - bool CompareTargetBlendState(const RHI::TargetBlendState& firstState, const RHI::TargetBlendState& secondState) - { - return !(firstState.m_enable != secondState.m_enable - || firstState.m_blendOp != secondState.m_blendOp - || firstState.m_blendDest != secondState.m_blendDest - || firstState.m_blendSource != secondState.m_blendSource - || firstState.m_blendAlphaDest != secondState.m_blendAlphaDest - || firstState.m_blendAlphaOp != secondState.m_blendAlphaOp - || firstState.m_blendAlphaSource != secondState.m_blendAlphaSource); - } - - bool CompareDepthState(const RHI::DepthState& firstState, const RHI::DepthState& secondState) - { - return !(firstState.m_enable != secondState.m_enable - || firstState.m_func != secondState.m_func - || firstState.m_writeMask != secondState.m_writeMask); - } - + void DynamicDrawContext::MultiStates::UpdateHash(const DrawStateOptions& drawStateOptions) { if (!m_isDirty) @@ -70,9 +52,19 @@ namespace AZ seed = TypeHash64(m_depthState.m_writeMask, seed); } - if (RHI::CheckBitsAny(drawStateOptions, DrawStateOptions::EnableStencil)) + if (RHI::CheckBitsAny(drawStateOptions, DrawStateOptions::StencilState)) { - seed = TypeHash64(m_enableStencil, seed); + seed = TypeHash64(m_stencilState.m_enable, seed); + seed = TypeHash64(m_stencilState.m_readMask, seed); + seed = TypeHash64(m_stencilState.m_writeMask, seed); + seed = TypeHash64(m_stencilState.m_frontFace.m_failOp, seed); + seed = TypeHash64(m_stencilState.m_frontFace.m_depthFailOp, seed); + seed = TypeHash64(m_stencilState.m_frontFace.m_passOp, seed); + seed = TypeHash64(m_stencilState.m_frontFace.m_func, seed); + seed = TypeHash64(m_stencilState.m_backFace.m_failOp, seed); + seed = TypeHash64(m_stencilState.m_backFace.m_depthFailOp, seed); + seed = TypeHash64(m_stencilState.m_backFace.m_passOp, seed); + seed = TypeHash64(m_stencilState.m_backFace.m_func, seed); } if (RHI::CheckBitsAny(drawStateOptions, DrawStateOptions::FaceCullMode)) @@ -203,7 +195,7 @@ namespace AZ m_currentStates.m_cullMode = m_pipelineState->ConstDescriptor().m_renderStates.m_rasterState.m_cullMode; m_currentStates.m_topology = m_pipelineState->ConstDescriptor().m_inputStreamLayout.GetTopology(); m_currentStates.m_depthState = m_pipelineState->ConstDescriptor().m_renderStates.m_depthStencilState.m_depth; - m_currentStates.m_enableStencil = m_pipelineState->ConstDescriptor().m_renderStates.m_depthStencilState.m_stencil.m_enable; + m_currentStates.m_stencilState = m_pipelineState->ConstDescriptor().m_renderStates.m_depthStencilState.m_stencil; m_currentStates.m_blendState0 = m_pipelineState->ConstDescriptor().m_renderStates.m_blendState.m_targets[0]; m_currentStates.UpdateHash(m_drawStateOptions); @@ -291,7 +283,7 @@ namespace AZ { if (RHI::CheckBitsAny(m_drawStateOptions, DrawStateOptions::DepthState)) { - if (!CompareDepthState(m_currentStates.m_depthState, depthState)) + if (!(m_currentStates.m_depthState == depthState)) { m_currentStates.m_depthState = depthState; m_currentStates.m_isDirty = true; @@ -303,19 +295,19 @@ namespace AZ } } - void DynamicDrawContext::SetEnableStencil(bool enable) + void DynamicDrawContext::SetStencilState(RHI::StencilState stencilState) { - if (RHI::CheckBitsAny(m_drawStateOptions, DrawStateOptions::EnableStencil)) + if (RHI::CheckBitsAny(m_drawStateOptions, DrawStateOptions::StencilState)) { - if (m_currentStates.m_enableStencil != enable) + if (!(m_currentStates.m_stencilState == stencilState)) { - m_currentStates.m_enableStencil = enable; + m_currentStates.m_stencilState = stencilState; m_currentStates.m_isDirty = true; } } else { - AZ_Warning("RHI", false, "Can't set SetEnableStencil if DrawVariation::EnableStencil wasn't enabled"); + AZ_Warning("RHI", false, "Can't set SetStencilState if DrawVariation::StencilState wasn't enabled"); } } @@ -340,7 +332,7 @@ namespace AZ { if (RHI::CheckBitsAny(m_drawStateOptions, DrawStateOptions::BlendMode)) { - if (!CompareTargetBlendState(m_currentStates.m_blendState0, blendState)) + if (!(m_currentStates.m_blendState0 == blendState)) { m_currentStates.m_blendState0 = blendState; m_currentStates.m_isDirty = true; @@ -695,9 +687,9 @@ namespace AZ { m_pipelineState->RenderStatesOverlay().m_depthStencilState.m_depth = m_currentStates.m_depthState; } - if (RHI::CheckBitsAny(m_drawStateOptions, DrawStateOptions::EnableStencil)) + if (RHI::CheckBitsAny(m_drawStateOptions, DrawStateOptions::StencilState)) { - m_pipelineState->RenderStatesOverlay().m_depthStencilState.m_stencil.m_enable = m_currentStates.m_enableStencil; + m_pipelineState->RenderStatesOverlay().m_depthStencilState.m_stencil = m_currentStates.m_stencilState; } if (RHI::CheckBitsAny(m_drawStateOptions, DrawStateOptions::FaceCullMode)) { diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Model/Model.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Model/Model.cpp index 15c8aaf528..86477bf785 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Model/Model.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Model/Model.cpp @@ -140,8 +140,9 @@ namespace AZ bool Model::LocalRayIntersection(const AZ::Vector3& rayStart, const AZ::Vector3& dir, float& distance, AZ::Vector3& normal) const { AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender); - float firstHit; - const int result = Intersect::IntersectRayAABB2(rayStart, dir.GetReciprocal(), m_aabb, firstHit, distance); + float start; + float end; + const int result = Intersect::IntersectRayAABB2(rayStart, dir.GetReciprocal(), m_aabb, start, end); if (Intersect::ISECT_RAY_AABB_NONE != result) { if (ModelAsset* modelAssetPtr = m_modelAsset.Get()) @@ -164,7 +165,9 @@ namespace AZ return false; } - bool Model::RayIntersection(const AZ::Transform& modelTransform, const AZ::Vector3& nonUniformScale, const AZ::Vector3& rayStart, const AZ::Vector3& dir, float& distanceFactor, AZ::Vector3& normal) const + bool Model::RayIntersection( + const AZ::Transform& modelTransform, const AZ::Vector3& nonUniformScale, const AZ::Vector3& rayStart, const AZ::Vector3& dir, + float& distanceFactor, AZ::Vector3& normal) const { AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender); const AZ::Vector3 clampedScale = nonUniformScale.GetMax(AZ::Vector3(AZ::MinTransformScale)); diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelKdTree.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelKdTree.cpp index 6a1897815f..bee489c2fd 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelKdTree.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelKdTree.cpp @@ -11,6 +11,7 @@ */ #include +#include #include #include @@ -191,10 +192,10 @@ namespace AZ if (ModelLodAsset* lodAssetPtr = model->GetLodAssets()[0].Get()) { - AZ_Warning("ModelKdTree", lodAssetPtr->GetMeshes().size() <= std::numeric_limits::max() + 1, + AZ_Warning("ModelKdTree", lodAssetPtr->GetMeshes().size() <= AZStd::numeric_limits::max() + 1, "KdTree generation doesn't support models with greater than 256 meshes. RayIntersection results will be incorrect " "unless the meshes are merged or broken up into multiple models"); - const size_t size = AZStd::min(lodAssetPtr->GetMeshes().size(), std::numeric_limits::max() + 1); + const size_t size = AZStd::min(lodAssetPtr->GetMeshes().size(), AZStd::numeric_limits::max() + 1); m_meshes.reserve(size); AZStd::transform( lodAssetPtr->GetMeshes().begin(), AZStd::next(lodAssetPtr->GetMeshes().begin(), size), @@ -204,20 +205,42 @@ namespace AZ } } - bool ModelKdTree::RayIntersection(const AZ::Vector3& raySrc, const AZ::Vector3& rayDir, float& distance, AZ::Vector3& normal) const + bool ModelKdTree::RayIntersection( + const AZ::Vector3& raySrc, const AZ::Vector3& rayDir, float& distanceNormalized, AZ::Vector3& normal) const { - return RayIntersectionRecursively(m_pRootNode.get(), raySrc, rayDir, distance, normal); + float closestDistanceNormalized = AZStd::numeric_limits::max(); + if (RayIntersectionRecursively(m_pRootNode.get(), raySrc, rayDir, closestDistanceNormalized, normal)) + { + distanceNormalized = closestDistanceNormalized; + return true; + } + + return false; } - bool ModelKdTree::RayIntersectionRecursively(ModelKdTreeNode* pNode, const AZ::Vector3& raySrc, const AZ::Vector3& rayDir, float& distance, AZ::Vector3& normal) const + bool ModelKdTree::RayIntersectionRecursively( + ModelKdTreeNode* pNode, + const AZ::Vector3& raySrc, + const AZ::Vector3& rayDir, + float& distanceNormalized, + AZ::Vector3& normal) const { + using Intersect::IntersectRayAABB2; + using Intersect::IntersectSegmentTriangleCCW; + using Intersect::ISECT_RAY_AABB_NONE; + if (!pNode) { return false; } float start, end; - if (AZ::Intersect::IntersectRayAABB2(raySrc, rayDir.GetReciprocal(), pNode->GetBoundBox(), start, end) == Intersect::ISECT_RAY_AABB_NONE) + if (IntersectRayAABB2(raySrc, rayDir.GetReciprocal(), pNode->GetBoundBox(), start, end) == ISECT_RAY_AABB_NONE) + { + return false; + } + + if (start > distanceNormalized) { return false; } @@ -235,17 +258,13 @@ namespace AZ return false; } - AZ::Vector3 intersectionNormal; - float hitDistanceNormalized; - const float maxDist(FLT_MAX); - float nearestDist = maxDist; - + float nearestDistanceNormalized = distanceNormalized; for (AZ::u32 i = 0; i < nVBuffSize; ++i) { const auto& [first, second, third] = pNode->GetVertexIndex(i); const AZ::u32 nObjIndex = pNode->GetObjIndex(i); - AZStd::array_view positionBuffer = m_meshes[nObjIndex].m_vertexData; + const AZStd::array_view positionBuffer = m_meshes[nObjIndex].m_vertexData; if (positionBuffer.empty()) { @@ -258,25 +277,23 @@ namespace AZ AZ::Vector3{positionBuffer[third * 3 + 0], positionBuffer[third * 3 + 1], positionBuffer[third * 3 + 2]}, }; - const AZ::Vector3 rayEnd = raySrc + rayDir * distance; - - if (AZ::Intersect::IntersectSegmentTriangleCCW(raySrc, rayEnd, trianglePoints[0], trianglePoints[1], trianglePoints[2], - intersectionNormal, hitDistanceNormalized) != Intersect::ISECT_RAY_AABB_NONE) + float hitDistanceNormalized; + AZ::Vector3 intersectionNormal; + const AZ::Vector3 rayEnd = raySrc + rayDir; + if (IntersectSegmentTriangleCCW(raySrc, rayEnd, trianglePoints[0], trianglePoints[1], trianglePoints[2], + intersectionNormal, hitDistanceNormalized) != ISECT_RAY_AABB_NONE) { - float hitDistance = hitDistanceNormalized * distance; - - if (nearestDist > hitDistance) + if (nearestDistanceNormalized > hitDistanceNormalized) { normal = intersectionNormal; + nearestDistanceNormalized = hitDistanceNormalized; } - - nearestDist = AZStd::GetMin(nearestDist, hitDistance); } } - if (nearestDist < maxDist) + if (nearestDistanceNormalized < distanceNormalized) { - distance = AZStd::GetMin(distance, nearestDist); + distanceNormalized = nearestDistanceNormalized; return true; } @@ -284,8 +301,8 @@ namespace AZ } // running both sides to find the closest intersection - const bool bFoundChild0 = RayIntersectionRecursively(pNode->GetChild(0), raySrc, rayDir, distance, normal); - const bool bFoundChild1 = RayIntersectionRecursively(pNode->GetChild(1), raySrc, rayDir, distance, normal); + const bool bFoundChild0 = RayIntersectionRecursively(pNode->GetChild(0), raySrc, rayDir, distanceNormalized, normal); + const bool bFoundChild1 = RayIntersectionRecursively(pNode->GetChild(1), raySrc, rayDir, distanceNormalized, normal); return bFoundChild0 || bFoundChild1; } @@ -311,5 +328,5 @@ namespace AZ GetPenetratedBoxesRecursively(pNode->GetChild(0), raySrc, rayDir, outBoxes); GetPenetratedBoxesRecursively(pNode->GetChild(1), raySrc, rayDir, outBoxes); } - } -} + } // namespace RPI +} // namespace AZ diff --git a/Gems/Atom/RPI/Code/Tests/Model/ModelTests.cpp b/Gems/Atom/RPI/Code/Tests/Model/ModelTests.cpp index 1ec14c6169..3ce17bee8b 100644 --- a/Gems/Atom/RPI/Code/Tests/Model/ModelTests.cpp +++ b/Gems/Atom/RPI/Code/Tests/Model/ModelTests.cpp @@ -1074,13 +1074,13 @@ namespace UnitTest } }; - class KdTreeIntersectsFixture + class KdTreeIntersectsParameterizedFixture : public ModelTests , public ::testing::WithParamInterface { }; - TEST_P(KdTreeIntersectsFixture, KdTreeIntersects) + TEST_P(KdTreeIntersectsParameterizedFixture, KdTreeIntersects) { TwoSeparatedPlanesMesh mesh; @@ -1090,7 +1090,10 @@ namespace UnitTest float distance = AZStd::numeric_limits::max(); AZ::Vector3 normal; - EXPECT_THAT(kdTree.RayIntersection(AZ::Vector3(GetParam().xpos, GetParam().ypos, GetParam().zpos), AZ::Vector3::CreateAxisZ(-1.0f), distance, normal), testing::Eq(GetParam().expectedShouldIntersect)); + EXPECT_THAT( + kdTree.RayIntersection( + AZ::Vector3(GetParam().xpos, GetParam().ypos, GetParam().zpos), AZ::Vector3::CreateAxisZ(-1.0f), distance, normal), + testing::Eq(GetParam().expectedShouldIntersect)); EXPECT_THAT(distance, testing::FloatEq(GetParam().expectedDistance)); } @@ -1119,5 +1122,63 @@ namespace UnitTest KdTreeIntersectParams{0.778f, 0.111f, 1.0f, 0.5f, true}, KdTreeIntersectParams{0.778f, 0.778f, 1.0f, 0.5f, true}, }; - INSTANTIATE_TEST_CASE_P(KdTreeIntersectsPlane, KdTreeIntersectsFixture, ::testing::ValuesIn(intersectTestData)); + + INSTANTIATE_TEST_CASE_P(KdTreeIntersectsPlane, KdTreeIntersectsParameterizedFixture, ::testing::ValuesIn(intersectTestData)); + + class KdTreeIntersectsFixture + : public ModelTests + { + public: + void SetUp() override + { + ModelTests::SetUp(); + + m_mesh = AZStd::make_unique(); + m_kdTree = AZStd::make_unique(); + ASSERT_TRUE(m_kdTree->Build(m_mesh->GetModel().Get())); + } + + void TearDown() override + { + m_kdTree.reset(); + m_mesh.reset(); + + ModelTests::TearDown(); + } + + AZStd::unique_ptr m_mesh; + AZStd::unique_ptr m_kdTree; + }; + + TEST_F(KdTreeIntersectsFixture, KdTreeIntersectionReturnsNormalizedDistance) + { + float t = AZStd::numeric_limits::max(); + AZ::Vector3 normal; + + constexpr float rayLength = 100.0f; + EXPECT_THAT( + m_kdTree->RayIntersection( + AZ::Vector3::CreateZero(), AZ::Vector3::CreateAxisZ(-rayLength), t, normal), testing::Eq(true)); + EXPECT_THAT(t, testing::FloatEq(0.005f)); + } + + TEST_F(KdTreeIntersectsFixture, KdTreeIntersectionHandlesInvalidStartingNormalizedDistance) + { + float t = -0.5f; // invalid starting distance + AZ::Vector3 normal; + + constexpr float rayLength = 10.0f; + EXPECT_THAT( + m_kdTree->RayIntersection(AZ::Vector3::CreateAxisZ(0.75f), AZ::Vector3::CreateAxisZ(-rayLength), t, normal), testing::Eq(true)); + EXPECT_THAT(t, testing::FloatEq(0.025f)); + } + + TEST_F(KdTreeIntersectsFixture, KdTreeIntersectionDoesNotScaleRayByStartingDistance) + { + float t = 10.0f; // starting distance (used to check it is not read from initially by RayIntersection) + AZ::Vector3 normal; + + EXPECT_THAT( + m_kdTree->RayIntersection(AZ::Vector3::CreateAxisZ(5.0f), -AZ::Vector3::CreateAxisZ(), t, normal), testing::Eq(false)); + } } // namespace UnitTest diff --git a/Gems/Atom/RPI/gem.json b/Gems/Atom/RPI/gem.json new file mode 100644 index 0000000000..7e822611a9 --- /dev/null +++ b/Gems/Atom/RPI/gem.json @@ -0,0 +1,10 @@ +{ + "gem_name": "Atom_RPI", + "display_name": "Atom API", + "summary": "", + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + ] +} diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/ModularViewportCameraController.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/ModularViewportCameraController.cpp index 6fb3edfa22..896d9f8043 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/ModularViewportCameraController.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/ModularViewportCameraController.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -24,6 +25,11 @@ namespace AtomToolsFramework { + AZ_CVAR( + AZ::Color, ed_cameraSystemOrbitPointColor, AZ::Color::CreateFromRgba(255, 255, 255, 255), nullptr, AZ::ConsoleFunctorFlags::Null, + ""); + AZ_CVAR(float, ed_cameraSystemOrbitPointSize, 0.5f, nullptr, AZ::ConsoleFunctorFlags::Null, ""); + // debug void DrawPreviewAxis(AzFramework::DebugDisplayRequests& display, const AZ::Transform& transform, const float axisLength) { @@ -73,7 +79,8 @@ namespace AtomToolsFramework if (auto viewportContext = RetrieveViewportContext(GetViewportId())) { - auto handleCameraChange = [this, viewportContext](const AZ::Matrix4x4&) { + auto handleCameraChange = [this, viewportContext](const AZ::Matrix4x4&) + { if (!m_updatingTransform) { UpdateCameraFromTransform(m_targetCamera, viewportContext->GetCameraTransform()); @@ -137,7 +144,10 @@ namespace AtomToolsFramework } else if (m_cameraMode == CameraMode::Animation) { - const auto smootherStepFn = [](const float t) { return t * t * t * (t * (t * 6.0f - 15.0f) + 10.0f); }; + const auto smootherStepFn = [](const float t) + { + return t * t * t * (t * (t * 6.0f - 15.0f) + 10.0f); + }; const float transitionT = smootherStepFn(m_animationT); const AZ::Transform current = AZ::Transform::CreateFromQuaternionAndTranslation( @@ -169,8 +179,9 @@ namespace AtomToolsFramework { if (const float alpha = AZStd::min(-m_camera.m_lookDist / 5.0f, 1.0f); alpha > AZ::Constants::FloatEpsilon) { - debugDisplay.SetColor(1.0f, 1.0f, 1.0f, alpha); - debugDisplay.DrawWireSphere(m_camera.m_lookAt, 0.5f); + const AZ::Color orbitPointColor = ed_cameraSystemOrbitPointColor; + debugDisplay.SetColor(orbitPointColor.GetR(), orbitPointColor.GetG(), orbitPointColor.GetB(), alpha); + debugDisplay.DrawWireSphere(m_camera.m_lookAt, ed_cameraSystemOrbitPointSize); } } diff --git a/Gems/Atom/Tools/AtomToolsFramework/gem.json b/Gems/Atom/Tools/AtomToolsFramework/gem.json new file mode 100644 index 0000000000..3060d3f51a --- /dev/null +++ b/Gems/Atom/Tools/AtomToolsFramework/gem.json @@ -0,0 +1,10 @@ +{ + "gem_name": "AtomToolsFramework", + "display_name": "Atom Tools Framework", + "summary": "", + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + ] +} diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Include/Atom/Document/MaterialDocumentRequestBus.h b/Gems/Atom/Tools/MaterialEditor/Code/Include/Atom/Document/MaterialDocumentRequestBus.h index c71d500d8c..7f36d3fabc 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Include/Atom/Document/MaterialDocumentRequestBus.h +++ b/Gems/Atom/Tools/MaterialEditor/Code/Include/Atom/Document/MaterialDocumentRequestBus.h @@ -33,7 +33,7 @@ namespace AZ namespace MaterialEditor { //! UVs are processed in a property group but will be handled differently. - static constexpr const char UvGroupName[] = "UvNames"; + static constexpr const char UvGroupName[] = "uvSets"; class MaterialDocumentRequests : public AZ::EBusTraits diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp b/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp index 301fd69025..2242d3af1b 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp @@ -819,10 +819,10 @@ namespace MaterialEditor // is implemented. AtomToolsFramework::DynamicPropertyConfig propertyConfig; propertyConfig.m_dataType = AtomToolsFramework::DynamicPropertyType::Asset; - propertyConfig.m_id = "details.materialType"; + propertyConfig.m_id = "overview.materialType"; propertyConfig.m_nameId = "materialType"; propertyConfig.m_displayName = "Material Type"; - propertyConfig.m_groupName = "Details"; + propertyConfig.m_groupName = "Overview"; propertyConfig.m_description = "The material type defines the layout, properties, default values, shader connections, and other " "data needed to create and edit a derived material."; propertyConfig.m_defaultValue = AZStd::any(materialTypeAsset); @@ -834,10 +834,10 @@ namespace MaterialEditor propertyConfig = {}; propertyConfig.m_dataType = AtomToolsFramework::DynamicPropertyType::Asset; - propertyConfig.m_id = "details.parentMaterial"; + propertyConfig.m_id = "overview.parentMaterial"; propertyConfig.m_nameId = "parentMaterial"; propertyConfig.m_displayName = "Parent Material"; - propertyConfig.m_groupName = "Details"; + propertyConfig.m_groupName = "Overview"; propertyConfig.m_description = "The parent material provides an initial configuration whose properties are inherited and overriden by a derived material."; propertyConfig.m_defaultValue = AZStd::any(parentMaterialAsset); @@ -860,7 +860,7 @@ namespace MaterialEditor propertyConfig.m_id = MaterialPropertyId(UvGroupName, shaderInput).GetCStr(); propertyConfig.m_nameId = shaderInput; propertyConfig.m_displayName = shaderInput; - propertyConfig.m_groupName = "UV Names"; + propertyConfig.m_groupName = "UV Sets"; propertyConfig.m_description = shaderInput; propertyConfig.m_defaultValue = uvName; propertyConfig.m_originalValue = uvName; diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/CreateMaterialDialog/CreateMaterialDialog.cpp b/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/CreateMaterialDialog/CreateMaterialDialog.cpp index 6a4bb8c0f4..887019787b 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/CreateMaterialDialog/CreateMaterialDialog.cpp +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/CreateMaterialDialog/CreateMaterialDialog.cpp @@ -71,6 +71,8 @@ namespace MaterialEditor QObject::connect(m_ui->m_materialTypeComboBox, static_cast(&QComboBox::currentIndexChanged), this, [this]() { UpdateMaterialTypeSelection(); }); QObject::connect(m_ui->m_materialTypeComboBox, &QComboBox::currentTextChanged, this, [this]() { UpdateMaterialTypeSelection(); }); + m_ui->m_materialTypeComboBox->model()->sort(0, Qt::AscendingOrder); + // Select the default material type from settings auto settings = AZ::UserSettings::CreateFind(AZ::Crc32("MaterialDocumentSettings"), AZ::UserSettings::CT_GLOBAL); diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialInspector/MaterialInspector.cpp b/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialInspector/MaterialInspector.cpp index 706d365027..560c82be9b 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialInspector/MaterialInspector.cpp +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialInspector/MaterialInspector.cpp @@ -79,8 +79,8 @@ namespace MaterialEditor if (!m_documentId.IsNull() && isOpen) { - // Create the top group for displaying details about the material - AddDetailsGroup(); + // Create the top group for displaying overview info about the material + AddOverviewGroup(); // Create groups for displaying editable UV names AddUvNamesGroup(); // Create groups for displaying editable properties @@ -105,25 +105,25 @@ namespace MaterialEditor return property && AtomToolsFramework::ArePropertyValuesEqual(property->GetValue(), property->GetConfig().m_parentValue); } - void MaterialInspector::AddDetailsGroup() + void MaterialInspector::AddOverviewGroup() { const AZ::RPI::MaterialTypeSourceData* materialTypeSourceData = nullptr; MaterialDocumentRequestBus::EventResult( materialTypeSourceData, m_documentId, &MaterialDocumentRequestBus::Events::GetMaterialTypeSourceData); - const AZStd::string groupNameId = "details"; - const AZStd::string groupDisplayName = "Details"; + const AZStd::string groupNameId = "overview"; + const AZStd::string groupDisplayName = "Overview"; const AZStd::string groupDescription = materialTypeSourceData->m_description; auto& group = m_groups[groupNameId]; AtomToolsFramework::DynamicProperty property; MaterialDocumentRequestBus::EventResult( - property, m_documentId, &MaterialDocumentRequestBus::Events::GetProperty, AZ::Name("details.materialType")); + property, m_documentId, &MaterialDocumentRequestBus::Events::GetProperty, AZ::Name("overview.materialType")); group.m_properties.push_back(property); property = {}; MaterialDocumentRequestBus::EventResult( - property, m_documentId, &MaterialDocumentRequestBus::Events::GetProperty, AZ::Name("details.parentMaterial")); + property, m_documentId, &MaterialDocumentRequestBus::Events::GetProperty, AZ::Name("overview.parentMaterial")); group.m_properties.push_back(property); // Passing in same group as main and comparison instance to enable custom value comparison for highlighting modified properties @@ -139,7 +139,7 @@ namespace MaterialEditor MaterialDocumentRequestBus::EventResult(materialAsset, m_documentId, &MaterialDocumentRequestBus::Events::GetAsset); const AZStd::string groupNameId = UvGroupName; - const AZStd::string groupDisplayName = "UV Names"; + const AZStd::string groupDisplayName = "UV Sets"; const AZStd::string groupDescription = "UV set names in this material, which can be renamed to match those in the model."; auto& group = m_groups[groupNameId]; diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialInspector/MaterialInspector.h b/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialInspector/MaterialInspector.h index 4080430ff5..65d095f42f 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialInspector/MaterialInspector.h +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialInspector/MaterialInspector.h @@ -52,7 +52,7 @@ namespace MaterialEditor bool CompareInstanceNodeProperties( const AzToolsFramework::InstanceDataNode* source, const AzToolsFramework::InstanceDataNode* target) const; - void AddDetailsGroup(); + void AddOverviewGroup(); void AddUvNamesGroup(); void AddPropertiesGroup(); diff --git a/Gems/Atom/gem.json b/Gems/Atom/gem.json index c74a9013f3..91bc9bcf53 100644 --- a/Gems/Atom/gem.json +++ b/Gems/Atom/gem.json @@ -1,3 +1,5 @@ { - "gem_name": "Atom" + "gem_name": "Atom", + "display_name": "Atom", + "summary": "Next-Gen Rendering Package for the O3DE engine" } diff --git a/AutomatedTesting/Gem/Code/Platform/Android/runtime_dependencies.cmake b/Gems/AtomContent/CMakeLists.txt similarity index 100% rename from AutomatedTesting/Gem/Code/Platform/Android/runtime_dependencies.cmake rename to Gems/AtomContent/CMakeLists.txt diff --git a/Gems/AtomContent/gem.json b/Gems/AtomContent/gem.json new file mode 100644 index 0000000000..941e7dea20 --- /dev/null +++ b/Gems/AtomContent/gem.json @@ -0,0 +1,14 @@ +{ + "gem_name": "AtomContent", + "origin": "The primary repo for Atom goes here: i.e. http://www.mydomain.com", + "license": "What license Atom uses goes here: i.e. https://opensource.org/licenses/MIT", + "display_name": "Atom Content", + "summary": "ontains multiple packages containing source Assets that can be used with Atom", + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "AtomConent" + ], + "icon_path": "preview.png" +} diff --git a/Gems/AtomLyIntegration/AtomBridge/Code/CMakeLists.txt b/Gems/AtomLyIntegration/AtomBridge/Code/CMakeLists.txt index 0723ca46b7..4df40e3d13 100644 --- a/Gems/AtomLyIntegration/AtomBridge/Code/CMakeLists.txt +++ b/Gems/AtomLyIntegration/AtomBridge/Code/CMakeLists.txt @@ -66,6 +66,10 @@ ly_add_target( Gem::AtomViewportDisplayInfo ) +# Any 'runtime-like' applications should use Gem::Atom_AtomBridge: +ly_create_alias(NAME Atom_AtomBridge.Clients NAMESPACE Gem TARGETS Gem::Atom_AtomBridge) +ly_create_alias(NAME Atom_AtomBridge.Servers NAMESPACE Gem TARGETS Gem::Atom_AtomBridge) + if(PAL_TRAIT_BUILD_HOST_TOOLS) ly_add_target( NAME Atom_AtomBridge.Editor ${PAL_TRAIT_MONOLITHIC_DRIVEN_MODULE_TYPE} @@ -108,4 +112,9 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS) Gem::AtomViewportDisplayInfo Gem::AtomViewportDisplayIcons.Editor ) + + + # Any 'tool' and 'builder' type applications should use Gem::Atom_AtomBridge.Editor: + ly_create_alias(NAME Atom_AtomBridge.Builders NAMESPACE Gem TARGETS Gem::Atom_AtomBridge.Editor) + ly_create_alias(NAME Atom_AtomBridge.Tools NAMESPACE Gem TARGETS Gem::Atom_AtomBridge.Editor) endif() diff --git a/Gems/AtomLyIntegration/AtomBridge/Code/Source/AtomDebugDisplayViewportInterface.cpp b/Gems/AtomLyIntegration/AtomBridge/Code/Source/AtomDebugDisplayViewportInterface.cpp index 6c68618f78..620d5d1fb8 100644 --- a/Gems/AtomLyIntegration/AtomBridge/Code/Source/AtomDebugDisplayViewportInterface.cpp +++ b/Gems/AtomLyIntegration/AtomBridge/Code/Source/AtomDebugDisplayViewportInterface.cpp @@ -1328,7 +1328,7 @@ namespace AZ::AtomBridge params.m_hAlign = center ? AzFramework::TextHorizontalAlignment::Center : AzFramework::TextHorizontalAlignment::Left; //! Horizontal text alignment params.m_monospace = false; //! disable character proportional spacing params.m_depthTest = false; //! Test character against the depth buffer - params.m_virtual800x600ScreenSize = true; //! Text placement and size are scaled relative to a virtual 800x600 resolution + params.m_virtual800x600ScreenSize = false; //! Text placement and size are scaled in viewport pixel coordinates params.m_scaleWithWindow = false; //! Font gets bigger as the window gets bigger params.m_multiline = true; //! text respects ascii newline characters @@ -1364,7 +1364,7 @@ namespace AZ::AtomBridge params.m_hAlign = center ? AzFramework::TextHorizontalAlignment::Center : AzFramework::TextHorizontalAlignment::Left; //! Horizontal text alignment params.m_monospace = false; //! disable character proportional spacing params.m_depthTest = false; //! Test character against the depth buffer - params.m_virtual800x600ScreenSize = true; //! Text placement and size are scaled relative to a virtual 800x600 resolution + params.m_virtual800x600ScreenSize = false; //! Text placement and size are scaled in viewport pixel coordinates params.m_scaleWithWindow = false; //! Font gets bigger as the window gets bigger params.m_multiline = true; //! text respects ascii newline characters diff --git a/Gems/AtomLyIntegration/AtomBridge/gem.json b/Gems/AtomLyIntegration/AtomBridge/gem.json new file mode 100644 index 0000000000..329741bb8e --- /dev/null +++ b/Gems/AtomLyIntegration/AtomBridge/gem.json @@ -0,0 +1,10 @@ +{ + "gem_name": "Atom_AtomBridge", + "display_name": "Atom Bridge", + "summary": "", + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + ] +} diff --git a/Gems/AtomLyIntegration/AtomFont/gem.json b/Gems/AtomLyIntegration/AtomFont/gem.json new file mode 100644 index 0000000000..a609061ea3 --- /dev/null +++ b/Gems/AtomLyIntegration/AtomFont/gem.json @@ -0,0 +1,10 @@ +{ + "gem_name": "AtomFont", + "display_name": "Atom Font", + "summary": "", + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + ] +} diff --git a/Gems/AtomLyIntegration/AtomImGuiTools/gem.json b/Gems/AtomLyIntegration/AtomImGuiTools/gem.json new file mode 100644 index 0000000000..5cee62f7bb --- /dev/null +++ b/Gems/AtomLyIntegration/AtomImGuiTools/gem.json @@ -0,0 +1,10 @@ +{ + "gem_name": "AtomImGuiTools", + "display_name": "Atom ImGui", + "summary": "", + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + ] +} diff --git a/Gems/AtomLyIntegration/AtomViewportDisplayIcons/gem.json b/Gems/AtomLyIntegration/AtomViewportDisplayIcons/gem.json new file mode 100644 index 0000000000..41f69e33a0 --- /dev/null +++ b/Gems/AtomLyIntegration/AtomViewportDisplayIcons/gem.json @@ -0,0 +1,10 @@ +{ + "gem_name": "AtomViewportDisplayIcons", + "display_name": "Atom Viewport Display Icons", + "summary": "", + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + ] +} diff --git a/Gems/AtomLyIntegration/AtomViewportDisplayInfo/Code/CMakeLists.txt b/Gems/AtomLyIntegration/AtomViewportDisplayInfo/Code/CMakeLists.txt index de4ee9b4b5..0f134d4218 100644 --- a/Gems/AtomLyIntegration/AtomViewportDisplayInfo/Code/CMakeLists.txt +++ b/Gems/AtomLyIntegration/AtomViewportDisplayInfo/Code/CMakeLists.txt @@ -10,7 +10,7 @@ # ly_add_target( - NAME AtomViewportDisplayInfo GEM_MODULE + NAME AtomViewportDisplayInfo ${PAL_TRAIT_MONOLITHIC_DRIVEN_MODULE_TYPE} NAMESPACE Gem FILES_CMAKE atomviewportdisplayinfo_files.cmake diff --git a/Gems/AtomLyIntegration/AtomViewportDisplayInfo/Code/Source/AtomViewportDisplayInfoSystemComponent.cpp b/Gems/AtomLyIntegration/AtomViewportDisplayInfo/Code/Source/AtomViewportDisplayInfoSystemComponent.cpp index 7d830b4ca9..d47ce44ab2 100644 --- a/Gems/AtomLyIntegration/AtomViewportDisplayInfo/Code/Source/AtomViewportDisplayInfoSystemComponent.cpp +++ b/Gems/AtomLyIntegration/AtomViewportDisplayInfo/Code/Source/AtomViewportDisplayInfoSystemComponent.cpp @@ -162,7 +162,7 @@ namespace AZ::Render m_drawParams.m_hAlign = AzFramework::TextHorizontalAlignment::Right; m_drawParams.m_monospace = false; m_drawParams.m_depthTest = false; - m_drawParams.m_virtual800x600ScreenSize = true; + m_drawParams.m_virtual800x600ScreenSize = false; m_drawParams.m_scaleWithWindow = false; m_drawParams.m_multiline = true; m_drawParams.m_lineSpacing = 0.5f; @@ -303,7 +303,10 @@ namespace AZ::Render lastTime = time; } - const double averageFPS = aznumeric_cast(m_fpsHistory.size()) / actualInterval.count(); + const double averageFPS = (actualInterval.count() != 0.0) + ? aznumeric_cast(m_fpsHistory.size()) / actualInterval.count() + : 0.0; + const double frameIntervalSeconds = m_fpsInterval.count(); DrawLine( diff --git a/Gems/AtomLyIntegration/AtomViewportDisplayInfo/gem.json b/Gems/AtomLyIntegration/AtomViewportDisplayInfo/gem.json new file mode 100644 index 0000000000..04e2464a26 --- /dev/null +++ b/Gems/AtomLyIntegration/AtomViewportDisplayInfo/gem.json @@ -0,0 +1,10 @@ +{ + "gem_name": "AtomViewportDisplayInfo", + "display_name": "Atom Viewport Display Info", + "summary": "", + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + ] +} diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentInspector.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentInspector.cpp index 3192900ca4..229606a238 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentInspector.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentInspector.cpp @@ -184,7 +184,7 @@ namespace AZ void MaterialPropertyInspector::AddUvNamesGroup() { const AZStd::string groupNameId = AZ::RPI::UvGroupName; - const AZStd::string groupDisplayName = "UV Names"; + const AZStd::string groupDisplayName = "UV Sets"; const AZStd::string groupDescription = "UV set names in this material, which can be renamed to match those in the model."; auto& group = m_groups[groupNameId]; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSlot.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSlot.cpp index 7ebf25454d..bdc82acda6 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSlot.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSlot.cpp @@ -78,11 +78,9 @@ namespace AZ if (AZ::SerializeContext* serializeContext = azrtti_cast(context)) { serializeContext->Class() - ->Version(4, &EditorMaterialComponentSlot::ConvertVersion) + ->Version(5, &EditorMaterialComponentSlot::ConvertVersion) ->Field("id", &EditorMaterialComponentSlot::m_id) ->Field("materialAsset", &EditorMaterialComponentSlot::m_materialAsset) - ->Field("propertyOverrides", &EditorMaterialComponentSlot::m_propertyOverrides) - ->Field("matModUvOverrides", &EditorMaterialComponentSlot::m_matModUvOverrides) ; if (AZ::EditContext* editContext = serializeContext->GetEditContext()) diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/EditorMeshComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/EditorMeshComponent.cpp index f2ad9a13e6..e1c9026c74 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/EditorMeshComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/EditorMeshComponent.cpp @@ -140,9 +140,16 @@ namespace AZ AZ::Vector3 nonUniformScale = AZ::Vector3::CreateOne(); AZ::NonUniformScaleRequestBus::EventResult(nonUniformScale, GetEntityId(), &AZ::NonUniformScaleRequests::GetScale); + float t; AZ::Vector3 ignoreNormal; + constexpr float rayLength = 1000.0f; + if (m_controller.GetModel()->RayIntersection(transform, nonUniformScale, src, dir * rayLength, t, ignoreNormal)) + { + distance = rayLength * t; + return true; + } - return m_controller.GetModel()->RayIntersection(transform, nonUniformScale, src, dir, distance, ignoreNormal); + return false; } bool EditorMeshComponent::SupportsEditorRayIntersect() diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/EditorMeshComponent.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/EditorMeshComponent.h index 1acaa6fdf8..41ea80f8d1 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/EditorMeshComponent.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/EditorMeshComponent.h @@ -35,14 +35,13 @@ namespace AZ , private MeshComponentNotificationBus::Handler { public: - using BaseClass = EditorRenderComponentAdapter; AZ_EDITOR_COMPONENT(AZ::Render::EditorMeshComponent, EditorMeshComponentTypeId, BaseClass); static void Reflect(AZ::ReflectContext* context); EditorMeshComponent() = default; - EditorMeshComponent(const MeshComponentConfig& config); + explicit EditorMeshComponent(const MeshComponentConfig& config); // AZ::Component overrides ... void Activate() override; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponent.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponent.h index a57780c384..fe7f45d7d1 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponent.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponent.h @@ -25,12 +25,11 @@ namespace AZ : public AzFramework::Components::ComponentAdapter { public: - using BaseClass = AzFramework::Components::ComponentAdapter; AZ_COMPONENT(AZ::Render::MeshComponent, MeshComponentTypeId, BaseClass); MeshComponent() = default; - MeshComponent(const MeshComponentConfig& config); + explicit MeshComponent(const MeshComponentConfig& config); static void Reflect(AZ::ReflectContext* context); }; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.cpp index a4a91cf708..e7eecd3c7f 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.cpp @@ -177,28 +177,33 @@ namespace AZ FixUpModelAsset(m_configuration.m_modelAsset); } - void MeshComponentController::Activate(AZ::EntityId entityId) + void MeshComponentController::Activate(const AZ::EntityComponentIdPair& entityComponentIdPair) { FixUpModelAsset(m_configuration.m_modelAsset); - m_entityId = entityId; + const AZ::EntityId entityId = entityComponentIdPair.GetEntityId(); + m_entityComponentIdPair = entityComponentIdPair; - m_transformInterface = TransformBus::FindFirstHandler(m_entityId); + m_transformInterface = TransformBus::FindFirstHandler(entityId); AZ_Warning("MeshComponentController", m_transformInterface, "Unable to attach to a TransformBus handler. This mesh will always be rendered at the origin."); - m_meshFeatureProcessor = RPI::Scene::GetFeatureProcessorForEntity(m_entityId); + m_meshFeatureProcessor = RPI::Scene::GetFeatureProcessorForEntity(entityId); AZ_Error("MeshComponentController", m_meshFeatureProcessor, "Unable to find a MeshFeatureProcessorInterface on the entityId."); m_cachedNonUniformScale = AZ::Vector3::CreateOne(); - AZ::NonUniformScaleRequestBus::EventResult(m_cachedNonUniformScale, m_entityId, &AZ::NonUniformScaleRequests::GetScale); - AZ::NonUniformScaleRequestBus::Event(m_entityId, &AZ::NonUniformScaleRequests::RegisterScaleChangedEvent, + AZ::NonUniformScaleRequestBus::EventResult(m_cachedNonUniformScale, entityId, &AZ::NonUniformScaleRequests::GetScale); + AZ::NonUniformScaleRequestBus::Event(entityId, &AZ::NonUniformScaleRequests::RegisterScaleChangedEvent, m_nonUniformScaleChangedHandler); - MeshComponentRequestBus::Handler::BusConnect(m_entityId); - TransformNotificationBus::Handler::BusConnect(m_entityId); - MaterialReceiverRequestBus::Handler::BusConnect(m_entityId); - MaterialComponentNotificationBus::Handler::BusConnect(m_entityId); - AzFramework::BoundsRequestBus::Handler::BusConnect(m_entityId); + MeshComponentRequestBus::Handler::BusConnect(entityId); + TransformNotificationBus::Handler::BusConnect(entityId); + MaterialReceiverRequestBus::Handler::BusConnect(entityId); + MaterialComponentNotificationBus::Handler::BusConnect(entityId); + AzFramework::BoundsRequestBus::Handler::BusConnect(entityId); + AzFramework::EntityContextId contextId; + AzFramework::EntityIdContextQueryBus::EventResult( + contextId, entityId, &AzFramework::EntityIdContextQueries::GetOwningContextId); + AzFramework::RenderGeometry::IntersectionRequestBus::Handler::BusConnect({entityId, contextId}); //Buses must be connected before RegisterModel in case requests are made as a result of HandleModelChange RegisterModel(); @@ -209,6 +214,7 @@ namespace AZ // Buses must be disconnected after unregistering the model, otherwise they can't deliver the events during the process. UnregisterModel(); + AzFramework::RenderGeometry::IntersectionRequestBus::Handler::BusDisconnect(); AzFramework::BoundsRequestBus::Handler::BusDisconnect(); MeshComponentRequestBus::Handler::BusDisconnect(); TransformNotificationBus::Handler::BusDisconnect(); @@ -219,7 +225,7 @@ namespace AZ m_meshFeatureProcessor = nullptr; m_transformInterface = nullptr; - m_entityId = AZ::EntityId(AZ::EntityId::InvalidEntityId); + m_entityComponentIdPair = AZ::EntityComponentIdPair(AZ::EntityId(), AZ::InvalidComponentId); m_configuration.m_modelAsset.Release(); } @@ -293,10 +299,11 @@ namespace AZ Data::Asset modelAsset = m_meshFeatureProcessor->GetModelAsset(m_meshHandle); if (model && modelAsset) { + const AZ::EntityId entityId = m_entityComponentIdPair.GetEntityId(); m_configuration.m_modelAsset = modelAsset; - MeshComponentNotificationBus::Event(m_entityId, &MeshComponentNotificationBus::Events::OnModelReady, m_configuration.m_modelAsset, model); - MaterialReceiverNotificationBus::Event(m_entityId, &MaterialReceiverNotificationBus::Events::OnMaterialAssignmentsChanged); - AZ::Interface::Get()->RefreshEntityLocalBoundsUnion(m_entityId); + MeshComponentNotificationBus::Event(entityId, &MeshComponentNotificationBus::Events::OnModelReady, m_configuration.m_modelAsset, model); + MaterialReceiverNotificationBus::Event(entityId, &MaterialReceiverNotificationBus::Events::OnMaterialAssignmentsChanged); + AZ::Interface::Get()->RefreshEntityLocalBoundsUnion(entityId); } } @@ -304,8 +311,10 @@ namespace AZ { if (m_meshFeatureProcessor && m_configuration.m_modelAsset.GetId().IsValid()) { + const AZ::EntityId entityId = m_entityComponentIdPair.GetEntityId(); + MaterialAssignmentMap materials; - MaterialComponentRequestBus::EventResult(materials, m_entityId, &MaterialComponentRequests::GetMaterialOverrides); + MaterialComponentRequestBus::EventResult(materials, entityId, &MaterialComponentRequests::GetMaterialOverrides); m_meshFeatureProcessor->ReleaseMesh(m_meshHandle); m_meshHandle = m_meshFeatureProcessor->AcquireMesh(m_configuration.m_modelAsset, materials, @@ -330,7 +339,8 @@ namespace AZ { if (m_meshFeatureProcessor && m_meshHandle.IsValid()) { - MeshComponentNotificationBus::Event(m_entityId, &MeshComponentNotificationBus::Events::OnModelPreDestroy); + MeshComponentNotificationBus::Event( + m_entityComponentIdPair.GetEntityId(), &MeshComponentNotificationBus::Events::OnModelPreDestroy); m_meshFeatureProcessor->ReleaseMesh(m_meshHandle); } } @@ -462,5 +472,30 @@ namespace AZ return Aabb::CreateNull(); } } + + AzFramework::RenderGeometry::RayResult MeshComponentController::RenderGeometryIntersect( + const AzFramework::RenderGeometry::RayRequest& ray) + { + AzFramework::RenderGeometry::RayResult result; + if (const Data::Instance model = GetModel()) + { + float t; + AZ::Vector3 normal; + if (model->RayIntersection( + m_transformInterface->GetWorldTM(), m_cachedNonUniformScale, ray.m_startWorldPosition, + ray.m_endWorldPosition - ray.m_startWorldPosition, t, normal)) + { + // fill in ray result structure after successful intersection + const auto intersectionLine = (ray.m_endWorldPosition - ray.m_startWorldPosition); + result.m_uv = AZ::Vector2::CreateZero(); + result.m_worldPosition = ray.m_startWorldPosition + intersectionLine * t; + result.m_worldNormal = normal; + result.m_distance = intersectionLine.GetLength() * t; + result.m_entityAndComponent = m_entityComponentIdPair; + } + } + + return result; + } } // namespace Render } // namespace AZ diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.h index 0cda34ea42..4d63e5e88d 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.h @@ -13,11 +13,13 @@ #pragma once #include +#include #include #include #include +#include #include #include @@ -32,9 +34,7 @@ namespace AZ { namespace Render { - /** - * A configuration structure for the MeshComponentController - */ + //! A configuration structure for the MeshComponentController class MeshComponentConfig final : public AZ::ComponentConfig { @@ -57,6 +57,7 @@ namespace AZ class MeshComponentController final : private MeshComponentRequestBus::Handler , public AzFramework::BoundsRequestBus::Handler + , public AzFramework::RenderGeometry::IntersectionRequestBus::Handler , private TransformNotificationBus::Handler , private MaterialReceiverRequestBus::Handler , private MaterialComponentNotificationBus::Handler @@ -77,7 +78,7 @@ namespace AZ MeshComponentController() = default; MeshComponentController(const MeshComponentConfig& config); - void Activate(AZ::EntityId entityId); + void Activate(const AZ::EntityComponentIdPair& entityComponentIdPair); void Deactivate(); void SetConfiguration(const MeshComponentConfig& config); const MeshComponentConfig& GetConfiguration() const; @@ -103,10 +104,13 @@ namespace AZ void SetVisibility(bool visible) override; bool GetVisibility() const override; - // BoundsRequestBus and MeshComponentRequestBus ... + // BoundsRequestBus and MeshComponentRequestBus overrides ... AZ::Aabb GetWorldBounds() override; AZ::Aabb GetLocalBounds() override; + // IntersectionRequestBus overrides ... + AzFramework::RenderGeometry::RayResult RenderGeometryIntersect(const AzFramework::RenderGeometry::RayRequest& ray) override; + // TransformNotificationBus::Handler overrides ... void OnTransformChanged(const AZ::Transform& local, const AZ::Transform& world) override; @@ -134,7 +138,7 @@ namespace AZ Render::MeshFeatureProcessorInterface* m_meshFeatureProcessor = nullptr; Render::MeshFeatureProcessorInterface::MeshHandle m_meshHandle; TransformInterface* m_transformInterface = nullptr; - AZ::EntityId m_entityId; + AZ::EntityComponentIdPair m_entityComponentIdPair; bool m_isVisible = true; MeshComponentConfig m_configuration; AZ::Vector3 m_cachedNonUniformScale = AZ::Vector3::CreateOne(); diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ReflectionProbe/EditorReflectionProbeComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ReflectionProbe/EditorReflectionProbeComponent.cpp index 735eab5368..7880d5e88c 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ReflectionProbe/EditorReflectionProbeComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ReflectionProbe/EditorReflectionProbeComponent.cpp @@ -209,8 +209,8 @@ namespace AZ AZ::Vector3 position = AZ::Vector3::CreateZero(); AZ::TransformBus::EventResult(position, GetEntityId(), &AZ::TransformBus::Events::GetWorldTranslation); - AZ::Vector3 scale = AZ::Vector3::CreateOne(); - AZ::TransformBus::EventResult(scale, GetEntityId(), &AZ::TransformBus::Events::GetLocalScale); + float scale = 1.0f; + AZ::TransformBus::EventResult(scale, GetEntityId(), &AZ::TransformBus::Events::GetLocalUniformScale); // draw AABB at probe position using the inner dimensions Color color(0.0f, 0.0f, 1.0f, 1.0f); diff --git a/Gems/AtomLyIntegration/CommonFeatures/gem.json b/Gems/AtomLyIntegration/CommonFeatures/gem.json new file mode 100644 index 0000000000..306c61e6d7 --- /dev/null +++ b/Gems/AtomLyIntegration/CommonFeatures/gem.json @@ -0,0 +1,10 @@ +{ + "gem_name": "CommonFeaturesAtom", + "display_name": "Common Features Atom", + "summary": "", + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + ] +} diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/CMakeLists.txt b/Gems/AtomLyIntegration/EMotionFXAtom/Code/CMakeLists.txt index 6492f4f13a..9a9b389227 100644 --- a/Gems/AtomLyIntegration/EMotionFXAtom/Code/CMakeLists.txt +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/CMakeLists.txt @@ -43,6 +43,8 @@ ly_add_target( PRIVATE AZ::AzCore Gem::EMotionFX_Atom.Static + RUNTIME_DEPENDENCIES + Gem::EMotionFX ) if(PAL_TRAIT_BUILD_HOST_TOOLS) diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/gem.json b/Gems/AtomLyIntegration/EMotionFXAtom/gem.json new file mode 100644 index 0000000000..e2a81d0a5e --- /dev/null +++ b/Gems/AtomLyIntegration/EMotionFXAtom/gem.json @@ -0,0 +1,10 @@ +{ + "gem_name": "EMotionFX_Atom", + "display_name": "EMotionFX Atom", + "summary": "", + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + ] +} diff --git a/Gems/AtomLyIntegration/ImguiAtom/gem.json b/Gems/AtomLyIntegration/ImguiAtom/gem.json new file mode 100644 index 0000000000..6d6551b5fa --- /dev/null +++ b/Gems/AtomLyIntegration/ImguiAtom/gem.json @@ -0,0 +1,10 @@ +{ + "gem_name": "ImguiAtom", + "display_name": "Imgui Atom", + "summary": "", + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + ] +} diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/gem.json b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/gem.json index ca80c62dd0..a867fe0f0f 100644 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/gem.json +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/gem.json @@ -1,17 +1,14 @@ { - "gem_name": "Atom_DccScriptingInterface", - "GemFormatVersion": 4, - "Uuid": "7bf5a77dacd8438bb4966a66b5a678d8", - "Name": "Atom_DccScriptingInterface", - "DisplayName": "Atom DccScriptingInterface (DCCsi)", - "Version": "0.1.0", - "Summary": "A python framework for working with various DCC tools and workflows.", - "Tags": ["DCC","Digital","Content","Creation"], - "IconPath": "preview.png", - "Modules": [ - { - "Name": "Editor", - "Type": "EditorModule" - } + "gem_name": "DccScriptingInterface", + "display_name": "Atom DccScriptingInterface (DCCsi)", + "summary": "A python framework for working with various DCC tools and workflows.", + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "DCC", + "Digital", + "Content", + "Creation" ] } diff --git a/Gems/AtomLyIntegration/gem.json b/Gems/AtomLyIntegration/gem.json index 0971ad53c2..4f587a8806 100644 --- a/Gems/AtomLyIntegration/gem.json +++ b/Gems/AtomLyIntegration/gem.json @@ -1,3 +1,5 @@ { - "gem_name": "AtomLyIntegration" + "gem_name": "AtomLyIntegration", + "display_name": "Atom O3DE Integration", + "summary": "Collection of module targets for integrating Atom with the O3DE engine" } diff --git a/AutomatedTesting/Gem/Code/Platform/Android/tool_dependencies.cmake b/Gems/AtomTressFX/CMakeLists.txt similarity index 100% rename from AutomatedTesting/Gem/Code/Platform/Android/tool_dependencies.cmake rename to Gems/AtomTressFX/CMakeLists.txt diff --git a/Gems/AudioEngineWwise/Code/CMakeLists.txt b/Gems/AudioEngineWwise/Code/CMakeLists.txt index 5ea6a6d461..f90064908a 100644 --- a/Gems/AudioEngineWwise/Code/CMakeLists.txt +++ b/Gems/AudioEngineWwise/Code/CMakeLists.txt @@ -93,6 +93,9 @@ ly_add_target( Gem::AudioEngineWwise.Static ) +# we'll load the above "Gem::AudioEngineWwise" module in clients. +ly_create_alias(NAME AudioEngineWwise.Clients NAMESPACE Gem TARGETS Gem::AudioEngineWwise) + ################################################################################ # Tests ################################################################################ @@ -206,6 +209,8 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS) AZ::AssetBuilderSDK Gem::AudioEngineWwise.Static Gem::AudioSystem.Editor + RUNTIME_DEPENDENCIES + Gem::AudioSystem.Editor ) ly_add_target( @@ -230,6 +235,10 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS) Gem::AudioSystem.Editor ) + # by default, we'll load the above "Gem::AudioEngineWwise.Editor" module in builders and tools. + ly_create_alias(NAME AudioEngineWwise.Builders NAMESPACE Gem TARGETS Gem::AudioEngineWwise.Editor) + ly_create_alias(NAME AudioEngineWwise.Tools NAMESPACE Gem TARGETS Gem::AudioEngineWwise.Editor) + if (PAL_TRAIT_BUILD_TESTS_SUPPORTED) ly_add_target( NAME AudioEngineWwise.Editor.Tests ${PAL_TRAIT_TEST_TARGET_TYPE} diff --git a/Gems/AudioSystem/Code/CMakeLists.txt b/Gems/AudioSystem/Code/CMakeLists.txt index 8a6f2c417e..3963a71ad0 100644 --- a/Gems/AudioSystem/Code/CMakeLists.txt +++ b/Gems/AudioSystem/Code/CMakeLists.txt @@ -61,22 +61,8 @@ ly_add_target( Gem::AudioSystem.Static ) -################################################################################ -# Server -################################################################################ -if (PAL_TRAIT_BUILD_SERVER_SUPPORTED) - # Stub gem for server. Audio system is client only - ly_add_target( - NAME AudioSystem.Server GEM_MODULE - - NAMESPACE Gem - FILES_CMAKE - audiosystem_stub_files.cmake - BUILD_DEPENDENCIES - PRIVATE - AZ::AzCore - ) -endif () +# AudioSystem should use the above target on clients. +ly_create_alias(NAME AudioSystem.Clients NAMESPACE Gem TARGETS Gem::AudioSystem) ################################################################################ # Tests @@ -101,7 +87,8 @@ if (PAL_TRAIT_BUILD_TESTS_SUPPORTED) AZ::AzFramework Legacy::CryCommon Gem::AudioSystem.Static - Gem::LmbrCentral + RUNTIME_DEPENDENCIES + Gem::LmbrCentral ) ly_add_googletest( NAME Gem::AudioSystem.Tests @@ -230,6 +217,10 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS) Gem::AudioSystem.Editor.Static ) + # use the above "Editor" target in tools and builders: + ly_create_alias(NAME AudioSystem.Tools NAMESPACE Gem TARGETS Gem::AudioSystem.Editor) + ly_create_alias(NAME AudioSystem.Builders NAMESPACE Gem TARGETS Gem::AudioSystem.Editor) + if (PAL_TRAIT_BUILD_TESTS_SUPPORTED) ly_add_target( NAME AudioSystem.Editor.Tests ${PAL_TRAIT_TEST_TARGET_TYPE} @@ -253,3 +244,6 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS) ) endif() endif () + + + diff --git a/Gems/AutomatedLauncherTesting/Code/CMakeLists.txt b/Gems/AutomatedLauncherTesting/Code/CMakeLists.txt index 551f76da02..6215ae7697 100644 --- a/Gems/AutomatedLauncherTesting/Code/CMakeLists.txt +++ b/Gems/AutomatedLauncherTesting/Code/CMakeLists.txt @@ -42,3 +42,7 @@ ly_add_target( RUNTIME_DEPENDENCIES Gem::LmbrCentral ) + +# servers and clients use the above module. +ly_create_alias(NAME AutomatedLauncherTesting.Servers NAMESPACE Gem TARGETS Gem::AutomatedLauncherTesting) +ly_create_alias(NAME AutomatedLauncherTesting.Clients NAMESPACE Gem TARGETS Gem::AutomatedLauncherTesting) diff --git a/Gems/Blast/Code/CMakeLists.txt b/Gems/Blast/Code/CMakeLists.txt index 6c90357364..143e3af095 100644 --- a/Gems/Blast/Code/CMakeLists.txt +++ b/Gems/Blast/Code/CMakeLists.txt @@ -59,6 +59,11 @@ ly_add_target( Gem::PhysX ) +# clients and servers use the above Gem module. +ly_create_alias(NAME Blast.Servers NAMESPACE Gem TARGETS Gem::Blast) +ly_create_alias(NAME Blast.Clients NAMESPACE Gem TARGETS Gem::Blast) + + if(PAL_TRAIT_BUILD_HOST_TOOLS) ly_add_target( @@ -110,6 +115,9 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS) Gem::PhysX.Editor ) + # tools and builders use the above module. + ly_create_alias(NAME Blast.Tools NAMESPACE Gem TARGETS Gem::Blast.Editor) + ly_create_alias(NAME Blast.Builders NAMESPACE Gem TARGETS Gem::Blast.Editor) endif() ################################################################################ diff --git a/Gems/Blast/Code/Source/Actor/BlastActorImpl.cpp b/Gems/Blast/Code/Source/Actor/BlastActorImpl.cpp index 6c4c78c412..8f82f3366f 100644 --- a/Gems/Blast/Code/Source/Actor/BlastActorImpl.cpp +++ b/Gems/Blast/Code/Source/Actor/BlastActorImpl.cpp @@ -151,8 +151,6 @@ namespace Blast colliderConfiguration.m_position = transform.GetTranslation(); colliderConfiguration.m_rotation = transform.GetRotation(); colliderConfiguration.m_isExclusive = true; - colliderConfiguration.m_materialSelection.SetMaterialLibrary( - AZ::Interface::Get()->GetDefaultMaterialLibrary()->GetId()); colliderConfiguration.m_materialSelection.SetMaterialId(material); colliderConfiguration.m_collisionGroupId = actorConfiguration.m_collisionGroupId; colliderConfiguration.m_collisionLayer = actorConfiguration.m_collisionLayer; diff --git a/Gems/Blast/Code/Source/Components/BlastFamilyComponent.cpp b/Gems/Blast/Code/Source/Components/BlastFamilyComponent.cpp index 0f2668442c..1da9663b8f 100644 --- a/Gems/Blast/Code/Source/Components/BlastFamilyComponent.cpp +++ b/Gems/Blast/Code/Source/Components/BlastFamilyComponent.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -265,16 +266,29 @@ namespace Blast auto solverPtr = Nv::Blast::ExtStressSolver::create( const_cast(*m_family->GetTkFamily()->getFamilyLL()), stressSolverSettings); m_solver = physx::unique_ptr(solverPtr); - Physics::MaterialFromAssetConfiguration material; - AZ::Interface::Get()->GetDefaultMaterialLibrary()->GetDataForMaterialId( - m_physicsMaterialId, material); - m_solver->setAllNodesInfoFromLL(material.m_configuration.m_density); + + AZStd::shared_ptr physicsMaterial; + Physics::PhysicsMaterialRequestBus::BroadcastResult( + physicsMaterial, + &Physics::PhysicsMaterialRequestBus::Events::GetMaterialById, + m_physicsMaterialId); + if (!physicsMaterial) + { + AZ_Warning("BlastFamilyComponent", false, "Material Id %s was not found, using default material instead.", + m_physicsMaterialId.GetUuid().ToString().c_str()); + + Physics::PhysicsMaterialRequestBus::BroadcastResult( + physicsMaterial, + &Physics::PhysicsMaterialRequestBus::Events::GetGenericDefaultMaterial); + AZ_Assert(physicsMaterial, "BlastFamilyComponent: Invalid default physics material"); + } + m_solver->setAllNodesInfoFromLL(physicsMaterial->GetDensity()); // Create damage and actor render managers m_damageManager = AZStd::make_unique(blastMaterial, m_family->GetActorTracker()); m_actorRenderManager = AZStd::make_unique( AZ::RPI::Scene::GetFeatureProcessorForEntity(GetEntityId()), - m_meshDataComponent, GetEntityId(), m_blastAsset->GetPxAsset()->getChunkCount(), transform.GetScale()); + m_meshDataComponent, GetEntityId(), m_blastAsset->GetPxAsset()->getChunkCount(), AZ::Vector3(transform.GetUniformScale())); // Spawn the family m_family->Spawn(transform); diff --git a/Gems/Blast/Code/Source/Editor/EditorBlastFamilyComponent.cpp b/Gems/Blast/Code/Source/Editor/EditorBlastFamilyComponent.cpp index 9241449483..873ef7248d 100644 --- a/Gems/Blast/Code/Source/Editor/EditorBlastFamilyComponent.cpp +++ b/Gems/Blast/Code/Source/Editor/EditorBlastFamilyComponent.cpp @@ -131,6 +131,6 @@ namespace Blast AZ::Data::AssetId EditorBlastFamilyComponent::GetPhysicsMaterialLibraryAssetId() const { - return AZ::Interface::Get()->GetDefaultMaterialLibrary()->GetId(); + return AZ::Interface::Get()->GetConfiguration()->m_materialLibraryAsset.GetId(); } } // namespace Blast diff --git a/Gems/Blast/Code/Tests/Mocks/BlastMocks.h b/Gems/Blast/Code/Tests/Mocks/BlastMocks.h index 00aa12cb84..ca78623a8a 100644 --- a/Gems/Blast/Code/Tests/Mocks/BlastMocks.h +++ b/Gems/Blast/Code/Tests/Mocks/BlastMocks.h @@ -654,9 +654,7 @@ namespace Blast MOCK_METHOD1(RotateAroundLocalZ, void(float)); MOCK_METHOD0(GetLocalRotation, AZ::Vector3()); MOCK_METHOD0(GetLocalRotationQuaternion, AZ::Quaternion()); - MOCK_METHOD1(SetLocalScale, void(const AZ::Vector3&)); MOCK_METHOD0(GetLocalScale, AZ::Vector3()); - MOCK_METHOD0(GetWorldScale, AZ::Vector3()); MOCK_METHOD1(SetLocalUniformScale, void(float)); MOCK_METHOD0(GetLocalUniformScale, float()); MOCK_METHOD0(GetWorldUniformScale, float()); diff --git a/Gems/Camera/Code/CMakeLists.txt b/Gems/Camera/Code/CMakeLists.txt index 950ff451ff..703424416b 100644 --- a/Gems/Camera/Code/CMakeLists.txt +++ b/Gems/Camera/Code/CMakeLists.txt @@ -39,6 +39,10 @@ ly_add_target( Gem::Camera.Static ) +# clients and servers use the above module: +ly_create_alias(NAME Camera.Clients NAMESPACE Gem TARGETS Gem::Camera) +ly_create_alias(NAME Camera.Servers NAMESPACE Gem TARGETS Gem::Camera) + if (PAL_TRAIT_BUILD_HOST_TOOLS) ly_add_target( @@ -62,4 +66,7 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS) Gem::Camera.Static ) + # tools and builders use the above module. + ly_create_alias(NAME Camera.Tools NAMESPACE Gem TARGETS Gem::Camera.Editor) + ly_create_alias(NAME Camera.Builders NAMESPACE Gem TARGETS Gem::Camera.Editor) endif() diff --git a/Gems/Camera/Code/Source/CameraComponentController.cpp b/Gems/Camera/Code/Source/CameraComponentController.cpp index 3dcee68169..d0a124067b 100644 --- a/Gems/Camera/Code/Source/CameraComponentController.cpp +++ b/Gems/Camera/Code/Source/CameraComponentController.cpp @@ -240,7 +240,9 @@ namespace Camera CameraBus::Handler::BusConnect(); CameraNotificationBus::Broadcast(&CameraNotificationBus::Events::OnCameraAdded, m_entityId); - if (m_config.m_makeActiveViewOnActivation) + // Activate our camera if we're running from the launcher or Editor game mode + // Otherwise, let the Editor keep managing the active camera + if (m_config.m_makeActiveViewOnActivation && (!gEnv || !gEnv->IsEditor() || gEnv->IsEditorGameMode())) { MakeActiveView(); } diff --git a/Gems/Camera/Code/Source/EditorCameraComponent.cpp b/Gems/Camera/Code/Source/EditorCameraComponent.cpp index 359fa936bc..80057e7a77 100644 --- a/Gems/Camera/Code/Source/EditorCameraComponent.cpp +++ b/Gems/Camera/Code/Source/EditorCameraComponent.cpp @@ -34,20 +34,9 @@ namespace Camera auto controllerConfig = m_controller.GetConfiguration(); controllerConfig.m_editorEntityId = GetEntityId().operator AZ::u64(); - // The Editor manages active camera state, so while we're in Editor we explicitly - // disable the request to make this the active view at edit component activation time. - bool prevShouldActivateViewOnActivation = controllerConfig.m_makeActiveViewOnActivation; - controllerConfig.m_makeActiveViewOnActivation = false; - - m_controller.SetConfiguration(controllerConfig); - // Call base class activate, which in turn calls Activate on our controller. EditorCameraComponentBase::Activate(); - // Reset the original `m_makeActiveViewOnActivation' setting, so that the intended value is serialized, used in BuildGameEntity, etc. - controllerConfig.m_makeActiveViewOnActivation = prevShouldActivateViewOnActivation; - m_controller.SetConfiguration(controllerConfig); - AzFramework::EntityDebugDisplayEventBus::Handler::BusConnect(GetEntityId()); EditorCameraNotificationBus::Handler::BusConnect(); EditorCameraViewRequestBus::Handler::BusConnect(GetEntityId()); diff --git a/Gems/CameraFramework/Code/CMakeLists.txt b/Gems/CameraFramework/Code/CMakeLists.txt index 1ec9dc0ad9..6b0d084e28 100644 --- a/Gems/CameraFramework/Code/CMakeLists.txt +++ b/Gems/CameraFramework/Code/CMakeLists.txt @@ -38,3 +38,9 @@ ly_add_target( PRIVATE Gem::CameraFramework.Static ) + +# Every kind of application uses the above target module. +ly_create_alias(NAME CameraFramework.Clients NAMESPACE Gem TARGETS Gem::CameraFramework) +ly_create_alias(NAME CameraFramework.Servers NAMESPACE Gem TARGETS Gem::CameraFramework) +ly_create_alias(NAME CameraFramework.Tools NAMESPACE Gem TARGETS Gem::CameraFramework) +ly_create_alias(NAME CameraFramework.Builders NAMESPACE Gem TARGETS Gem::CameraFramework) diff --git a/Gems/CertificateManager/Code/CMakeLists.txt b/Gems/CertificateManager/Code/CMakeLists.txt index 93e78bb86a..2307ebed40 100644 --- a/Gems/CertificateManager/Code/CMakeLists.txt +++ b/Gems/CertificateManager/Code/CMakeLists.txt @@ -41,3 +41,7 @@ ly_add_target( PRIVATE Gem::CertificateManager.Static ) + +# we'll load the above "Gem::CertificateManager" module in Clients and Servers +ly_create_alias(NAME CertificateManager.Clients NAMESPACE Gem TARGETS Gem::CertificateManager) +ly_create_alias(NAME CertificateManager.Servers NAMESPACE Gem TARGETS Gem::CertificateManager) diff --git a/Gems/CrashReporting/Code/CMakeLists.txt b/Gems/CrashReporting/Code/CMakeLists.txt index d52600ea9b..2d77d563d9 100644 --- a/Gems/CrashReporting/Code/CMakeLists.txt +++ b/Gems/CrashReporting/Code/CMakeLists.txt @@ -33,6 +33,11 @@ ly_add_target( AZ::CrashHandler ) +# Load the "Gem::CrashReporting" module in Clients and Servers +ly_create_alias(NAME CrashReporting.Clients NAMESPACE Gem TARGETS Gem::CrashReporting) +ly_create_alias(NAME CrashReporting.Servers NAMESPACE Gem TARGETS Gem::CrashReporting) + + ly_add_target( NAME CrashReporting.Uploader APPLICATION NAMESPACE AZ diff --git a/Gems/CustomAssetExample/Code/CMakeLists.txt b/Gems/CustomAssetExample/Code/CMakeLists.txt index 661b1950ce..3debe27919 100644 --- a/Gems/CustomAssetExample/Code/CMakeLists.txt +++ b/Gems/CustomAssetExample/Code/CMakeLists.txt @@ -22,6 +22,10 @@ ly_add_target( AZ::AzCore ) +# clients and servers use the above module. +ly_create_alias(NAME CustomAssetExample.Clients NAMESPACE Gem TARGETS CustomAssetExample) +ly_create_alias(NAME CustomAssetExample.Servers NAMESPACE Gem TARGETS CustomAssetExample) + if(PAL_TRAIT_BUILD_HOST_TOOLS) ly_add_target( NAME CustomAssetExample.Editor GEM_MODULE @@ -37,4 +41,9 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS) AZ::AzCore AZ::AssetBuilderSDK ) + + # other tools use the above tools module: + ly_create_alias(NAME CustomAssetExample.Builders NAMESPACE Gem TARGETS CustomAssetExample.Editor) + ly_create_alias(NAME CustomAssetExample.Tools NAMESPACE Gem TARGETS CustomAssetExample.Editor) + endif() diff --git a/Gems/DebugDraw/Code/CMakeLists.txt b/Gems/DebugDraw/Code/CMakeLists.txt index 0954d6366c..69488b1493 100644 --- a/Gems/DebugDraw/Code/CMakeLists.txt +++ b/Gems/DebugDraw/Code/CMakeLists.txt @@ -42,6 +42,9 @@ ly_add_target( Gem::DebugDraw.Static ) +# servers do not need debug draw components, only clients +ly_create_alias(NAME DebugDraw.Clients NAMESPACE Gem TARGETS DebugDraw) + if(PAL_TRAIT_BUILD_HOST_TOOLS) ly_add_target( NAME DebugDraw.Editor GEM_MODULE @@ -62,4 +65,9 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS) Gem::DebugDraw.Static AZ::AzToolsFramework ) + + # builders and tools use DebugDraw.Editor + ly_create_alias(NAME DebugDraw.Builders NAMESPACE Gem TARGETS DebugDraw.Editor) + ly_create_alias(NAME DebugDraw.Tools NAMESPACE Gem TARGETS DebugDraw.Editor) + endif() diff --git a/AutomatedTesting/Gem/Code/Platform/Linux/runtime_dependencies.cmake b/Gems/DevTextures/CMakeLists.txt similarity index 100% rename from AutomatedTesting/Gem/Code/Platform/Linux/runtime_dependencies.cmake rename to Gems/DevTextures/CMakeLists.txt diff --git a/Gems/EMotionFX/Code/CMakeLists.txt b/Gems/EMotionFX/Code/CMakeLists.txt index b90902a948..bc0268cd60 100644 --- a/Gems/EMotionFX/Code/CMakeLists.txt +++ b/Gems/EMotionFX/Code/CMakeLists.txt @@ -36,10 +36,10 @@ ly_add_target( AZ::AzCore AZ::AzFramework Legacy::CryCommon - Gem::LmbrCentral PUBLIC AZ::AtomCore Gem::Atom_RPI.Public + Gem::LmbrCentral COMPILE_DEFINITIONS PUBLIC EMFX_DEVELOPMENT_BUILD @@ -67,6 +67,10 @@ ly_add_target( Gem::LmbrCentral ) +# Clients and servers use the above EMotionFX module +ly_create_alias(NAME EMotionFX.Clients NAMESPACE Gem TARGETS EMotionFX) +ly_create_alias(NAME EMotionFX.Servers NAMESPACE Gem TARGETS EMotionFX) + if (PAL_TRAIT_BUILD_HOST_TOOLS) ly_add_target( @@ -129,6 +133,11 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS) RUNTIME_DEPENDENCIES Gem::LmbrCentral.Editor ) + + # builders and tools use the above EMotionFX.Editor module + ly_create_alias(NAME EMotionFX.Builders NAMESPACE Gem TARGETS EMotionFX.Editor) + ly_create_alias(NAME EMotionFX.Tools NAMESPACE Gem TARGETS EMotionFX.Editor) + endif() ################################################################################ diff --git a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/Commands.cpp b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/Commands.cpp index 294f4c7c99..46760af8f0 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/Commands.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/Commands.cpp @@ -218,6 +218,10 @@ namespace EMStudio } const bool saveResult = manifest.SaveToFile(manifestFilename.c_str()); + if (saveResult) + { + actor->SetDirtyFlag(false); + } // Source Control: Add file in case it did not exist before (when saving it the first time). if (saveResult && !fileExisted) diff --git a/Gems/EMotionFX/Code/Tests/AnimGraphParameterConditionTests.cpp b/Gems/EMotionFX/Code/Tests/AnimGraphParameterConditionTests.cpp index ef3e9820a5..b1ddede059 100644 --- a/Gems/EMotionFX/Code/Tests/AnimGraphParameterConditionTests.cpp +++ b/Gems/EMotionFX/Code/Tests/AnimGraphParameterConditionTests.cpp @@ -10,6 +10,7 @@ * */ +#include "EMotionFX/CommandSystem/Source/AnimGraphParameterCommands.h" #include #include #include @@ -87,26 +88,16 @@ namespace EMotionFX const AnimGraphParameterCondition* condition = GetAnimGraph()->GetParameterCondition(); EXPECT_EQ(condition->GetParameterType(), azrtti_typeid()); - { - AZStd::string result; - EXPECT_TRUE(manager.ExecuteCommand("AnimGraphRemoveParameter -animGraphID 0 -name P0", result)) << result.c_str(); - } - + EXPECT_TRUE(CommandSystem::BuildRemoveParametersCommandGroup(GetAnimGraph(), {"P0"})); EXPECT_EQ(condition->GetParameterType(), azrtti_typeid()); - { - AZStd::string result; - EXPECT_TRUE(manager.ExecuteCommand("AnimGraphRemoveParameter -animGraphID 0 -name P1", result)) << result.c_str(); - } - + EXPECT_TRUE(CommandSystem::BuildRemoveParametersCommandGroup(GetAnimGraph(), {"P1"})); EXPECT_EQ(condition->GetParameterType(), AZ::TypeId::CreateNull()); - // Will be fixed by LY-109269 - //{ - // AZStd::string result; - // EXPECT_TRUE(manager.Undo(result)) << result.c_str(); - //} - - //EXPECT_EQ(condition->GetParameterType(), azrtti_typeid()); + { + AZStd::string result; + EXPECT_TRUE(manager.Undo(result)) << result.c_str(); + } + EXPECT_EQ(condition->GetParameterType(), azrtti_typeid()); } } // namespace EMotionFX diff --git a/Gems/EMotionFX/Code/Tests/BlendTreeBlendNNodeTests.cpp b/Gems/EMotionFX/Code/Tests/BlendTreeBlendNNodeTests.cpp index 657599bcc9..1e910fb62d 100644 --- a/Gems/EMotionFX/Code/Tests/BlendTreeBlendNNodeTests.cpp +++ b/Gems/EMotionFX/Code/Tests/BlendTreeBlendNNodeTests.cpp @@ -264,8 +264,8 @@ namespace EMotionFX BlendTreeBlendNNode* m_blendNNode = nullptr; }; - // Make sure we don't crash when we have no inputs, such as reported by bug LY-114828 - // Also make sure removing connections on BlendN doesn't crash, as reported by LY-114846 + // Make sure we don't crash when we have no inputs + // Also make sure removing connections on BlendN doesn't crash TEST_F(BlendTreeBlendNNodeTests, NoInputsNoCrashTest) { // Remove all input connections of the blendN node. diff --git a/Gems/EMotionFX/Code/Tests/Bugs/LY-92860.cpp b/Gems/EMotionFX/Code/Tests/Bugs/CanUndoParameterDeletionAndRestoreBlendTreeConnections.cpp similarity index 98% rename from Gems/EMotionFX/Code/Tests/Bugs/LY-92860.cpp rename to Gems/EMotionFX/Code/Tests/Bugs/CanUndoParameterDeletionAndRestoreBlendTreeConnections.cpp index cd4773b221..0d47b86606 100644 --- a/Gems/EMotionFX/Code/Tests/Bugs/LY-92860.cpp +++ b/Gems/EMotionFX/Code/Tests/Bugs/CanUndoParameterDeletionAndRestoreBlendTreeConnections.cpp @@ -106,7 +106,7 @@ namespace EMotionFX R"str(AnimGraphCreateConnection -animGraphID 0 -sourceNode Parameters0 -targetNode Smoothing2 -sourcePort 2 -targetPort 0 -startOffsetX 119 -startOffsetY 70 -endOffsetX -2 -endOffsetY 40)str" }; - class LY92860Fixture + class UndoParameterDeletionTests : public CommandRunnerFixture { public: @@ -183,10 +183,10 @@ namespace EMotionFX } }; - TEST_P(LY92860Fixture, ExecuteCommands) + TEST_P(UndoParameterDeletionTests, CanUndoParameterDeletionAndRestoreBlendTreeConnections) { Run(); }; - INSTANTIATE_TEST_CASE_P(LY92860, LY92860Fixture, ::testing::Values(prepareLY92860Commands)); + INSTANTIATE_TEST_CASE_P(UndoParameterDeletionTests, UndoParameterDeletionTests, ::testing::Values(prepareLY92860Commands)); } // EMotionFX diff --git a/Gems/EMotionFX/Code/Tests/Mocks/PhysicsSystem.h b/Gems/EMotionFX/Code/Tests/Mocks/PhysicsSystem.h index 22102fd38a..3aafdb4e2b 100644 --- a/Gems/EMotionFX/Code/Tests/Mocks/PhysicsSystem.h +++ b/Gems/EMotionFX/Code/Tests/Mocks/PhysicsSystem.h @@ -35,9 +35,6 @@ namespace Physics MOCK_METHOD2(CreateShape, AZStd::shared_ptr(const Physics::ColliderConfiguration& colliderConfiguration, const Physics::ShapeConfiguration& configuration)); MOCK_METHOD1(ReleaseNativeMeshObject, void(void* nativeMeshObject)); MOCK_METHOD1(CreateMaterial, AZStd::shared_ptr(const Physics::MaterialConfiguration& materialConfiguration)); - MOCK_METHOD0(GetDefaultMaterial, AZStd::shared_ptr()); - MOCK_METHOD1(CreateMaterialsFromLibrary, AZStd::vector>(const Physics::MaterialSelection& materialSelection)); - MOCK_METHOD2(UpdateMaterialSelection, bool(const Physics::ShapeConfiguration& shapeConfiguration, Physics::ColliderConfiguration& colliderConfiguration)); MOCK_METHOD0(GetSupportedJointTypes, AZStd::vector()); MOCK_METHOD1(CreateJointLimitConfiguration, AZStd::shared_ptr(AZ::TypeId jointType)); MOCK_METHOD3(CreateJoint, AZStd::shared_ptr(const AZStd::shared_ptr& configuration, AzPhysics::SimulatedBody* parentBody, AzPhysics::SimulatedBody* childBody)); @@ -59,7 +56,6 @@ namespace Physics void Shutdown() override {} void Simulate([[maybe_unused]] float deltaTime) override {} void UpdateConfiguration([[maybe_unused]] const AzPhysics::SystemConfiguration* newConfig, [[maybe_unused]] bool forceReinitialization = false) override {} - void UpdateDefaultMaterialLibrary([[maybe_unused]] const AZ::Data::Asset& materialLibrary) override {} void UpdateDefaultSceneConfiguration([[maybe_unused]] const AzPhysics::SceneConfiguration& sceneConfiguration) override {} void RemoveScene([[maybe_unused]] AzPhysics::SceneHandle handle) override {} void RemoveScenes([[maybe_unused]] const AzPhysics::SceneHandleList& handles) override {} @@ -73,7 +69,6 @@ namespace Physics MOCK_METHOD0(GetAllScenes, AzPhysics::SceneList& ()); MOCK_METHOD1(FindAttachedBodyHandleFromEntityId, AZStd::pair(AZ::EntityId entityId)); MOCK_CONST_METHOD0(GetConfiguration, const AzPhysics::SystemConfiguration* ()); - MOCK_CONST_METHOD0(GetDefaultMaterialLibrary, const AZ::Data::Asset& ()); MOCK_CONST_METHOD0(GetDefaultSceneConfiguration, const AzPhysics::SceneConfiguration& ()); }; diff --git a/Gems/EMotionFX/Code/Tests/ProvidesUI/Ragdoll/CanCopyPasteColliders.cpp b/Gems/EMotionFX/Code/Tests/ProvidesUI/Ragdoll/CanCopyPasteColliders.cpp index 858980fbe8..eaef6f31ee 100644 --- a/Gems/EMotionFX/Code/Tests/ProvidesUI/Ragdoll/CanCopyPasteColliders.cpp +++ b/Gems/EMotionFX/Code/Tests/ProvidesUI/Ragdoll/CanCopyPasteColliders.cpp @@ -54,14 +54,11 @@ namespace EMotionFX [[maybe_unused]] const AZ::Vector3& axis, [[maybe_unused]] const AZStd::vector& exampleLocalRotations) { return AZStd::make_unique(); }); - EXPECT_CALL(m_physicsInterface, GetDefaultMaterialLibrary) - .WillRepeatedly(testing::ReturnRef(m_materialLibraryAsset)); } private: Physics::MockPhysicsSystem m_physicsSystem; Physics::MockPhysicsInterface m_physicsInterface; - AZ::Data::Asset m_materialLibraryAsset; }; #if AZ_TRAIT_DISABLE_FAILED_EMOTION_FX_EDITOR_TESTS diff --git a/Gems/EMotionFX/Code/Tests/ProvidesUI/Ragdoll/CanCopyPasteJointLimits.cpp b/Gems/EMotionFX/Code/Tests/ProvidesUI/Ragdoll/CanCopyPasteJointLimits.cpp index b4b4d14d58..6223af3599 100644 --- a/Gems/EMotionFX/Code/Tests/ProvidesUI/Ragdoll/CanCopyPasteJointLimits.cpp +++ b/Gems/EMotionFX/Code/Tests/ProvidesUI/Ragdoll/CanCopyPasteJointLimits.cpp @@ -59,9 +59,6 @@ namespace EMotionFX .WillRepeatedly(testing::Return(AZStd::vector{azrtti_typeid()})); EXPECT_CALL(physicsSystem, ComputeInitialJointLimitConfiguration(azrtti_typeid(), _, _, _, _)) .WillRepeatedly([]([[maybe_unused]] const AZ::TypeId& jointLimitTypeId, [[maybe_unused]] const AZ::Quaternion& parentWorldRotation, [[maybe_unused]] const AZ::Quaternion& childWorldRotation, [[maybe_unused]] const AZ::Vector3& axis, [[maybe_unused]] const AZStd::vector& exampleLocalRotations) { return AZStd::make_unique(); }); - AZ::Data::Asset materialLibraryAsset; - EXPECT_CALL(physicsInterface, GetDefaultMaterialLibrary) - .WillRepeatedly(testing::ReturnRef(materialLibraryAsset)); AutoRegisteredActor actor {ActorFactory::CreateAndInit(4)}; diff --git a/Gems/EMotionFX/Code/Tests/UI/LY-93621.cpp b/Gems/EMotionFX/Code/Tests/UI/CanAdjustGroupParameter.cpp similarity index 95% rename from Gems/EMotionFX/Code/Tests/UI/LY-93621.cpp rename to Gems/EMotionFX/Code/Tests/UI/CanAdjustGroupParameter.cpp index 8e4f9a1fc9..4568dd1b03 100644 --- a/Gems/EMotionFX/Code/Tests/UI/LY-93621.cpp +++ b/Gems/EMotionFX/Code/Tests/UI/CanAdjustGroupParameter.cpp @@ -14,7 +14,7 @@ namespace EMotionFX { - INSTANTIATE_TEST_CASE_P(LY93621, CommandRunnerFixture, + INSTANTIATE_TEST_CASE_P(CanAdjustGroupParameter, CommandRunnerFixture, ::testing::Values(std::vector { R"str(CreateAnimGraph)str", R"str(AnimGraphAddGroupParameter -animGraphID 0 -name Group0)str", diff --git a/Gems/EMotionFX/Code/Tests/UI/LY-92748.cpp b/Gems/EMotionFX/Code/Tests/UI/CanDeleteAnimGraphNode_AnimGraphModelUpdates.cpp similarity index 95% rename from Gems/EMotionFX/Code/Tests/UI/LY-92748.cpp rename to Gems/EMotionFX/Code/Tests/UI/CanDeleteAnimGraphNode_AnimGraphModelUpdates.cpp index 9534edb8eb..ad6172d02b 100644 --- a/Gems/EMotionFX/Code/Tests/UI/LY-92748.cpp +++ b/Gems/EMotionFX/Code/Tests/UI/CanDeleteAnimGraphNode_AnimGraphModelUpdates.cpp @@ -21,12 +21,12 @@ namespace EMotionFX { - class LY92748Fixture + class CanDeleteAnimGraphNode : public CommandRunnerFixture { }; - TEST_P(LY92748Fixture, ExecuteCommands) + TEST_P(CanDeleteAnimGraphNode, CanDeleteAnimGraphNode_AnimGraphModelUpdates) { ExecuteCommands(GetParam()); @@ -68,7 +68,7 @@ namespace EMotionFX } - INSTANTIATE_TEST_CASE_P(DISABLED_LY92748, LY92748Fixture, + INSTANTIATE_TEST_CASE_P(CanDeleteAnimGraphNode_AnimGraphModelUpdates, CanDeleteAnimGraphNode, ::testing::Values(std::vector { R"str(CreateAnimGraph)str", R"str(Select -animGraphID 0)str", diff --git a/Gems/EMotionFX/Code/Tests/UI/LY-92269.cpp b/Gems/EMotionFX/Code/Tests/UI/CanRenameParameter_ParameterNodeUpdates.cpp similarity index 98% rename from Gems/EMotionFX/Code/Tests/UI/LY-92269.cpp rename to Gems/EMotionFX/Code/Tests/UI/CanRenameParameter_ParameterNodeUpdates.cpp index 95f42f7e33..fac6f51943 100644 --- a/Gems/EMotionFX/Code/Tests/UI/LY-92269.cpp +++ b/Gems/EMotionFX/Code/Tests/UI/CanRenameParameter_ParameterNodeUpdates.cpp @@ -14,7 +14,7 @@ namespace EMotionFX { - INSTANTIATE_TEST_CASE_P(LY92269, CommandRunnerFixture, + INSTANTIATE_TEST_CASE_P(CanRenameParameter_ParameterNodeUpdates, CommandRunnerFixture, ::testing::Values(std::vector { R"str(CreateAnimGraph)str", R"str(AnimGraphCreateNode -animGraphID 0 -type {A8B5BB1E-5BA9-4B0A-88E9-21BB7A199ED2} -parentName Root -xPos 240 -yPos 230 -name GENERATE -namePrefix BlendTree)str", diff --git a/Gems/EMotionFX/Code/emotionfx_editor_tests_files.cmake b/Gems/EMotionFX/Code/emotionfx_editor_tests_files.cmake index 4a7d1414f9..e2670c20f7 100644 --- a/Gems/EMotionFX/Code/emotionfx_editor_tests_files.cmake +++ b/Gems/EMotionFX/Code/emotionfx_editor_tests_files.cmake @@ -54,15 +54,15 @@ set(FILES Tests/UI/AnimGraphUIFixture.h Tests/UI/MenuUIFixture.cpp Tests/UI/MenuUIFixture.h - Tests/UI/LY-92269.cpp - Tests/UI/LY-92748.cpp - Tests/UI/LY-93621.cpp + Tests/UI/CanRenameParameter_ParameterNodeUpdates.cpp + Tests/UI/CanDeleteAnimGraphNode_AnimGraphModelUpdates.cpp + Tests/UI/CanAdjustGroupParameter.cpp Tests/UI/CanAddJointAndChildren.cpp Tests/Integration/CanAddActor.cpp Tests/Integration/CanAddSimpleMotionComponent.cpp Tests/Integration/CanDeleteJackEntity.cpp Tests/Bugs/CanDeleteMotionWhenMotionIsBeingBlended.cpp - Tests/Bugs/LY-92860.cpp + Tests/Bugs/CanUndoParameterDeletionAndRestoreBlendTreeConnections.cpp Tests/D6JointLimitConfiguration.cpp Tests/D6JointLimitConfiguration.h Tests/Editor/FileManagerTests.cpp diff --git a/Gems/EditorPythonBindings/Code/CMakeLists.txt b/Gems/EditorPythonBindings/Code/CMakeLists.txt index 3a34a8491d..a8d4382b45 100644 --- a/Gems/EditorPythonBindings/Code/CMakeLists.txt +++ b/Gems/EditorPythonBindings/Code/CMakeLists.txt @@ -64,6 +64,10 @@ ly_add_target( Gem::EditorPythonBindings.Static ) +# builders and tools use EditorPythonBindings.Editor +ly_create_alias(NAME EditorPythonBindings.Builders NAMESPACE Gem TARGETS EditorPythonBindings.Editor) +ly_create_alias(NAME EditorPythonBindings.Tools NAMESPACE Gem TARGETS EditorPythonBindings.Editor) + ################################################################################ # Tests ################################################################################ diff --git a/Gems/ExpressionEvaluation/Code/CMakeLists.txt b/Gems/ExpressionEvaluation/Code/CMakeLists.txt index 563e3f3341..456129f05d 100644 --- a/Gems/ExpressionEvaluation/Code/CMakeLists.txt +++ b/Gems/ExpressionEvaluation/Code/CMakeLists.txt @@ -41,6 +41,12 @@ ly_add_target( Gem::ExpressionEvaluation.Static ) +# all types of applications use the above module +ly_create_alias(NAME ExpressionEvaluation.Clients NAMESPACE Gem TARGETS ExpressionEvaluation) +ly_create_alias(NAME ExpressionEvaluation.Servers NAMESPACE Gem TARGETS ExpressionEvaluation) +ly_create_alias(NAME ExpressionEvaluation.Builders NAMESPACE Gem TARGETS ExpressionEvaluation) +ly_create_alias(NAME ExpressionEvaluation.Tools NAMESPACE Gem TARGETS ExpressionEvaluation) + ################################################################################ # Tests ################################################################################ diff --git a/Gems/FastNoise/Code/CMakeLists.txt b/Gems/FastNoise/Code/CMakeLists.txt index a49126303a..ae42af771a 100644 --- a/Gems/FastNoise/Code/CMakeLists.txt +++ b/Gems/FastNoise/Code/CMakeLists.txt @@ -23,6 +23,7 @@ ly_add_target( PUBLIC Legacy::CryCommon Gem::GradientSignal + PRIVATE Gem::LmbrCentral ) @@ -42,6 +43,10 @@ ly_add_target( Gem::GradientSignal ) +# Clients and Servers use the above module +ly_create_alias(NAME FastNoise.Clients NAMESPACE Gem TARGETS FastNoise) +ly_create_alias(NAME FastNoise.Servers NAMESPACE Gem TARGETS FastNoise) + if(PAL_TRAIT_BUILD_HOST_TOOLS) ly_add_target( NAME FastNoise.Editor.Static STATIC @@ -57,6 +62,8 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS) PUBLIC Include BUILD_DEPENDENCIES + PRIVATE + Gem::LmbrCentral.Editor PUBLIC Gem::FastNoise.Static AZ::AzToolsFramework @@ -65,7 +72,6 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS) ly_add_target( NAME FastNoise.Editor GEM_MODULE - NAMESPACE Gem FILES_CMAKE fastnoise_editor_shared_files.cmake @@ -76,11 +82,18 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS) Include BUILD_DEPENDENCIES PRIVATE - FastNoise.Editor.Static + Gem::FastNoise.Editor.Static + Gem::LmbrCentral.Editor RUNTIME_DEPENDENCIES Gem::LmbrCentral.Editor Gem::SurfaceData.Editor ) + + # builders and tools load the above tool module. + ly_create_alias(NAME FastNoise.Builders NAMESPACE Gem TARGETS FastNoise.Editor) + ly_create_alias(NAME FastNoise.Tools NAMESPACE Gem TARGETS FastNoise.Editor) + + endif() ################################################################################ @@ -103,6 +116,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) PRIVATE AZ::AzTest FastNoise.Editor.Static + Gem::LmbrCentral.Editor ) ly_add_googletest( NAME Gem::FastNoise.Editor.Tests @@ -120,6 +134,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) PRIVATE AZ::AzTest Gem::FastNoise.Static + Gem::LmbrCentral ) ly_add_googletest( NAME Gem::FastNoise.Tests diff --git a/Gems/GameState/Code/CMakeLists.txt b/Gems/GameState/Code/CMakeLists.txt index d57cf8feed..828dfcbb35 100644 --- a/Gems/GameState/Code/CMakeLists.txt +++ b/Gems/GameState/Code/CMakeLists.txt @@ -40,6 +40,10 @@ ly_add_target( Gem::GameState.Static ) +# Clients and Servers use the above module. There is no editor or tools module required. +ly_create_alias(NAME GameState.Clients NAMESPACE Gem TARGETS GameState) +ly_create_alias(NAME GameState.Servers NAMESPACE Gem TARGETS GameState) + ################################################################################ # Tests ################################################################################ diff --git a/Gems/GameStateSamples/Code/CMakeLists.txt b/Gems/GameStateSamples/Code/CMakeLists.txt index e3ebc25016..abf3ca6121 100644 --- a/Gems/GameStateSamples/Code/CMakeLists.txt +++ b/Gems/GameStateSamples/Code/CMakeLists.txt @@ -44,4 +44,12 @@ ly_add_target( AZ::AzFramework Gem::LmbrCentral Gem::GameStateSamples.Headers + RUNTIME_DEPENDENCIES + Gem::GameState + Gem::LocalUser + Gem::LmbrCentral ) + +# Clients and Servers use the above module. There is no editor or tools module required. +ly_create_alias(NAME GameStateSamples.Clients NAMESPACE Gem TARGETS GameStateSamples) +ly_create_alias(NAME GameStateSamples.Servers NAMESPACE Gem TARGETS GameStateSamples) diff --git a/Gems/Gestures/Code/CMakeLists.txt b/Gems/Gestures/Code/CMakeLists.txt index 677886c0ed..8c4ec5ad55 100644 --- a/Gems/Gestures/Code/CMakeLists.txt +++ b/Gems/Gestures/Code/CMakeLists.txt @@ -43,6 +43,12 @@ ly_add_target( Gem::Gestures.Static ) +# All types of applications use the same module. +ly_create_alias(NAME Gestures.Clients NAMESPACE Gem TARGETS Gestures) +ly_create_alias(NAME Gestures.Servers NAMESPACE Gem TARGETS Gestures) +ly_create_alias(NAME Gestures.Builders NAMESPACE Gem TARGETS Gestures) +ly_create_alias(NAME Gestures.Tools NAMESPACE Gem TARGETS Gestures) + ################################################################################ # Tests ################################################################################ diff --git a/Gems/GradientSignal/Code/CMakeLists.txt b/Gems/GradientSignal/Code/CMakeLists.txt index 7b8c9813e6..244f7360ea 100644 --- a/Gems/GradientSignal/Code/CMakeLists.txt +++ b/Gems/GradientSignal/Code/CMakeLists.txt @@ -22,9 +22,10 @@ ly_add_target( BUILD_DEPENDENCIES PUBLIC Legacy::CryCommon - Gem::LmbrCentral Gem::SurfaceData Gem::ImageProcessingAtom.Headers + PRIVATE + Gem::LmbrCentral ) ly_add_target( @@ -40,13 +41,17 @@ ly_add_target( BUILD_DEPENDENCIES PRIVATE Gem::GradientSignal.Static + Gem::LmbrCentral PUBLIC Gem::ImageProcessingAtom.Headers # Atom/ImageProcessing/PixelFormats.h is part of a header in Includes RUNTIME_DEPENDENCIES Gem::LmbrCentral - Gem::SurfaceData ) +# Load the "Gem::GradientSignal" module in Clients and Servers +ly_create_alias(NAME GradientSignal.Clients NAMESPACE Gem TARGETS Gem::GradientSignal) +ly_create_alias(NAME GradientSignal.Servers NAMESPACE Gem TARGETS Gem::GradientSignal) + if(PAL_TRAIT_BUILD_HOST_TOOLS) ly_add_target( @@ -63,19 +68,21 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS) PUBLIC GRADIENTSIGNAL_EDITOR BUILD_DEPENDENCIES + PRIVATE + Gem::LmbrCentral.Editor PUBLIC 3rdParty::Qt::Widgets Legacy::CryCommon AZ::AzToolsFramework - Gem::LmbrCentral - Gem::SurfaceData AZ::AssetBuilderSDK Gem::GradientSignal.Static + Gem::SurfaceData + RUNTIME_DEPENDENCIES + Gem::LmbrCentral.Editor ) ly_add_target( NAME GradientSignal.Editor GEM_MODULE - NAMESPACE Gem FILES_CMAKE gradientsignal_editor_shared_files.cmake @@ -87,11 +94,16 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS) BUILD_DEPENDENCIES PRIVATE Gem::GradientSignal.Editor.Static + Gem::LmbrCentral.Editor RUNTIME_DEPENDENCIES Gem::LmbrCentral.Editor - Gem::SurfaceData.Editor ) + # Load the "Gem::GradientSignal.Editor" module in Builders and Tools + ly_create_alias(NAME GradientSignal.Builders NAMESPACE Gem TARGETS Gem::GradientSignal.Editor) + ly_create_alias(NAME GradientSignal.Tools NAMESPACE Gem TARGETS Gem::GradientSignal.Editor) + + endif() ################################################################################ @@ -112,6 +124,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) PRIVATE AZ::AzTest Gem::GradientSignal.Static + Gem::LmbrCentral ) ly_add_googletest( NAME Gem::GradientSignal.Tests @@ -132,6 +145,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) AZ::AzTest Gem::GradientSignal.Static Gem::GradientSignal.Editor.Static + Gem::LmbrCentral.Editor ) ly_add_googletest( NAME Gem::GradientSignal.Editor.Tests diff --git a/Gems/GradientSignal/Code/Include/GradientSignal/GradientSampler.h b/Gems/GradientSignal/Code/Include/GradientSignal/GradientSampler.h index 8a8eacc952..9e7823c8d3 100644 --- a/Gems/GradientSignal/Code/Include/GradientSignal/GradientSampler.h +++ b/Gems/GradientSignal/Code/Include/GradientSignal/GradientSampler.h @@ -103,12 +103,12 @@ namespace GradientSignal //apply transform if set if (m_enableTransform && GradientSamplerUtil::AreTransformParamsSet(*this)) { - const AZ::Transform transform = - AZ::Transform::CreateTranslation(m_translate) * - AZ::ConvertEulerDegreesToTransform(m_rotate) * - AZ::Transform::CreateScale(m_scale); + AZ::Matrix3x4 matrix3x4; + matrix3x4.SetFromEulerDegrees(m_rotate); + matrix3x4.MultiplyByScale(m_scale); + matrix3x4.SetTranslation(m_translate); - sampleParamsTransformed.m_position = transform.TransformPoint(sampleParamsTransformed.m_position); + sampleParamsTransformed.m_position = matrix3x4 * sampleParamsTransformed.m_position; } float output = 0.0f; diff --git a/Gems/GradientSignal/Code/Source/Components/GradientTransformComponent.cpp b/Gems/GradientSignal/Code/Source/Components/GradientTransformComponent.cpp index 336f428582..a2d979313e 100644 --- a/Gems/GradientSignal/Code/Source/Components/GradientTransformComponent.cpp +++ b/Gems/GradientSignal/Code/Source/Components/GradientTransformComponent.cpp @@ -493,7 +493,7 @@ namespace GradientSignal if (!m_configuration.m_advancedMode || !m_configuration.m_overrideScale) { - m_configuration.m_scale = shapeTransform.GetScale(); + m_configuration.m_scale = AZ::Vector3(shapeTransform.GetUniformScale()); } //rebuild bounds from parameters diff --git a/Gems/GraphCanvas/Code/CMakeLists.txt b/Gems/GraphCanvas/Code/CMakeLists.txt index 683b0e4bdf..fe24686f95 100644 --- a/Gems/GraphCanvas/Code/CMakeLists.txt +++ b/Gems/GraphCanvas/Code/CMakeLists.txt @@ -51,7 +51,6 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS) ly_add_target( NAME GraphCanvas.Editor GEM_MODULE - NAMESPACE Gem AUTOMOC AUTORCC @@ -75,4 +74,10 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS) 3rdParty::Qt::Xml AZ::AzQtComponents ) + + # Load the "Gem::GraphCanvas" module in Builders and Tools + ly_create_alias(NAME GraphCanvas.Builders NAMESPACE Gem TARGETS Gem::GraphCanvas.Editor) + ly_create_alias(NAME GraphCanvas.Tools NAMESPACE Gem TARGETS Gem::GraphCanvas.Editor) + + endif () diff --git a/Gems/GraphModel/Code/CMakeLists.txt b/Gems/GraphModel/Code/CMakeLists.txt index 2c4fc57252..86141140ee 100644 --- a/Gems/GraphModel/Code/CMakeLists.txt +++ b/Gems/GraphModel/Code/CMakeLists.txt @@ -52,6 +52,11 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS) RUNTIME_DEPENDENCIES Gem::GraphCanvas.Editor ) + + # Load the "Gem::GraphModel" module in Builders and Tools + ly_create_alias(NAME GraphModel.Builders NAMESPACE Gem TARGETS Gem::GraphModel.Editor) + ly_create_alias(NAME GraphModel.Tools NAMESPACE Gem TARGETS Gem::GraphModel.Editor) + endif() ################################################################################ diff --git a/Gems/HttpRequestor/Code/CMakeLists.txt b/Gems/HttpRequestor/Code/CMakeLists.txt index 71181f9ff4..bfbc4305b0 100644 --- a/Gems/HttpRequestor/Code/CMakeLists.txt +++ b/Gems/HttpRequestor/Code/CMakeLists.txt @@ -48,6 +48,12 @@ ly_add_target( Gem::HttpRequestor.Static ) +# Load the "Gem::HttpRequestor" module in all types of applicatons. +ly_create_alias(NAME HttpRequestor.Clients NAMESPACE Gem TARGETS Gem::HttpRequestor) +ly_create_alias(NAME HttpRequestor.Servers NAMESPACE Gem TARGETS Gem::HttpRequestor) +ly_create_alias(NAME HttpRequestor.Builders NAMESPACE Gem TARGETS Gem::HttpRequestor) +ly_create_alias(NAME HttpRequestor.Tools NAMESPACE Gem TARGETS Gem::HttpRequestor) + ################################################################################ # Tests ################################################################################ diff --git a/Gems/ImGui/Code/CMakeLists.txt b/Gems/ImGui/Code/CMakeLists.txt index 2f7d6c6ce7..fccdb6fc08 100644 --- a/Gems/ImGui/Code/CMakeLists.txt +++ b/Gems/ImGui/Code/CMakeLists.txt @@ -53,6 +53,8 @@ ly_add_target( PUBLIC Gem::ImGui.imguilib Legacy::CryCommon + RUNTIME_DEPENDENCIES + Gem::ImGui.imguilib ) ly_add_target( @@ -70,6 +72,7 @@ ly_add_target( BUILD_DEPENDENCIES PUBLIC Gem::ImGui.ImGuiLYUtils + PRIVATE Gem::LmbrCentral ) @@ -90,6 +93,10 @@ ly_add_target( Gem::LmbrCentral ) +# Load the above "Gem::ImGui" module in Clients and Servers: +ly_create_alias(NAME ImGui.Clients NAMESPACE Gem TARGETS Gem::ImGui) +ly_create_alias(NAME ImGui.Servers NAMESPACE Gem TARGETS Gem::ImGui) + if(PAL_TRAIT_BUILD_HOST_TOOLS) ly_add_target( NAME ImGui.Editor GEM_MODULE @@ -113,4 +120,9 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS) RUNTIME_DEPENDENCIES Gem::LmbrCentral.Editor ) + + # Load the above "Gem::ImGui.Editor" module in only tools and builders. + ly_create_alias(NAME ImGui.Builders NAMESPACE Gem TARGETS Gem::ImGui.Editor) + ly_create_alias(NAME ImGui.Tools NAMESPACE Gem TARGETS Gem::ImGui.Editor) + endif() diff --git a/Gems/InAppPurchases/Code/CMakeLists.txt b/Gems/InAppPurchases/Code/CMakeLists.txt index 61f9839841..889b651838 100644 --- a/Gems/InAppPurchases/Code/CMakeLists.txt +++ b/Gems/InAppPurchases/Code/CMakeLists.txt @@ -43,3 +43,8 @@ ly_add_target( PRIVATE Gem::InAppPurchases.Static ) +# Load the above "Gem::InAppPurchases" module in all app types +ly_create_alias(NAME InAppPurchases.Clients NAMESPACE Gem TARGETS Gem::InAppPurchases) +ly_create_alias(NAME InAppPurchases.Servers NAMESPACE Gem TARGETS Gem::InAppPurchases) +ly_create_alias(NAME InAppPurchases.Builders NAMESPACE Gem TARGETS Gem::InAppPurchases) +ly_create_alias(NAME InAppPurchases.Tools NAMESPACE Gem TARGETS Gem::InAppPurchases) diff --git a/Gems/LandscapeCanvas/Code/CMakeLists.txt b/Gems/LandscapeCanvas/Code/CMakeLists.txt index e8e2fce689..5288db9cc1 100644 --- a/Gems/LandscapeCanvas/Code/CMakeLists.txt +++ b/Gems/LandscapeCanvas/Code/CMakeLists.txt @@ -35,16 +35,21 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS) Legacy::CryCommon Legacy::Editor.Headers Legacy::EditorCommon - Gem::LmbrCentral - Gem::GraphCanvasWidgets Gem::GraphModel.Editor.Static Gem::GradientSignal.Editor Gem::SurfaceData.Editor Gem::Vegetation.Editor + Gem::LmbrCentral.Editor + PUBLIC + Gem::GraphCanvasWidgets + RUNTIME_DEPENDENCIES + Gem::GradientSignal.Editor + Gem::SurfaceData.Editor + Gem::Vegetation.Editor + Gem::LmbrCentral.Editor ) ly_add_target( NAME LandscapeCanvas.Editor GEM_MODULE - NAMESPACE Gem FILES_CMAKE landscapecanvas_editor_files.cmake @@ -61,7 +66,6 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS) AZ::AzCore AZ::AzToolsFramework Legacy::Editor.Headers - Gem::GraphCanvasWidgets Gem::GraphModel.Editor.Static Gem::LandscapeCanvas.Editor.Static RUNTIME_DEPENDENCIES @@ -72,6 +76,11 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS) Gem::SurfaceData.Editor Gem::Vegetation.Editor ) + + # by default, load the above "Gem::LandscapeCanvas.Editor" module in dev applications + ly_create_alias(NAME LandscapeCanvas.Builders NAMESPACE Gem TARGETS Gem::LandscapeCanvas.Editor) + ly_create_alias(NAME LandscapeCanvas.Tools NAMESPACE Gem TARGETS Gem::LandscapeCanvas.Editor) + endif() ################################################################################ @@ -97,9 +106,10 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) AZ::AzTest AZ::AzFramework AZ::AzToolsFramework - Gem::GraphCanvasWidgets Gem::GraphModel.Editor.Static Gem::LandscapeCanvas.Editor.Static + RUNTIME_DEPENDENCIES + Gem::GraphCanvasWidgets ) ly_add_googletest( NAME Gem::LandscapeCanvas.Editor.Tests diff --git a/Gems/LmbrCentral/Code/CMakeLists.txt b/Gems/LmbrCentral/Code/CMakeLists.txt index c1fbd744e6..4d03d30923 100644 --- a/Gems/LmbrCentral/Code/CMakeLists.txt +++ b/Gems/LmbrCentral/Code/CMakeLists.txt @@ -48,6 +48,11 @@ ly_add_target( Gem::LmbrCentral.Static ) +# by default, load the above "Gem::LmbrCentral" module in Client and Server +ly_create_alias(NAME LmbrCentral.Clients NAMESPACE Gem TARGETS Gem::LmbrCentral) +ly_create_alias(NAME LmbrCentral.Servers NAMESPACE Gem TARGETS Gem::LmbrCentral) + + if (PAL_TRAIT_BUILD_HOST_TOOLS) ly_add_target( NAME LmbrCentral.Editor.Static STATIC @@ -102,6 +107,10 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS) FILES ${QT_LRELEASE_EXECUTABLE} ) + # by default, load the above "Gem::LmbrCentral.Editor" module in dev tools + ly_create_alias(NAME LmbrCentral.Builders NAMESPACE Gem TARGETS Gem::LmbrCentral.Editor) + ly_create_alias(NAME LmbrCentral.Tools NAMESPACE Gem TARGETS Gem::LmbrCentral.Editor) + endif() ################################################################################ diff --git a/Gems/LmbrCentral/Code/Source/Shape/QuadShape.cpp b/Gems/LmbrCentral/Code/Source/Shape/QuadShape.cpp index 052eac47d0..a395dbac2b 100644 --- a/Gems/LmbrCentral/Code/Source/Shape/QuadShape.cpp +++ b/Gems/LmbrCentral/Code/Source/Shape/QuadShape.cpp @@ -205,8 +205,8 @@ namespace LmbrCentral { m_position = currentTransform.GetTranslation(); m_quaternion = currentTransform.GetRotation(); - m_scaledWidth = configuration.m_width * currentTransform.GetScale().GetX() * currentNonUniformScale.GetX(); - m_scaledHeight = configuration.m_height * currentTransform.GetScale().GetY() * currentNonUniformScale.GetY(); + m_scaledWidth = configuration.m_width * currentTransform.GetUniformScale() * currentNonUniformScale.GetX(); + m_scaledHeight = configuration.m_height * currentTransform.GetUniformScale() * currentNonUniformScale.GetY(); } const QuadShapeConfig& QuadShape::GetQuadConfiguration() const diff --git a/Gems/LocalUser/Code/CMakeLists.txt b/Gems/LocalUser/Code/CMakeLists.txt index 6198d88e15..3f2b513282 100644 --- a/Gems/LocalUser/Code/CMakeLists.txt +++ b/Gems/LocalUser/Code/CMakeLists.txt @@ -43,6 +43,9 @@ ly_add_target( Gem::LocalUser.Static ) +# by default, load the above "Gem::LocalUser" module in client applications +ly_create_alias(NAME LocalUser.Clients NAMESPACE Gem TARGETS Gem::LocalUser) + ################################################################################ # Tests ################################################################################ diff --git a/Gems/LyShine/Code/CMakeLists.txt b/Gems/LyShine/Code/CMakeLists.txt index 796cb22292..4dede1c6ac 100644 --- a/Gems/LyShine/Code/CMakeLists.txt +++ b/Gems/LyShine/Code/CMakeLists.txt @@ -27,12 +27,12 @@ ly_add_target( PRIVATE Legacy::CryCommon Gem::LmbrCentral - Gem::TextureAtlas PUBLIC Gem::Atom_RPI.Public Gem::Atom_Utils.Static Gem::Atom_Bootstrap.Headers Gem::AtomFont + Gem::TextureAtlas ) ly_add_target( @@ -50,12 +50,14 @@ ly_add_target( Gem::LyShine.Static Legacy::CryCommon Gem::LmbrCentral - Gem::TextureAtlas RUNTIME_DEPENDENCIES Gem::LmbrCentral Gem::TextureAtlas ) +# by default, load the above "Gem::LyShine" module in Client applications: +ly_create_alias(NAME LyShine.Clients NAMESPACE Gem TARGETS Gem::LyShine) + if (PAL_TRAIT_BUILD_HOST_TOOLS) ly_add_target( NAME LyShine.Editor.Static STATIC @@ -85,7 +87,7 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS) Legacy::EditorCore Gem::LyShine.Static Legacy::CryCommon - Gem::LmbrCentral + Gem::LmbrCentral.Editor Gem::TextureAtlas.Editor Gem::AtomToolsFramework.Static Gem::AtomToolsFramework.Editor @@ -94,6 +96,8 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS) Gem::Atom_RPI.Public Gem::Atom_Utils.Static Gem::Atom_Bootstrap.Headers + RUNTIME_DEPENDENCIES + Gem::TextureAtlas.Editor ) ly_add_target( @@ -117,12 +121,16 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS) Legacy::CryCommon AZ::AssetBuilderSDK Gem::LyShine.Editor.Static - Gem::LmbrCentral + Gem::LmbrCentral.Editor Gem::TextureAtlas.Editor RUNTIME_DEPENDENCIES Gem::LmbrCentral.Editor Gem::TextureAtlas.Editor ) + + # by default, load the above "Gem::LyShine.Editor" module in dev tools: + ly_create_alias(NAME LyShine.Builders NAMESPACE Gem TARGETS Gem::LyShine.Editor) + ly_create_alias(NAME LyShine.Tools NAMESPACE Gem TARGETS Gem::LyShine.Editor) endif() ################################################################################ @@ -146,7 +154,9 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) Gem::LyShine.Static Legacy::CryCommon Gem::LmbrCentral - Gem::TextureAtlas + RUNTIME_DEPENDENCIES + Gem::LmbrCentral + Gem::TextureAtlas ) ly_add_googletest( NAME Gem::LyShine.Tests @@ -175,9 +185,12 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) AZ::AzTest Legacy::CryCommon AZ::AssetBuilderSDK - Gem::LmbrCentral - Gem::TextureAtlas.Editor Gem::LyShine.Editor.Static + Gem::LmbrCentral.Editor + Gem::TextureAtlas + RUNTIME_DEPENDENCIES + Gem::LmbrCentral.Editor + Gem::TextureAtlas.Editor ) ly_add_googletest( NAME Gem::LyShine.Editor.Tests diff --git a/Gems/LyShine/Code/Editor/PropertyHandlerDirectory.cpp b/Gems/LyShine/Code/Editor/PropertyHandlerDirectory.cpp index 61f6e0f3dc..893790d0f5 100644 --- a/Gems/LyShine/Code/Editor/PropertyHandlerDirectory.cpp +++ b/Gems/LyShine/Code/Editor/PropertyHandlerDirectory.cpp @@ -158,7 +158,10 @@ bool PropertyHandlerDirectory::ReadValuesIntoGUI(size_t index, PropertyDirectory ctrl->blockSignals(true); { + // Set currently selected folder path + // Note: this must be done before setting asset type below which updates the GUI display ctrl->SetCurrentAssetHint(instance); + ctrl->SetFolderSelection(instance); // We need to set the asset type so the property panel labels get // populated properly (via SetCurrentAssetType). To avoid defining @@ -166,8 +169,6 @@ bool PropertyHandlerDirectory::ReadValuesIntoGUI(size_t index, PropertyDirectory // logic to run (otherwise it will early-out due to invalid asset type). const char* throwAwayAssetType = "{43EDD212-F589-43C8-BC02-A8F9243271CB}"; ctrl->SetCurrentAssetType(AZ::Data::AssetType(throwAwayAssetType)); - - ctrl->SetFolderSelection(instance); } ctrl->blockSignals(false); diff --git a/Gems/LyShine/Code/Source/LyShineSystemComponent.cpp b/Gems/LyShine/Code/Source/LyShineSystemComponent.cpp index f55c8c5957..0eab7705f6 100644 --- a/Gems/LyShine/Code/Source/LyShineSystemComponent.cpp +++ b/Gems/LyShine/Code/Source/LyShineSystemComponent.cpp @@ -157,6 +157,7 @@ namespace LyShine UiSystemBus::Handler::BusConnect(); UiSystemToolsBus::Handler::BusConnect(); UiFrameworkBus::Handler::BusConnect(); + CrySystemEventBus::Handler::BusConnect(); // register all the component types internal to the LyShine module // These are registered in the order we want them to appear in the Add Component menu @@ -201,6 +202,7 @@ namespace LyShine UiSystemToolsBus::Handler::BusDisconnect(); UiFrameworkBus::Handler::BusDisconnect(); LyShineRequestBus::Handler::BusDisconnect(); + CrySystemEventBus::Handler::BusDisconnect(); LyShineAllocatorScope::DeactivateAllocators(); } @@ -208,8 +210,6 @@ namespace LyShine //////////////////////////////////////////////////////////////////////////////////////////////////// void LyShineSystemComponent::InitializeSystem() { - m_pLyShine = new CLyShine(gEnv->pSystem); - gEnv->pLyShine = m_pLyShine; BroadcastCursorImagePathname(); } @@ -374,6 +374,25 @@ namespace LyShine } } + /////////////////////////////////////////////////////////////////////////////////////////////// + void LyShineSystemComponent::OnCrySystemInitialized(ISystem& system, [[maybe_unused]] const SSystemInitParams& startupParams) + { +#if !defined(AZ_MONOLITHIC_BUILD) + // When module is linked dynamically, we must set our gEnv pointer. + // When module is linked statically, we'll share the application's gEnv pointer. + gEnv = system.GetGlobalEnvironment(); +#endif + m_pLyShine = new CLyShine(gEnv->pSystem); + gEnv->pLyShine = m_pLyShine; + } + + void LyShineSystemComponent::OnCrySystemShutdown([[maybe_unused]] ISystem& system) + { + gEnv->pLyShine = nullptr; + delete m_pLyShine; + m_pLyShine = nullptr; + } + //////////////////////////////////////////////////////////////////////////////////////////////////// void LyShineSystemComponent::BroadcastCursorImagePathname() { diff --git a/Gems/LyShine/Code/Source/LyShineSystemComponent.h b/Gems/LyShine/Code/Source/LyShineSystemComponent.h index 5f45f22823..f65dc75463 100644 --- a/Gems/LyShine/Code/Source/LyShineSystemComponent.h +++ b/Gems/LyShine/Code/Source/LyShineSystemComponent.h @@ -38,6 +38,7 @@ namespace LyShine , protected UiSystemToolsBus::Handler , protected LyShineAllocatorScope , protected UiFrameworkBus::Handler + , protected CrySystemEventBus::Handler { public: AZ_COMPONENT(LyShineSystemComponent, lyShineSystemComponentUuid); @@ -89,6 +90,11 @@ namespace LyShine void HandleEditorOnlyEntities(const EntityList& exportSliceEntities, const EntityIdSet& editorOnlyEntityIds) override; //////////////////////////////////////////////////////////////////////// + // CrySystemEventBus /////////////////////////////////////////////////////// + void OnCrySystemInitialized(ISystem& system, const SSystemInitParams&) override; + virtual void OnCrySystemShutdown(ISystem&) override; + //////////////////////////////////////////////////////////////////////////// + void BroadcastCursorImagePathname(); protected: // data diff --git a/Gems/LyShineExamples/Code/CMakeLists.txt b/Gems/LyShineExamples/Code/CMakeLists.txt index 372bfa948b..96c41bbb64 100644 --- a/Gems/LyShineExamples/Code/CMakeLists.txt +++ b/Gems/LyShineExamples/Code/CMakeLists.txt @@ -20,9 +20,10 @@ ly_add_target( PUBLIC Include BUILD_DEPENDENCIES + PRIVATE + Gem::LmbrCentral PUBLIC Legacy::CryCommon - Gem::LmbrCentral Gem::LyShine.Static ) @@ -39,4 +40,13 @@ ly_add_target( BUILD_DEPENDENCIES PRIVATE Gem::LyShineExamples.Static + Gem::LmbrCentral ) + +# if enabled, LyShineExamples is used by all kinds of applications, however, the dependency to LmbrCentral is different +# per application type +ly_create_alias(NAME LyShineExamples.Builders NAMESPACE Gem TARGETS Gem::LyShineExamples Gem::LmbrCentral.Editor) +ly_create_alias(NAME LyShineExamples.Tools NAMESPACE Gem TARGETS Gem::LyShineExamples Gem::LmbrCentral.Editor) +ly_create_alias(NAME LyShineExamples.Clients NAMESPACE Gem TARGETS Gem::LyShineExamples Gem::LmbrCentral) +ly_create_alias(NAME LyShineExamples.Servers NAMESPACE Gem TARGETS Gem::LyShineExamples Gem::LmbrCentral) + diff --git a/Gems/Maestro/Code/CMakeLists.txt b/Gems/Maestro/Code/CMakeLists.txt index fe58ba03a6..01b22b1abf 100644 --- a/Gems/Maestro/Code/CMakeLists.txt +++ b/Gems/Maestro/Code/CMakeLists.txt @@ -22,7 +22,6 @@ ly_add_target( BUILD_DEPENDENCIES PRIVATE Legacy::CryCommon - Gem::LmbrCentral ) ly_add_target( @@ -39,11 +38,14 @@ ly_add_target( PRIVATE Legacy::CryCommon Gem::Maestro.Static - Gem::LmbrCentral RUNTIME_DEPENDENCIES Gem::LmbrCentral ) +# if enabled, "Maestro" module is used for Clients and Servers: +ly_create_alias(NAME Maestro.Clients NAMESPACE Gem TARGETS Gem::Maestro) +ly_create_alias(NAME Maestro.Servers NAMESPACE Gem TARGETS Gem::Maestro) + if (PAL_TRAIT_BUILD_HOST_TOOLS) ly_add_target( NAME Maestro.Editor GEM_MODULE @@ -69,10 +71,13 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS) AZ::AzToolsFramework AZ::AssetBuilderSDK Gem::Maestro.Static - Gem::LmbrCentral RUNTIME_DEPENDENCIES Gem::LmbrCentral.Editor ) + # the .Editor variant is used in dev tools: + ly_create_alias(NAME Maestro.Tools NAMESPACE Gem TARGETS Gem::Maestro.Editor) + ly_create_alias(NAME Maestro.Builders NAMESPACE Gem TARGETS Gem::Maestro.Editor) + endif() ################################################################################ diff --git a/Gems/Maestro/Code/Source/Cinematics/AnimComponentNode.cpp b/Gems/Maestro/Code/Source/Cinematics/AnimComponentNode.cpp index ea7322014c..324b712f40 100644 --- a/Gems/Maestro/Code/Source/Cinematics/AnimComponentNode.cpp +++ b/Gems/Maestro/Code/Source/Cinematics/AnimComponentNode.cpp @@ -324,11 +324,11 @@ void CAnimComponentNode::ConvertBetweenWorldAndLocalRotation(Quat& rotation, ETr { AZ::Quaternion rot(rotation.v.x, rotation.v.y, rotation.v.z, rotation.w); AZ::Transform rotTransform = AZ::Transform::CreateFromQuaternion(rot); - rotTransform.ExtractScale(); + rotTransform.ExtractUniformScale(); AZ::Transform parentTransform = AZ::Transform::Identity(); GetParentWorldTransform(parentTransform); - parentTransform.ExtractScale(); + parentTransform.ExtractUniformScale(); if (conversionDirection == eTransformConverstionDirection_toLocalSpace) { parentTransform.Invert(); @@ -344,7 +344,7 @@ void CAnimComponentNode::ConvertBetweenWorldAndLocalRotation(Quat& rotation, ETr void CAnimComponentNode::ConvertBetweenWorldAndLocalScale(Vec3& scale, ETransformSpaceConversionDirection conversionDirection) const { AZ::Transform parentTransform = AZ::Transform::Identity(); - AZ::Transform scaleTransform = AZ::Transform::CreateScale(AZ::Vector3(scale.x, scale.y, scale.z)); + AZ::Transform scaleTransform = AZ::Transform::CreateUniformScale(AZ::Vector3(scale.x, scale.y, scale.z).GetMaxElement()); GetParentWorldTransform(parentTransform); if (conversionDirection == eTransformConverstionDirection_toLocalSpace) @@ -353,8 +353,8 @@ void CAnimComponentNode::ConvertBetweenWorldAndLocalScale(Vec3& scale, ETransfor } scaleTransform = parentTransform * scaleTransform; - AZ::Vector3 vScale = scaleTransform.GetScale(); - scale.Set(vScale.GetX(), vScale.GetY(), vScale.GetZ()); + const float uniformScale = scaleTransform.GetUniformScale(); + scale.Set(uniformScale, uniformScale, uniformScale); } ////////////////////////////////////////////////////////////////////////// diff --git a/Gems/Maestro/Code/Source/Cinematics/AnimNode.cpp b/Gems/Maestro/Code/Source/Cinematics/AnimNode.cpp index ec6bf4258a..7fea8097ed 100644 --- a/Gems/Maestro/Code/Source/Cinematics/AnimNode.cpp +++ b/Gems/Maestro/Code/Source/Cinematics/AnimNode.cpp @@ -280,6 +280,45 @@ static bool AnimNodeVersionConverter( rootElement.AddElement(serializeContext, "BaseClass1", azrtti_typeid()); } + if (rootElement.GetVersion() < 4) + { + // remove vector scale tracks from transform anim nodes + AZStd::string name; + if (rootElement.FindSubElementAndGetData(AZ_CRC_CE("Name"), name) && name == "Transform") + { + auto tracksElement = rootElement.FindSubElement(AZ_CRC_CE("Tracks")); + if (tracksElement) + { + for (int trackIndex = tracksElement->GetNumSubElements() - 1; trackIndex >= 0; trackIndex--) + { + auto trackElement = tracksElement->GetSubElement(trackIndex); + bool isScale = false; + + // trackElement should be an intrusive_ptr with one child + if (trackElement.GetNumSubElements() == 1) + { + auto ptrElement = trackElement.GetSubElement(0); + auto paramTypeElement = ptrElement.FindSubElement(AZ_CRC_CE("ParamType")); + if (paramTypeElement) + { + AZStd::string paramName; + if (paramTypeElement->FindSubElementAndGetData(AZ_CRC_CE("Name"), paramName) && paramName == "Scale") + { + isScale = true; + } + } + } + + if (isScale) + { + tracksElement->RemoveElement(trackIndex); + } + } + } + } + + } + return true; } @@ -288,7 +327,7 @@ void CAnimNode::Reflect(AZ::ReflectContext* context) if (auto serializeContext = azrtti_cast(context)) { serializeContext->Class() - ->Version(3, &AnimNodeVersionConverter) + ->Version(4, &AnimNodeVersionConverter) ->Field("ID", &CAnimNode::m_id) ->Field("Name", &CAnimNode::m_name) ->Field("Flags", &CAnimNode::m_flags) diff --git a/Gems/MessagePopup/Code/CMakeLists.txt b/Gems/MessagePopup/Code/CMakeLists.txt index 2d0ad1ebcc..fa89b61f21 100644 --- a/Gems/MessagePopup/Code/CMakeLists.txt +++ b/Gems/MessagePopup/Code/CMakeLists.txt @@ -38,3 +38,7 @@ ly_add_target( PRIVATE Gem::MessagePopup.Static ) + +# MessagePopup is used only in client applications +ly_create_alias(NAME MessagePopup.Clients NAMESPACE Gem TARGETS Gem::MessagePopup) + diff --git a/Gems/Metastream/Code/CMakeLists.txt b/Gems/Metastream/Code/CMakeLists.txt index 326c21c5a9..95f7746b91 100644 --- a/Gems/Metastream/Code/CMakeLists.txt +++ b/Gems/Metastream/Code/CMakeLists.txt @@ -50,6 +50,11 @@ ly_add_target( Legacy::CryCommon ) +# The above "Metastream" target is used by all types of applications, including dev tools. +ly_create_alias(NAME Metastream.Clients NAMESPACE Gem TARGETS Gem::Metastream) +ly_create_alias(NAME Metastream.Servers NAMESPACE Gem TARGETS Gem::Metastream) +ly_create_alias(NAME Metastream.Builders NAMESPACE Gem TARGETS Gem::Metastream) +ly_create_alias(NAME Metastream.Tools NAMESPACE Gem TARGETS Gem::Metastream) ################################################################################ # Tests diff --git a/Gems/Microphone/Code/CMakeLists.txt b/Gems/Microphone/Code/CMakeLists.txt index 942899735c..17d492d786 100644 --- a/Gems/Microphone/Code/CMakeLists.txt +++ b/Gems/Microphone/Code/CMakeLists.txt @@ -46,3 +46,7 @@ ly_add_target( RUNTIME_DEPENDENCIES Gem::AudioSystem ) + +# The above "Microphone" target is used by all interactive applications +ly_create_alias(NAME Microphone.Clients NAMESPACE Gem TARGETS Gem::Microphone) +ly_create_alias(NAME Microphone.Tools NAMESPACE Gem TARGETS Gem::Microphone) diff --git a/Gems/Multiplayer/Code/CMakeLists.txt b/Gems/Multiplayer/Code/CMakeLists.txt index 019f341d0c..430fe5ca4b 100644 --- a/Gems/Multiplayer/Code/CMakeLists.txt +++ b/Gems/Multiplayer/Code/CMakeLists.txt @@ -58,9 +58,35 @@ ly_add_target( Gem::CertificateManager ) +ly_add_target( + NAME Multiplayer.Debug ${PAL_TRAIT_MONOLITHIC_DRIVEN_MODULE_TYPE} + NAMESPACE Gem + FILES_CMAKE + multiplayer_debug_files.cmake + INCLUDE_DIRECTORIES + PRIVATE + Source + . + PUBLIC + Include + BUILD_DEPENDENCIES + PRIVATE + AZ::AzCore + AZ::AtomCore + AZ::AzFramework + AZ::AzNetworking + Gem::Atom_Feature_Common.Static + Gem::Multiplayer.Static + Gem::ImGui.Static +) + +# The "Multiplayer" target is used by clients and servers, Debug is used only on clients. +ly_create_alias(NAME Multiplayer.Clients NAMESPACE Gem TARGETS Gem::Multiplayer Gem::Multiplayer.Debug) +ly_create_alias(NAME Multiplayer.Servers NAMESPACE Gem TARGETS Gem::Multiplayer) + if (PAL_TRAIT_BUILD_HOST_TOOLS) ly_add_target( - NAME Multiplayer.Tools.Static STATIC + NAME Multiplayer.Builders.Static STATIC NAMESPACE Gem FILES_CMAKE multiplayer_tools_files.cmake @@ -80,10 +106,12 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS) Gem::Multiplayer.Static ) + # by naming this target Multiplayer.Builders it ensures that it is loaded + # in any pipeline tools (Like Asset Processor, AssetBuilder, etc) ly_add_target( - NAME Multiplayer.Tools MODULE + NAME Multiplayer.Builders GEM_MODULE NAMESPACE Gem - OUTPUT_NAME Gem.Multiplayer.Tools + OUTPUT_NAME Gem.Multiplayer.Builders FILES_CMAKE multiplayer_tools_files.cmake INCLUDE_DIRECTORIES @@ -94,7 +122,7 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS) Include BUILD_DEPENDENCIES PRIVATE - Gem::Multiplayer.Tools.Static + Gem::Multiplayer.Builders.Static ) ly_add_target( @@ -121,9 +149,11 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS) AZ::AzNetworking AZ::AzToolsFramework Gem::Multiplayer.Static - Gem::Multiplayer.Tools + Gem::Multiplayer.Builders ) + # use the Multiplayer.Editor module in tools like the Editor: Such tools also get the visual debug view: + ly_create_alias(NAME Multiplayer.Tools NAMESPACE Gem TARGETS Gem::Multiplayer.Editor Gem::Multiplayer.Debug) endif() if (PAL_TRAIT_BUILD_TESTS_SUPPORTED) @@ -151,7 +181,7 @@ if (PAL_TRAIT_BUILD_TESTS_SUPPORTED) if (PAL_TRAIT_BUILD_HOST_TOOLS) ly_add_target( - NAME Multiplayer.Tools.Tests ${PAL_TRAIT_TEST_TARGET_TYPE} + NAME Multiplayer.Builders.Tests ${PAL_TRAIT_TEST_TARGET_TYPE} NAMESPACE Gem FILES_CMAKE multiplayer_tools_tests_files.cmake @@ -165,33 +195,11 @@ if (PAL_TRAIT_BUILD_TESTS_SUPPORTED) AZ::AzTest AZ::AzTestShared AZ::AzToolsFrameworkTestCommon - Gem::Multiplayer.Tools.Static + Gem::Multiplayer.Builders.Static ) ly_add_googletest( - NAME Gem::Multiplayer.Tools.Tests + NAME Gem::Multiplayer.Builders.Tests ) endif() endif() - -ly_add_target( - NAME Multiplayer.Debug ${PAL_TRAIT_MONOLITHIC_DRIVEN_MODULE_TYPE} - NAMESPACE Gem - FILES_CMAKE - multiplayer_debug_files.cmake - INCLUDE_DIRECTORIES - PRIVATE - Source - . - PUBLIC - Include - BUILD_DEPENDENCIES - PRIVATE - AZ::AzCore - AZ::AtomCore - AZ::AzFramework - AZ::AzNetworking - Gem::Atom_Feature_Common.Static - Gem::Multiplayer.Static - Gem::ImGui.Static -) diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerTypes.h b/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerTypes.h index 16cc4146dd..85c7e85c0a 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerTypes.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerTypes.h @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -41,7 +42,7 @@ namespace Multiplayer //! This is a strong typedef for representing the number of application frames since application start. AZ_TYPE_SAFE_INTEGRAL(HostFrameId, uint32_t); - static constexpr HostFrameId InvalidHostFrameId = HostFrameId{ 0xFFFFFFFF }; + static constexpr HostFrameId InvalidHostFrameId = HostFrameId{ AzPhysics::SimulatedBody::UndefinedFrameId }; using LongNetworkString = AZ::CVarFixedString; using ReliabilityType = AzNetworking::ReliabilityType; diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Physics/PhysicsUtils.h b/Gems/Multiplayer/Code/Include/Multiplayer/Physics/PhysicsUtils.h new file mode 100644 index 0000000000..4013e20a1e --- /dev/null +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Physics/PhysicsUtils.h @@ -0,0 +1,37 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include + +namespace Multiplayer +{ + namespace Physics + { + //! Performs rewind-aware ray cast in the default physics world. + //! @param request The ray cast request to make. + //! @return Returns a structure that contains a list of Hits. + AzPhysics::SceneQueryHits RayCast(const AzPhysics::RayCastRequest& request); + + //! Performs rewind-aware shape cast in the default physics world. + //! @param request The shape cast request to make. + //! @return Returns a structure that contains a list of Hits. + AzPhysics::SceneQueryHits ShapeCast(const AzPhysics::ShapeCastRequest& request); + + //! Performs rewind-aware overlap in the default physics world. + //! @param request The overlap request to make. + //! @return Returns a structure that contains a list of Hits. + AzPhysics::SceneQueryHits Overlap(const AzPhysics::OverlapRequest& request); + + } // namespace Physics +} // namespace Multiplayer diff --git a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Header.jinja b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Header.jinja index 8cf1eeeb58..79fbb4a99e 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Header.jinja +++ b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Header.jinja @@ -383,6 +383,8 @@ namespace {{ Component.attrib['Namespace'] }} {{ DeclareNetworkPropertyAccessors(Component, 'Authority', 'Client', true)|indent(8) -}} {{ DeclareNetworkPropertyAccessors(Component, 'Authority', 'Autonomous', false)|indent(8) -}} {{ DeclareNetworkPropertyAccessors(Component, 'Authority', 'Autonomous', true)|indent(8) -}} + {{ DeclareNetworkPropertyAccessors(Component, 'Autonomous', 'Authority', false)|indent(8) -}} + {{ DeclareNetworkPropertyAccessors(Component, 'Autonomous', 'Authority', true)|indent(8) -}} {{ DeclareArchetypePropertyGetters(Component)|indent(8) -}} {{ DeclareRpcInvocations(Component, 'Server', 'Authority', false)|indent(8) -}} {{ DeclareRpcInvocations(Component, 'Server', 'Authority', true)|indent(8) -}} @@ -456,7 +458,6 @@ namespace {{ Component.attrib['Namespace'] }} {% endif %} {{ DeclareNetworkPropertyGetters(Component, 'Authority', 'Server', false)|indent(8) -}} - {{ DeclareNetworkPropertyGetters(Component, 'Authority', 'Autonomous', false)|indent(8) -}} {{ DeclareNetworkPropertyGetters(Component, 'Authority', 'Client', false)|indent(8) -}} {{ DeclareArchetypePropertyGetters(Component)|indent(8) -}} {{ DeclareRpcInvocations(Component, 'Server', 'Authority', false)|indent(8) -}} @@ -481,8 +482,6 @@ namespace {{ Component.attrib['Namespace'] }} //! @} {{ DeclareNetworkPropertyGetters(Component, 'Authority', 'Server', true)|indent(8) -}} - {{ DeclareNetworkPropertyGetters(Component, 'Authority', 'Autonomous', true)|indent(8) -}} - {{ DeclareNetworkPropertyGetters(Component, 'Autonomous', 'Authority', true)|indent(8) -}} {{ DeclareNetworkPropertyGetters(Component, 'Authority', 'Client', true)|indent(8) -}} {{ DeclareRpcInvocations(Component, 'Server', 'Authority', true)|indent(8) -}} {{ AutoComponentMacros.DeclareRpcHandlers(Component, 'Authority', 'Client', false)|indent(8) -}} diff --git a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja index 6b2c5b199a..5cfd0bfc4d 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja +++ b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja @@ -118,22 +118,25 @@ void {{ ClassName }}::Set{{ UpperFirst(Property.attrib['Name']) }}(int32_t index bool {{ ClassName }}::{{ UpperFirst(Property.attrib['Name']) }}PushBack(const {{ Property.attrib['Type'] }} &value) { - if (GetParent().m_{{ LowerFirst(Property.attrib['Name']) }}.push_back(value)) + int32_t indexToSet = GetParent().m_{{ LowerFirst(Property.attrib['Name']) }}.size(); + if (indexToSet < {{ Property.attrib['Count'] }}) { - int32_t indexToSet = GetParent().m_{{ LowerFirst(Property.attrib['Name']) }}.size(); + GetParent().m_{{ LowerFirst(Property.attrib['Name']) }}.push_back(value); int32_t bitIndex = indexToSet + static_cast({{ AutoComponentMacros.GetNetPropertiesQualifiedPropertyDirtyEnum(Component.attrib['Name'], ReplicateFrom, ReplicateTo, Property, 'Start') }}); GetParent().m_currentRecord->m_{{ LowerFirst(AutoComponentMacros.GetNetPropertiesSetName(ReplicateFrom, ReplicateTo)) }}.SetBit(bitIndex, true); - GetParent().m_currentRecord->m_{{ LowerFirst(AutoComponentMacros.GetNetPropertiesSetName(ReplicateFrom, ReplicateTo)) }}.SetBit(static_cast({{ AutoComponentMacros.GetNetPropertiesQualifiedPropertyDirtyEnum(Component.attrib['Name'], ReplicateFrom, ReplicateTo, Property, 'Size') }}), true); + GetParent().m_currentRecord->m_{{ LowerFirst(AutoComponentMacros.GetNetPropertiesSetName(ReplicateFrom, ReplicateTo)) }}.SetBit(aznumeric_cast({{ AutoComponentMacros.GetNetPropertiesQualifiedPropertyDirtyEnum(Component.attrib['Name'], ReplicateFrom, ReplicateTo, Property, 'Size') }}), true); GetParent().MarkDirty(); return true; } + return false; } bool {{ ClassName }}::{{ UpperFirst(Property.attrib['Name']) }}PopBack(const Multiplayer::NetworkInput&) { - if (GetParent().m_{{ LowerFirst(Property.attrib['Name']) }}.pop_back()) + if (!GetParent().m_{{ LowerFirst(Property.attrib['Name']) }}.empty()) { - GetParent().m_currentRecord->m_{{ LowerFirst(AutoComponentMacros.GetNetPropertiesSetName(ReplicateFrom, ReplicateTo)) }}.SetBit(static_cast({{ AutoComponentMacros.GetNetPropertiesQualifiedPropertyDirtyEnum(Component.attrib['Name'], ReplicateFrom, ReplicateTo, Property, 'Size') }}), true); + GetParent().m_{{ LowerFirst(Property.attrib['Name']) }}.pop_back(); + GetParent().m_currentRecord->m_{{ LowerFirst(AutoComponentMacros.GetNetPropertiesSetName(ReplicateFrom, ReplicateTo)) }}.SetBit(aznumeric_cast({{ AutoComponentMacros.GetNetPropertiesQualifiedPropertyDirtyEnum(Component.attrib['Name'], ReplicateFrom, ReplicateTo, Property, 'Size') }}), true); GetParent().MarkDirty(); return true; } @@ -216,10 +219,11 @@ void {{ ClassName }}::Set{{ UpperFirst(Property.attrib['Name']) }}(int32_t index bool {{ ClassName }}::{{ UpperFirst(Property.attrib['Name']) }}PushBack(const {{ Property.attrib['Type'] }} &value) { - if (GetParent().m_{{ LowerFirst(Property.attrib['Name']) }}.push_back(value)) + int32_t indexToSet = GetParent().m_{{ LowerFirst(Property.attrib['Name']) }}.size(); + if (indexToSet < {{ Property.attrib['Count'] }}) { - uint32_t indexToSet = aznumeric_cast(GetParent().m_{{ LowerFirst(Property.attrib['Name']) }}.size()); - uint32_t bitIndex = indexToSet + aznumeric_cast({{ AutoComponentMacros.GetNetPropertiesQualifiedPropertyDirtyEnum(Component.attrib['Name'], ReplicateFrom, ReplicateTo, Property, 'Start') }}); + GetParent().m_{{ LowerFirst(Property.attrib['Name']) }}.push_back(value); + int32_t bitIndex = indexToSet + static_cast({{ AutoComponentMacros.GetNetPropertiesQualifiedPropertyDirtyEnum(Component.attrib['Name'], ReplicateFrom, ReplicateTo, Property, 'Start') }}); GetParent().m_currentRecord->m_{{ LowerFirst(AutoComponentMacros.GetNetPropertiesSetName(ReplicateFrom, ReplicateTo)) }}.SetBit(bitIndex, true); GetParent().m_currentRecord->m_{{ LowerFirst(AutoComponentMacros.GetNetPropertiesSetName(ReplicateFrom, ReplicateTo)) }}.SetBit(aznumeric_cast({{ AutoComponentMacros.GetNetPropertiesQualifiedPropertyDirtyEnum(Component.attrib['Name'], ReplicateFrom, ReplicateTo, Property, 'Size') }}), true); GetParent().MarkDirty(); @@ -230,9 +234,10 @@ bool {{ ClassName }}::{{ UpperFirst(Property.attrib['Name']) }}PushBack(const {{ bool {{ ClassName }}::{{ UpperFirst(Property.attrib['Name']) }}PopBack() { - if (GetParent().m_{{ LowerFirst(Property.attrib['Name']) }}.pop_back()) + if (!GetParent().m_{{ LowerFirst(Property.attrib['Name']) }}.empty()) { - GetParent().m_currentRecord->m_{{ LowerFirst(AutoComponentMacros.GetNetPropertiesSetName(ReplicateFrom, ReplicateTo)) }}.SetBit(static_cast({{ AutoComponentMacros.GetNetPropertiesQualifiedPropertyDirtyEnum(Component.attrib['Name'], ReplicateFrom, ReplicateTo, Property, 'Size') }}), true); + GetParent().m_{{ LowerFirst(Property.attrib['Name']) }}.pop_back(); + GetParent().m_currentRecord->m_{{ LowerFirst(AutoComponentMacros.GetNetPropertiesSetName(ReplicateFrom, ReplicateTo)) }}.SetBit(aznumeric_cast({{ AutoComponentMacros.GetNetPropertiesQualifiedPropertyDirtyEnum(Component.attrib['Name'], ReplicateFrom, ReplicateTo, Property, 'Size') }}), true); GetParent().MarkDirty(); return true; } @@ -241,7 +246,7 @@ bool {{ ClassName }}::{{ UpperFirst(Property.attrib['Name']) }}PopBack() void {{ ClassName }}::{{ UpperFirst(Property.attrib['Name']) }}Clear() { - GetParent().m_currentRecord->m_{{ LowerFirst(AutoComponentMacros.GetNetPropertiesSetName(ReplicateFrom, ReplicateTo)) }}.SetBit(static_cast({{ AutoComponentMacros.GetNetPropertiesQualifiedPropertyDirtyEnum(Component.attrib['Name'], ReplicateFrom, ReplicateTo, Property, 'Size') }}), true); + GetParent().m_currentRecord->m_{{ LowerFirst(AutoComponentMacros.GetNetPropertiesSetName(ReplicateFrom, ReplicateTo)) }}.SetBit(aznumeric_cast({{ AutoComponentMacros.GetNetPropertiesQualifiedPropertyDirtyEnum(Component.attrib['Name'], ReplicateFrom, ReplicateTo, Property, 'Size') }}), true); GetParent().m_{{ LowerFirst(Property.attrib['Name']) }}.clear(); GetParent().MarkDirty(); } @@ -586,8 +591,16 @@ bool {{ ClassName }}::Serialize{{ AutoComponentMacros.GetNetPropertiesSetName(Re const uint32_t lastBit = static_cast({{ AutoComponentMacros.GetNetPropertiesQualifiedPropertyDirtyEnum(Component.attrib['Name'], ReplicateFrom, ReplicateTo, Property, 'End') }}); {% endif %} +{% if Property.attrib['IsRewindable']|booleanTrue %} AzNetworking::FixedSizeBitsetView deltaRecord(replicationRecord.m_{{ LowerFirst(AutoComponentMacros.GetNetPropertiesSetName(ReplicateFrom, ReplicateTo)) }}, firstBit, lastBit - firstBit + 1); m_{{ LowerFirst(Property.attrib['Name']) }}.Serialize(serializer, deltaRecord); +{% else %} +{% if Property.attrib['Container'] == 'Vector' %} + serializer.Serialize>(m_{{ LowerFirst(Property.attrib['Name']) }}, "{{ LowerFirst(Property.attrib['Name']) }}"); +{% elif Property.attrib['Container'] == 'Array' %} + serializer.Serialize>(m_{{ LowerFirst(Property.attrib['Name']) }}, "{{ LowerFirst(Property.attrib['Name']) }}"); +{% endif %} +{% endif %} } {% else %} Multiplayer::SerializeNetworkPropertyHelper @@ -618,11 +631,11 @@ void {{ ClassName }}::NotifyChanges{{ AutoComponentMacros.GetNetPropertiesSetNam {% if (Property.attrib['GenerateEventBindings']|booleanTrue) %} {% if Property.attrib['Container'] != 'None' and Property.attrib['Container'] != 'Object' %} // NotifyChangesAuthorityToClientProperties for Arrays and Vectors - for (uint32_t bitIndex = static_cast({{ AutoComponentMacros.GetNetPropertiesQualifiedPropertyDirtyEnum(Component.attrib['Name'], ReplicateFrom, ReplicateTo, Property, 'Start') }}), elementIndex = 0; bitIndex <= static_cast({{ AutoComponentMacros.GetNetPropertiesQualifiedPropertyDirtyEnum(Component, ReplicateFrom, ReplicateTo, Property, 'End') }}); ++bitIndex, ++elementIndex) + for (uint32_t bitIndex = static_cast({{ AutoComponentMacros.GetNetPropertiesQualifiedPropertyDirtyEnum(Component.attrib['Name'], ReplicateFrom, ReplicateTo, Property, 'Start') }}), elementIndex = 0; bitIndex <= static_cast({{ AutoComponentMacros.GetNetPropertiesQualifiedPropertyDirtyEnum(Component.attrib['Name'], ReplicateFrom, ReplicateTo, Property, 'End') }}); ++bitIndex, ++elementIndex) { - if (replicationRecord.m_{{ LowerFirst(AutoComponentMacros.GetNetPropertiesSetName(ReplicateFrom, ReplicateTo)) }}.GetBit(bitIndex){% if Property.attrib['Container'] == 'Vector' %} && elementIndex < m_{{ Property.attrib['Name'] }}.GetSize(){% endif %}) + if (replicationRecord.m_{{ LowerFirst(AutoComponentMacros.GetNetPropertiesSetName(ReplicateFrom, ReplicateTo)) }}.GetBit(bitIndex){% if Property.attrib['Container'] == 'Vector' %} && elementIndex < m_{{ LowerFirst(Property.attrib['Name']) }}.size(){% endif %}) { - m_LowerFirst( Property.attrib['Name']) }}Event.Signal(elementIndex, m_{{ LowerFirst(Property.attrib['Name']) }}[elementIndex]); + m_{{ LowerFirst(Property.attrib['Name']) }}Event.Signal(elementIndex, m_{{ LowerFirst(Property.attrib['Name']) }}[elementIndex]); } } {% if Property.attrib['Container'] == 'Vector' %} @@ -783,12 +796,27 @@ enum class NetworkProperties AZ_Warning("Network Property", false, "{{ ClassName }} Get{{ UpperFirst(Property.attrib['Name']) }} failed. Entity '%s' (id: %s) is missing {{ ClassName }}, be sure to add {{ ClassName }} to this entity.", entity->GetName().c_str(), id.ToString().c_str()) return {{ Property.attrib['Type'] }}(); } +{% if (ReplicateTo == 'Autonomous') or (ReplicateFrom == 'Autonomous' and ReplicateTo == 'Authority') %} + // {{ UpperFirst(Property.attrib['Name']) }} is only sent and received between contoller objects (ie Authority, Autonomous); we must go through the controller in order to get this property + {{ ClassName }}Controller* controller = static_cast<{{ ClassName }}Controller*>(networkComponent->GetController()); + if (!controller) + { + AZ_Warning("Network Property", false, "{{ ClassName }} Get{{ UpperFirst(Property.attrib['Name']) }} method failed. Entity '%s' (id: %s) {{ ClassName }} is missing the network controller. This property is replicated to autonomous network entities, because this entity doesn't have a controller, it must not be automonous. Please check your network context before attempting to get {{ UpperFirst(Property.attrib['Name']) }}.", entity->GetName().c_str(), id.ToString().c_str()) + return {{ Property.attrib['Type'] }}(); + } +{% if Property.attrib['Container'] == 'Vector' or Property.attrib['Container'] == 'Array' %} + return controller->Get{{ UpperFirst(Property.attrib['Name']) }}(index); +{% else %} + return controller->Get{{ UpperFirst(Property.attrib['Name']) }}(); +{% endif %} +{% else %} {% if Property.attrib['Container'] == 'Vector' or Property.attrib['Container'] == 'Array' %} return networkComponent->Get{{ UpperFirst(Property.attrib['Name']) }}(index); {% else %} return networkComponent->Get{{ UpperFirst(Property.attrib['Name']) }}(); {% endif %} +{% endif %} }) {% if Property.attrib['Container'] == 'Vector' or Property.attrib['Container'] == 'Array' %} ->Method("Set{{ UpperFirst(Property.attrib['Name']) }}", [](AZ::EntityId id, int32_t index, const {{ Property.attrib['Type'] }}& value) -> void @@ -1467,12 +1495,8 @@ namespace {{ Component.attrib['Namespace'] }} } {{ DefineNetworkPropertyGets(Component, 'Authority', 'Server', false, ComponentBaseName)|indent(4) -}} -{{ DefineNetworkPropertyGets(Component, 'Authority', 'Autonomous', false, ComponentBaseName)|indent(4) -}} -{{ DefineNetworkPropertyGets(Component, 'Autonomous', 'Authority', false, ComponentBaseName)|indent(4) -}} {{ DefineNetworkPropertyGets(Component, 'Authority', 'Client', false, ComponentBaseName)|indent(4) -}} {{ DefineNetworkPropertyGets(Component, 'Authority', 'Server', true, ComponentBaseName)|indent(4) -}} -{{ DefineNetworkPropertyGets(Component, 'Authority', 'Autonomous', true, ComponentBaseName)|indent(4) -}} -{{ DefineNetworkPropertyGets(Component, 'Autonomous', 'Authority', true, ComponentBaseName)|indent(4) -}} {{ DefineNetworkPropertyGets(Component, 'Authority', 'Client', true, ComponentBaseName)|indent(4) }} {{ DefineArchetypePropertyGets(Component, ClassType, ComponentBaseName)|indent(4) -}} {{ DefineRpcInvocations(Component, ComponentBaseName, 'Server', 'Authority', false)|indent(4) -}} diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp index 38f0fda94e..074e9af86c 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp @@ -469,19 +469,16 @@ namespace Multiplayer m_connAcquiredEvent.Signal(datum); } + // Hosts will spawn a new default player prefab for the user that just connected if (GetAgentType() == MultiplayerAgentType::ClientServer || GetAgentType() == MultiplayerAgentType::DedicatedServer) { - PrefabEntityId playerPrefabEntityId(AZ::Name(static_cast(sv_defaultPlayerSpawnAsset).c_str()), 1); - INetworkEntityManager::EntityList entityList = m_networkEntityManager.CreateEntitiesImmediate(playerPrefabEntityId, NetEntityRole::Authority, AZ::Transform::CreateIdentity()); - - NetworkEntityHandle controlledEntity; - if (entityList.size() > 0) + NetworkEntityHandle controlledEntity = SpawnDefaultPlayerPrefab(); + if (controlledEntity.Exists()) { - controlledEntity = entityList[0]; controlledEntity.GetNetBindComponent()->SetOwningConnectionId(connection->GetConnectionId()); } - + if (connection->GetUserData() == nullptr) // Only add user data if the connect event handler has not already done so { connection->SetUserData(new ServerToClientConnectionData(connection, *this, controlledEntity)); @@ -553,6 +550,17 @@ namespace Multiplayer } } m_agentType = multiplayerType; + + // Spawn the default player for this host since the host is also a player (not a dedicated server) + if (m_agentType == MultiplayerAgentType::ClientServer) + { + NetworkEntityHandle controlledEntity = SpawnDefaultPlayerPrefab(); + if (NetBindComponent* controlledEntityNetBindComponent = controlledEntity.GetNetBindComponent()) + { + controlledEntityNetBindComponent->SetAllowAutonomy(true); + } + } + AZLOG_INFO("Multiplayer operating in %s mode", GetEnumString(m_agentType)); } @@ -728,6 +736,19 @@ namespace Multiplayer } } + NetworkEntityHandle MultiplayerSystemComponent::SpawnDefaultPlayerPrefab() + { + PrefabEntityId playerPrefabEntityId(AZ::Name(static_cast(sv_defaultPlayerSpawnAsset).c_str()), 1); + INetworkEntityManager::EntityList entityList = m_networkEntityManager.CreateEntitiesImmediate(playerPrefabEntityId, NetEntityRole::Authority, AZ::Transform::CreateIdentity()); + + NetworkEntityHandle controlledEntity; + if (entityList.size() > 0) + { + controlledEntity = entityList[0]; + } + return controlledEntity; + } + void host([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments) { Multiplayer::MultiplayerAgentType serverType = sv_isDedicated ? MultiplayerAgentType::DedicatedServer : MultiplayerAgentType::ClientServer; diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h index a38bb935a2..3b1faafd40 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h @@ -105,7 +105,8 @@ namespace Multiplayer void TickVisibleNetworkEntities(float deltaTime, float serverRateSeconds); void OnConsoleCommandInvoked(AZStd::string_view command, const AZ::ConsoleCommandContainer& args, AZ::ConsoleFunctorFlags flags, AZ::ConsoleInvokedFrom invokedFrom); void ExecuteConsoleCommandList(AzNetworking::IConnection* connection, const AZStd::fixed_vector& commands); - + NetworkEntityHandle SpawnDefaultPlayerPrefab(); + AZ_CONSOLEFUNC(MultiplayerSystemComponent, DumpStats, AZ::ConsoleFunctorFlags::Null, "Dumps stats for the current multiplayer session"); AzNetworking::INetworkInterface* m_networkInterface = nullptr; diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp index dda3f18ad4..eaf89f3489 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp @@ -402,8 +402,12 @@ namespace Multiplayer const AZ::Transform& transform ) { - INetworkEntityManager::EntityList returnList; - + EntityList returnList; + if (!AZ::Data::AssetManager::IsReady()) + { + return returnList; + } + auto spawnableAssetId = m_networkPrefabLibrary.GetAssetIdByName(prefabEntryId.m_prefabName); // Required for sync-instantiation. Todo: keep the reference in NetworkSpawnableLibrary auto netSpawnableAsset = AZ::Data::AssetManager::Instance().GetAsset(spawnableAssetId, AZ::Data::AssetLoadBehavior::PreLoad); diff --git a/Gems/Multiplayer/Code/Source/Physics/PhysicsUtils.cpp b/Gems/Multiplayer/Code/Source/Physics/PhysicsUtils.cpp new file mode 100644 index 0000000000..6341f5b060 --- /dev/null +++ b/Gems/Multiplayer/Code/Source/Physics/PhysicsUtils.cpp @@ -0,0 +1,104 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include + +#include +#include +#include +#include + +namespace +{ + template + AzPhysics::SceneQueryHits SceneQueryInternal(const RequestT& request) + { + auto* sceneInterface = AZ::Interface::Get(); + if (!sceneInterface) + { + return {}; + } + + AzPhysics::SceneHandle sceneHandle = sceneInterface->GetSceneHandle(AzPhysics::DefaultPhysicsSceneName); + if (sceneHandle == AzPhysics::InvalidSceneHandle) + { + return {}; + } + + Multiplayer::INetworkTime* currentNetTime = Multiplayer::GetNetworkTime(); + + if(!currentNetTime->IsTimeRewound()) + { + // If the time is not rewound, we simply execute the scene query as is. + AzPhysics::SceneQueryHits result = sceneInterface->QueryScene(sceneHandle, &request); + return result; + } + + // If the time is rewound, we want to query against rigid bodies present at the same frame ID: the same as the current rewound time is. + RequestT netSceneQueryRequest = request; + netSceneQueryRequest.m_filterCallback = [&request, currentFrameId = (uint32_t)currentNetTime->GetHostFrameId()]( + const AzPhysics::SimulatedBody* body, const ::Physics::Shape* shape) + { + if (body->GetFrameId() == AzPhysics::SimulatedBody::UndefinedFrameId || body->GetFrameId() == currentFrameId) + { + if (request.m_filterCallback) + { + return request.m_filterCallback(body, shape); + } + + // Overlap filter callbacks return true/false rather than Touch/Block/None + if constexpr (AZStd::is_same_v) + { + return true; + } + else + { + return AzPhysics::SceneQuery::QueryHitType::Touch; + } + } + + if constexpr (AZStd::is_same_v) + { + return false; + } + else + { + return AzPhysics::SceneQuery::QueryHitType::None; + } + }; + + // Execute the scene query modified for the time rewind. + AzPhysics::SceneQueryHits result = sceneInterface->QueryScene(sceneHandle, &netSceneQueryRequest); + return result; + } +} + +namespace Multiplayer +{ + namespace Physics + { + AzPhysics::SceneQueryHits RayCast(const AzPhysics::RayCastRequest& request) + { + return SceneQueryInternal(request); + } + + AzPhysics::SceneQueryHits ShapeCast(const AzPhysics::ShapeCastRequest& request) + { + return SceneQueryInternal(request); + } + + AzPhysics::SceneQueryHits Overlap(const AzPhysics::OverlapRequest& request) + { + return SceneQueryInternal(request); + } + } // namespace Physics +} // namespace Multiplayer diff --git a/Gems/Multiplayer/Code/Source/Pipeline/NetBindMarkerComponent.cpp b/Gems/Multiplayer/Code/Source/Pipeline/NetBindMarkerComponent.cpp index 1696e851a5..c93c09cfbc 100644 --- a/Gems/Multiplayer/Code/Source/Pipeline/NetBindMarkerComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Pipeline/NetBindMarkerComponent.cpp @@ -59,7 +59,7 @@ namespace Multiplayer AZ::Transform worldTm = GetEntity()->FindComponent()->GetWorldTM(); auto preInsertionCallback = [worldTm = AZStd::move(worldTm), netEntityIndex = m_netEntityIndex, spawnableAssetId = m_networkSpawnableAsset.GetId()] - (AzFramework::EntitySpawnTicket&, AzFramework::SpawnableEntityContainerView entities) + (AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableEntityContainerView entities) { if (entities.size() == 1) { @@ -81,7 +81,8 @@ namespace Multiplayer }; m_netSpawnTicket = AzFramework::EntitySpawnTicket(m_networkSpawnableAsset); - AzFramework::SpawnableEntitiesInterface::Get()->SpawnEntities(m_netSpawnTicket, {m_netEntityIndex}, preInsertionCallback); + AzFramework::SpawnableEntitiesInterface::Get()->SpawnEntities( + m_netSpawnTicket, AzFramework::SpawnablePriority_Default, { m_netEntityIndex }, preInsertionCallback); } } @@ -89,7 +90,7 @@ namespace Multiplayer { if(m_netSpawnTicket.IsValid()) { - AzFramework::SpawnableEntitiesInterface::Get()->DespawnAllEntities(m_netSpawnTicket); + AzFramework::SpawnableEntitiesInterface::Get()->DespawnAllEntities(m_netSpawnTicket, AzFramework::SpawnablePriority_Default); } } diff --git a/Gems/Multiplayer/Code/multiplayer_files.cmake b/Gems/Multiplayer/Code/multiplayer_files.cmake index 1cfef93240..73de45ba9d 100644 --- a/Gems/Multiplayer/Code/multiplayer_files.cmake +++ b/Gems/Multiplayer/Code/multiplayer_files.cmake @@ -39,6 +39,7 @@ set(FILES Include/Multiplayer/NetworkTime/RewindableFixedVector.inl Include/Multiplayer/NetworkTime/RewindableObject.h Include/Multiplayer/NetworkTime/RewindableObject.inl + Include/Multiplayer/Physics/PhysicsUtils.h Include/Multiplayer/ReplicationWindows/IReplicationWindow.h Source/Multiplayer_precompiled.cpp Source/Multiplayer_precompiled.h @@ -107,6 +108,7 @@ set(FILES Source/Pipeline/NetBindMarkerComponent.h Source/Pipeline/NetworkSpawnableHolderComponent.cpp Source/Pipeline/NetworkSpawnableHolderComponent.h + Source/Physics/PhysicsUtils.cpp Source/ReplicationWindows/NullReplicationWindow.cpp Source/ReplicationWindows/NullReplicationWindow.h Source/ReplicationWindows/ServerToClientReplicationWindow.cpp diff --git a/Gems/MultiplayerCompression/Code/CMakeLists.txt b/Gems/MultiplayerCompression/Code/CMakeLists.txt index 58ce546543..acc7978e88 100644 --- a/Gems/MultiplayerCompression/Code/CMakeLists.txt +++ b/Gems/MultiplayerCompression/Code/CMakeLists.txt @@ -39,6 +39,11 @@ ly_add_target( Gem::MultiplayerCompression.Static ) +# use the MultiplayerCompression module everywhere except builders: +ly_create_alias(NAME MultiplayerCompression.Clients NAMESPACE Gem TARGETS Gem::MultiplayerCompression) +ly_create_alias(NAME MultiplayerCompression.Tools NAMESPACE Gem TARGETS Gem::MultiplayerCompression) +ly_create_alias(NAME MultiplayerCompression.Servers NAMESPACE Gem TARGETS Gem::MultiplayerCompression) + ################################################################################ # Tests ################################################################################ diff --git a/Gems/NvCloth/Code/CMakeLists.txt b/Gems/NvCloth/Code/CMakeLists.txt index 0f019a985f..d7eaf80b16 100644 --- a/Gems/NvCloth/Code/CMakeLists.txt +++ b/Gems/NvCloth/Code/CMakeLists.txt @@ -56,6 +56,10 @@ ly_add_target( Gem::AtomLyIntegration_CommonFeatures ) +# use the NvCloth module in clients and servers: +ly_create_alias(NAME NvCloth.Clients NAMESPACE Gem TARGETS Gem::NvCloth) +ly_create_alias(NAME NvCloth.Servers NAMESPACE Gem TARGETS Gem::NvCloth) + if(PAL_TRAIT_BUILD_HOST_TOOLS) ly_add_target( NAME NvCloth.Editor.Static STATIC @@ -97,6 +101,10 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS) RUNTIME_DEPENDENCIES Gem::AtomLyIntegration_CommonFeatures.Editor ) + + # use the NvCloth.Editor module in dev tools: + ly_create_alias(NAME NvCloth.Builders NAMESPACE Gem TARGETS Gem::NvCloth.Editor) + ly_create_alias(NAME NvCloth.Tools NAMESPACE Gem TARGETS Gem::NvCloth.Editor) endif() ################################################################################ diff --git a/AutomatedTesting/Gem/Code/Platform/Linux/tool_dependencies.cmake b/Gems/PBSreferenceMaterials/CMakeLists.txt similarity index 100% rename from AutomatedTesting/Gem/Code/Platform/Linux/tool_dependencies.cmake rename to Gems/PBSreferenceMaterials/CMakeLists.txt diff --git a/Gems/PhysX/Assets/PhysX/TemplateMaterialLibrary.physmaterial b/Gems/PhysX/Assets/PhysX/TemplateMaterialLibrary.physmaterial new file mode 100644 index 0000000000..481cd2fbfa --- /dev/null +++ b/Gems/PhysX/Assets/PhysX/TemplateMaterialLibrary.physmaterial @@ -0,0 +1,158 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Gems/PhysX/Code/CMakeLists.txt b/Gems/PhysX/Code/CMakeLists.txt index b4c7b580a6..d281270ce4 100644 --- a/Gems/PhysX/Code/CMakeLists.txt +++ b/Gems/PhysX/Code/CMakeLists.txt @@ -46,6 +46,7 @@ ly_add_target( AZ::AzCore AZ::AzFramework Legacy::CryCommon + PRIVATE Gem::LmbrCentral ) @@ -66,10 +67,15 @@ ly_add_target( BUILD_DEPENDENCIES PRIVATE Gem::PhysX.Static + Gem::LmbrCentral RUNTIME_DEPENDENCIES Gem::LmbrCentral ) +# use the PhysX module in clients and servers: +ly_create_alias(NAME PhysX.Clients NAMESPACE Gem TARGETS Gem::PhysX) +ly_create_alias(NAME PhysX.Servers NAMESPACE Gem TARGETS Gem::PhysX) + if(PAL_TRAIT_BUILD_HOST_TOOLS) ly_associate_package(PACKAGE_NAME poly2tri-0.3.3-rev2-multiplatform TARGETS poly2tri PACKAGE_HASH 04092d06716f59b936b61906eaf3647db23b685d81d8b66131eb53e0aeaa1a38) @@ -107,7 +113,7 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS) AZ::SceneCore AZ::SceneData Legacy::CryCommon - Gem::LmbrCentral + Gem::LmbrCentral.Editor Gem::PhysX.NumericalMethods Gem::PhysX.Static Gem::AtomLyIntegration_CommonFeatures.Static @@ -122,6 +128,9 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS) AUTOMOC FILES_CMAKE physx_editor_shared_files.cmake + COMPILE_DEFINITIONS + PUBLIC + PHYSX_ENABLE_MULTI_THREADING INCLUDE_DIRECTORIES PRIVATE . @@ -136,6 +145,10 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS) Gem::LmbrCentral.Editor ) + # use the PhysX.Editor module in dev tools: + ly_create_alias(NAME PhysX.Builders NAMESPACE Gem TARGETS Gem::PhysX.Editor) + ly_create_alias(NAME PhysX.Tools NAMESPACE Gem TARGETS Gem::PhysX.Editor) + endif() ################################################################################ @@ -157,6 +170,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) AZ::AzTestShared AZ::AzTest Gem::PhysX.Static + Gem::LmbrCentral RUNTIME_DEPENDENCIES Gem::LmbrCentral ) diff --git a/Gems/PhysX/Code/Editor/DebugDraw.cpp b/Gems/PhysX/Code/Editor/DebugDraw.cpp index 23a9a3cb44..829b776e28 100644 --- a/Gems/PhysX/Code/Editor/DebugDraw.cpp +++ b/Gems/PhysX/Code/Editor/DebugDraw.cpp @@ -17,13 +17,13 @@ #include #include #include +#include #include #include #include #include -#include -#include +#include namespace PhysX { @@ -415,11 +415,16 @@ namespace PhysX { case GlobalCollisionDebugColorMode::MaterialColor: { - Physics::MaterialFromAssetConfiguration materialConfiguration; const Physics::MaterialId materialId = colliderConfig.m_materialSelection.GetMaterialId(elementDebugInfo.m_materialSlotIndex); - if (colliderConfig.m_materialSelection.GetMaterialConfiguration(materialConfiguration, materialId)) + + AZStd::shared_ptr physicsMaterial; + Physics::PhysicsMaterialRequestBus::BroadcastResult( + physicsMaterial, + &Physics::PhysicsMaterialRequestBus::Events::GetMaterialById, + materialId); + if (physicsMaterial) { - debugColor = materialConfiguration.m_configuration.m_debugColor; + debugColor = physicsMaterial->GetDebugColor(); } break; } diff --git a/Gems/PhysX/Code/Editor/SettingsWidget.cpp b/Gems/PhysX/Code/Editor/SettingsWidget.cpp index 38022e8c92..20a67a778d 100644 --- a/Gems/PhysX/Code/Editor/SettingsWidget.cpp +++ b/Gems/PhysX/Code/Editor/SettingsWidget.cpp @@ -37,14 +37,15 @@ namespace PhysX const Debug::DebugDisplayData& debugDisplayData) { m_physxSystemConfiguration = physxSystemConfiguration; - m_defaultPhysicsMaterialLibrary.m_asset = m_physxSystemConfiguration.m_defaultMaterialLibrary; + m_physicsMaterialInfo.m_defaultMaterialConfiguration = m_physxSystemConfiguration.m_defaultMaterialConfiguration; + m_physicsMaterialInfo.m_materialLibraryAsset = m_physxSystemConfiguration.m_materialLibraryAsset; m_defaultSceneConfiguration = defaultSceneConfiguration; m_debugDisplayData = debugDisplayData; blockSignals(true); m_propertyEditor->ClearInstances(); m_propertyEditor->AddInstance(&m_physxSystemConfiguration); - m_propertyEditor->AddInstance(&m_defaultPhysicsMaterialLibrary); + m_propertyEditor->AddInstance(&m_physicsMaterialInfo); m_propertyEditor->AddInstance(&m_defaultSceneConfiguration); m_propertyEditor->AddInstance(&m_debugDisplayData); m_propertyEditor->AddInstance(&m_physxSystemConfiguration.m_windConfiguration); @@ -88,7 +89,8 @@ namespace PhysX void SettingsWidget::SetPropertyEditingComplete(AzToolsFramework::InstanceDataNode* /*node*/) { - m_physxSystemConfiguration.m_defaultMaterialLibrary = m_defaultPhysicsMaterialLibrary.m_asset; + m_physxSystemConfiguration.m_defaultMaterialConfiguration = m_physicsMaterialInfo.m_defaultMaterialConfiguration; + m_physxSystemConfiguration.m_materialLibraryAsset = m_physicsMaterialInfo.m_materialLibraryAsset; emit onValueChanged(m_physxSystemConfiguration, m_defaultSceneConfiguration, m_debugDisplayData diff --git a/Gems/PhysX/Code/Editor/SettingsWidget.h b/Gems/PhysX/Code/Editor/SettingsWidget.h index 78df72b6d3..a4001e13b7 100644 --- a/Gems/PhysX/Code/Editor/SettingsWidget.h +++ b/Gems/PhysX/Code/Editor/SettingsWidget.h @@ -56,7 +56,7 @@ namespace PhysX AzToolsFramework::ReflectedPropertyEditor* m_propertyEditor; DocumentationLinkWidget* m_documentationLinkWidget; - Physics::DefaultMaterialLibraryAssetReflectionWrapper m_defaultPhysicsMaterialLibrary; + Physics::MaterialInfoReflectionWrapper m_physicsMaterialInfo; PhysX::PhysXSystemConfiguration m_physxSystemConfiguration; AzPhysics::SceneConfiguration m_defaultSceneConfiguration; Debug::DebugDisplayData m_debugDisplayData; diff --git a/Gems/PhysX/Code/Editor/Source/Components/EditorSystemComponent.cpp b/Gems/PhysX/Code/Editor/Source/Components/EditorSystemComponent.cpp index e510aa505e..b28bf6ab4a 100644 --- a/Gems/PhysX/Code/Editor/Source/Components/EditorSystemComponent.cpp +++ b/Gems/PhysX/Code/Editor/Source/Components/EditorSystemComponent.cpp @@ -30,40 +30,91 @@ namespace PhysX { - static bool CreateSurfaceTypeMaterialLibrary(const AZStd::string & targetFilePath) + constexpr const char* DefaultAssetFilename = "SurfaceTypeMaterialLibrary"; + constexpr const char* TemplateAssetFilename = "PhysX/TemplateMaterialLibrary"; + + static AZStd::optional> GetMaterialLibraryTemplate() { - auto assetType = AZ::AzTypeInfo::Uuid(); + const auto& assetType = AZ::AzTypeInfo::Uuid(); - // Create File - AZ::Data::Asset newAsset = AZ::Data::AssetManager::Instance().CreateAsset(AZ::Uuid::CreateRandom(), assetType, AZ::Data::AssetLoadBehavior::Default); + AZStd::vector assetTypeExtensions; + AZ::AssetTypeInfoBus::Event(assetType, &AZ::AssetTypeInfo::GetAssetTypeExtensions, assetTypeExtensions); - AZ::IO::FileIOStream fileStream(targetFilePath.c_str(), AZ::IO::OpenMode::ModeWrite); + if (assetTypeExtensions.size() == 1) + { + // Constructing the path to the library asset + const AZStd::string& assetExtension = assetTypeExtensions[0]; + + // Use the path relative to the asset root to avoid hardcoding full path in the configuration + AZStd::string relativePath = TemplateAssetFilename; + AzFramework::StringFunc::Path::ReplaceExtension(relativePath, assetExtension.c_str()); + + AZ::Data::AssetId assetId; + AZ::Data::AssetCatalogRequestBus::BroadcastResult( + assetId, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath, relativePath.c_str(), assetType, false /*autoRegisterIfNotFound*/); + + if (assetId.IsValid()) + { + return AZ::Data::AssetManager::Instance().GetAsset(assetId, AZ::Data::AssetLoadBehavior::NoLoad); + } + } + + return AZStd::nullopt; + } + + static AZStd::optional> CreateMaterialLibrary(const AZStd::string& fullTargetFilePath, const AZStd::string& relativePath) + { + AZ::IO::FileIOStream fileStream(fullTargetFilePath.c_str(), AZ::IO::OpenMode::ModeWrite); if (fileStream.IsOpen()) { - Physics::MaterialLibraryAsset* materialLibraryAsset = azrtti_cast(newAsset.GetData()); - if (materialLibraryAsset) - { - // check it out in the source control system - AzToolsFramework::SourceControlCommandBus::Broadcast( - &AzToolsFramework::SourceControlCommandBus::Events::RequestEdit, targetFilePath.c_str(), true, - [](bool /*success*/, const AzToolsFramework::SourceControlFileInfo& /*info*/) {}); + const auto& assetType = AZ::AzTypeInfo::Uuid(); + AZ::Data::AssetId assetId; - // Save the material library asset into a file - auto assetHandler = const_cast(AZ::Data::AssetManager::Instance().GetHandler(assetType)); - if (assetHandler->SaveAssetData(newAsset, &fileStream)) + AZ::Data::AssetCatalogRequestBus::BroadcastResult( + assetId, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath, relativePath.c_str(), assetType, true /*autoRegisterIfNotFound*/); + + AZ::Data::Asset newAsset = + AZ::Data::AssetManager::Instance().GetAsset(assetId, assetType, AZ::Data::AssetLoadBehavior::Default); + + if (auto* newMaterialLibraryData = azrtti_cast(newAsset.GetData())) + { + if (auto templateLibraryOpt = GetMaterialLibraryTemplate()) { - return true; - } - else - { - AZ_Error("PhysX", false, - "CreateSurfaceTypeMaterialLibrary: Unable to save Surface Types Material Library Asset to %s", - targetFilePath.c_str()); + if (const auto* templateMaterialLibData = azrtti_cast(templateLibraryOpt->GetData())) + { + templateLibraryOpt->QueueLoad(); + templateLibraryOpt->BlockUntilLoadComplete(); + + // Fill the newly created material library using the template data + for (const auto& materialData : templateMaterialLibData->GetMaterialsData()) + { + newMaterialLibraryData->AddMaterialData(materialData); + } + + // check it out in the source control system + AzToolsFramework::SourceControlCommandBus::Broadcast( + &AzToolsFramework::SourceControlCommandBus::Events::RequestEdit, fullTargetFilePath.c_str(), true /*allowMultiCheckout*/, + [](bool /*success*/, const AzToolsFramework::SourceControlFileInfo& /*info*/) {}); + + // Save the material library asset into a file + auto assetHandler = AZ::Data::AssetManager::Instance().GetHandler(assetType); + if (assetHandler->SaveAssetData(newAsset, &fileStream)) + { + return newAsset; + } + else + { + AZ_Error( + "PhysX", false, + "CreateSurfaceTypeMaterialLibrary: Unable to save Surface Types Material Library Asset to %s", + fullTargetFilePath.c_str()); + } + } } } } - return false; + return AZStd::nullopt; } void EditorSystemComponent::Reflect(AZ::ReflectContext* context) @@ -84,11 +135,26 @@ namespace PhysX { Physics::EditorWorldBus::Handler::BusConnect(); + m_onMaterialLibraryLoadErrorEventHandler = AzPhysics::SystemEvents::OnMaterialLibraryLoadErrorEvent::Handler( + [this]([[maybe_unused]] AzPhysics::SystemEvents::MaterialLibraryLoadErrorType error) + { + // Attempt to set/create the default material library if there was an error + if (auto* physxSystem = GetPhysXSystem()) + { + if (auto retrievedMaterialLibrary = RetrieveDefaultMaterialLibrary()) + { + physxSystem->UpdateMaterialLibrary(retrievedMaterialLibrary.value()); + } + } + } + ); + if (auto* physicsSystem = AZ::Interface::Get()) { AzPhysics::SceneConfiguration editorWorldConfiguration = physicsSystem->GetDefaultSceneConfiguration(); editorWorldConfiguration.m_sceneName = AzPhysics::EditorPhysicsSceneName; m_editorWorldSceneHandle = physicsSystem->AddScene(editorWorldConfiguration); + physicsSystem->RegisterOnMaterialLibraryLoadErrorEventHandler(m_onMaterialLibraryLoadErrorEventHandler); } PhysX::RegisterConfigStringLineEditHandler(); // Register custom unique string line edit control @@ -109,6 +175,8 @@ namespace PhysX physicsSystem->RemoveScene(m_editorWorldSceneHandle); } m_editorWorldSceneHandle = AzPhysics::InvalidSceneHandle; + + m_onMaterialLibraryLoadErrorEventHandler.Disconnect(); } AzPhysics::SceneHandle EditorSystemComponent::GetEditorSceneHandle() const @@ -148,7 +216,7 @@ namespace PhysX PhysX::Editor::EditorWindow::RegisterViewClass(); } - AZ::Data::AssetId EditorSystemComponent::GenerateSurfaceTypesLibrary() + AZStd::optional> EditorSystemComponent::RetrieveDefaultMaterialLibrary() { AZ::Data::AssetId resultAssetId; @@ -159,8 +227,6 @@ namespace PhysX if (assetTypeExtensions.size() == 1) { - const char* DefaultAssetFilename = "SurfaceTypeMaterialLibrary"; - // Constructing the path to the library asset const AZStd::string& assetExtension = assetTypeExtensions[0]; @@ -169,40 +235,44 @@ namespace PhysX AzFramework::StringFunc::Path::ReplaceExtension(relativePath, assetExtension.c_str()); // Try to find an already existing material library - AZ::Data::AssetCatalogRequestBus::BroadcastResult(resultAssetId, &AZ::Data::AssetCatalogRequests::GetAssetIdByPath, relativePath.c_str(), azrtti_typeid(), false); + AZ::Data::AssetCatalogRequestBus::BroadcastResult(resultAssetId, + &AZ::Data::AssetCatalogRequests::GetAssetIdByPath, relativePath.c_str(), azrtti_typeid(), false /*autoRegisterIfNotFound*/); if (!resultAssetId.IsValid()) { + // No file for the default material library, create it const char* assetRoot = AZ::IO::FileIOBase::GetInstance()->GetAlias("@devassets@"); - AZStd::string fullPath; AzFramework::StringFunc::Path::ConstructFull(assetRoot, DefaultAssetFilename, assetExtension.c_str(), fullPath); - if (CreateSurfaceTypeMaterialLibrary(fullPath)) + if (auto materialLibraryOpt = CreateMaterialLibrary(fullPath, relativePath)) { - // Find out the asset ID for the material library we've just created - AZ::Data::AssetCatalogRequestBus::BroadcastResult( - resultAssetId, &AZ::Data::AssetCatalogRequests::GetAssetIdByPath, - relativePath.c_str(), - azrtti_typeid(), true); + return materialLibraryOpt; } else { AZ_Warning("PhysX", false, - "GenerateSurfaceTypesLibrary: Failed to create material library at %s. " + "CreateMaterialLibrary: Failed to create material library at %s. " "Please check if the file is writable", fullPath.c_str()); } } + else + { + AZ::Data::Asset existingMaterialLibrary = + AZ::Data::AssetManager::Instance().GetAsset(resultAssetId, AZ::Data::AssetLoadBehavior::NoLoad); + + return existingMaterialLibrary; + } } else { AZ_Warning("PhysX", false, - "GenerateSurfaceTypesLibrary: Number of extensions for the physics material library asset is %u" + "RetrieveDefaultMaterialLibrary: Number of extensions for the physics material library asset is %u" " but should be 1. Please check if the asset registered itself with the asset system correctly", assetTypeExtensions.size()) } - return resultAssetId; + return AZStd::nullopt; } } diff --git a/Gems/PhysX/Code/Editor/Source/Components/EditorSystemComponent.h b/Gems/PhysX/Code/Editor/Source/Components/EditorSystemComponent.h index 9ebca05ccd..4bd11a951f 100644 --- a/Gems/PhysX/Code/Editor/Source/Components/EditorSystemComponent.h +++ b/Gems/PhysX/Code/Editor/Source/Components/EditorSystemComponent.h @@ -14,6 +14,7 @@ #include #include +#include #include namespace AzPhysics @@ -65,8 +66,9 @@ namespace PhysX void PopulateEditorGlobalContextMenu(QMenu* menu, const AZ::Vector2& point, int flags) override; void NotifyRegisterViews() override; - AZ::Data::AssetId GenerateSurfaceTypesLibrary(); + AZStd::optional> RetrieveDefaultMaterialLibrary(); + AzPhysics::SystemEvents::OnMaterialLibraryLoadErrorEvent::Handler m_onMaterialLibraryLoadErrorEventHandler; AzPhysics::SceneHandle m_editorWorldSceneHandle = AzPhysics::InvalidSceneHandle; }; } diff --git a/Gems/PhysX/Code/Include/PhysX/Configuration/PhysXConfiguration.h b/Gems/PhysX/Code/Include/PhysX/Configuration/PhysXConfiguration.h index 936eadad18..598cdc1b4c 100644 --- a/Gems/PhysX/Code/Include/PhysX/Configuration/PhysXConfiguration.h +++ b/Gems/PhysX/Code/Include/PhysX/Configuration/PhysXConfiguration.h @@ -13,7 +13,6 @@ #pragma once #include #include -#include #include #include @@ -55,7 +54,6 @@ namespace PhysX static PhysXSystemConfiguration CreateDefault(); WindConfiguration m_windConfiguration; //!< Wind configuration for PhysX. - AZ::Data::Asset m_defaultMaterialLibrary = AZ::Data::AssetLoadBehavior::NoLoad; //!< Material Library exposed by the system component SystemBus API. bool operator==(const PhysXSystemConfiguration& other) const; bool operator!=(const PhysXSystemConfiguration& other) const; diff --git a/Gems/PhysX/Code/Include/PhysX/MeshColliderComponentBus.h b/Gems/PhysX/Code/Include/PhysX/MeshColliderComponentBus.h index 33305be145..ac36ebbc7f 100644 --- a/Gems/PhysX/Code/Include/PhysX/MeshColliderComponentBus.h +++ b/Gems/PhysX/Code/Include/PhysX/MeshColliderComponentBus.h @@ -38,10 +38,6 @@ namespace PhysX /// @param id The asset ID to set it to. virtual void SetMeshAsset(const AZ::Data::AssetId& id) = 0; - /// Sets the material library asset to the collider. - /// @param id The asset ID to set it to. - virtual void SetMaterialAsset(const AZ::Data::AssetId& id) = 0; - /// Sets the material id from the material library. /// @param id The asset ID to set it to. virtual void SetMaterialId(const Physics::MaterialId& id) = 0; diff --git a/Gems/PhysX/Code/Source/Configuration/PhysXConfiguration.cpp b/Gems/PhysX/Code/Source/Configuration/PhysXConfiguration.cpp index 3ba520513f..a0db4d5d30 100644 --- a/Gems/PhysX/Code/Source/Configuration/PhysXConfiguration.cpp +++ b/Gems/PhysX/Code/Source/Configuration/PhysXConfiguration.cpp @@ -36,6 +36,18 @@ namespace PhysX return configuration; } + + bool PhysXSystemConfigurationConverter([[maybe_unused]] AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& dataElement) + { + if (dataElement.GetVersion() <= 1) + { + dataElement.RemoveElementByName(AZ_CRC_CE("DefaultMaterialLibrary")); + AZ_Warning("PhysXSystemConfigurationConverter", false, + "Old version of PhysX Configuration data found. Physics material library will be reset to default."); + } + + return true; + } } AZ_CLASS_ALLOCATOR_IMPL(WindConfiguration, AZ::SystemAllocator, 0); @@ -89,9 +101,8 @@ namespace PhysX if (auto* serializeContext = azdynamic_cast(context)) { serializeContext->Class() - ->Version(1) + ->Version(2, &PhysXInternal::PhysXSystemConfigurationConverter) ->Field("WindConfiguration", &PhysXSystemConfiguration::m_windConfiguration) - ->Field("MaterialLibrary", &PhysXSystemConfiguration::m_defaultMaterialLibrary) ; if (AZ::EditContext* editContext = serializeContext->GetEditContext()) @@ -115,7 +126,6 @@ namespace PhysX bool PhysXSystemConfiguration::operator==(const PhysXSystemConfiguration& other) const { return AzPhysics::SystemConfiguration::operator==(other) && - m_defaultMaterialLibrary == other.m_defaultMaterialLibrary && m_windConfiguration == other.m_windConfiguration ; } diff --git a/Gems/PhysX/Code/Source/EditorColliderComponent.cpp b/Gems/PhysX/Code/Source/EditorColliderComponent.cpp index 87bb702184..4454429092 100644 --- a/Gems/PhysX/Code/Source/EditorColliderComponent.cpp +++ b/Gems/PhysX/Code/Source/EditorColliderComponent.cpp @@ -17,7 +17,7 @@ #include #include #include -#include +#include #include #include #include @@ -352,10 +352,13 @@ namespace PhysX AzToolsFramework::PropertyModificationRefreshLevel::Refresh_AttributesAndValues); }); - m_onDefaultMaterialLibraryChangedEventHandler = AzPhysics::SystemEvents::OnDefaultMaterialLibraryChangedEvent::Handler( + m_onMaterialLibraryChangedEventHandler = AzPhysics::SystemEvents::OnMaterialLibraryChangedEvent::Handler( [this](const AZ::Data::AssetId& defaultMaterialLibrary) { - m_configuration.m_materialSelection.OnDefaultMaterialLibraryChanged(defaultMaterialLibrary); + m_configuration.m_materialSelection.OnMaterialLibraryChanged(defaultMaterialLibrary); + + AzToolsFramework::PropertyEditorGUIMessages::Bus::Broadcast(&AzToolsFramework::PropertyEditorGUIMessages::RequestRefresh, + AzToolsFramework::PropertyModificationRefreshLevel::Refresh_AttributesAndValues); }); AzToolsFramework::Components::EditorComponentBase::Activate(); @@ -463,13 +466,13 @@ namespace PhysX if (auto* physXSystem = GetPhysXSystem()) { physXSystem->RegisterSystemConfigurationChangedEvent(m_physXConfigChangedHandler); - physXSystem->RegisterOnDefaultMaterialLibraryChangedEventHandler(m_onDefaultMaterialLibraryChangedEventHandler); + physXSystem->RegisterOnMaterialLibraryChangedEventHandler(m_onMaterialLibraryChangedEventHandler); } } void EditorColliderComponent::OnDeselected() { - m_onDefaultMaterialLibraryChangedEventHandler.Disconnect(); + m_onMaterialLibraryChangedEventHandler.Disconnect(); m_physXConfigChangedHandler.Disconnect(); } @@ -681,11 +684,6 @@ namespace PhysX } } - void EditorColliderComponent::SetMaterialAsset(const AZ::Data::AssetId& id) - { - m_configuration.m_materialSelection.SetMaterialLibrary(id); - } - void EditorColliderComponent::SetMaterialId(const Physics::MaterialId& id) { m_configuration.m_materialSelection.SetMaterialId(id); @@ -693,8 +691,10 @@ namespace PhysX void EditorColliderComponent::UpdateMaterialSlotsFromMeshAsset() { - Physics::SystemRequestBus::Broadcast(&Physics::SystemRequests::UpdateMaterialSelection, - m_shapeConfiguration.GetCurrent(), m_configuration); + Physics::PhysicsMaterialRequestBus::Broadcast( + &Physics::PhysicsMaterialRequestBus::Events::UpdateMaterialSelectionFromPhysicsAsset, + m_shapeConfiguration.GetCurrent(), + m_configuration.m_materialSelection); AzToolsFramework::ToolsApplicationEvents::Bus::Broadcast(&AzToolsFramework::ToolsApplicationEvents::InvalidatePropertyDisplay, AzToolsFramework::Refresh_EntireTree); diff --git a/Gems/PhysX/Code/Source/EditorColliderComponent.h b/Gems/PhysX/Code/Source/EditorColliderComponent.h index 818de62a04..07e1131ea9 100644 --- a/Gems/PhysX/Code/Source/EditorColliderComponent.h +++ b/Gems/PhysX/Code/Source/EditorColliderComponent.h @@ -158,7 +158,6 @@ namespace PhysX AZ::Data::Asset GetMeshAsset() const override; Physics::MaterialId GetMaterialId() const override; void SetMeshAsset(const AZ::Data::AssetId& id) override; - void SetMaterialAsset(const AZ::Data::AssetId& id) override; void SetMaterialId(const Physics::MaterialId& id) override; void UpdateMaterialSlotsFromMeshAsset(); @@ -251,7 +250,7 @@ namespace PhysX DebugDraw::Collider m_colliderDebugDraw; AzPhysics::SystemEvents::OnConfigurationChangedEvent::Handler m_physXConfigChangedHandler; - AzPhysics::SystemEvents::OnDefaultMaterialLibraryChangedEvent::Handler m_onDefaultMaterialLibraryChangedEventHandler; + AzPhysics::SystemEvents::OnMaterialLibraryChangedEvent::Handler m_onMaterialLibraryChangedEventHandler; AZ::Transform m_cachedWorldTransform; AZ::NonUniformScaleChangedEvent::Handler m_nonUniformScaleChangedHandler; //!< Responds to changes in non-uniform scale. diff --git a/Gems/PhysX/Code/Source/EditorShapeColliderComponent.cpp b/Gems/PhysX/Code/Source/EditorShapeColliderComponent.cpp index 692fbf96f3..0cdba94a34 100644 --- a/Gems/PhysX/Code/Source/EditorShapeColliderComponent.cpp +++ b/Gems/PhysX/Code/Source/EditorShapeColliderComponent.cpp @@ -44,11 +44,14 @@ namespace PhysX AzToolsFramework::PropertyEditorGUIMessages::Bus::Broadcast(&AzToolsFramework::PropertyEditorGUIMessages::RequestRefresh, AzToolsFramework::PropertyModificationRefreshLevel::Refresh_AttributesAndValues); }) - , m_onDefaultMaterialLibraryChangedEventHandler( + , m_onMaterialLibraryChangedEventHandler( [this](const AZ::Data::AssetId& defaultMaterialLibrary) { - m_colliderConfig.m_materialSelection.OnDefaultMaterialLibraryChanged(defaultMaterialLibrary); + m_colliderConfig.m_materialSelection.OnMaterialLibraryChanged(defaultMaterialLibrary); Physics::ColliderComponentEventBus::Event(GetEntityId(), &Physics::ColliderComponentEvents::OnColliderChanged); + + AzToolsFramework::PropertyEditorGUIMessages::Bus::Broadcast(&AzToolsFramework::PropertyEditorGUIMessages::RequestRefresh, + AzToolsFramework::PropertyModificationRefreshLevel::Refresh_AttributesAndValues); }) , m_nonUniformScaleChangedHandler([this](const AZ::Vector3& scale) {OnNonUniformScaleChanged(scale);}) { @@ -694,16 +697,16 @@ namespace PhysX { physXSystem->RegisterSystemConfigurationChangedEvent(m_physXConfigChangedHandler); } - if (!m_onDefaultMaterialLibraryChangedEventHandler.IsConnected()) + if (!m_onMaterialLibraryChangedEventHandler.IsConnected()) { - physXSystem->RegisterOnDefaultMaterialLibraryChangedEventHandler(m_onDefaultMaterialLibraryChangedEventHandler); + physXSystem->RegisterOnMaterialLibraryChangedEventHandler(m_onMaterialLibraryChangedEventHandler); } } } void EditorShapeColliderComponent::OnDeselected() { - m_onDefaultMaterialLibraryChangedEventHandler.Disconnect(); + m_onMaterialLibraryChangedEventHandler.Disconnect(); m_physXConfigChangedHandler.Disconnect(); } diff --git a/Gems/PhysX/Code/Source/EditorShapeColliderComponent.h b/Gems/PhysX/Code/Source/EditorShapeColliderComponent.h index 7b7fab789a..1ee87c9564 100644 --- a/Gems/PhysX/Code/Source/EditorShapeColliderComponent.h +++ b/Gems/PhysX/Code/Source/EditorShapeColliderComponent.h @@ -155,7 +155,7 @@ namespace PhysX mutable GeometryCache m_geometryCache; //!< Cached data for generating sample points inside the attached shape. AzPhysics::SystemEvents::OnConfigurationChangedEvent::Handler m_physXConfigChangedHandler; - AzPhysics::SystemEvents::OnDefaultMaterialLibraryChangedEvent::Handler m_onDefaultMaterialLibraryChangedEventHandler; + AzPhysics::SystemEvents::OnMaterialLibraryChangedEvent::Handler m_onMaterialLibraryChangedEventHandler; AZ::Transform m_cachedWorldTransform; AZ::NonUniformScaleChangedEvent::Handler m_nonUniformScaleChangedHandler; //!< Responds to changes in non-uniform scale. AZ::Vector3 m_currentNonUniformScale = AZ::Vector3::CreateOne(); //!< Caches the current non-uniform scale. diff --git a/Gems/PhysX/Code/Source/Material.cpp b/Gems/PhysX/Code/Source/Material.cpp index e8e5cab76e..5e8e3bf759 100644 --- a/Gems/PhysX/Code/Source/Material.cpp +++ b/Gems/PhysX/Code/Source/Material.cpp @@ -15,6 +15,8 @@ #include "Material.h" #include #include +#include +#include namespace PhysX { @@ -22,6 +24,9 @@ namespace PhysX : m_pxMaterial(AZStd::move(material.m_pxMaterial)) , m_surfaceType(material.m_surfaceType) , m_surfaceString(AZStd::move(material.m_surfaceString)) + , m_cryEngineSurfaceId(material.m_cryEngineSurfaceId) + , m_density(material.m_density) + , m_debugColor(AZStd::move(material.m_debugColor)) { m_pxMaterial->userData = this; } @@ -31,6 +36,11 @@ namespace PhysX m_pxMaterial = AZStd::move(material.m_pxMaterial); m_surfaceType = material.m_surfaceType; m_surfaceString = AZStd::move(material.m_surfaceString); + m_cryEngineSurfaceId = material.m_cryEngineSurfaceId; + m_density = material.m_density; + m_debugColor = AZStd::move(material.m_debugColor); + + m_pxMaterial->userData = this; return *this; } @@ -93,8 +103,10 @@ namespace PhysX pxMaterial->userData = this; m_pxMaterial = PxMaterialUniquePtr(pxMaterial, materialDestructor); - m_surfaceType = AZ::Crc32(materialConfiguration.m_surfaceType.c_str()); - m_surfaceString = materialConfiguration.m_surfaceType; + + SetSurfaceTypeName(materialConfiguration.m_surfaceType); + + SetDebugColor(materialConfiguration.m_debugColor); Physics::LegacySurfaceTypeRequestsBus::BroadcastResult( m_cryEngineSurfaceId, @@ -115,8 +127,9 @@ namespace PhysX SetDensity(configuration.m_density); - m_surfaceType = AZ::Crc32(configuration.m_surfaceType.c_str()); - m_surfaceString = configuration.m_surfaceType; + SetSurfaceTypeName(configuration.m_surfaceType); + + SetDebugColor(configuration.m_debugColor); Physics::LegacySurfaceTypeRequestsBus::BroadcastResult( m_cryEngineSurfaceId, @@ -134,9 +147,15 @@ namespace PhysX return m_surfaceType; } - void Material::SetSurfaceType(AZ::Crc32 surfaceType) + const AZStd::string& Material::GetSurfaceTypeName() const { - m_surfaceType = surfaceType; + return m_surfaceString; + } + + void Material::SetSurfaceTypeName(const AZStd::string& surfaceTypeName) + { + m_surfaceString = surfaceTypeName; + m_surfaceType = AZ::Crc32(m_surfaceString.c_str()); } float Material::GetDynamicFriction() const @@ -232,6 +251,16 @@ namespace PhysX MaterialConfiguration::MinDensityLimit, MaterialConfiguration::MaxDensityLimit); } + AZ::Color Material::GetDebugColor() const + { + return m_debugColor; + } + + void Material::SetDebugColor(const AZ::Color& debugColor) + { + m_debugColor = debugColor; + } + AZ::u32 Material::GetCryEngineSurfaceId() const { return m_cryEngineSurfaceId; @@ -243,6 +272,16 @@ namespace PhysX } MaterialsManager::MaterialsManager() + : m_physicsConfigChangedHandler( + [this](const AzPhysics::SystemConfiguration* config) + { + OnPhysicsConfigurationChanged(config); + }) + , m_materialLibraryChangedHandler( + [this](const AZ::Data::AssetId& materialLibraryAssetId) + { + OnMaterialLibraryChanged(materialLibraryAssetId); + }) { } @@ -254,133 +293,152 @@ namespace PhysX { Physics::PhysicsMaterialRequestBus::Handler::BusConnect(); MaterialManagerRequestsBus::Handler::BusConnect(); + + if (auto* physicsSystem = AZ::Interface::Get()) + { + physicsSystem->RegisterSystemConfigurationChangedEvent(m_physicsConfigChangedHandler); + physicsSystem->RegisterOnMaterialLibraryChangedEventHandler(m_materialLibraryChangedHandler); + } } void MaterialsManager::Disconnect() { + m_materialLibraryChangedHandler.Disconnect(); + m_physicsConfigChangedHandler.Disconnect(); MaterialManagerRequestsBus::Handler::BusDisconnect(); Physics::PhysicsMaterialRequestBus::Handler::BusDisconnect(); } void MaterialsManager::GetMaterials(const Physics::MaterialSelection& materialSelection - , AZStd::vector>& outMaterials) + , AZStd::vector>& outMaterials) { outMaterials.clear(); - outMaterials.reserve(materialSelection.GetMaterialIdsAssignedToSlots().size()); - // Ensure PxMaterial instances are initialized if possible. - InitializeMaterials(materialSelection); - - for (const auto& id : materialSelection.GetMaterialIdsAssignedToSlots()) + const auto& materialIdsAssignedToSlots = materialSelection.GetMaterialIdsAssignedToSlots(); + if (materialIdsAssignedToSlots.empty()) { - Physics::MaterialFromAssetConfiguration configuration; - if (materialSelection.GetMaterialConfiguration(configuration, id)) + // The material selection doesn't have any slots, return empty list. + return; + } + + // It is important to return exactly the amount of materials specified in materialSelection + // If a number of materials different to what was cooked is assigned on a physx mesh it will lead to undefined + // behavior and subtle bugs. Unfortunately, there's no warning or assertion on physx side at the shape creation time, + // nor mention of this in the documentation + outMaterials.resize(materialIdsAssignedToSlots.size(), GetDefaultMaterial()); + + for (size_t slotIndex = 0; slotIndex < materialIdsAssignedToSlots.size(); ++slotIndex) + { + const auto& materialId = materialIdsAssignedToSlots[slotIndex]; + + if (auto iterator = FindOrCreateMaterial(materialId); + iterator != m_materials.end()) { - auto iterator = m_materialsFromAssets.find(id.GetUuid()); - if (iterator != m_materialsFromAssets.end()) - { - outMaterials.push_back(iterator->second); - } - else - { - outMaterials.push_back(GetDefaultMaterial()); - } - } - else - { - // It is important to return exactly the amount of materials specified in materialSelection - // If a number of materials different to what was cooked is assigned on a physx mesh it will lead to undefined - // behavior and subtle bugs. Unfortunately, there's no warning or assertion on physx side at the shape creation time, - // nor mention of this in the documentation - outMaterials.push_back(GetDefaultMaterial()); + outMaterials[slotIndex] = iterator->second; } } } - AZStd::weak_ptr MaterialsManager::GetMaterialByName(const AZStd::string& name) + AZStd::shared_ptr MaterialsManager::GetMaterialById(Physics::MaterialId id) { - auto it = AZStd::find_if(m_materialsFromAssets.begin(), m_materialsFromAssets.end(), - [&name](const AZStd::pair>& elem) - { - return elem.second.get()->GetSurfaceTypeName() == name; - }); - - if (it != m_materialsFromAssets.end()) + if (auto it = FindOrCreateMaterial(id); + it != m_materials.end()) { return it->second; } - return {}; + return nullptr; } - AZ::u32 MaterialsManager::GetFirstSelectedMaterialIndex(const Physics::MaterialSelection& materialSelection) + AZStd::shared_ptr MaterialsManager::GetMaterialByName(const AZStd::string& name) { - const AZ::u32 defaultMaterialIndex = 0; - - if (!materialSelection.IsMaterialLibraryValid()) + if (auto it = FindOrCreateMaterial(name); + it != m_materials.end()) { - return defaultMaterialIndex; + return it->second; } - - auto materialAsset = AZ::Data::AssetManager::Instance().GetAsset(materialSelection.GetMaterialLibraryAssetId(), AZ::Data::AssetLoadBehavior::Default); - - materialAsset.BlockUntilLoadComplete(); - - AZStd::vector materialList = materialAsset.Get()->GetMaterialsData(); - - const AZStd::vector& selectedMaterials = materialSelection.GetMaterialIdsAssignedToSlots(); - if (selectedMaterials.size() == 0) - { - return defaultMaterialIndex; - } - for (AZ::u32 i=0; i < materialList.size(); ++i) - { - if (materialList[i].m_id == selectedMaterials[0]) - { - return i + 1; // Index 0 is reserved for Default material. - } - } - - return defaultMaterialIndex; + return nullptr; } void MaterialsManager::GetPxMaterials(const Physics::MaterialSelection& materialSelection , AZStd::vector& outMaterials) { - outMaterials.clear(); - if (materialSelection.GetMaterialIdsAssignedToSlots().empty()) + AZStd::vector> materials; + GetMaterials(materialSelection, materials); + + outMaterials.reserve(materials.size()); + for (const auto& material : materials) + { + PhysX::Material* physxMaterial = azrtti_cast(material.get()); + AZ_Assert(physxMaterial, "Invalid physx material"); + + outMaterials.emplace_back(physxMaterial->GetPxMaterial()); + } + } + + void MaterialsManager::UpdateMaterialSelectionFromPhysicsAsset( + const Physics::ShapeConfiguration& shapeConfiguration, + Physics::MaterialSelection& materialSelection) + { + if (shapeConfiguration.GetShapeType() != Physics::ShapeType::PhysicsAsset) { - // if the materialSelection is invalid we still - // return a default material as a fallback behavior - outMaterials.push_back(GetDefaultMaterial()->GetPxMaterial()); return; } - outMaterials.reserve(materialSelection.GetMaterialIdsAssignedToSlots().size()); - // Ensure PxMaterial instances are initialized if possible. - InitializeMaterials(materialSelection); + const Physics::PhysicsAssetShapeConfiguration& assetConfiguration = + static_cast(shapeConfiguration); - for (const auto& id : materialSelection.GetMaterialIdsAssignedToSlots()) + if (!assetConfiguration.m_asset.GetId().IsValid()) { - Physics::MaterialFromAssetConfiguration configuration; - if (materialSelection.GetMaterialConfiguration(configuration, id)) + // Set the default selection if there's no physics asset. + materialSelection.SetMaterialSlots(Physics::MaterialSelection::SlotsArray()); + return; + } + + if (!assetConfiguration.m_asset.IsReady()) + { + // The asset is valid but is still loading, + // Do not set the empty slots in this case to avoid the entity being in invalid state + return; + } + + Pipeline::MeshAsset* meshAsset = assetConfiguration.m_asset.GetAs(); + if (!meshAsset) + { + materialSelection.SetMaterialSlots(Physics::MaterialSelection::SlotsArray()); + AZ_Warning("PhysX", false, "UpdateMaterialSelectionFromPhysicsAsset: MeshAsset is invalid"); + return; + } + + // Set the slots from the mesh asset + materialSelection.SetMaterialSlots(meshAsset->m_assetData.m_surfaceNames); + + if (!assetConfiguration.m_useMaterialsFromAsset) + { + // Not using the materials from the asset. Nothing else to do. + return; + } + + // Update material IDs in the selection for each slot + const AZStd::vector& meshMaterialNames = meshAsset->m_assetData.m_materialNames; + for (size_t slotIndex = 0; slotIndex < meshMaterialNames.size(); ++slotIndex) + { + const AZStd::string& physicsMaterialNameFromPhysicsAsset = meshMaterialNames[slotIndex]; + if (physicsMaterialNameFromPhysicsAsset == DefaultPhysicsMaterialNameFromPhysicsAsset) { - auto iterator = m_materialsFromAssets.find(id.GetUuid()); - if (iterator != m_materialsFromAssets.end()) - { - outMaterials.push_back(iterator->second->GetPxMaterial()); - } - else - { - outMaterials.push_back(GetDefaultMaterial()->GetPxMaterial()); - } + continue; + } + + if (auto it = FindOrCreateMaterial(physicsMaterialNameFromPhysicsAsset); + it != m_materials.end()) + { + materialSelection.SetMaterialId(Physics::MaterialId::FromUUID(it->first), slotIndex); } else { - // It is important to return exactly the amount of materials specified in materialSelection - // If a number of materials different to what was cooked is assigned on a physx mesh it will lead to undefined - // behavior and subtle bugs. Unfortunately, there's no warning or assertion on physx side at the shape creation time, - // nor mention of this in the documentation - outMaterials.push_back(GetDefaultMaterial()->GetPxMaterial()); + AZ_Warning("PhysX", false, + "UpdateMaterialSelectionFromPhysicsAsset: Physics material '%s' not found in the material library. Mesh surface '%s' will use the default material.", + physicsMaterialNameFromPhysicsAsset.c_str(), + meshAsset->m_assetData.m_surfaceNames[slotIndex].c_str()); } } } @@ -390,11 +448,21 @@ namespace PhysX return GetDefaultMaterial(); } - const AZStd::shared_ptr& MaterialsManager::GetDefaultMaterial() + AZStd::shared_ptr MaterialsManager::GetDefaultMaterial() { if (!m_defaultMaterial) { - m_defaultMaterial = AZStd::make_shared(Physics::MaterialConfiguration()); + // Get default material from physics configuration + if (auto* physicsSystem = AZ::Interface::Get()) + { + m_defaultMaterialConfiguration = physicsSystem->GetConfiguration()->m_defaultMaterialConfiguration; + } + else + { + AZ_Warning("MaterialsManager", false, "Unable to get Physics System, default material will not be in sync with PhysX Configuration"); + } + + m_defaultMaterial = AZStd::make_shared(m_defaultMaterialConfiguration); } return m_defaultMaterial; @@ -403,38 +471,138 @@ namespace PhysX void MaterialsManager::ReleaseAllMaterials() { m_defaultMaterial = nullptr; - m_materialsFromAssets.clear(); + m_materials.clear(); Physics::PhysicsMaterialNotificationsBus::Broadcast(&Physics::PhysicsMaterialNotificationsBus::Events::MaterialsReleased); } - void MaterialsManager::InitializeMaterials(const Physics::MaterialSelection& materialSelection) + MaterialsManager::Materials::iterator MaterialsManager::FindOrCreateMaterial(Physics::MaterialId materialId) { - const AZStd::vector& materialIds = materialSelection.GetMaterialIdsAssignedToSlots(); - for (const auto& id : materialIds) + if (materialId.IsNull()) { - Physics::MaterialFromAssetConfiguration configuration; - if (!materialSelection.GetMaterialConfiguration(configuration, id)) - { - continue; // Default material skips code below. - } - - auto materialId = configuration.m_id; + return m_materials.end(); + } + if (auto it = m_materials.find(materialId.GetUuid()); + it != m_materials.end()) + { + return it; + } + else + { + auto* materialLibrary = GetMaterialLibrary(); + if (!materialLibrary) + { + return m_materials.end(); + } + + Physics::MaterialFromAssetConfiguration configuration; + if (!materialLibrary->GetDataForMaterialId(materialId, configuration)) + { + return m_materials.end(); + } + + auto newMaterial = AZStd::make_shared(configuration.m_configuration); + auto insertedPair = m_materials.emplace(materialId.GetUuid(), AZStd::move(newMaterial)); + return insertedPair.first; + } + } + + MaterialsManager::Materials::iterator MaterialsManager::FindOrCreateMaterial(const AZStd::string& materialName) + { + if (materialName.empty()) + { + return m_materials.end(); + } + + auto it = AZStd::find_if(m_materials.begin(), m_materials.end(), [&materialName](const auto& data) + { + return data.second->GetSurfaceTypeName() == materialName; + }); + if (it != m_materials.end()) + { + return it; + } + else + { + auto* materialLibrary = GetMaterialLibrary(); + if (!materialLibrary) + { + return m_materials.end(); + } + + Physics::MaterialFromAssetConfiguration configuration; + if (!materialLibrary->GetDataForMaterialName(materialName, configuration)) + { + return m_materials.end(); + } + + auto newMaterial = AZStd::make_shared(configuration.m_configuration); + auto insertedPair = m_materials.emplace(configuration.m_id.GetUuid(), AZStd::move(newMaterial)); + return insertedPair.first; + } + } + + Physics::MaterialLibraryAsset* MaterialsManager::GetMaterialLibrary() + { + if (auto* physicsSystem = AZ::Interface::Get()) + { + if (const auto* physicsConfiguration = physicsSystem->GetConfiguration()) + { + return physicsConfiguration->m_materialLibraryAsset.Get(); + } + } + return nullptr; + } + + void MaterialsManager::OnPhysicsConfigurationChanged(const AzPhysics::SystemConfiguration* config) + { + if (m_defaultMaterial && + m_defaultMaterialConfiguration != config->m_defaultMaterialConfiguration) + { + m_defaultMaterialConfiguration = config->m_defaultMaterialConfiguration; + + m_defaultMaterial->UpdateWithConfiguration(m_defaultMaterialConfiguration); + } + } + + void MaterialsManager::OnMaterialLibraryChanged([[maybe_unused]] const AZ::Data::AssetId& materialLibraryAssetId) + { + auto* materialLibrary = GetMaterialLibrary(); + if (!materialLibrary) + { + AZ_Warning("PhysX", false, "MaterialsManager: invalid material library"); + return; + } + + AZStd::vector materialsToRemove; + + for (auto& idMaterialPair : m_materials) + { + const Physics::MaterialId materialId = Physics::MaterialId::FromUUID(idMaterialPair.first); + + // Remove null materials if (materialId.IsNull()) { - materialId = Physics::MaterialId::Create(); + materialsToRemove.push_back(materialId.GetUuid()); + continue; } - auto iterator = m_materialsFromAssets.find(materialId.GetUuid()); - if (iterator != m_materialsFromAssets.end()) + Physics::MaterialFromAssetConfiguration configuration; + if (materialLibrary->GetDataForMaterialId(materialId, configuration)) { - iterator->second->UpdateWithConfiguration(configuration.m_configuration); + // Update materials found in the library. + idMaterialPair.second->UpdateWithConfiguration(configuration.m_configuration); } else { - auto newMaterial = AZStd::make_shared(configuration.m_configuration); - m_materialsFromAssets.emplace(materialId.GetUuid(), newMaterial); + // Add for removal the materials not present in the library anymore. + materialsToRemove.push_back(materialId.GetUuid()); } } + + for (const auto& id : materialsToRemove) + { + m_materials.erase(id); + } } } diff --git a/Gems/PhysX/Code/Source/Material.h b/Gems/PhysX/Code/Source/Material.h index a44c9766fc..1541d156d8 100644 --- a/Gems/PhysX/Code/Source/Material.h +++ b/Gems/PhysX/Code/Source/Material.h @@ -15,11 +15,18 @@ #include #include #include -#include +#include +#include #include namespace PhysX { + /// Name used by physx asset exporter to indicate that the default + /// physics material should be used for a mesh surface. The exporter + /// will use it as the fallback option when it's not possible to obtain + /// the surface information from the mesh material. + static const char* const DefaultPhysicsMaterialNameFromPhysicsAsset = ""; + /// PhysX implementation of Physics::Material interface /// =================================================== /// @@ -58,9 +65,9 @@ namespace PhysX // Physics::Material AZ::Crc32 GetSurfaceType() const override; - void SetSurfaceType(AZ::Crc32 surfaceType) override; - const AZStd::string& GetSurfaceTypeName() const override { return m_surfaceString; } + const AZStd::string& GetSurfaceTypeName() const override; + void SetSurfaceTypeName(const AZStd::string& surfaceTypeName) override; float GetDynamicFriction() const override; void SetDynamicFriction(float dynamicFriction) override; @@ -80,6 +87,9 @@ namespace PhysX float GetDensity() const override; void SetDensity(float density) override; + AZ::Color GetDebugColor() const override; + void SetDebugColor(const AZ::Color& debugColor) override; + AZ::u32 GetCryEngineSurfaceId() const override; void* GetNativePointer() override; @@ -92,6 +102,7 @@ namespace PhysX AZ::u32 m_cryEngineSurfaceId = -1; AZStd::string m_surfaceString; float m_density = 1000.0f; + AZ::Color m_debugColor = AZ::Colors::White; }; /// Bus with requests to MaterialsManager @@ -108,9 +119,17 @@ namespace PhysX static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; + /// Returns weak pointers to physx::PxMaterial. + /// Equivalent to PhysicsMaterialRequests::GetMaterials but it returns physx::PxMaterial pointers instead. + /// @param materialSelection MaterialSelection instance to create or get materials for + /// @param outMaterials vector of pointers to physx::PxMaterial to fill with. The vector will be cleared inside the function. virtual void GetPxMaterials(const Physics::MaterialSelection& materialSelection, AZStd::vector& outMaterials) = 0; - virtual const AZStd::shared_ptr& GetDefaultMaterial() = 0; + /// Returns default material + /// @return default PhysX::Material instance + virtual AZStd::shared_ptr GetDefaultMaterial() = 0; + + /// Releases ownership of all materials created before. virtual void ReleaseAllMaterials() = 0; }; using MaterialManagerRequestsBus = AZ::EBus; @@ -120,6 +139,9 @@ namespace PhysX /// /// Material managers creates PhysX::Material instances from MaterialLibraryAsset and assumes their ownership. /// Also keeps a reference to the default material. + /// + /// Note: Materials will be created on the fly while doing queries and + /// they will be updated when the material library changes. class MaterialsManager : public MaterialManagerRequestsBus::Handler , public Physics::PhysicsMaterialRequestBus::Handler @@ -131,35 +153,19 @@ namespace PhysX MaterialsManager(); ~MaterialsManager() override; - /// Returns a vector of weak pointers to materials selected. - /// To be notified if the pointers are deleted, connect to PhysicsMaterialNotifications::MaterialsReleased(). - /// @param materialSelection MaterialSelection instance to create or get materials for. - /// @param outMaterials Collection of material weak pointers corresponding to the material selection to be returned. + // PhysicsMaterialRequestBus::Handler overrides... void GetMaterials(const Physics::MaterialSelection& materialSelection - , AZStd::vector>& outMaterials) override; - - /// Returns a weak pointer to physics material with the given name. - AZStd::weak_ptr GetMaterialByName(const AZStd::string& name) override; - - /// Returns index of selected material in its material library. 0 is the Default material. - /// @param materialSelection Selection of materials. - AZ::u32 GetFirstSelectedMaterialIndex(const Physics::MaterialSelection& materialSelection) override; - - /// Slightly faster version of GetMaterials that returns physx::PxMaterial pointers instead. \n - /// The rest is equivalent to GetMaterials function. - /// @param materialSelection MaterialSelection instance to create or get materials for - /// @param outMaterials vector of pointers to physx::PxMaterial to fill with. The vector will be cleared inside the function. - void GetPxMaterials(const Physics::MaterialSelection& materialSelection, AZStd::vector& outMaterials) override; - - /// Returns default material - /// @return default PhysX::Material instance - const AZStd::shared_ptr& GetDefaultMaterial() override; - - /// Return default material - /// @return default Physics::Material instance + , AZStd::vector>& outMaterials) override; + AZStd::shared_ptr GetMaterialById(Physics::MaterialId id) override; + AZStd::shared_ptr GetMaterialByName(const AZStd::string& name) override; + void UpdateMaterialSelectionFromPhysicsAsset( + const Physics::ShapeConfiguration& shapeConfiguration, + Physics::MaterialSelection& materialSelection) override; AZStd::shared_ptr GetGenericDefaultMaterial() override; - /// Releases ownership of all materials created before. + // MaterialManagerRequestsBus::Handler overrides... + void GetPxMaterials(const Physics::MaterialSelection& materialSelection, AZStd::vector& outMaterials) override; + AZStd::shared_ptr GetDefaultMaterial() override; void ReleaseAllMaterials() override; /// Connect to any necessary buses @@ -169,9 +175,31 @@ namespace PhysX void Disconnect(); private: - void InitializeMaterials(const Physics::MaterialSelection& materialSelection); + using Materials = AZStd::unordered_map>; - AZStd::unordered_map> m_materialsFromAssets; + /// Search a material by id, if it exists already it returns its iterator, + /// if it doesn't exist it tries to create it and add it to the list. + /// If the material id is null or not part of the material library then the + /// iterator returned is end of material list. + Materials::iterator FindOrCreateMaterial(Physics::MaterialId materialId); + + /// Search a material by name, if it exists already it returns its iterator, + /// if it doesn't exist it tries to create it and add it to the list. + /// If the material id is null or not part of the material library then the + /// iterator returned is end of material list. + Materials::iterator FindOrCreateMaterial(const AZStd::string& materialName); + + /// Returns the material library of the project. + Physics::MaterialLibraryAsset* GetMaterialLibrary(); + + void OnPhysicsConfigurationChanged(const AzPhysics::SystemConfiguration* config); + void OnMaterialLibraryChanged(const AZ::Data::AssetId& materialLibraryAssetId); + + Materials m_materials; AZStd::shared_ptr m_defaultMaterial; + Physics::MaterialConfiguration m_defaultMaterialConfiguration; + + AzPhysics::SystemEvents::OnConfigurationChangedEvent::Handler m_physicsConfigChangedHandler; + AzPhysics::SystemEvents::OnMaterialLibraryChangedEvent::Handler m_materialLibraryChangedHandler; }; } diff --git a/Gems/PhysX/Code/Source/MeshColliderComponent.cpp b/Gems/PhysX/Code/Source/MeshColliderComponent.cpp index d383ba97de..1c6fdca640 100644 --- a/Gems/PhysX/Code/Source/MeshColliderComponent.cpp +++ b/Gems/PhysX/Code/Source/MeshColliderComponent.cpp @@ -16,7 +16,7 @@ #include #include #include -#include +#include #include #include @@ -85,11 +85,6 @@ namespace PhysX UpdateMeshAsset(); } - void MeshColliderComponent::SetMaterialAsset(const AZ::Data::AssetId& id) - { - m_colliderConfiguration->m_materialSelection.SetMaterialLibrary(id); - } - void MeshColliderComponent::SetMaterialId(const Physics::MaterialId& id) { m_colliderConfiguration->m_materialSelection.SetMaterialId(id); @@ -111,8 +106,10 @@ namespace PhysX { m_shapeConfiguration->m_asset = asset; - Physics::SystemRequestBus::Broadcast(&Physics::SystemRequests::UpdateMaterialSelection, - *m_shapeConfiguration, *m_colliderConfiguration); + Physics::PhysicsMaterialRequestBus::Broadcast( + &Physics::PhysicsMaterialRequestBus::Events::UpdateMaterialSelectionFromPhysicsAsset, + *m_shapeConfiguration, + m_colliderConfiguration->m_materialSelection); } } @@ -122,8 +119,10 @@ namespace PhysX { m_shapeConfiguration->m_asset = asset; - Physics::SystemRequestBus::Broadcast(&Physics::SystemRequests::UpdateMaterialSelection, - *m_shapeConfiguration, *m_colliderConfiguration); + Physics::PhysicsMaterialRequestBus::Broadcast( + &Physics::PhysicsMaterialRequestBus::Events::UpdateMaterialSelectionFromPhysicsAsset, + *m_shapeConfiguration, + m_colliderConfiguration->m_materialSelection); } } diff --git a/Gems/PhysX/Code/Source/MeshColliderComponent.h b/Gems/PhysX/Code/Source/MeshColliderComponent.h index 4014f7ab5a..9d781fd6eb 100644 --- a/Gems/PhysX/Code/Source/MeshColliderComponent.h +++ b/Gems/PhysX/Code/Source/MeshColliderComponent.h @@ -40,7 +40,6 @@ namespace PhysX AZ::Data::Asset GetMeshAsset() const override; Physics::MaterialId GetMaterialId() const override; void SetMeshAsset(const AZ::Data::AssetId& id) override; - void SetMaterialAsset(const AZ::Data::AssetId& id) override; void SetMaterialId(const Physics::MaterialId& id) override; // BaseColliderComponent diff --git a/Gems/PhysX/Code/Source/PhysXCharacters/API/CharacterUtils.cpp b/Gems/PhysX/Code/Source/PhysXCharacters/API/CharacterUtils.cpp index d1502a0c65..87e3304a90 100644 --- a/Gems/PhysX/Code/Source/PhysXCharacters/API/CharacterUtils.cpp +++ b/Gems/PhysX/Code/Source/PhysXCharacters/API/CharacterUtils.cpp @@ -15,9 +15,7 @@ #include #include #include -#include -#include -#include +#include #include #include #include @@ -51,18 +49,32 @@ namespace PhysX static void AppendShapeIndependentProperties(physx::PxControllerDesc& controllerDesc, const Physics::CharacterConfiguration& characterConfig, CharacterControllerCallbackManager* callbackManager) { - AZStd::vector > materials; + AZStd::vector> materials; - Physics::SystemRequestBus::BroadcastResult( - materials, - &Physics::SystemRequests::CreateMaterialsFromLibrary, - characterConfig.m_materialSelection - ); - - if (materials.empty()) + if (characterConfig.m_materialSelection.GetMaterialIdsAssignedToSlots().empty()) { - AZ_Error("PhysX Character Controller", false, "Could not create character controller, material was invalid."); - return; + // If material selection has no slots, falling back to default material. + AZStd::shared_ptr defaultMaterial; + Physics::PhysicsMaterialRequestBus::BroadcastResult(defaultMaterial, + &Physics::PhysicsMaterialRequestBus::Events::GetGenericDefaultMaterial); + if (!defaultMaterial) + { + AZ_Error("PhysX Character Controller", false, "Invalid default material."); + return; + } + materials.push_back(AZStd::move(defaultMaterial)); + } + else + { + Physics::PhysicsMaterialRequestBus::Broadcast( + &Physics::PhysicsMaterialRequestBus::Events::GetMaterials, + characterConfig.m_materialSelection, + materials); + if (materials.empty()) + { + AZ_Error("PhysX Character Controller", false, "Could not create character controller, material list was empty."); + return; + } } physx::PxMaterial* pxMaterial = static_cast(materials.front()->GetNativePointer()); diff --git a/Gems/PhysX/Code/Source/Pipeline/MeshExporter.cpp b/Gems/PhysX/Code/Source/Pipeline/MeshExporter.cpp index 3aeb3ad797..24e532e2d1 100644 --- a/Gems/PhysX/Code/Source/Pipeline/MeshExporter.cpp +++ b/Gems/PhysX/Code/Source/Pipeline/MeshExporter.cpp @@ -25,7 +25,7 @@ #include #include -#include +#include #include #include #include @@ -153,7 +153,7 @@ namespace PhysX AZ::SerializeContext* serializeContext = azrtti_cast(context); if (serializeContext) { - serializeContext->Class()->Version(3); + serializeContext->Class()->Version(4); } } @@ -215,7 +215,7 @@ namespace PhysX if (nameAttribute) { AZStd::string materialName = nameAttribute->value(); - AZStd::string surfaceTypeName = DefaultMaterialName; + AZStd::string surfaceTypeName = DefaultPhysicsMaterialNameFromPhysicsAsset; AZ::rapidxml::xml_attribute* surfaceTypeNode = materialNode->first_attribute("SurfaceType"); if (surfaceTypeNode && surfaceTypeNode->value_size() != 0) @@ -268,7 +268,7 @@ namespace PhysX } else { - materialName = DefaultMaterialName; + materialName = DefaultPhysicsMaterialNameFromPhysicsAsset; } materialNames.emplace_back(AZStd::move(materialName)); @@ -794,6 +794,9 @@ namespace PhysX ); } + // Convex and primitive methods can only have 1 material + const bool limitToOneMaterial = pxMeshGroup.GetExportAsConvex() || pxMeshGroup.GetExportAsPrimitive(); + for (AZ::u32 faceIndex = 0; faceIndex < faceCount; ++faceIndex) { AZStd::string materialName = DefaultMaterialName; @@ -810,6 +813,14 @@ namespace PhysX } materialName = localFbxMaterialsList[materialId]; + + // Keep using the first material when it has to be limited to one. + if (limitToOneMaterial && + assetMaterialData.m_fbxMaterialNames.size() == 1 && + assetMaterialData.m_fbxMaterialNames[0] != materialName) + { + materialName = assetMaterialData.m_fbxMaterialNames[0]; + } } const AZ::SceneAPI::DataTypes::IMeshData::Face& face = nodeMesh->GetFaceInfo(faceIndex); diff --git a/Gems/PhysX/Code/Source/System/PhysXSystem.cpp b/Gems/PhysX/Code/Source/System/PhysXSystem.cpp index 8df9e9a86f..92f68ea13b 100644 --- a/Gems/PhysX/Code/Source/System/PhysXSystem.cpp +++ b/Gems/PhysX/Code/Source/System/PhysXSystem.cpp @@ -40,8 +40,8 @@ namespace PhysX } #endif - PhysXSystem::MaterialLibraryAssetHelper::MaterialLibraryAssetHelper(PhysXSystem* physXSystem) - : m_physXSystem(physXSystem) + PhysXSystem::MaterialLibraryAssetHelper::MaterialLibraryAssetHelper(OnMaterialLibraryReloadedCallback callback) + : m_onMaterialLibraryReloadedCallback(callback) { } @@ -62,16 +62,16 @@ namespace PhysX void PhysXSystem::MaterialLibraryAssetHelper::OnAssetReloaded(AZ::Data::Asset asset) { - if (m_physXSystem == nullptr || m_physXSystem->GetDefaultMaterialLibrary() != asset) - { - return; - } - m_physXSystem->UpdateDefaultMaterialLibrary(asset); + m_onMaterialLibraryReloadedCallback(asset); } PhysXSystem::PhysXSystem(PhysXSettingsRegistryManager* registryManager, const physx::PxCookingParams& cookingParams) : m_registryManager(*registryManager) - , m_materialLibraryAssetHelper(this) + , m_materialLibraryAssetHelper( + [this](const AZ::Data::Asset& materialLibrary) + { + UpdateMaterialLibrary(materialLibrary); + }) , m_sceneInterface(this) { // Start PhysX allocator @@ -127,7 +127,7 @@ namespace PhysX m_materialLibraryAssetHelper.Disconnect(); // Clear the asset reference in deactivate. The asset system is shut down before destructors are called // for system components, causing any hanging asset references to become crashes on shutdown in release builds. - m_systemConfig.m_defaultMaterialLibrary.Reset(); + m_systemConfig.m_materialLibraryAsset.Reset(); m_accumulatedTime = 0.0f; m_state = State::Shutdown; @@ -369,8 +369,18 @@ namespace PhysX void PhysXSystem::OnCatalogLoaded([[maybe_unused]]const char* catalogFile) { - //now that assets can be resolved, lets load the default material library. - LoadDefaultMaterialLibrary(); + // now that assets can be resolved, lets load the default material library. + + if (!m_systemConfig.m_materialLibraryAsset.GetId().IsValid()) + { + m_onMaterialLibraryLoadErrorEvent.Signal(AzPhysics::SystemEvents::MaterialLibraryLoadErrorType::InvalidId); + } + + bool success = LoadMaterialLibrary(); + if (!success) + { + m_onMaterialLibraryLoadErrorEvent.Signal(AzPhysics::SystemEvents::MaterialLibraryLoadErrorType::ErrorLoading); + } } void PhysXSystem::UpdateConfiguration(const AzPhysics::SystemConfiguration* newConfig, [[maybe_unused]] bool forceReinitialization /*= false*/) @@ -378,7 +388,7 @@ namespace PhysX if (const auto* physXConfig = azdynamic_cast(newConfig); m_systemConfig != (*physXConfig)) { - const bool newMaterialLibrary = m_systemConfig.m_defaultMaterialLibrary != physXConfig->m_defaultMaterialLibrary; + const bool newMaterialLibrary = m_systemConfig.m_materialLibraryAsset != physXConfig->m_materialLibraryAsset; m_systemConfig = (*physXConfig); m_configChangeEvent.Signal(physXConfig); @@ -386,9 +396,11 @@ namespace PhysX if (newMaterialLibrary) { - LoadDefaultMaterialLibrary(); - m_onDefaultMaterialLibraryChangedEvent.Signal(m_systemConfig.m_defaultMaterialLibrary.GetId()); + LoadMaterialLibrary(); + m_onMaterialLibraryChangedEvent.Signal(m_systemConfig.m_materialLibraryAsset.GetId()); } + // This function is not called from reloading the material library asset, + // which means we don't need to check if the materials inside the library have been modified. } } @@ -445,23 +457,6 @@ namespace PhysX return m_systemConfig; } - void PhysXSystem::UpdateDefaultMaterialLibrary(const AZ::Data::Asset& materialLibrary) - { - if (m_systemConfig.m_defaultMaterialLibrary == materialLibrary) - { - return; - } - m_systemConfig.m_defaultMaterialLibrary = materialLibrary; - - LoadDefaultMaterialLibrary(); - m_onDefaultMaterialLibraryChangedEvent.Signal(materialLibrary.GetId()); - } - - const AZ::Data::Asset& PhysXSystem::GetDefaultMaterialLibrary() const - { - return m_systemConfig.m_defaultMaterialLibrary; - } - void PhysXSystem::UpdateDefaultSceneConfiguration(const AzPhysics::SceneConfiguration& sceneConfiguration) { if (m_defaultSceneConfiguration != sceneConfiguration) @@ -482,9 +477,30 @@ namespace PhysX return m_registryManager; } - bool PhysXSystem::LoadDefaultMaterialLibrary() + void PhysXSystem::UpdateMaterialLibrary(const AZ::Data::Asset& materialLibrary) { - AZ::Data::Asset& materialLibrary = m_systemConfig.m_defaultMaterialLibrary; + if (m_systemConfig.m_materialLibraryAsset == materialLibrary) + { + // Same library asset, check if its data has changed. + if (m_systemConfig.m_materialLibraryAsset->GetMaterialsData() != materialLibrary->GetMaterialsData()) + { + m_systemConfig.m_materialLibraryAsset = materialLibrary; + m_onMaterialLibraryChangedEvent.Signal(materialLibrary.GetId()); + } + } + else + { + // New material library asset + m_systemConfig.m_materialLibraryAsset = materialLibrary; + + LoadMaterialLibrary(); + m_onMaterialLibraryChangedEvent.Signal(materialLibrary.GetId()); + } + } + + bool PhysXSystem::LoadMaterialLibrary() + { + AZ::Data::Asset& materialLibrary = m_systemConfig.m_materialLibraryAsset; const AZ::Data::AssetId& materialLibraryId = materialLibrary.GetId(); if (!materialLibraryId.IsValid()) { @@ -503,7 +519,7 @@ namespace PhysX AZ_Warning("PhysX", (materialLibrary.GetData() != nullptr), "LoadDefaultMaterialLibrary: Default Material Library asset data is invalid."); - return materialLibrary.GetData() != nullptr; + return materialLibrary.GetData() != nullptr && !materialLibrary.IsError(); } //TEMP -- until these are fully moved over here diff --git a/Gems/PhysX/Code/Source/System/PhysXSystem.h b/Gems/PhysX/Code/Source/System/PhysXSystem.h index 04d23ad47d..533685bbd1 100644 --- a/Gems/PhysX/Code/Source/System/PhysXSystem.h +++ b/Gems/PhysX/Code/Source/System/PhysXSystem.h @@ -64,8 +64,6 @@ namespace PhysX AZStd::pair FindAttachedBodyHandleFromEntityId(AZ::EntityId entityId) override; const AzPhysics::SystemConfiguration* GetConfiguration() const override; void UpdateConfiguration(const AzPhysics::SystemConfiguration* newConfig, bool forceReinitialization = false) override; - void UpdateDefaultMaterialLibrary(const AZ::Data::Asset& materialLibrary) override; - const AZ::Data::Asset& GetDefaultMaterialLibrary() const override; void UpdateDefaultSceneConfiguration(const AzPhysics::SceneConfiguration& sceneConfiguration) override; const AzPhysics::SceneConfiguration& GetDefaultSceneConfiguration() const override; @@ -75,6 +73,8 @@ namespace PhysX //! Accessor to get the Settings Registry Manager. const PhysXSettingsRegistryManager& GetSettingsRegistryManager() const; + void UpdateMaterialLibrary(const AZ::Data::Asset& materialLibrary); + //TEMP -- until these are fully moved over here physx::PxPhysics* GetPxPhysics() { return m_physXSdk.m_physics; } physx::PxCooking* GetPxCooking() { return m_physXSdk.m_cooking; } @@ -92,7 +92,7 @@ namespace PhysX //! @param cookingParams The cooking params to use when setting up PhysX cooking interface. void InitializePhysXSdk(const physx::PxCookingParams& cookingParams); void ShutdownPhysXSdk(); - bool LoadDefaultMaterialLibrary(); + bool LoadMaterialLibrary(); // AzFramework::AssetCatalogEventBus::Handler ... void OnCatalogLoaded(const char* catalogFile) override; @@ -133,7 +133,9 @@ namespace PhysX : private AZ::Data::AssetBus::Handler { public: - MaterialLibraryAssetHelper(PhysXSystem* physXSystem); + using OnMaterialLibraryReloadedCallback = AZStd::function& materialLibrary)>; + + MaterialLibraryAssetHelper(OnMaterialLibraryReloadedCallback callback); void Connect(const AZ::Data::AssetId& materialLibraryId); void Disconnect(); @@ -142,7 +144,7 @@ namespace PhysX // AZ::Data::AssetBus::Handler void OnAssetReloaded(AZ::Data::Asset asset) override; - PhysXSystem* m_physXSystem; + OnMaterialLibraryReloadedCallback m_onMaterialLibraryReloadedCallback; }; MaterialLibraryAssetHelper m_materialLibraryAssetHelper; }; diff --git a/Gems/PhysX/Code/Source/SystemComponent.cpp b/Gems/PhysX/Code/Source/SystemComponent.cpp index 9ef4dc5099..b4c17f672d 100644 --- a/Gems/PhysX/Code/Source/SystemComponent.cpp +++ b/Gems/PhysX/Code/Source/SystemComponent.cpp @@ -347,27 +347,6 @@ namespace PhysX return AZStd::make_shared(materialConfiguration); } - AZStd::vector> SystemComponent::CreateMaterialsFromLibrary(const Physics::MaterialSelection& materialSelection) - { - AZStd::vector pxMaterials; - m_materialManager.GetPxMaterials(materialSelection, pxMaterials); - - AZStd::vector> genericMaterials; - genericMaterials.reserve(pxMaterials.size()); - - for (physx::PxMaterial* pxMaterial : pxMaterials) - { - genericMaterials.push_back(static_cast(pxMaterial->userData)->shared_from_this()); - } - - return genericMaterials; - } - - AZStd::shared_ptr SystemComponent::GetDefaultMaterial() - { - return m_materialManager.GetDefaultMaterial(); - } - AZStd::vector SystemComponent::GetSupportedJointTypes() { return JointUtils::GetSupportedJointTypes(); @@ -484,58 +463,6 @@ namespace PhysX return m_physXSystem->GetPxCooking(); } - bool SystemComponent::UpdateMaterialSelection(const Physics::ShapeConfiguration& shapeConfiguration, - Physics::ColliderConfiguration& colliderConfiguration) - { - Physics::MaterialSelection& materialSelection = colliderConfiguration.m_materialSelection; - - // If the material library is still not set, we can't update the material selection - if (!materialSelection.IsMaterialLibraryValid()) - { - AZ_Warning("PhysX", false, - "UpdateMaterialSelection: Material Selection tried to use an invalid/non-existing Physics material library: \"%s\". " - "Please make sure the file exists or re-assign another library", materialSelection.GetMaterialLibraryAssetHint().c_str()); - return false; - } - - // If there's no material library data loaded, try to load it - if (materialSelection.GetMaterialLibraryAssetData() == nullptr) - { - AZ::Data::AssetId materialLibraryAssetId = materialSelection.GetMaterialLibraryAssetId(); - materialSelection.SetMaterialLibrary(materialLibraryAssetId); - } - - // If there's still not material library data, we can't update the material selection - if (materialSelection.GetMaterialLibraryAssetData() == nullptr) - { - AZ::Data::AssetId materialLibraryAssetId = materialSelection.GetMaterialLibraryAssetId(); - - auto materialLibraryAsset = - AZ::Data::AssetManager::Instance().GetAsset(materialLibraryAssetId, AZ::Data::AssetLoadBehavior::Default); - - materialLibraryAsset.BlockUntilLoadComplete(); - - // Log the asset path to help find out the incorrect library reference - AZStd::string assetPath = materialLibraryAsset.GetHint(); - AZ_Warning("PhysX", false, - "UpdateMaterialSelection: Unable to load the material library for a material selection." - " Please check if the asset %s exists in the asset cache.", assetPath.c_str()); - - return false; - } - - if (shapeConfiguration.GetShapeType() == Physics::ShapeType::PhysicsAsset) - { - const Physics::PhysicsAssetShapeConfiguration& assetConfiguration = - static_cast(shapeConfiguration); - - // Use the materials data from the asset to update the collider data - return UpdateMaterialSelectionFromPhysicsAsset(assetConfiguration, colliderConfiguration); - } - - return true; - } - void SystemComponent::OnTick(float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time) { if (m_physXSystem) @@ -614,65 +541,4 @@ namespace PhysX m_windProvider = AZStd::make_unique(); } - - bool SystemComponent::UpdateMaterialSelectionFromPhysicsAsset( - const Physics::PhysicsAssetShapeConfiguration& assetConfiguration, - Physics::ColliderConfiguration& colliderConfiguration) - { - Physics::MaterialSelection& materialSelection = colliderConfiguration.m_materialSelection; - - if (!assetConfiguration.m_asset.GetId().IsValid()) - { - // Set the default selection if there's no physics asset. - materialSelection.SetMaterialSlots(Physics::MaterialSelection::SlotsArray()); - return false; - } - - if (!assetConfiguration.m_asset.IsReady()) - { - // The asset is valid but is still loading, - // Do not set the empty slots in this case to avoid the entity being in invalid state - return false; - } - - Pipeline::MeshAsset* meshAsset = assetConfiguration.m_asset.GetAs(); - if (!meshAsset) - { - materialSelection.SetMaterialSlots(Physics::MaterialSelection::SlotsArray()); - AZ_Warning("PhysX", false, "UpdateMaterialSelectionFromPhysicsAsset: MeshAsset is invalid"); - return false; - } - - // Set the slots from the mesh asset - materialSelection.SetMaterialSlots(meshAsset->m_assetData.m_surfaceNames); - - if (!assetConfiguration.m_useMaterialsFromAsset) - { - return false; - } - - const Physics::MaterialLibraryAsset* materialLibrary = materialSelection.GetMaterialLibraryAssetData(); - const AZStd::vector& meshMaterialNames = meshAsset->m_assetData.m_materialNames; - - // Update material IDs in the selection for each slot - int slotIndex = 0; - for (const AZStd::string& meshMaterialName : meshMaterialNames) - { - Physics::MaterialFromAssetConfiguration materialData; - bool found = materialLibrary->GetDataForMaterialName(meshMaterialName, materialData); - - AZ_Warning("PhysX", found, - "UpdateMaterialSelectionFromPhysicsAsset: No material found for surfaceType (%s) in the collider material library", - meshMaterialName.c_str()); - - if (found) - { - materialSelection.SetMaterialId(materialData.m_id, slotIndex); - } - - slotIndex++; - } - - return true; - } } // namespace PhysX diff --git a/Gems/PhysX/Code/Source/SystemComponent.h b/Gems/PhysX/Code/Source/SystemComponent.h index 8871adfeeb..4609fde0ce 100644 --- a/Gems/PhysX/Code/Source/SystemComponent.h +++ b/Gems/PhysX/Code/Source/SystemComponent.h @@ -114,8 +114,6 @@ namespace PhysX // Physics::SystemRequestBus::Handler AZStd::shared_ptr CreateShape(const Physics::ColliderConfiguration& colliderConfiguration, const Physics::ShapeConfiguration& configuration) override; AZStd::shared_ptr CreateMaterial(const Physics::MaterialConfiguration& materialConfiguration) override; - AZStd::shared_ptr GetDefaultMaterial() override; - AZStd::vector> CreateMaterialsFromLibrary(const Physics::MaterialSelection& materialSelection) override; AZStd::vector GetSupportedJointTypes() override; AZStd::shared_ptr CreateJointLimitConfiguration(AZ::TypeId jointType) override; @@ -147,8 +145,6 @@ namespace PhysX static bool VersionConverter(AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement); - bool UpdateMaterialSelection(const Physics::ShapeConfiguration& shapeConfiguration, - Physics::ColliderConfiguration& colliderConfiguration) override; private: // AZ::TickBus::Handler ... void OnTick(float deltaTime, AZ::ScriptTimePoint time) override; @@ -157,9 +153,6 @@ namespace PhysX void EnableAutoManagedPhysicsTick(bool shouldTick); void ActivatePhysXSystem(); - bool UpdateMaterialSelectionFromPhysicsAsset( - const Physics::PhysicsAssetShapeConfiguration& assetConfiguration, - Physics::ColliderConfiguration& colliderConfiguration); bool m_enabled; ///< If false, this component will not activate itself in the Activate() function. diff --git a/Gems/PhysX/Code/Source/Utils.cpp b/Gems/PhysX/Code/Source/Utils.cpp index 55be7c92f7..270a352fc0 100644 --- a/Gems/PhysX/Code/Source/Utils.cpp +++ b/Gems/PhysX/Code/Source/Utils.cpp @@ -607,48 +607,6 @@ namespace PhysX return true; } - void GetMaterialList( - AZStd::vector& pxMaterials, const AZStd::vector& terrainSurfaceIdIndexMapping, - const Physics::TerrainMaterialSurfaceIdMap& terrainMaterialsToSurfaceIds) - { - pxMaterials.reserve(terrainSurfaceIdIndexMapping.size()); - - AZStd::shared_ptr defaultMaterial; - MaterialManagerRequestsBus::BroadcastResult(defaultMaterial, &MaterialManagerRequestsBus::Events::GetDefaultMaterial); - - if (terrainSurfaceIdIndexMapping.empty()) - { - pxMaterials.push_back(defaultMaterial->GetPxMaterial()); - return; - } - - AZStd::vector materials; - - for (auto& surfaceId : terrainSurfaceIdIndexMapping) - { - const auto& userAssignedMaterials = terrainMaterialsToSurfaceIds; - const auto& matSelectionIterator = userAssignedMaterials.find(surfaceId); - if (matSelectionIterator != userAssignedMaterials.end()) - { - MaterialManagerRequestsBus::Broadcast(&MaterialManagerRequests::GetPxMaterials, matSelectionIterator->second, materials); - - if (!materials.empty()) - { - pxMaterials.push_back(materials.front()); - } - else - { - AZ_Error("PhysX", false, "Creating materials: array with materials can't be empty"); - pxMaterials.push_back(defaultMaterial->GetPxMaterial()); - } - } - else - { - pxMaterials.push_back(defaultMaterial->GetPxMaterial()); - } - } - } - AZStd::string ReplaceAll(AZStd::string str, const AZStd::string& fromString, const AZStd::string& toString) { size_t positionBegin = 0; while ((positionBegin = str.find(fromString, positionBegin)) != AZStd::string::npos) @@ -920,9 +878,9 @@ namespace PhysX AZ::Vector3 GetTransformScale(AZ::EntityId entityId) { - AZ::Vector3 worldScale = AZ::Vector3::CreateOne(); - AZ::TransformBus::EventResult(worldScale, entityId, &AZ::TransformBus::Events::GetWorldScale); - return worldScale; + float worldUniformScale = 1.0f; + AZ::TransformBus::EventResult(worldUniformScale, entityId, &AZ::TransformBus::Events::GetWorldUniformScale); + return AZ::Vector3(worldUniformScale); } AZ::Vector3 GetUniformScale(AZ::EntityId entityId) diff --git a/Gems/PhysX/Code/Source/Utils.h b/Gems/PhysX/Code/Source/Utils.h index 2885e86a09..73ce813b77 100644 --- a/Gems/PhysX/Code/Source/Utils.h +++ b/Gems/PhysX/Code/Source/Utils.h @@ -115,9 +115,6 @@ namespace PhysX bool MeshDataToPxGeometry(physx::PxBase* meshData, physx::PxGeometryHolder &pxGeometry, const AZ::Vector3& scale); - void GetMaterialList( - AZStd::vector& pxMaterials, const AZStd::vector& materialIndexMapping, - const Physics::TerrainMaterialSurfaceIdMap& terrainMaterialsToSurfaceIds); //! Returns all connected busIds of the specified type. template AZStd::vector FindConnectedBusIds() diff --git a/Gems/PhysX/Code/Tests/PhysXMaterialLibraryTest.cpp b/Gems/PhysX/Code/Tests/PhysXMaterialLibraryTest.cpp deleted file mode 100644 index 2e09ec8dca..0000000000 --- a/Gems/PhysX/Code/Tests/PhysXMaterialLibraryTest.cpp +++ /dev/null @@ -1,181 +0,0 @@ -/* -* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -* its licensors. -* -* For complete copyright and license terms please see the LICENSE at the root of this -* distribution (the "License"). All use of this software is governed by the License, -* or, if provided, by the license below or the license accompanying this file. Do not -* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* -*/ -#include - -#include -#include -#include -#include -#include -#include -#include - -namespace PhysX -{ - class MaterialLibraryTest_MockCatalog - : public AZ::Data::AssetCatalog - , public AZ::Data::AssetCatalogRequestBus::Handler - { - - private: - AZ::Uuid m_randomUuid = AZ::Uuid::CreateRandom(); - AZStd::vector m_mockAssetIds; - - public: - AZ_CLASS_ALLOCATOR(MaterialLibraryTest_MockCatalog, AZ::SystemAllocator, 0); - - MaterialLibraryTest_MockCatalog() - { - AZ::Data::AssetCatalogRequestBus::Handler::BusConnect(); - } - - ~MaterialLibraryTest_MockCatalog() - { - AZ::Data::AssetCatalogRequestBus::Handler::BusDisconnect(); - } - - AZ::Data::AssetId GenerateMockAssetId() - { - AZ::Data::AssetId assetId = AZ::Data::AssetId(AZ::Uuid::CreateRandom(), 0); - m_mockAssetIds.push_back(assetId); - return assetId; - } - - ////////////////////////////////////////////////////////////////////////// - // AssetCatalogRequestBus - AZ::Data::AssetInfo GetAssetInfoById(const AZ::Data::AssetId& id) override - { - AZ::Data::AssetInfo result; - result.m_assetType = AZ::AzTypeInfo::Uuid(); - auto foundId = AZStd::find(m_mockAssetIds.begin(), m_mockAssetIds.end(), id); - if (foundId != m_mockAssetIds.end()) - { - result.m_assetId = *foundId; - } - - return result; - } - ////////////////////////////////////////////////////////////////////////// - - AZ::Data::AssetStreamInfo GetStreamInfoForLoad(const AZ::Data::AssetId& id, const AZ::Data::AssetType& type) override - { - EXPECT_TRUE(type == AZ::AzTypeInfo::Uuid()); - AZ::Data::AssetStreamInfo info; - info.m_dataOffset = 0; - info.m_streamFlags = AZ::IO::OpenMode::ModeRead; - - for (int i = 0; i < m_mockAssetIds.size(); ++i) - { - if (m_mockAssetIds[i] == id) - { - info.m_streamName = AZStd::string::format("MaterialLibraryAssetName%d", i); - } - } - - if (!info.m_streamName.empty()) - { - // this ensures tha parallel running unit tests do not overlap their files that they use. - AZStd::string fullName = AZStd::string::format("%s-%s", m_randomUuid.ToString().c_str(), info.m_streamName.c_str()); - info.m_streamName = fullName; - info.m_dataLen = static_cast(AZ::IO::SystemFile::Length(info.m_streamName.c_str())); - } - else - { - info.m_dataLen = 0; - } - - return info; - } - - AZ::Data::AssetStreamInfo GetStreamInfoForSave(const AZ::Data::AssetId& id, const AZ::Data::AssetType& type) override - { - AZ::Data::AssetStreamInfo info; - info = GetStreamInfoForLoad(id, type); - info.m_streamFlags = AZ::IO::OpenMode::ModeWrite; - return info; - } - - bool SaveAsset(AZ::Data::Asset& asset) - { - volatile bool isDone = false; - volatile bool succeeded = false; - AZ::Data::AssetBusCallbacks callbacks; - callbacks.SetCallbacks(nullptr, nullptr, nullptr, - [&isDone, &succeeded](const AZ::Data::Asset& /*asset*/, bool isSuccessful, AZ::Data::AssetBusCallbacks& /*callbacks*/) - { - isDone = true; - succeeded = isSuccessful; - }, nullptr, nullptr, nullptr); - - callbacks.BusConnect(asset.GetId()); - asset.Save(); - - while (!isDone) - { - AZ::Data::AssetManager::Instance().DispatchEvents(); - } - return succeeded; - } - }; - - class DISABLED_PhysXMaterialLibraryTest - : public ::testing::Test - { - protected: - void SetUp() override - { - m_catalog = AZStd::make_unique(); - AZ::Data::AssetManager::Instance().RegisterCatalog(m_catalog.get(), AZ::AzTypeInfo::Uuid()); - } - - void TearDown() override - { - AZ::Data::AssetManager::Instance().UnregisterCatalog(m_catalog.get()); - } - - AZStd::unique_ptr m_catalog; - }; - - TEST_F(DISABLED_PhysXMaterialLibraryTest, DISABLED_DefaultMaterialLibrary_CorrectMaterialLibraryIsInferred) - { - AZ::Data::Asset materialLibrary = AZ::Interface::Get()->GetDefaultMaterialLibrary(); - - AZ::Data::AssetId dummyAssetId = AZ::Data::AssetId(AZ::Uuid::CreateName("DummyLibrary.physmaterial")); - AZ::Data::Asset dummyMaterialLibAsset = AZ::Data::AssetManager::Instance().GetAsset(dummyAssetId, AZ::Data::AssetLoadBehavior::Default); - materialLibrary = dummyMaterialLibAsset; - AZ::Interface::Get()->UpdateDefaultMaterialLibrary(materialLibrary); - - // We must have now a default material library setup - ASSERT_TRUE(materialLibrary.GetId().IsValid()); - - AZ::Data::AssetId otherDummyAssetId = AZ::Data::AssetId(AZ::Uuid::CreateName("OtherDummyLibrary.physmaterial")); - AZ::Data::Asset otherDummyMaterialLibAsset = AZ::Data::AssetManager::Instance().GetAsset(otherDummyAssetId, AZ::Data::AssetLoadBehavior::Default); - - // Set selection's material library to a different one than default material library - Physics::MaterialSelection selectionTest; - selectionTest.SetMaterialLibrary(otherDummyAssetId); - - ASSERT_TRUE(selectionTest.GetMaterialLibraryAssetId().IsValid()); - ASSERT_EQ(selectionTest.GetMaterialLibraryAssetId(), selectionTest.GetMaterialLibraryAssetId()); - ASSERT_NE(selectionTest.GetMaterialLibraryAssetId(), materialLibrary.GetId()); - - // By reseting the selection, now it should infer to the default material library set in the global configuration - selectionTest.ResetToDefaultMaterialLibrary(); - - ASSERT_TRUE(selectionTest.GetMaterialLibraryAssetId().IsValid()); - ASSERT_EQ(selectionTest.GetMaterialLibraryAssetId(), materialLibrary.GetId()); - - // Release material library so we exit gracefully - materialLibrary = {}; - AZ::Interface::Get()->UpdateDefaultMaterialLibrary(materialLibrary); - } -} diff --git a/Gems/PhysX/Code/physx_tests_files.cmake b/Gems/PhysX/Code/physx_tests_files.cmake index 406aed64a7..79999c5f74 100644 --- a/Gems/PhysX/Code/physx_tests_files.cmake +++ b/Gems/PhysX/Code/physx_tests_files.cmake @@ -23,7 +23,6 @@ set(FILES Tests/PhysXGenericTest.cpp Tests/PhysXSpecificTest.cpp Tests/PhysXForceRegionTest.cpp - Tests/PhysXMaterialLibraryTest.cpp Tests/PhysXCollisionFilteringTest.cpp Tests/PhysXJointsTest.cpp Tests/PhysXSceneTests.cpp diff --git a/Gems/PhysXDebug/Code/CMakeLists.txt b/Gems/PhysXDebug/Code/CMakeLists.txt index f198f6f26e..b0a05ef146 100644 --- a/Gems/PhysXDebug/Code/CMakeLists.txt +++ b/Gems/PhysXDebug/Code/CMakeLists.txt @@ -44,6 +44,9 @@ ly_add_target( Gem::PhysX Gem::ImGui ) +# use the PhysXDebug module in Clients and Servers: +ly_create_alias(NAME PhysXDebug.Clients NAMESPACE Gem TARGETS Gem::PhysXDebug) +ly_create_alias(NAME PhysXDebug.Servers NAMESPACE Gem TARGETS Gem::PhysXDebug) if(PAL_TRAIT_BUILD_HOST_TOOLS) ly_add_target( @@ -66,11 +69,15 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS) Legacy::CryCommon Legacy::Editor.Headers AZ::AzToolsFramework - Gem::PhysX + Gem::PhysX.Editor Gem::ImGui.imguilib - Gem::ImGui + Gem::ImGui.Editor RUNTIME_DEPENDENCIES Gem::PhysX.Editor Gem::ImGui.Editor ) + # use the PhysXDebug.Editor module in dev tools: + ly_create_alias(NAME PhysXDebug.Builders NAMESPACE Gem TARGETS Gem::PhysXDebug.Editor) + ly_create_alias(NAME PhysXDebug.Tools NAMESPACE Gem TARGETS Gem::PhysXDebug.Editor) + endif() diff --git a/AutomatedTesting/Gem/Code/Platform/iOS/runtime_dependencies.cmake b/Gems/PhysXSamples/CMakeLists.txt similarity index 100% rename from AutomatedTesting/Gem/Code/Platform/iOS/runtime_dependencies.cmake rename to Gems/PhysXSamples/CMakeLists.txt diff --git a/AutomatedTesting/Gem/Code/Platform/iOS/tool_dependencies.cmake b/Gems/PhysicsEntities/CMakeLists.txt similarity index 100% rename from AutomatedTesting/Gem/Code/Platform/iOS/tool_dependencies.cmake rename to Gems/PhysicsEntities/CMakeLists.txt diff --git a/Gems/Prefab/PrefabBuilder/CMakeLists.txt b/Gems/Prefab/PrefabBuilder/CMakeLists.txt index 22b89287ca..dbd3c2281b 100644 --- a/Gems/Prefab/PrefabBuilder/CMakeLists.txt +++ b/Gems/Prefab/PrefabBuilder/CMakeLists.txt @@ -38,14 +38,16 @@ ly_add_target( Gem::PrefabBuilder.Static ) -ly_add_target_dependencies( - TARGETS - AssetBuilder - AssetProcessor - AssetProcessorBatch - DEPENDENT_TARGETS - Gem::PrefabBuilder -) +# the prefab builder only needs to be active in builders +# use the PrefabBuilder module in Clients and Servers: +ly_create_alias(NAME PrefabBuilder.Builders NAMESPACE Gem TARGETS Gem::PrefabBuilder) + +# we automatically add this gem, if it is present, to all our known set of builder applications: +ly_enable_gems(GEMS PrefabBuilder VARIANTS Builders TARGETS AssetProcessor AssetProcessorBatch AssetBuilder) + +# if you have a custom builder application in your project, then use ly_enable_gems() to +# add it to that application for your project, like this to make YOUR_TARGET_NAME load it automatically +# ly_enable_gems(PROJECT (YOUR_PROJECT_NAME) GEMS PrefabBuilder VARIANTS Builders TARGETS (YOUR_TARGET_NAME) ) if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) ly_add_target( diff --git a/Gems/Presence/Code/CMakeLists.txt b/Gems/Presence/Code/CMakeLists.txt index 07780fc8eb..b9db324996 100644 --- a/Gems/Presence/Code/CMakeLists.txt +++ b/Gems/Presence/Code/CMakeLists.txt @@ -44,3 +44,6 @@ ly_add_target( AZ::AzFramework Gem::Presence.Headers ) + +# we activate the presence gem (if enabled) only on client applications such as the launcher: +ly_create_alias(NAME Presence.Clients NAMESPACE Gem TARGETS Gem::Presence) diff --git a/AutomatedTesting/Gem/Code/Platform/Windows/runtime_dependencies.cmake b/Gems/PrimitiveAssets/CMakeLists.txt similarity index 95% rename from AutomatedTesting/Gem/Code/Platform/Windows/runtime_dependencies.cmake rename to Gems/PrimitiveAssets/CMakeLists.txt index ffcaf7293a..4d5680a30d 100644 --- a/AutomatedTesting/Gem/Code/Platform/Windows/runtime_dependencies.cmake +++ b/Gems/PrimitiveAssets/CMakeLists.txt @@ -8,6 +8,3 @@ # remove or modify any license notices. This file is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # - -set(GEM_DEPENDENCIES -) \ No newline at end of file diff --git a/Gems/PythonAssetBuilder/Code/CMakeLists.txt b/Gems/PythonAssetBuilder/Code/CMakeLists.txt index 60af675bc6..73fa90ab67 100644 --- a/Gems/PythonAssetBuilder/Code/CMakeLists.txt +++ b/Gems/PythonAssetBuilder/Code/CMakeLists.txt @@ -13,24 +13,11 @@ if(NOT PAL_TRAIT_BUILD_HOST_TOOLS) return() endif() -set(static_files pythonassetbuilder_common_files.cmake) -set(editor_files pythonassetbuilder_editor_files.cmake) -set(shared_files pythonassetbuilder_shared_files.cmake) -set(static_dependencies - 3rdParty::Python - Gem::EditorPythonBindings.Static - AZ::AssetBuilderSDK -) -set(editor_dependencies - Gem::EditorPythonBindings.Static - AZ::AssetBuilderSDK -) - ly_add_target( NAME PythonAssetBuilder.Static STATIC NAMESPACE Gem FILES_CMAKE - ${static_files} + pythonassetbuilder_common_files.cmake PLATFORM_INCLUDE_FILES Source/Platform/Common/${PAL_TRAIT_COMPILER_ID}/pythonassetbuilder_static_${PAL_TRAIT_COMPILER_ID_LOWERCASE}.cmake INCLUDE_DIRECTORIES @@ -43,7 +30,9 @@ ly_add_target( PRIVATE AZ::AzCore PUBLIC - ${static_dependencies} + 3rdParty::Python + Gem::EditorPythonBindings.Static + AZ::AssetBuilderSDK AZ::AzToolsFramework ) @@ -52,8 +41,8 @@ ly_add_target( NAMESPACE Gem FILES_CMAKE - ${editor_files} - ${shared_files} + pythonassetbuilder_editor_files.cmake + pythonassetbuilder_shared_files.cmake PLATFORM_INCLUDE_FILES Source/Platform/Common/${PAL_TRAIT_COMPILER_ID}/pythonassetbuilder_static_${PAL_TRAIT_COMPILER_ID_LOWERCASE}.cmake INCLUDE_DIRECTORIES @@ -64,11 +53,17 @@ ly_add_target( Include BUILD_DEPENDENCIES PRIVATE - ${editor_dependencies} + Gem::EditorPythonBindings.Static + AZ::AssetBuilderSDK RUNTIME_DEPENDENCIES Gem::EditorPythonBindings.Editor ) +# the above target is used in both builders like AssetProcessor and Tools like the Editor +# but is not used in clients or servers +ly_create_alias(NAME PythonAssetBuilder.Tools NAMESPACE Gem TARGETS Gem::PythonAssetBuilder.Editor) +ly_create_alias(NAME PythonAssetBuilder.Builders NAMESPACE Gem TARGETS Gem::PythonAssetBuilder.Editor) + ################################################################################ # Tests ################################################################################ diff --git a/Gems/QtForPython/Code/CMakeLists.txt b/Gems/QtForPython/Code/CMakeLists.txt index 74c660043f..c11d93634e 100644 --- a/Gems/QtForPython/Code/CMakeLists.txt +++ b/Gems/QtForPython/Code/CMakeLists.txt @@ -55,3 +55,9 @@ ly_add_target( RUNTIME_DEPENDENCIES Gem::EditorPythonBindings.Editor ) + +# the above target is used in both builders like AssetProcessor and Tools like the Editor +# but is not used in clients or servers +ly_create_alias(NAME QtForPython.Tools NAMESPACE Gem TARGETS Gem::QtForPython.Editor) +ly_create_alias(NAME QtForPython.Builders NAMESPACE Gem TARGETS Gem::QtForPython.Editor) + diff --git a/Gems/RADTelemetry/Code/CMakeLists.txt b/Gems/RADTelemetry/Code/CMakeLists.txt index 78a5561b7c..8b3cc70570 100644 --- a/Gems/RADTelemetry/Code/CMakeLists.txt +++ b/Gems/RADTelemetry/Code/CMakeLists.txt @@ -42,3 +42,8 @@ ly_add_target( PRIVATE Gem::RADTelemetry.Static ) + +# the RADTelemetry module above can be used in all kinds of applications, but we don't enable it in asset builders +ly_create_alias(NAME RADTelemetry.Clients NAMESPACE Gem TARGETS Gem::RADTelemetry) +ly_create_alias(NAME RADTelemetry.Tools NAMESPACE Gem TARGETS Gem::RADTelemetry) +ly_create_alias(NAME RADTelemetry.Servers NAMESPACE Gem TARGETS Gem::RADTelemetry) diff --git a/Gems/SaveData/Code/CMakeLists.txt b/Gems/SaveData/Code/CMakeLists.txt index 46c6e91f58..d9dc62ef03 100644 --- a/Gems/SaveData/Code/CMakeLists.txt +++ b/Gems/SaveData/Code/CMakeLists.txt @@ -46,6 +46,9 @@ ly_add_target( Gem::SaveData.Static ) +# the SaveData module above is only used in Clients by default. +ly_create_alias(NAME SaveData.Clients NAMESPACE Gem TARGETS Gem::SaveData) + ################################################################################ # Tests ################################################################################ diff --git a/Gems/SceneLoggingExample/Code/CMakeLists.txt b/Gems/SceneLoggingExample/Code/CMakeLists.txt index 8cd012c4c2..6370420928 100644 --- a/Gems/SceneLoggingExample/Code/CMakeLists.txt +++ b/Gems/SceneLoggingExample/Code/CMakeLists.txt @@ -40,3 +40,7 @@ ly_add_target( PRIVATE Gem::SceneLoggingExample.Static ) + +# the SceneLoggingExample module above is only used in Builders and Tools by default. +ly_create_alias(NAME SceneLoggingExample.Builders NAMESPACE Gem TARGETS Gem::SceneLoggingExample) +ly_create_alias(NAME SceneLoggingExample.Tools NAMESPACE Gem TARGETS Gem::SceneLoggingExample) diff --git a/Gems/SceneProcessing/Code/CMakeLists.txt b/Gems/SceneProcessing/Code/CMakeLists.txt index 67124a74d5..4b0dfbab27 100644 --- a/Gems/SceneProcessing/Code/CMakeLists.txt +++ b/Gems/SceneProcessing/Code/CMakeLists.txt @@ -66,6 +66,10 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS) AZ::SceneCore AZ::SceneData ) + # the SceneProcessing.Editor module above is only used in Builders and Tools. + ly_create_alias(NAME SceneProcessing.Builders NAMESPACE Gem TARGETS Gem::SceneProcessing.Editor) + ly_create_alias(NAME SceneProcessing.Tools NAMESPACE Gem TARGETS Gem::SceneProcessing.Editor) + endif() ################################################################################ @@ -84,7 +88,8 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) BUILD_DEPENDENCIES PRIVATE AZ::AzTest - Gem::SceneProcessing + RUNTIME_DEPENDENCIES + Gem::SceneProcessing ) ly_add_googletest( NAME Gem::SceneProcessing.Tests diff --git a/Gems/ScriptCanvas/Code/Builder/ScriptCanvasBuilderWorker.h b/Gems/ScriptCanvas/Code/Builder/ScriptCanvasBuilderWorker.h index 1d1ea1d4aa..fc9613c3a7 100644 --- a/Gems/ScriptCanvas/Code/Builder/ScriptCanvasBuilderWorker.h +++ b/Gems/ScriptCanvas/Code/Builder/ScriptCanvasBuilderWorker.h @@ -59,6 +59,7 @@ namespace ScriptCanvasBuilder QuantumLeap, DependencyArguments, DependencyRequirementsData, + AddAssetDependencySearch, // add new entries above Current, }; diff --git a/Gems/ScriptCanvas/Code/Builder/ScriptCanvasBuilderWorkerUtility.cpp b/Gems/ScriptCanvas/Code/Builder/ScriptCanvasBuilderWorkerUtility.cpp index ab59ddd840..ba22789cd1 100644 --- a/Gems/ScriptCanvas/Code/Builder/ScriptCanvasBuilderWorkerUtility.cpp +++ b/Gems/ScriptCanvas/Code/Builder/ScriptCanvasBuilderWorkerUtility.cpp @@ -681,6 +681,19 @@ namespace ScriptCanvasBuilder } AssetBuilderSDK::JobProduct jobProduct; + + // Scan our runtime input for any asset references + // Store them as product dependencies + AssetBuilderSDK::OutputObject(&runtimeData.m_input, + azrtti_typeid(), + input.runtimeScriptCanvasOutputPath, + azrtti_typeid(), + AZ_CRC("RuntimeData", 0x163310ae), jobProduct); + + // Output Object marks dependencies as handled. + // We still have more to evaluate + jobProduct.m_dependenciesHandled = false; + jobProduct.m_dependencies.push_back({ runtimeData.m_script.GetId(), {} }); for (const auto& assetDependency : runtimeData.m_requiredAssets) @@ -713,9 +726,6 @@ namespace ScriptCanvasBuilder } jobProduct.m_dependenciesHandled = true; - jobProduct.m_productFileName = input.runtimeScriptCanvasOutputPath; - jobProduct.m_productAssetType = azrtti_typeid(); - jobProduct.m_productSubID = AZ_CRC("RuntimeData", 0x163310ae); input.response->m_outputProducts.push_back(AZStd::move(jobProduct)); return AZ::Success(); } diff --git a/Gems/ScriptCanvas/Code/CMakeLists.txt b/Gems/ScriptCanvas/Code/CMakeLists.txt index 32efa74520..f9165192f0 100644 --- a/Gems/ScriptCanvas/Code/CMakeLists.txt +++ b/Gems/ScriptCanvas/Code/CMakeLists.txt @@ -49,6 +49,14 @@ ly_add_target( Gem::ScriptEvents.Static ) +# the script canvas debugger is an optional gem module +# To Enable it: ly_enable_gems( ... TARGETS xxxyyzzz GEMS ScriptCanvasDebugger ...) +# in any particular target. +ly_create_alias(NAME ScriptCanvasDebugger.Builders NAMESPACE Gem TARGETS Gem::ScriptCanvasDebugger) +ly_create_alias(NAME ScriptCanvasDebugger.Tools NAMESPACE Gem TARGETS Gem::ScriptCanvasDebugger) +ly_create_alias(NAME ScriptCanvasDebugger.Clients NAMESPACE Gem TARGETS Gem::ScriptCanvasDebugger) +ly_create_alias(NAME ScriptCanvasDebugger.Servers NAMESPACE Gem TARGETS Gem::ScriptCanvasDebugger) + ly_add_target( NAME ScriptCanvas.Static STATIC NAMESPACE Gem @@ -81,6 +89,8 @@ ly_add_target( *.ScriptCanvasGrammar.xml,ScriptCanvasGrammar_Source.jinja,$path/$fileprefix.generated.cpp *.ScriptCanvasNodeable.xml,ScriptCanvasNodeable_Header.jinja,$path/$fileprefix.generated.h *.ScriptCanvasNodeable.xml,ScriptCanvasNodeable_Source.jinja,$path/$fileprefix.generated.cpp + RUNTIME_DEPENDENCIES + Gem::ScriptCanvasDebugger ) ly_add_target( @@ -109,6 +119,10 @@ ly_add_target( Gem::ExpressionEvaluation ) +# the "ScriptCanvas" target is active in Clients and Servers +ly_create_alias(NAME ScriptCanvas.Clients NAMESPACE Gem TARGETS Gem::ScriptCanvas) +ly_create_alias(NAME ScriptCanvas.Servers NAMESPACE Gem TARGETS Gem::ScriptCanvas) + if(PAL_TRAIT_BUILD_HOST_TOOLS) ly_add_target( NAME ScriptCanvasEditor STATIC @@ -170,6 +184,8 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS) Gem::ExpressionEvaluation.Static PRIVATE Legacy::EditorCore + RUNTIME_DEPENDENCIES + Gem::ScriptCanvas ) ly_add_target( @@ -204,6 +220,12 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS) Gem::ScriptEvents.Editor Gem::ExpressionEvaluation ) + + # the "ScriptCanvas.Editor" target is active in all dev tools: + ly_create_alias(NAME ScriptCanvas.Builders NAMESPACE Gem TARGETS Gem::ScriptCanvas.Editor) + ly_create_alias(NAME ScriptCanvas.Tools NAMESPACE Gem TARGETS Gem::ScriptCanvas.Editor) + + endif() ################################################################################ @@ -228,7 +250,8 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) PRIVATE AZ::AzTest AZ::AzFramework - Gem::ScriptCanvas + RUNTIME_DEPENDENCIES + Gem::ScriptCanvas ) ly_add_googletest( NAME Gem::ScriptCanvas.Tests diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/Datum.cpp b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/Datum.cpp index a0db02c3a8..30e55cee49 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/Datum.cpp +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/Datum.cpp @@ -2527,15 +2527,15 @@ namespace ScriptCanvas { Data::TransformType copy(source); AZ::Vector3 pos = copy.GetTranslation(); - AZ::Vector3 scale = copy.ExtractScale(); + float scale = copy.ExtractUniformScale(); AZ::Vector3 rotation = AZ::ConvertTransformToEulerDegrees(copy); return AZStd::string::format ( "(Position: X: %f, Y: %f, Z: %f," " Rotation: X: %f, Y: %f, Z: %f," - " Scale: X: %f, Y: %f, Z: %f)" + " Scale: %f)" , static_cast(pos.GetX()), static_cast(pos.GetY()), static_cast(pos.GetZ()) , static_cast(rotation.GetX()), static_cast(rotation.GetY()), static_cast(rotation.GetZ()) - , static_cast(scale.GetX()), static_cast(scale.GetY()), static_cast(scale.GetZ())); + , scale); } AZStd::string Datum::ToStringVector2(const AZ::Vector2& source) const diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Libraries.cpp b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Libraries.cpp index 641bdca8f7..8c2671ac6d 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Libraries.cpp +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Libraries.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -33,6 +34,7 @@ namespace ScriptCanvas Entity::InitNodeRegistry(*g_nodeRegistry); Comparison::InitNodeRegistry(*g_nodeRegistry); Time::InitNodeRegistry(*g_nodeRegistry); + Spawning::InitNodeRegistry(*g_nodeRegistry); String::InitNodeRegistry(*g_nodeRegistry); Operators::InitNodeRegistry(*g_nodeRegistry); @@ -61,6 +63,7 @@ namespace ScriptCanvas Entity::Reflect(reflectContext); Comparison::Reflect(reflectContext); Time::Reflect(reflectContext); + Spawning::Reflect(reflectContext); String::Reflect(reflectContext); Operators::Reflect(reflectContext); @@ -90,6 +93,9 @@ namespace ScriptCanvas componentDescriptors = Time::GetComponentDescriptors(); libraryDescriptors.insert(libraryDescriptors.end(), componentDescriptors.begin(), componentDescriptors.end()); + componentDescriptors = Spawning::GetComponentDescriptors(); + libraryDescriptors.insert(libraryDescriptors.end(), componentDescriptors.begin(), componentDescriptors.end()); + componentDescriptors = String::GetComponentDescriptors(); libraryDescriptors.insert(libraryDescriptors.end(), componentDescriptors.begin(), componentDescriptors.end()); diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Libraries.h b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Libraries.h index 3388a029ac..67094f4db8 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Libraries.h +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Libraries.h @@ -143,6 +143,17 @@ namespace ScriptCanvas }; + struct Spawning : public LibraryDefinition + { + AZ_RTTI(Spawning, "{41E910AE-FBD2-41AD-9173-5105141F0466}", LibraryDefinition); + + static void Reflect(AZ::ReflectContext*); + static void InitNodeRegistry(NodeRegistry& nodeRegistry); + static AZStd::vector GetComponentDescriptors(); + + ~Spawning() override = default; + }; + struct String : public LibraryDefinition { AZ_RTTI(String, "{5B700838-21A2-4579-9303-F4A4822AFEF4}", LibraryDefinition); diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Math/TransformNodes.h b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Math/TransformNodes.h index 6a0f082272..9e66c6c6fc 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Math/TransformNodes.h +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Math/TransformNodes.h @@ -26,12 +26,12 @@ namespace ScriptCanvas using namespace MathNodeUtilities; static const char* k_categoryName = "Math/Transform"; - AZ_INLINE std::tuple ExtractScale(TransformType source) + AZ_INLINE std::tuple ExtractUniformScale(TransformType source) { - auto scale(source.ExtractScale()); + auto scale(source.ExtractUniformScale()); return std::make_tuple( scale, source ); } - SCRIPT_CANVAS_GENERIC_FUNCTION_MULTI_RESULTS_NODE(ExtractScale, k_categoryName, "{8DFE5247-0950-4CD1-87E6-0CAAD42F1637}", "returns a vector which is the length of the scale components, and a transform with the scale extracted ", "Source", "Scale", "Extracted"); + SCRIPT_CANVAS_GENERIC_FUNCTION_MULTI_RESULTS_NODE(ExtractUniformScale, k_categoryName, "{8DFE5247-0950-4CD1-87E6-0CAAD42F1637}", "returns the uniform scale as a float, and a transform with the scale extracted ", "Source", "Uniform Scale", "Extracted"); AZ_INLINE TransformType FromMatrix3x3(Matrix3x3Type source) { @@ -57,11 +57,11 @@ namespace ScriptCanvas } SCRIPT_CANVAS_GENERIC_FUNCTION_NODE(FromRotationAndTranslation, k_categoryName, "{99A4D55D-6EFB-4E24-8113-F5B46DE3A194}", "returns a transform from the rotation and the translation", "Rotation", "Translation"); - AZ_INLINE TransformType FromScale(Vector3Type scale) + AZ_INLINE TransformType FromScale(NumberType scale) { - return TransformType::CreateScale(scale); + return TransformType::CreateUniformScale(scale); } - SCRIPT_CANVAS_GENERIC_FUNCTION_NODE(FromScale, k_categoryName, "{4B6454BC-015C-41BB-9C78-34ADBCF70187}", "returns a scale matrix and the translation set to zero", "Scale"); + SCRIPT_CANVAS_GENERIC_FUNCTION_NODE(FromScale, k_categoryName, "{4B6454BC-015C-41BB-9C78-34ADBCF70187}", "returns a transform which applies the specified uniform Scale, but no rotation or translation", "Scale"); AZ_INLINE TransformType FromTranslation(Vector3Type translation) { @@ -145,12 +145,12 @@ namespace ScriptCanvas } SCRIPT_CANVAS_GENERIC_FUNCTION_NODE(Multiply3x3ByVector3, k_categoryName, "{4F2ABFC6-2E93-4A9D-8639-C7967DB318DB}", "returns Source's 3x3 upper matrix post multiplied by Multiplier", "Source", "Multiplier"); - AZ_INLINE TransformType MultiplyByScale(TransformType source, Vector3Type scale) + AZ_INLINE TransformType MultiplyByUniformScale(TransformType source, NumberType scale) { - source.MultiplyByScale(scale); + source.MultiplyByUniformScale(scale); return source; } - SCRIPT_CANVAS_GENERIC_FUNCTION_NODE(MultiplyByScale, k_categoryName, "{90472D62-65A8-40C1-AB08-FA66D793F689}", "returns Source multiplied by the scale matrix produced by Scale", "Source", "Scale"); + SCRIPT_CANVAS_GENERIC_FUNCTION_NODE(MultiplyByUniformScale, k_categoryName, "{90472D62-65A8-40C1-AB08-FA66D793F689}", "returns Source multiplied uniformly by Scale", "Source", "Scale"); AZ_INLINE TransformType MultiplyByTransform(const TransformType& a, const TransformType& b) { @@ -194,16 +194,16 @@ namespace ScriptCanvas } SCRIPT_CANVAS_GENERIC_FUNCTION_NODE(RotationZDegrees, k_categoryName, "{F848306A-C07C-4586-B52F-BEEE489045D2}", "returns a transform representing a rotation Degrees around the Z-Axis", "Degrees"); - AZ_INLINE Vector3Type ToScale(const TransformType& source) + AZ_INLINE NumberType ToScale(const TransformType& source) { - return source.GetScale(); + return source.GetUniformScale(); } - SCRIPT_CANVAS_GENERIC_FUNCTION_NODE(ToScale, k_categoryName, "{063C58AD-F567-464D-A432-F298FE3953A6}", "returns the scale part of the Source, the length of the scale components", "Source"); + SCRIPT_CANVAS_GENERIC_FUNCTION_NODE(ToScale, k_categoryName, "{063C58AD-F567-464D-A432-F298FE3953A6}", "returns the uniform scale of the Source", "Source"); using Registrar = RegistrarGeneric < #if ENABLE_EXTENDED_MATH_SUPPORT - ExtractScaleNode , + ExtractUniformScaleNode , #endif FromMatrix3x3AndTranslationNode , FromMatrix3x3Node @@ -230,7 +230,7 @@ namespace ScriptCanvas , Multiply3x3ByVector3Node #endif - , MultiplyByScaleNode + , MultiplyByUniformScaleNode , MultiplyByTransformNode , MultiplyByVector3Node , MultiplyByVector4Node diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Spawning/SpawnNodeable.ScriptCanvasNodeable.xml b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Spawning/SpawnNodeable.ScriptCanvasNodeable.xml new file mode 100644 index 0000000000..e9b1ce9f4e --- /dev/null +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Spawning/SpawnNodeable.ScriptCanvasNodeable.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + /> + + + + + + + + + + + + diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Spawning/SpawnNodeable.cpp b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Spawning/SpawnNodeable.cpp new file mode 100644 index 0000000000..ad53236108 --- /dev/null +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Spawning/SpawnNodeable.cpp @@ -0,0 +1,140 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#include + +#include +#include + +namespace ScriptCanvas::Nodeables::Spawning +{ + SpawnNodeable::SpawnNodeable(const SpawnNodeable& rhs) + : m_spawnableAsset(rhs.m_spawnableAsset) + {} + + SpawnNodeable& SpawnNodeable::operator=(SpawnNodeable& rhs) + { + m_spawnableAsset = rhs.m_spawnableAsset; + return *this; + } + + void SpawnNodeable::OnInitializeExecutionState() + { + if (!AZ::TickBus::Handler::BusIsConnected()) + { + AZ::TickBus::Handler::BusConnect(); + } + + m_spawnTicket = AzFramework::EntitySpawnTicket(m_spawnableAsset); + } + + void SpawnNodeable::OnDeactivate() + { + AZ::TickBus::Handler::BusDisconnect(); + + m_spawnTicket = AzFramework::EntitySpawnTicket(); + } + + void SpawnNodeable::OnTick([[maybe_unused]] float delta, [[maybe_unused]] AZ::ScriptTimePoint timePoint) + { + AZStd::vector swappedSpawnedEntityList; + AZStd::vector swappedSpawnBatchSizes; + { + AZStd::lock_guard lock(m_idBatchMutex); + + swappedSpawnedEntityList.swap(m_spawnedEntityList); + swappedSpawnBatchSizes.swap(m_spawnBatchSizes); + } + + AZ::EntityId* batchBegin = swappedSpawnedEntityList.data(); + for (size_t batchSize : swappedSpawnBatchSizes) + { + if (batchSize == 0) + { + continue; + } + + AZStd::vector spawnedEntitiesBatch( + batchBegin, batchBegin + batchSize); + + CallOnSpawn(AZStd::move(spawnedEntitiesBatch)); + + batchBegin += batchSize; + } + } + + void SpawnNodeable::OnSpawnAssetChanged() + { + if (m_spawnableAsset.GetId().IsValid()) + { + AZStd::string rootSpawnableFile; + AzFramework::StringFunc::Path::GetFileName(m_spawnableAsset.GetHint().c_str(), rootSpawnableFile); + + rootSpawnableFile += AzFramework::Spawnable::DotFileExtension; + + AZ::u32 rootSubId = AzFramework::SpawnableAssetHandler::BuildSubId(AZStd::move(rootSpawnableFile)); + + if (m_spawnableAsset.GetId().m_subId != rootSubId) + { + AZ::Data::AssetId rootAssetId = m_spawnableAsset.GetId(); + rootAssetId.m_subId = rootSubId; + + m_spawnableAsset = AZ::Data::AssetManager::Instance(). + FindOrCreateAsset(rootAssetId, AZ::Data::AssetLoadBehavior::Default); + } + else + { + m_spawnableAsset.SetAutoLoadBehavior(AZ::Data::AssetLoadBehavior::Default); + } + } + } + + void SpawnNodeable::RequestSpawn(Data::Vector3Type translation, Data::Vector3Type rotation, Data::NumberType scale) + { + if (m_spawnableAsset.GetAutoLoadBehavior() == AZ::Data::AssetLoadBehavior::NoLoad) + { + return; + } + + auto preSpawnCB = [this, translation, rotation, scale]([[maybe_unused]] AzFramework::EntitySpawnTicket::Id ticketId, + AzFramework::SpawnableEntityContainerView view) + { + AZ::Entity* rootEntity = *view.begin(); + + AzFramework::TransformComponent* entityTransform = + rootEntity->FindComponent(); + + if (entityTransform) + { + AZ::Vector3 rotationCopy = rotation; + AZ::Quaternion rotationQuat = AZ::Quaternion::CreateFromEulerAnglesDegrees(rotationCopy); + + entityTransform->SetWorldTM(AZ::Transform(translation, rotationQuat, scale)); + } + }; + + auto spawnCompleteCB = [this]([[maybe_unused]] AzFramework::EntitySpawnTicket::Id ticketId, + AzFramework::SpawnableConstEntityContainerView view) + { + AZStd::lock_guard lock(m_idBatchMutex); + m_spawnedEntityList.reserve(m_spawnedEntityList.size() + view.size()); + for (const AZ::Entity* entity : view) + { + m_spawnedEntityList.emplace_back(entity->GetId()); + } + m_spawnBatchSizes.push_back(view.size()); + }; + + AzFramework::SpawnableEntitiesInterface::Get()->SpawnAllEntities( + m_spawnTicket, AzFramework::SpawnablePriority_Default, preSpawnCB, spawnCompleteCB); + } +} diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Spawning/SpawnNodeable.h b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Spawning/SpawnNodeable.h new file mode 100644 index 0000000000..0f3a27d2ea --- /dev/null +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Spawning/SpawnNodeable.h @@ -0,0 +1,52 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +#include + +#include + +#include + +#include +#include +#include + +namespace ScriptCanvas::Nodeables::Spawning +{ + class SpawnNodeable + : public ScriptCanvas::Nodeable, + public AZ::TickBus::Handler + { + SCRIPTCANVAS_NODE(SpawnNodeable); + public: + SpawnNodeable() = default; + SpawnNodeable(const SpawnNodeable& rhs); + SpawnNodeable& operator=(SpawnNodeable& rhs); + + void OnInitializeExecutionState() override; + void OnDeactivate() override; + + //TickBus + void OnTick(float delta, AZ::ScriptTimePoint timePoint) override; + + void OnSpawnAssetChanged(); + + private: + AzFramework::EntitySpawnTicket m_spawnTicket; + + AZStd::vector m_spawnedEntityList; + AZStd::vector m_spawnBatchSizes; + AZStd::recursive_mutex m_idBatchMutex; + }; +} diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Spawning/Spawning.cpp b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Spawning/Spawning.cpp new file mode 100644 index 0000000000..6591950b77 --- /dev/null +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Spawning/Spawning.cpp @@ -0,0 +1,50 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#include +#include + +namespace ScriptCanvas +{ + namespace Library + { + void Spawning::Reflect(AZ::ReflectContext* reflection) + { + if (AZ::SerializeContext* serializeContext = azrtti_cast(reflection)) + { + serializeContext->Class() + ->Version(1) + ; + + AZ::EditContext* editContext = serializeContext->GetEditContext(); + if (editContext) + { + editContext->Class("Spawning", "") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::Icon, "Icons/ScriptCanvas/Libraries/Entity.png"); + } + } + } + + void Spawning::InitNodeRegistry(NodeRegistry& nodeRegistry) + { + AddNodeToRegistry(nodeRegistry); + } + + AZStd::vector Spawning::GetComponentDescriptors() + { + return AZStd::vector({ + ScriptCanvas::Nodes::SpawnNodeableNode::CreateDescriptor(), + }); + } + } +} diff --git a/Gems/ScriptCanvasDiagnosticLibrary/Code/Source/precompiled.cpp b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Spawning/Spawning.h similarity index 76% rename from Gems/ScriptCanvasDiagnosticLibrary/Code/Source/precompiled.cpp rename to Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Spawning/Spawning.h index 6fdd7bdc45..fa92ee97a9 100644 --- a/Gems/ScriptCanvasDiagnosticLibrary/Code/Source/precompiled.cpp +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Spawning/Spawning.h @@ -10,4 +10,8 @@ * */ -#include "precompiled.h" +#pragma once + +// This header is only meant to include the nodes and should not contain +// shared code +#include diff --git a/Gems/ScriptCanvas/Code/scriptcanvasgem_common_files.cmake b/Gems/ScriptCanvas/Code/scriptcanvasgem_common_files.cmake index 84391021df..f0be1accc9 100644 --- a/Gems/ScriptCanvas/Code/scriptcanvasgem_common_files.cmake +++ b/Gems/ScriptCanvas/Code/scriptcanvasgem_common_files.cmake @@ -454,6 +454,11 @@ set(FILES Include/ScriptCanvas/Libraries/Time/TimerNodeable.h Include/ScriptCanvas/Libraries/Time/TimerNodeable.cpp Include/ScriptCanvas/Libraries/Time/TimerNodeable.ScriptCanvasNodeable.xml + Include/ScriptCanvas/Libraries/Spawning/Spawning.cpp + Include/ScriptCanvas/Libraries/Spawning/Spawning.h + Include/ScriptCanvas/Libraries/Spawning/SpawnNodeable.cpp + Include/ScriptCanvas/Libraries/Spawning/SpawnNodeable.h + Include/ScriptCanvas/Libraries/Spawning/SpawnNodeable.ScriptCanvasNodeable.xml Include/ScriptCanvas/Libraries/String/Contains.cpp Include/ScriptCanvas/Libraries/String/Contains.h Include/ScriptCanvas/Libraries/String/Contains.ScriptCanvasGrammar.xml diff --git a/Gems/ScriptCanvasDeveloper/Code/CMakeLists.txt b/Gems/ScriptCanvasDeveloper/Code/CMakeLists.txt index d9ce9004d3..8f2f6d3fc0 100644 --- a/Gems/ScriptCanvasDeveloper/Code/CMakeLists.txt +++ b/Gems/ScriptCanvasDeveloper/Code/CMakeLists.txt @@ -54,6 +54,11 @@ ly_add_target( Gem::ScriptCanvas ) +# By default, the above module is the Client/Server module +ly_create_alias(NAME ScriptCanvasDeveloper.Clients NAMESPACE Gem TARGETS Gem::ScriptCanvasDeveloper) +ly_create_alias(NAME ScriptCanvasDeveloper.Servers NAMESPACE Gem TARGETS Gem::ScriptCanvasDeveloper) + + if(PAL_TRAIT_BUILD_HOST_TOOLS) ly_add_target( NAME ScriptCanvasDeveloper.Editor GEM_MODULE @@ -81,5 +86,10 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS) Gem::GraphCanvasWidgets RUNTIME_DEPENDENCIES Gem::ScriptCanvas.Editor + Gem::GraphCanvasWidgets ) + # By Default the above module is the dev tools module + ly_create_alias(NAME ScriptCanvasDeveloper.Builders NAMESPACE Gem TARGETS Gem::ScriptCanvasDeveloper.Editor) + ly_create_alias(NAME ScriptCanvasDeveloper.Tools NAMESPACE Gem TARGETS Gem::ScriptCanvasDeveloper.Editor) + endif() diff --git a/Gems/ScriptCanvasDiagnosticLibrary/Code/Tests/ScriptCanvasDiagnosticLibraryTest.cpp b/Gems/ScriptCanvasDiagnosticLibrary/Code/Tests/ScriptCanvasDiagnosticLibraryTest.cpp deleted file mode 100644 index 4052b9dee4..0000000000 --- a/Gems/ScriptCanvasDiagnosticLibrary/Code/Tests/ScriptCanvasDiagnosticLibraryTest.cpp +++ /dev/null @@ -1,44 +0,0 @@ -/* -* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -* its licensors. -* -* For complete copyright and license terms please see the LICENSE at the root of this -* distribution (the "License"). All use of this software is governed by the License, -* or, if provided, by the license below or the license accompanying this file. Do not -* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* -*/ -#include "precompiled.h" - -#include - -class ScriptCanvasDiagnosticLibraryTest - : public ::testing::Test -{ -protected: - static void SetUpTestCase() - { - } - - static void TearDownTestCase() - { - } - - void SetUp() override - { - } - - void TearDown() override - { - } - -}; - -TEST_F(ScriptCanvasDiagnosticLibraryTest, Sanity_Pass) -{ - EXPECT_TRUE(true); -} - - -AZ_UNIT_TEST_HOOK(); diff --git a/Gems/ScriptCanvasPhysics/Code/CMakeLists.txt b/Gems/ScriptCanvasPhysics/Code/CMakeLists.txt index 23ee6937c7..107db38f5e 100644 --- a/Gems/ScriptCanvasPhysics/Code/CMakeLists.txt +++ b/Gems/ScriptCanvasPhysics/Code/CMakeLists.txt @@ -18,10 +18,9 @@ ly_add_target( PRIVATE Source BUILD_DEPENDENCIES - PUBLIC - Gem::ScriptCanvas PRIVATE Legacy::CryCommon + Gem::ScriptCanvas ) ly_add_target( @@ -36,8 +35,16 @@ ly_add_target( PRIVATE Legacy::CryCommon Gem::ScriptCanvasPhysics.Static + Gem::ScriptCanvas ) +# By default, the above module is used by all application types, however, the module depends at runtime to ScriptCanvas +# and the dependency needs to be different per application type +ly_create_alias(NAME ScriptCanvasPhysics.Clients NAMESPACE Gem TARGETS Gem::ScriptCanvasPhysics Gem::ScriptCanvas) +ly_create_alias(NAME ScriptCanvasPhysics.Servers NAMESPACE Gem TARGETS Gem::ScriptCanvasPhysics Gem::ScriptCanvas) +ly_create_alias(NAME ScriptCanvasPhysics.Tools NAMESPACE Gem TARGETS Gem::ScriptCanvasPhysics Gem::ScriptCanvas.Editor) +ly_create_alias(NAME ScriptCanvasPhysics.Builders NAMESPACE Gem TARGETS Gem::ScriptCanvasPhysics Gem::ScriptCanvas.Editor) + ################################################################################ # Tests ################################################################################ @@ -57,6 +64,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) AZ::AzTest Legacy::CryCommon Gem::ScriptCanvasPhysics.Static + Gem::ScriptCanvas ) ly_add_googletest( NAME Gem::ScriptCanvasPhysics.Tests diff --git a/Gems/ScriptCanvasPhysics/Code/Tests/ScriptCanvasPhysicsTest.cpp b/Gems/ScriptCanvasPhysics/Code/Tests/ScriptCanvasPhysicsTest.cpp index 4b04434e13..07ccdc5011 100644 --- a/Gems/ScriptCanvasPhysics/Code/Tests/ScriptCanvasPhysicsTest.cpp +++ b/Gems/ScriptCanvasPhysics/Code/Tests/ScriptCanvasPhysicsTest.cpp @@ -207,8 +207,8 @@ namespace ScriptCanvasPhysicsTests { public: MOCK_CONST_METHOD0(GetSurfaceType, AZ::Crc32()); - MOCK_METHOD1(SetSurfaceType, void(AZ::Crc32)); MOCK_CONST_METHOD0(GetSurfaceTypeName, const AZStd::string&()); + MOCK_METHOD1(SetSurfaceTypeName, void(const AZStd::string&)); MOCK_CONST_METHOD0(GetDynamicFriction, float()); MOCK_METHOD1(SetDynamicFriction, void(float)); MOCK_CONST_METHOD0(GetStaticFriction, float()); @@ -223,6 +223,8 @@ namespace ScriptCanvasPhysicsTests MOCK_METHOD0(GetNativePointer, void*()); MOCK_CONST_METHOD0(GetDensity, float()); MOCK_METHOD1(SetDensity, void(float)); + MOCK_CONST_METHOD0(GetDebugColor, AZ::Color()); + MOCK_METHOD1(SetDebugColor, void(const AZ::Color&)); }; class ScriptCanvasPhysicsTestEnvironment diff --git a/Gems/ScriptCanvasTesting/Code/CMakeLists.txt b/Gems/ScriptCanvasTesting/Code/CMakeLists.txt index 639ef114fc..76a549d6b9 100644 --- a/Gems/ScriptCanvasTesting/Code/CMakeLists.txt +++ b/Gems/ScriptCanvasTesting/Code/CMakeLists.txt @@ -34,7 +34,7 @@ ly_add_target( Gem::ScriptCanvas Gem::ScriptCanvasEditor Gem::GraphCanvasWidgets - Gem::ScriptEvents + Gem::ScriptEvents.Editor PRIVATE AZ::AzCore AZ::AzFramework @@ -45,6 +45,11 @@ ly_add_target( *.ScriptCanvasGrammar.xml,ScriptCanvasGrammar_Source.jinja,$path/$fileprefix.generated.cpp *.ScriptCanvasNodeable.xml,ScriptCanvasNodeable_Header.jinja,$path/$fileprefix.generated.h *.ScriptCanvasNodeable.xml,ScriptCanvasNodeable_Source.jinja,$path/$fileprefix.generated.cpp + RUNTIME_DEPENDENCIES + Gem::ScriptCanvas.Editor + Gem::ScriptCanvasEditor + Gem::GraphCanvasWidgets + Gem::ScriptEvents ) ly_add_target( @@ -73,6 +78,9 @@ ly_add_target( Gem::ScriptCanvas.Editor ) +# By default, the above module is used only in tools: +ly_create_alias(NAME ScriptCanvasTesting.Tools NAMESPACE Gem TARGETS Gem::ScriptCanvasTesting.Editor) + ################################################################################ # Tests ################################################################################ @@ -101,6 +109,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) AZ::AzFramework AZ::AzToolsFramework Gem::ScriptCanvasTesting.Editor.Static + Gem::ScriptCanvas.Editor RUNTIME_DEPENDENCIES Gem::GraphCanvas.Editor Gem::ScriptCanvas.Editor diff --git a/Gems/ScriptEvents/Code/CMakeLists.txt b/Gems/ScriptEvents/Code/CMakeLists.txt index 12f8cfd7b6..6f75e35d71 100644 --- a/Gems/ScriptEvents/Code/CMakeLists.txt +++ b/Gems/ScriptEvents/Code/CMakeLists.txt @@ -40,6 +40,11 @@ ly_add_target( Gem::ScriptEvents.Static ) +# the above module is for use in clients and servers +ly_create_alias(NAME ScriptEvents.Clients NAMESPACE Gem TARGETS Gem::ScriptEvents) +ly_create_alias(NAME ScriptEvents.Servers NAMESPACE Gem TARGETS Gem::ScriptEvents) + + if(PAL_TRAIT_BUILD_HOST_TOOLS) ly_add_target( NAME ScriptEvents.Editor GEM_MODULE @@ -61,6 +66,10 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS) AZ::AssetBuilderSDK Gem::ScriptEvents.Static ) + + # the above module is for use in dev tools. + ly_create_alias(NAME ScriptEvents.Tools NAMESPACE Gem TARGETS Gem::ScriptEvents.Editor) + ly_create_alias(NAME ScriptEvents.Builders NAMESPACE Gem TARGETS Gem::ScriptEvents.Editor) endif() ################################################################################ diff --git a/Gems/ScriptedEntityTweener/Code/CMakeLists.txt b/Gems/ScriptedEntityTweener/Code/CMakeLists.txt index c5062c84b7..2488057f8c 100644 --- a/Gems/ScriptedEntityTweener/Code/CMakeLists.txt +++ b/Gems/ScriptedEntityTweener/Code/CMakeLists.txt @@ -40,3 +40,9 @@ ly_add_target( AZ::AzCore Legacy::CryCommon ) + +# the above module is for use in all application types: +ly_create_alias(NAME ScriptedEntityTweener.Tools NAMESPACE Gem TARGETS Gem::ScriptedEntityTweener) +ly_create_alias(NAME ScriptedEntityTweener.Clients NAMESPACE Gem TARGETS Gem::ScriptedEntityTweener) +ly_create_alias(NAME ScriptedEntityTweener.Builders NAMESPACE Gem TARGETS Gem::ScriptedEntityTweener) +ly_create_alias(NAME ScriptedEntityTweener.Servers NAMESPACE Gem TARGETS Gem::ScriptedEntityTweener) \ No newline at end of file diff --git a/Gems/SliceFavorites/Code/CMakeLists.txt b/Gems/SliceFavorites/Code/CMakeLists.txt index 4ad89ad6c3..35349c777c 100644 --- a/Gems/SliceFavorites/Code/CMakeLists.txt +++ b/Gems/SliceFavorites/Code/CMakeLists.txt @@ -51,3 +51,6 @@ ly_add_target( 3rdParty::Qt::Core Gem::SliceFavorites.Editor.Static ) + +# the above module is for use in Tools only (no need to load it in builders) +ly_create_alias(NAME SliceFavorites.Tools NAMESPACE Gem TARGETS Gem::SliceFavorites.Editor) \ No newline at end of file diff --git a/Gems/StartingPointCamera/Code/CMakeLists.txt b/Gems/StartingPointCamera/Code/CMakeLists.txt index d6dd1a7038..7bc57476a5 100644 --- a/Gems/StartingPointCamera/Code/CMakeLists.txt +++ b/Gems/StartingPointCamera/Code/CMakeLists.txt @@ -48,3 +48,9 @@ ly_add_target( RUNTIME_DEPENDENCIES Gem::CameraFramework ) + +# the above module is for use in all kinds of applications +ly_create_alias(NAME StartingPointCamera.Servers NAMESPACE Gem TARGETS Gem::StartingPointCamera) +ly_create_alias(NAME StartingPointCamera.Clients NAMESPACE Gem TARGETS Gem::StartingPointCamera) +ly_create_alias(NAME StartingPointCamera.Builders NAMESPACE Gem TARGETS Gem::StartingPointCamera) +ly_create_alias(NAME StartingPointCamera.Tools NAMESPACE Gem TARGETS Gem::StartingPointCamera) diff --git a/Gems/StartingPointInput/Code/CMakeLists.txt b/Gems/StartingPointInput/Code/CMakeLists.txt index 1372a79a78..c6e7fdcb52 100644 --- a/Gems/StartingPointInput/Code/CMakeLists.txt +++ b/Gems/StartingPointInput/Code/CMakeLists.txt @@ -56,6 +56,10 @@ ly_add_source_properties( VALUES ${LY_PAL_TOOLS_DEFINES} ) +# the above module is for use in clients and servers +ly_create_alias(NAME StartingPointInput.Servers NAMESPACE Gem TARGETS Gem::StartingPointInput) +ly_create_alias(NAME StartingPointInput.Clients NAMESPACE Gem TARGETS Gem::StartingPointInput) + if(PAL_TRAIT_BUILD_HOST_TOOLS) ly_add_target( NAME StartingPointInput.Editor GEM_MODULE @@ -74,6 +78,11 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS) AZ::AzFramework Gem::StartingPointInput.Static ) + + # by default, activate the ab ove module in builders and tools: + ly_create_alias(NAME StartingPointInput.Builders NAMESPACE Gem TARGETS Gem::StartingPointInput.Editor) + ly_create_alias(NAME StartingPointInput.Tools NAMESPACE Gem TARGETS Gem::StartingPointInput.Editor) + endif() ################################################################################ diff --git a/Gems/StartingPointMovement/Code/CMakeLists.txt b/Gems/StartingPointMovement/Code/CMakeLists.txt index d6434ccf78..417dfe01ee 100644 --- a/Gems/StartingPointMovement/Code/CMakeLists.txt +++ b/Gems/StartingPointMovement/Code/CMakeLists.txt @@ -40,3 +40,9 @@ ly_add_target( AZ::AzCore AZ::AzFramework ) + +# the above module is for use in all application types (there is no tool specialization) +ly_create_alias(NAME StartingPointMovement.Servers NAMESPACE Gem TARGETS Gem::StartingPointMovement) +ly_create_alias(NAME StartingPointMovement.Clients NAMESPACE Gem TARGETS Gem::StartingPointMovement) +ly_create_alias(NAME StartingPointMovement.Builders NAMESPACE Gem TARGETS Gem::StartingPointMovement) +ly_create_alias(NAME StartingPointMovement.Tools NAMESPACE Gem TARGETS Gem::StartingPointMovement) \ No newline at end of file diff --git a/Gems/SurfaceData/Code/CMakeLists.txt b/Gems/SurfaceData/Code/CMakeLists.txt index de1aa51938..642849675c 100644 --- a/Gems/SurfaceData/Code/CMakeLists.txt +++ b/Gems/SurfaceData/Code/CMakeLists.txt @@ -22,10 +22,10 @@ ly_add_target( BUILD_DEPENDENCIES PRIVATE Legacy::CryCommon + Gem::LmbrCentral PUBLIC Gem::Atom_RPI.Public Gem::Atom_Feature_Common.Static - Gem::LmbrCentral ) ly_add_target( @@ -42,10 +42,15 @@ ly_add_target( PRIVATE Legacy::CryCommon Gem::SurfaceData.Static + Gem::LmbrCentral RUNTIME_DEPENDENCIES Gem::LmbrCentral ) +# the above module is for use in all client/server types +ly_create_alias(NAME SurfaceData.Servers NAMESPACE Gem TARGETS Gem::SurfaceData) +ly_create_alias(NAME SurfaceData.Clients NAMESPACE Gem TARGETS Gem::SurfaceData) + if (PAL_TRAIT_BUILD_HOST_TOOLS) ly_add_target( @@ -67,9 +72,13 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS) Legacy::CryCommon AZ::AzToolsFramework Gem::SurfaceData.Static + Gem::LmbrCentral.Editor RUNTIME_DEPENDENCIES Gem::LmbrCentral.Editor ) + # the above module is for use in dev tool situations + ly_create_alias(NAME SurfaceData.Builders NAMESPACE Gem TARGETS Gem::SurfaceData.Editor) + ly_create_alias(NAME SurfaceData.Tools NAMESPACE Gem TARGETS Gem::SurfaceData.Editor) endif() @@ -93,6 +102,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) AZ::AzTest Legacy::CryCommon Gem::SurfaceData.Static + Gem::LmbrCentral ) ly_add_googletest( NAME Gem::SurfaceData.Tests diff --git a/Gems/TestAssetBuilder/Code/CMakeLists.txt b/Gems/TestAssetBuilder/Code/CMakeLists.txt index dbd2907033..ebd34140aa 100644 --- a/Gems/TestAssetBuilder/Code/CMakeLists.txt +++ b/Gems/TestAssetBuilder/Code/CMakeLists.txt @@ -40,3 +40,6 @@ ly_add_target( PRIVATE Gem::TestAssetBuilder.Static ) + +# the above module is for use in builders only +ly_create_alias(NAME TestAssetBuilder.Builders NAMESPACE Gem TARGETS Gem::TestAssetBuilder.Editor) diff --git a/Gems/TextureAtlas/Code/CMakeLists.txt b/Gems/TextureAtlas/Code/CMakeLists.txt index 5e29a7ea65..d67601235d 100644 --- a/Gems/TextureAtlas/Code/CMakeLists.txt +++ b/Gems/TextureAtlas/Code/CMakeLists.txt @@ -62,5 +62,10 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS) Gem::TextureAtlas.Static Gem::ImageProcessingAtom.Headers ) + ly_create_alias(NAME TextureAtlas.Builders NAMESPACE Gem TARGETS Gem::TextureAtlas.Editor) + ly_create_alias(NAME TextureAtlas.Tools NAMESPACE Gem TARGETS Gem::TextureAtlas.Editor) endif() +ly_create_alias(NAME TextureAtlas.Servers NAMESPACE Gem TARGETS Gem::TextureAtlas) +ly_create_alias(NAME TextureAtlas.Clients NAMESPACE Gem TARGETS Gem::TextureAtlas) + diff --git a/Gems/TickBusOrderViewer/Code/CMakeLists.txt b/Gems/TickBusOrderViewer/Code/CMakeLists.txt index 3f56a0d554..551c62c64c 100644 --- a/Gems/TickBusOrderViewer/Code/CMakeLists.txt +++ b/Gems/TickBusOrderViewer/Code/CMakeLists.txt @@ -38,3 +38,9 @@ ly_add_target( PRIVATE Gem::TickBusOrderViewer.Static ) + + +# the above module is for use in all application types except builders +ly_create_alias(NAME TickBusOrderViewer.Servers NAMESPACE Gem TARGETS Gem::TickBusOrderViewer) +ly_create_alias(NAME TickBusOrderViewer.Clients NAMESPACE Gem TARGETS Gem::TickBusOrderViewer) +ly_create_alias(NAME TickBusOrderViewer.Tools NAMESPACE Gem TARGETS Gem::TickBusOrderViewer) diff --git a/Gems/Twitch/Code/CMakeLists.txt b/Gems/Twitch/Code/CMakeLists.txt index 14d7a41532..b5c1751003 100644 --- a/Gems/Twitch/Code/CMakeLists.txt +++ b/Gems/Twitch/Code/CMakeLists.txt @@ -29,6 +29,8 @@ ly_add_target( AZ::AzCore Gem::HttpRequestor 3rdParty::AWSNativeSDK::Core + RUNTIME_DEPENDENCIES + Gem::HttpRequestor ) ly_add_target( @@ -47,3 +49,9 @@ ly_add_target( RUNTIME_DEPENDENCIES Gem::HttpRequestor ) + +# the above module is for use in all application types except builders +ly_create_alias(NAME Twitch.Servers NAMESPACE Gem TARGETS Gem::Twitch) +ly_create_alias(NAME Twitch.Clients NAMESPACE Gem TARGETS Gem::Twitch) +ly_create_alias(NAME Twitch.Tools NAMESPACE Gem TARGETS Gem::Twitch) + diff --git a/Templates/DefaultProject/Template/Code/Platform/Android/android_server_dependencies.cmake b/Gems/UiBasics/CMakeLists.txt similarity index 89% rename from Templates/DefaultProject/Template/Code/Platform/Android/android_server_dependencies.cmake rename to Gems/UiBasics/CMakeLists.txt index fe28e294e9..4d5680a30d 100644 --- a/Templates/DefaultProject/Template/Code/Platform/Android/android_server_dependencies.cmake +++ b/Gems/UiBasics/CMakeLists.txt @@ -1,4 +1,4 @@ -# {BEGIN_LICENSE} +# # All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or # its licensors. # @@ -7,7 +7,4 @@ # or, if provided, by the license below or the license accompanying this file. Do not # remove or modify any license notices. This file is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# {END_LICENSE} - -set(GEM_DEPENDENCIES -) +# diff --git a/Gems/Vegetation/Code/CMakeLists.txt b/Gems/Vegetation/Code/CMakeLists.txt index c4a003bb4a..7283f53c97 100644 --- a/Gems/Vegetation/Code/CMakeLists.txt +++ b/Gems/Vegetation/Code/CMakeLists.txt @@ -24,12 +24,14 @@ ly_add_target( PUBLIC Include BUILD_DEPENDENCIES + PRIVATE + Gem::LmbrCentral + Gem::SurfaceData PUBLIC Legacy::CryCommon - Gem::LmbrCentral - Gem::GradientSignal - Gem::SurfaceData.Static Gem::AtomLyIntegration_CommonFeatures.Static + RUNTIME_DEPENDENCIES + Gem::GradientSignal ) ly_add_target( @@ -51,6 +53,10 @@ ly_add_target( Gem::SurfaceData ) +# the above module is for use in clients and server type applications +ly_create_alias(NAME Vegetation.Servers NAMESPACE Gem TARGETS Gem::Vegetation) +ly_create_alias(NAME Vegetation.Clients NAMESPACE Gem TARGETS Gem::Vegetation) + if(PAL_TRAIT_BUILD_HOST_TOOLS) ly_add_target( NAME Vegetation.Editor GEM_MODULE @@ -75,6 +81,10 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS) Gem::GradientSignal.Editor Gem::SurfaceData.Editor ) + # the above module is for use in dev tools + ly_create_alias(NAME Vegetation.Builders NAMESPACE Gem TARGETS Gem::Vegetation.Editor) + ly_create_alias(NAME Vegetation.Tools NAMESPACE Gem TARGETS Gem::Vegetation.Editor) + endif() ################################################################################ diff --git a/Templates/DefaultProject/Template/Code/Platform/Linux/linux_server_dependencies.cmake b/Gems/Vegetation_Gem_Assets/CMakeLists.txt similarity index 89% rename from Templates/DefaultProject/Template/Code/Platform/Linux/linux_server_dependencies.cmake rename to Gems/Vegetation_Gem_Assets/CMakeLists.txt index fe28e294e9..4d5680a30d 100644 --- a/Templates/DefaultProject/Template/Code/Platform/Linux/linux_server_dependencies.cmake +++ b/Gems/Vegetation_Gem_Assets/CMakeLists.txt @@ -1,4 +1,4 @@ -# {BEGIN_LICENSE} +# # All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or # its licensors. # @@ -7,7 +7,4 @@ # or, if provided, by the license below or the license accompanying this file. Do not # remove or modify any license notices. This file is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# {END_LICENSE} - -set(GEM_DEPENDENCIES -) +# diff --git a/Gems/VideoPlaybackFramework/Code/CMakeLists.txt b/Gems/VideoPlaybackFramework/Code/CMakeLists.txt index 297f4cfaac..b29fe53216 100644 --- a/Gems/VideoPlaybackFramework/Code/CMakeLists.txt +++ b/Gems/VideoPlaybackFramework/Code/CMakeLists.txt @@ -42,6 +42,11 @@ ly_add_target( Gem::VideoPlaybackFramework.Static ) +# the video playback framework makes sense in everything but servers: +ly_create_alias(NAME VideoPlaybackFramework.Clients NAMESPACE Gem TARGETS Gem::VideoPlaybackFramework) +ly_create_alias(NAME VideoPlaybackFramework.Tools NAMESPACE Gem TARGETS Gem::VideoPlaybackFramework) +ly_create_alias(NAME VideoPlaybackFramework.Builders NAMESPACE Gem TARGETS Gem::VideoPlaybackFramework) + ################################################################################ # Tests ################################################################################ diff --git a/Gems/VirtualGamepad/Code/CMakeLists.txt b/Gems/VirtualGamepad/Code/CMakeLists.txt index 99a33db70b..4311796b57 100644 --- a/Gems/VirtualGamepad/Code/CMakeLists.txt +++ b/Gems/VirtualGamepad/Code/CMakeLists.txt @@ -40,3 +40,8 @@ ly_add_target( PRIVATE Gem::VirtualGamepad.Static ) + +# the virtual gamepad is needed everywhere except servers: +ly_create_alias(NAME VirtualGamepad.Clients NAMESPACE Gem TARGETS Gem::VirtualGamepad) +ly_create_alias(NAME VirtualGamepad.Tools NAMESPACE Gem TARGETS Gem::VirtualGamepad) +ly_create_alias(NAME VirtualGamepad.Builders NAMESPACE Gem TARGETS Gem::VirtualGamepad) diff --git a/Gems/WhiteBox/Code/CMakeLists.txt b/Gems/WhiteBox/Code/CMakeLists.txt index a15a4e150c..5985ff26a3 100644 --- a/Gems/WhiteBox/Code/CMakeLists.txt +++ b/Gems/WhiteBox/Code/CMakeLists.txt @@ -86,6 +86,10 @@ ly_add_target( Gem::WhiteBox.Static ) +# use the above WhiteBox module in runtimes: +ly_create_alias(NAME WhiteBox.Clients NAMESPACE Gem TARGETS Gem::WhiteBox) +ly_create_alias(NAME WhiteBox.Servers NAMESPACE Gem TARGETS Gem::WhiteBox) + if(PAL_TRAIT_BUILD_HOST_TOOLS) ly_add_target( NAME WhiteBox.Editor.Static STATIC @@ -129,6 +133,12 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS) PRIVATE Gem::WhiteBox.Editor.Static ) + + # use the above WhiteBox.Editor module in dev tools: + ly_create_alias(NAME WhiteBox.Tools NAMESPACE Gem TARGETS Gem::WhiteBox.Editor) + ly_create_alias(NAME WhiteBox.Builders NAMESPACE Gem TARGETS Gem::WhiteBox.Editor) + + endif() ################################################################################ diff --git a/README.md b/README.md index e0442adc16..0c59837a62 100644 --- a/README.md +++ b/README.md @@ -118,11 +118,6 @@ If you have the Git credential manager core or other credential helpers installe ``` python\get_python.bat ``` - -1. While still within the repo folder, register the engine with this command: - ``` - scripts\o3de.bat register --this-engine - ``` 1. Configure the source into a solution using this command line, replacing and <3rdParty cache path> to a path you've created: ``` @@ -146,7 +141,11 @@ If you have the Git credential manager core or other credential helpers installe 1. This will compile after some time and binaries will be available in the build path you've specified -### Setting up new projects +### Setting up new projects +1. While still within the repo folder, register the engine with this command: + ``` + scripts\o3de.bat register --this-engine + ``` 1. Setup new projects using the `o3de create-project` command. In the 0.5 branch, the project directory must be a subdirectory in the repo folder. ``` \scripts\o3de.bat create-project --project-path @@ -160,10 +159,10 @@ If you have the Git credential manager core or other credential helpers installe cmake -B -S -G "Visual Studio 16" -DLY_3RDPARTY_PATH=<3rdParty cache path> // For the 0.5 branch, you must build a new Editor for each project: - cmake --build --target Editor --config profile -- /m + cmake --build --target .GameLauncher Editor --config profile -- /m // For all other branches, just build the project: - cmake --build --target --config profile -- /m + cmake --build --target .GameLauncher --config profile -- /m ``` For a tutorial on project configuration, see [Creating Projects Using the Command Line](https://docs.o3de.org/docs/welcome-guide/get-started/project-config/creating-projects-using-cli) in the documentation. diff --git a/Registry/prefab.setreg b/Registry/prefab.setreg new file mode 100644 index 0000000000..903dc0c4f8 --- /dev/null +++ b/Registry/prefab.setreg @@ -0,0 +1,14 @@ +{ + "O3DE": + { + "AzFramework": + { + "Spawnables": + { + // Any requests with a priorty value equal or smaller than this will be considered a high priority request. + // The range for this value is between 0 and 255. + "HighPriorityThreshold" : 64 + } + } + } +} \ No newline at end of file diff --git a/Templates/DefaultGem/Template/CMakeLists.txt b/Templates/DefaultGem/Template/CMakeLists.txt index fb63008782..1a24bd488f 100644 --- a/Templates/DefaultGem/Template/CMakeLists.txt +++ b/Templates/DefaultGem/Template/CMakeLists.txt @@ -11,7 +11,7 @@ set(o3de_gem_path ${CMAKE_CURRENT_LIST_DIR}) set(o3de_gem_json ${o3de_gem_path}/gem.json) -o3de_gem_name(${o3de_gem_json} o3de_gem_name) +o3de_read_json_key(o3de_gem_name ${o3de_gem_json} "gem_name") o3de_restricted_path(${o3de_gem_json} o3de_gem_restricted_path) # Currently we are in the DefaultProjectSource folder: ${CMAKE_CURRENT_LIST_DIR} diff --git a/Templates/DefaultGem/Template/Code/CMakeLists.txt b/Templates/DefaultGem/Template/Code/CMakeLists.txt index 3511f8ff83..b0e52dd79f 100644 --- a/Templates/DefaultGem/Template/Code/CMakeLists.txt +++ b/Templates/DefaultGem/Template/Code/CMakeLists.txt @@ -59,6 +59,12 @@ ly_add_target( Gem::${Name}.Static ) +# By default, we will specify that the above target ${Name} would be used by +# Client and Server type targets when this gem is enabled. If you don't want it +# active in Clients or Servers by default, delete one of both of the following lines: +ly_create_alias(NAME ${Name}.Clients NAMESPACE Gem TARGETS Gem::${Name}) +ly_create_alias(NAME ${Name}.Servers NAMESPACE Gem TARGETS Gem::${Name}) + # If we are on a host platform, we want to add the host tools targets like the ${Name}.Editor target which # will also depend on ${Name}.Static if(PAL_TRAIT_BUILD_HOST_TOOLS) @@ -94,6 +100,14 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS) PUBLIC Gem::${Name}.Editor.Static ) + + # By default, we will specify that the above target ${Name} would be used by + # Tool and Builder type targets when this gem is enabled. If you don't want it + # active in Tools or Builders by default, delete one of both of the following lines: + ly_create_alias(NAME ${Name}.Tools NAMESPACE Gem TARGETS Gem::${Name}.Editor) + ly_create_alias(NAME ${Name}.Builders NAMESPACE Gem TARGETS Gem::${Name}.Editor) + + endif() ################################################################################ diff --git a/Templates/DefaultGem/template.json b/Templates/DefaultGem/template.json index 22d4eb27e6..b653718095 100644 --- a/Templates/DefaultGem/template.json +++ b/Templates/DefaultGem/template.json @@ -1,6 +1,6 @@ { "template_name": "DefaultGem", - "restricted": "o3de", + "restricted_name": "o3de", "restricted_platform_relative_path": "Templates", "origin": "The primary repo for DefaultGem goes here: i.e. http://www.mydomain.com", "license": "What license DefaultGem uses goes here: i.e. https://opensource.org/licenses/MIT", diff --git a/Templates/DefaultProject/Template/CMakeLists.txt b/Templates/DefaultProject/Template/CMakeLists.txt index ad0a4c869d..b5b8692059 100644 --- a/Templates/DefaultProject/Template/CMakeLists.txt +++ b/Templates/DefaultProject/Template/CMakeLists.txt @@ -23,64 +23,26 @@ function(add_vs_debugger_arguments) endforeach() endfunction() -set(o3de_project_path ${CMAKE_CURRENT_LIST_DIR}) -set(o3de_project_json ${o3de_project_path}/project.json) - if(NOT PROJECT_NAME) cmake_minimum_required(VERSION 3.19) project(${Name} LANGUAGES C CXX VERSION 1.0.0.0 ) + include(EngineFinder.cmake OPTIONAL) + find_package(o3de REQUIRED) + o3de_initialize() + add_vs_debugger_arguments() +else() + # Add the project_name to global LY_PROJECTS_TARGET_NAME property + file(READ "${CMAKE_CURRENT_LIST_DIR}/project.json" project_json) - # set this project as the only project - set(LY_PROJECTS ${CMAKE_CURRENT_LIST_DIR}) - - # o3de manifest - include(o3de_manifest.cmake) - - ################################################################################ - # Set the engine_path and resolve this engines restricted path if it has one - ################################################################################ - o3de_engine_path(${o3de_project_json} o3de_engine_path) - o3de_project_name(${o3de_project_json} o3de_project_name) - o3de_restricted_path(${o3de_project_json} o3de_project_restricted_path) - message(STATUS "O3DE Project Name: ${o3de_project_name}") - message(STATUS "O3DE Project Path: ${o3de_project_path}") - if(o3de_project_restricted_path) - message(STATUS "O3DE Project Restricted Path: ${o3de_project_restricted_path}") + string(JSON project_target_name ERROR_VARIABLE json_error GET ${project_json} "project_name") + if(json_error) + message(FATAL_ERROR "Unable to read key 'project_name' from 'project.json'") endif() - # add the engines cmake folder to the CMAKE_MODULE_PATH - list(APPEND CMAKE_MODULE_PATH "${o3de_engine_path}/cmake") - - # add subdirectory on the engine path for this project - add_subdirectory(${o3de_engine_path} o3de) - - # add this --project-path arguments to visual studio debugger - add_vs_debugger_arguments() - -else() - ###################################################### - # the engine is calling add sub_directory() on us - ###################################################### - o3de_project_name(${o3de_project_json} o3de_project_name) - o3de_restricted_path(${o3de_project_json} o3de_project_restricted_path) - - # Currently we are in the folder: ${CMAKE_CURRENT_LIST_DIR} - # Get the platform specific folder ${pal_dir} for the folder: ${CMAKE_CURRENT_LIST_DIR}/Platform/${PAL_PLATFORM_NAME} - # Note: ly_get_list_relative_pal_filename will take care of the details for us, as this may be a restricted platform - # in which case it will see if that platform is present here or in the restricted folder. - # i.e. It could here: TestDP/Platform/ or - # //TestDP - ly_get_list_relative_pal_filename(pal_dir ${CMAKE_CURRENT_LIST_DIR}/Platform/${PAL_PLATFORM_NAME} ${o3de_project_restricted_path} ${o3de_project_path} ${o3de_project_name}) - - # Now that we have the platform abstraction layer (PAL) folder for this folder, thats where we will find the - # project cmake for this platform. - include(${pal_dir}/${PAL_PLATFORM_NAME_LOWERCASE}_project.cmake) - - # Add the project_name to global LY_PROJECTS_TARGET_NAME property - set_property(GLOBAL APPEND PROPERTY LY_PROJECTS_TARGET_NAME ${o3de_project_name}) + set_property(GLOBAL APPEND PROPERTY LY_PROJECTS_TARGET_NAME ${project_target_name}) add_subdirectory(Code) endif() diff --git a/Templates/DefaultProject/Template/Code/${NameLower}_files.cmake b/Templates/DefaultProject/Template/Code/${NameLower}_files.cmake index 459e33f547..f77348395b 100644 --- a/Templates/DefaultProject/Template/Code/${NameLower}_files.cmake +++ b/Templates/DefaultProject/Template/Code/${NameLower}_files.cmake @@ -13,7 +13,5 @@ set(FILES Include/${Name}/${Name}Bus.h Source/${Name}SystemComponent.cpp Source/${Name}SystemComponent.h - runtime_dependencies.cmake - tool_dependencies.cmake - server_dependencies.cmake + enabled_gems.cmake ) diff --git a/Templates/DefaultProject/Template/Code/CMakeLists.txt b/Templates/DefaultProject/Template/Code/CMakeLists.txt index 38999c031c..43459b1606 100644 --- a/Templates/DefaultProject/Template/Code/CMakeLists.txt +++ b/Templates/DefaultProject/Template/Code/CMakeLists.txt @@ -33,7 +33,7 @@ endif() # in ${pal_dir}/${NameLower}_${PAL_PLATFORM_NAME_LOWERCASE}_files.cmake ly_add_target( NAME ${Name}.Static STATIC - NAMESPACE Project + NAMESPACE Gem FILES_CMAKE ${NameLower}_files.cmake ${pal_dir}/${NameLower}_${PAL_PLATFORM_NAME_LOWERCASE}_files.cmake @@ -48,7 +48,7 @@ ly_add_target( ly_add_target( NAME ${Name} ${PAL_TRAIT_MONOLITHIC_DRIVEN_MODULE_TYPE} - NAMESPACE Project + NAMESPACE Gem FILES_CMAKE ${NameLower}_shared_files.cmake ${pal_dir}/${NameLower}_shared_${PAL_PLATFORM_NAME_LOWERCASE}_files.cmake @@ -57,48 +57,60 @@ ly_add_target( Include BUILD_DEPENDENCIES PRIVATE - Project::${Name}.Static + Gem::${Name}.Static AZ::AzCore ) +# if enabled, ${Name} is used by all kinds of applications +ly_create_alias(NAME ${Name}.Builders NAMESPACE Gem TARGETS Gem::${Name}) +ly_create_alias(NAME ${Name}.Tools NAMESPACE Gem TARGETS Gem::${Name}) +ly_create_alias(NAME ${Name}.Clients NAMESPACE Gem TARGETS Gem::${Name}) +ly_create_alias(NAME ${Name}.Servers NAMESPACE Gem TARGETS Gem::${Name}) + ################################################################################ # Gem dependencies ################################################################################ -ly_add_project_dependencies( - PROJECT_NAME - ${Name} + +# The GameLauncher uses "Clients" gem variants: +ly_enable_gems( + PROJECT_NAME ${Name} GEM_FILE enabled_gems.cmake TARGETS ${Name}.GameLauncher - DEPENDENCIES_FILES - runtime_dependencies.cmake - ${pal_dir}/${PAL_PLATFORM_NAME_LOWERCASE}_runtime_dependencies.cmake -) + VARIANTS + Clients) if(PAL_TRAIT_BUILD_HOST_TOOLS) - ly_add_project_dependencies( - PROJECT_NAME - ${Name} + + # the builder type applications use the "Builders" variants of the enabled gems. + ly_enable_gems( + PROJECT_NAME ${Name} GEM_FILE enabled_gems.cmake TARGETS AssetBuilder AssetProcessor AssetProcessorBatch + VARIANTS + Builders) + + # the Editor applications use the "Tools" variants of the enabled gems. + ly_enable_gems( + PROJECT_NAME ${Name} GEM_FILE enabled_gems.cmake + TARGETS Editor - DEPENDENCIES_FILES - tool_dependencies.cmake - ${pal_dir}/${PAL_PLATFORM_NAME_LOWERCASE}_tool_dependencies.cmake - ) + VARIANTS + Tools) endif() if(PAL_TRAIT_BUILD_SERVER_SUPPORTED) - ly_add_project_dependencies( - PROJECT_NAME - ${Name} + # this property causes it to actually make a ServerLauncher. + # if you don't want a Server application, you can remove this and the + # following ly_enable_gems lines. + set_property(GLOBAL APPEND PROPERTY LY_LAUNCHER_SERVER_PROJECTS ${Name}) + + # The ServerLauncher uses the "Servers" variants of enabled gems: + ly_enable_gems( + PROJECT_NAME ${Name} GEM_FILE enabled_gems.cmake TARGETS ${Name}.ServerLauncher - DEPENDENCIES_FILES - server_dependencies.cmake - ${pal_dir}/${PAL_PLATFORM_NAME_LOWERCASE}_server_dependencies.cmake - ) - set_property(GLOBAL APPEND PROPERTY LY_LAUNCHER_SERVER_PROJECTS ${Name}) - + VARIANTS + Servers) endif() diff --git a/Templates/DefaultProject/Template/Code/Platform/Android/${NameLower}_android_files.cmake b/Templates/DefaultProject/Template/Code/Platform/Android/${NameLower}_android_files.cmake index b774cd944f..78fd98ba6c 100644 --- a/Templates/DefaultProject/Template/Code/Platform/Android/${NameLower}_android_files.cmake +++ b/Templates/DefaultProject/Template/Code/Platform/Android/${NameLower}_android_files.cmake @@ -11,7 +11,4 @@ set(FILES PAL_android.cmake - android_runtime_dependencies.cmake - android_tool_dependencies.cmake - android_server_dependencies.cmake ) diff --git a/Templates/DefaultProject/Template/Code/Platform/Android/android_runtime_dependencies.cmake b/Templates/DefaultProject/Template/Code/Platform/Android/android_runtime_dependencies.cmake deleted file mode 100644 index a1ebd6e455..0000000000 --- a/Templates/DefaultProject/Template/Code/Platform/Android/android_runtime_dependencies.cmake +++ /dev/null @@ -1,14 +0,0 @@ -# {BEGIN_LICENSE} -# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -# its licensors. -# -# For complete copyright and license terms please see the LICENSE at the root of this -# distribution (the "License"). All use of this software is governed by the License, -# or, if provided, by the license below or the license accompanying this file. Do not -# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# {END_LICENSE} - -set(GEM_DEPENDENCIES - Gem::Atom_RHI_Vulkan.Private -) diff --git a/Templates/DefaultProject/Template/Code/Platform/Android/android_tool_dependencies.cmake b/Templates/DefaultProject/Template/Code/Platform/Android/android_tool_dependencies.cmake deleted file mode 100644 index 14e6f1aa4c..0000000000 --- a/Templates/DefaultProject/Template/Code/Platform/Android/android_tool_dependencies.cmake +++ /dev/null @@ -1,14 +0,0 @@ -# {BEGIN_LICENSE} -# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -# its licensors. -# -# For complete copyright and license terms please see the LICENSE at the root of this -# distribution (the "License"). All use of this software is governed by the License, -# or, if provided, by the license below or the license accompanying this file. Do not -# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# {END_LICENSE} - -set(GEM_DEPENDENCIES -) - diff --git a/Templates/DefaultProject/Template/Code/Platform/Linux/${NameLower}_linux_files.cmake b/Templates/DefaultProject/Template/Code/Platform/Linux/${NameLower}_linux_files.cmake index 58fc59d265..ee0b06efc4 100644 --- a/Templates/DefaultProject/Template/Code/Platform/Linux/${NameLower}_linux_files.cmake +++ b/Templates/DefaultProject/Template/Code/Platform/Linux/${NameLower}_linux_files.cmake @@ -11,7 +11,4 @@ set(FILES PAL_linux.cmake - linux_runtime_dependencies.cmake - linux_tool_dependencies.cmake - linux_server_dependencies.cmake ) diff --git a/Templates/DefaultProject/Template/Code/Platform/Linux/linux_runtime_dependencies.cmake b/Templates/DefaultProject/Template/Code/Platform/Linux/linux_runtime_dependencies.cmake deleted file mode 100644 index a54c22de8c..0000000000 --- a/Templates/DefaultProject/Template/Code/Platform/Linux/linux_runtime_dependencies.cmake +++ /dev/null @@ -1,15 +0,0 @@ -# {BEGIN_LICENSE} -# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -# its licensors. -# -# For complete copyright and license terms please see the LICENSE at the root of this -# distribution (the "License"). All use of this software is governed by the License, -# or, if provided, by the license below or the license accompanying this file. Do not -# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# {END_LICENSE} - -set(GEM_DEPENDENCIES - Gem::Atom_RHI_Vulkan.Private - Gem::Atom_RHI_Vulkan.Builders -) diff --git a/Templates/DefaultProject/Template/Code/Platform/Linux/linux_tool_dependencies.cmake b/Templates/DefaultProject/Template/Code/Platform/Linux/linux_tool_dependencies.cmake deleted file mode 100644 index a1ebd6e455..0000000000 --- a/Templates/DefaultProject/Template/Code/Platform/Linux/linux_tool_dependencies.cmake +++ /dev/null @@ -1,14 +0,0 @@ -# {BEGIN_LICENSE} -# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -# its licensors. -# -# For complete copyright and license terms please see the LICENSE at the root of this -# distribution (the "License"). All use of this software is governed by the License, -# or, if provided, by the license below or the license accompanying this file. Do not -# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# {END_LICENSE} - -set(GEM_DEPENDENCIES - Gem::Atom_RHI_Vulkan.Private -) diff --git a/Templates/DefaultProject/Template/Code/Platform/Mac/${NameLower}_mac_files.cmake b/Templates/DefaultProject/Template/Code/Platform/Mac/${NameLower}_mac_files.cmake index 7eb776e3a6..e14e028c88 100644 --- a/Templates/DefaultProject/Template/Code/Platform/Mac/${NameLower}_mac_files.cmake +++ b/Templates/DefaultProject/Template/Code/Platform/Mac/${NameLower}_mac_files.cmake @@ -12,7 +12,4 @@ set(FILES ../../../Resources/Platform/Mac/Info.plist PAL_mac.cmake - mac_runtime_dependencies.cmake - mac_tool_dependencies.cmake - mac_server_dependencies.cmake ) diff --git a/Templates/DefaultProject/Template/Code/Platform/Mac/mac_runtime_dependencies.cmake b/Templates/DefaultProject/Template/Code/Platform/Mac/mac_runtime_dependencies.cmake deleted file mode 100644 index 2821493346..0000000000 --- a/Templates/DefaultProject/Template/Code/Platform/Mac/mac_runtime_dependencies.cmake +++ /dev/null @@ -1,15 +0,0 @@ -# {BEGIN_LICENSE} -# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -# its licensors. -# -# For complete copyright and license terms please see the LICENSE at the root of this -# distribution (the "License"). All use of this software is governed by the License, -# or, if provided, by the license below or the license accompanying this file. Do not -# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# {END_LICENSE} - -set(GEM_DEPENDENCIES - Gem::Atom_RHI_Metal.Private - Gem::Atom_RHI_Null.Private -) diff --git a/Templates/DefaultProject/Template/Code/Platform/Mac/mac_server_dependencies.cmake b/Templates/DefaultProject/Template/Code/Platform/Mac/mac_server_dependencies.cmake deleted file mode 100644 index fe28e294e9..0000000000 --- a/Templates/DefaultProject/Template/Code/Platform/Mac/mac_server_dependencies.cmake +++ /dev/null @@ -1,13 +0,0 @@ -# {BEGIN_LICENSE} -# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -# its licensors. -# -# For complete copyright and license terms please see the LICENSE at the root of this -# distribution (the "License"). All use of this software is governed by the License, -# or, if provided, by the license below or the license accompanying this file. Do not -# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# {END_LICENSE} - -set(GEM_DEPENDENCIES -) diff --git a/Templates/DefaultProject/Template/Code/Platform/Mac/mac_tool_dependencies.cmake b/Templates/DefaultProject/Template/Code/Platform/Mac/mac_tool_dependencies.cmake deleted file mode 100644 index adf5485ed4..0000000000 --- a/Templates/DefaultProject/Template/Code/Platform/Mac/mac_tool_dependencies.cmake +++ /dev/null @@ -1,18 +0,0 @@ -# {BEGIN_LICENSE} -# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -# its licensors. -# -# For complete copyright and license terms please see the LICENSE at the root of this -# distribution (the "License"). All use of this software is governed by the License, -# or, if provided, by the license below or the license accompanying this file. Do not -# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# {END_LICENSE} - -set(GEM_DEPENDENCIES - Gem::Atom_RHI_Metal.Private - Gem::Atom_RHI_Metal.Builders - Gem::Atom_RHI_Vulkan.Builders - Gem::Atom_RHI_DX12.Builders - Gem::Atom_RHI_Null.Builders -) diff --git a/Templates/DefaultProject/Template/Code/Platform/Windows/${NameLower}_windows_files.cmake b/Templates/DefaultProject/Template/Code/Platform/Windows/${NameLower}_windows_files.cmake index 8fee85a163..b6eb718a05 100644 --- a/Templates/DefaultProject/Template/Code/Platform/Windows/${NameLower}_windows_files.cmake +++ b/Templates/DefaultProject/Template/Code/Platform/Windows/${NameLower}_windows_files.cmake @@ -11,7 +11,4 @@ set(FILES PAL_windows.cmake - windows_runtime_dependencies.cmake - windows_tool_dependencies.cmake - windows_server_dependencies.cmake ) diff --git a/Templates/DefaultProject/Template/Code/Platform/Windows/windows_runtime_dependencies.cmake b/Templates/DefaultProject/Template/Code/Platform/Windows/windows_runtime_dependencies.cmake deleted file mode 100644 index 514a61aa57..0000000000 --- a/Templates/DefaultProject/Template/Code/Platform/Windows/windows_runtime_dependencies.cmake +++ /dev/null @@ -1,16 +0,0 @@ -# {BEGIN_LICENSE} -# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -# its licensors. -# -# For complete copyright and license terms please see the LICENSE at the root of this -# distribution (the "License"). All use of this software is governed by the License, -# or, if provided, by the license below or the license accompanying this file. Do not -# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# {END_LICENSE} - -set(GEM_DEPENDENCIES - Gem::Atom_RHI_Vulkan.Private - Gem::Atom_RHI_DX12.Private - Gem::Atom_RHI_Null.Private -) diff --git a/Templates/DefaultProject/Template/Code/Platform/Windows/windows_server_dependencies.cmake b/Templates/DefaultProject/Template/Code/Platform/Windows/windows_server_dependencies.cmake deleted file mode 100644 index fe28e294e9..0000000000 --- a/Templates/DefaultProject/Template/Code/Platform/Windows/windows_server_dependencies.cmake +++ /dev/null @@ -1,13 +0,0 @@ -# {BEGIN_LICENSE} -# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -# its licensors. -# -# For complete copyright and license terms please see the LICENSE at the root of this -# distribution (the "License"). All use of this software is governed by the License, -# or, if provided, by the license below or the license accompanying this file. Do not -# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# {END_LICENSE} - -set(GEM_DEPENDENCIES -) diff --git a/Templates/DefaultProject/Template/Code/Platform/Windows/windows_tool_dependencies.cmake b/Templates/DefaultProject/Template/Code/Platform/Windows/windows_tool_dependencies.cmake deleted file mode 100644 index b7f4b82126..0000000000 --- a/Templates/DefaultProject/Template/Code/Platform/Windows/windows_tool_dependencies.cmake +++ /dev/null @@ -1,19 +0,0 @@ -# {BEGIN_LICENSE} -# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -# its licensors. -# -# For complete copyright and license terms please see the LICENSE at the root of this -# distribution (the "License"). All use of this software is governed by the License, -# or, if provided, by the license below or the license accompanying this file. Do not -# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# {END_LICENSE} - -set(GEM_DEPENDENCIES - Gem::Atom_RHI_Vulkan.Private - Gem::Atom_RHI_Vulkan.Builders - Gem::Atom_RHI_DX12.Private - Gem::Atom_RHI_DX12.Builders - Gem::Atom_RHI_Null.Private - Gem::Atom_RHI_Null.Builders -) diff --git a/Templates/DefaultProject/Template/Code/Platform/iOS/${NameLower}_ios_files.cmake b/Templates/DefaultProject/Template/Code/Platform/iOS/${NameLower}_ios_files.cmake index 41a6d13884..44f15538c8 100644 --- a/Templates/DefaultProject/Template/Code/Platform/iOS/${NameLower}_ios_files.cmake +++ b/Templates/DefaultProject/Template/Code/Platform/iOS/${NameLower}_ios_files.cmake @@ -12,7 +12,4 @@ set(FILES ../Resources/Platform/iOS/Info.plist PAL_ios.cmake - ios_runtime_dependencies.cmake - ios_tool_dependencies.cmake - ios_server_dependencies.cmake ) diff --git a/Templates/DefaultProject/Template/Code/Platform/iOS/ios_runtime_dependencies.cmake b/Templates/DefaultProject/Template/Code/Platform/iOS/ios_runtime_dependencies.cmake deleted file mode 100644 index e49929c6e1..0000000000 --- a/Templates/DefaultProject/Template/Code/Platform/iOS/ios_runtime_dependencies.cmake +++ /dev/null @@ -1,14 +0,0 @@ -# {BEGIN_LICENSE} -# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -# its licensors. -# -# For complete copyright and license terms please see the LICENSE at the root of this -# distribution (the "License"). All use of this software is governed by the License, -# or, if provided, by the license below or the license accompanying this file. Do not -# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# {END_LICENSE} - -set(GEM_DEPENDENCIES - Gem::Atom_RHI_Metal.Private -) diff --git a/Templates/DefaultProject/Template/Code/Platform/iOS/ios_server_dependencies.cmake b/Templates/DefaultProject/Template/Code/Platform/iOS/ios_server_dependencies.cmake deleted file mode 100644 index fe28e294e9..0000000000 --- a/Templates/DefaultProject/Template/Code/Platform/iOS/ios_server_dependencies.cmake +++ /dev/null @@ -1,13 +0,0 @@ -# {BEGIN_LICENSE} -# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -# its licensors. -# -# For complete copyright and license terms please see the LICENSE at the root of this -# distribution (the "License"). All use of this software is governed by the License, -# or, if provided, by the license below or the license accompanying this file. Do not -# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# {END_LICENSE} - -set(GEM_DEPENDENCIES -) diff --git a/Templates/DefaultProject/Template/Code/Platform/iOS/ios_tool_dependencies.cmake b/Templates/DefaultProject/Template/Code/Platform/iOS/ios_tool_dependencies.cmake deleted file mode 100644 index 14e6f1aa4c..0000000000 --- a/Templates/DefaultProject/Template/Code/Platform/iOS/ios_tool_dependencies.cmake +++ /dev/null @@ -1,14 +0,0 @@ -# {BEGIN_LICENSE} -# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -# its licensors. -# -# For complete copyright and license terms please see the LICENSE at the root of this -# distribution (the "License"). All use of this software is governed by the License, -# or, if provided, by the license below or the license accompanying this file. Do not -# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# {END_LICENSE} - -set(GEM_DEPENDENCIES -) - diff --git a/Templates/DefaultProject/Template/Code/Source/${Name}Module.cpp b/Templates/DefaultProject/Template/Code/Source/${Name}Module.cpp index 003a984dd6..4f11828366 100644 --- a/Templates/DefaultProject/Template/Code/Source/${Name}Module.cpp +++ b/Templates/DefaultProject/Template/Code/Source/${Name}Module.cpp @@ -47,4 +47,4 @@ namespace ${Name} }; }// namespace ${Name} -AZ_DECLARE_MODULE_CLASS(Project_${Name}, ${Name}::${Name}Module) +AZ_DECLARE_MODULE_CLASS(Gem_${Name}, ${Name}::${Name}Module) diff --git a/Templates/DefaultProject/Template/Code/server_dependencies.cmake b/Templates/DefaultProject/Template/Code/enabled_gems.cmake similarity index 70% rename from Templates/DefaultProject/Template/Code/server_dependencies.cmake rename to Templates/DefaultProject/Template/Code/enabled_gems.cmake index 3982bbb166..dfb7d93233 100644 --- a/Templates/DefaultProject/Template/Code/server_dependencies.cmake +++ b/Templates/DefaultProject/Template/Code/enabled_gems.cmake @@ -9,8 +9,20 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # {END_LICENSE} -set(GEM_DEPENDENCIES +set(ENABLED_GEMS Project::${Name} - Gem::Maestro - Gem::LmbrCentral + Atom_AtomBridge + Camera + CameraFramework + EditorPythonBindings + EMotionFX + GradientSignal + ImGui + LmbrCentral + LyShine + Maestro + NvCloth + SceneProcessing + TextureAtlas + WhiteBox ) diff --git a/Templates/DefaultProject/Template/Code/gem.json b/Templates/DefaultProject/Template/Code/gem.json new file mode 100644 index 0000000000..5b8fb3fde0 --- /dev/null +++ b/Templates/DefaultProject/Template/Code/gem.json @@ -0,0 +1,14 @@ +{ + "gem_name": "${Name}", + "origin": "The primary repo for ${Name} goes here: i.e. http://www.mydomain.com", + "license": "What license ${Name} uses goes here: i.e. https://opensource.org/licenses/MIT", + "display_name": "${Name}", + "summary": "A short description of ${Name}.", + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "${Name}" + ], + "icon_path": "preview.png" +} diff --git a/Templates/DefaultProject/Template/Code/runtime_dependencies.cmake b/Templates/DefaultProject/Template/Code/runtime_dependencies.cmake deleted file mode 100644 index ce8df8152d..0000000000 --- a/Templates/DefaultProject/Template/Code/runtime_dependencies.cmake +++ /dev/null @@ -1,36 +0,0 @@ -# {BEGIN_LICENSE} -# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -# its licensors. -# -# For complete copyright and license terms please see the LICENSE at the root of this -# distribution (the "License"). All use of this software is governed by the License, -# or, if provided, by the license below or the license accompanying this file. Do not -# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# {END_LICENSE} - -set(GEM_DEPENDENCIES - Project::${Name} - Gem::Maestro - Gem::TextureAtlas - Gem::LmbrCentral - Gem::NvCloth - Gem::LyShine - Gem::Camera - Gem::CameraFramework - Gem::Atom_RHI.Private - Gem::EMotionFX - Gem::Atom_RPI.Private - Gem::Atom_Feature_Common - Gem::ImGui - Gem::Atom_Bootstrap - Gem::Atom_Component_DebugCamera - Gem::AtomImGuiTools - Gem::AtomLyIntegration_CommonFeatures - Gem::EMotionFX_Atom - Gem::ImguiAtom - Gem::Atom_AtomBridge - Gem::GradientSignal - Gem::AtomFont - Gem::WhiteBox -) diff --git a/Templates/DefaultProject/Template/Code/tool_dependencies.cmake b/Templates/DefaultProject/Template/Code/tool_dependencies.cmake deleted file mode 100644 index 010d45bd0f..0000000000 --- a/Templates/DefaultProject/Template/Code/tool_dependencies.cmake +++ /dev/null @@ -1,43 +0,0 @@ -# {BEGIN_LICENSE} -# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -# its licensors. -# -# For complete copyright and license terms please see the LICENSE at the root of this -# distribution (the "License"). All use of this software is governed by the License, -# or, if provided, by the license below or the license accompanying this file. Do not -# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# {END_LICENSE} - -set(GEM_DEPENDENCIES - Project::${Name} - Gem::Maestro.Editor - Gem::TextureAtlas - Gem::LmbrCentral.Editor - Gem::NvCloth.Editor - Gem::LyShine.Editor - Gem::SceneProcessing.Editor - Gem::EditorPythonBindings.Editor - Gem::Camera.Editor - Gem::CameraFramework - Gem::Atom_RHI.Private - Gem::EMotionFX.Editor - Gem::Atom_RPI.Builders - Gem::Atom_RPI.Editor - Gem::Atom_Feature_Common.Builders - Gem::Atom_Feature_Common.Editor - Gem::ImGui.Editor - Gem::Atom_Bootstrap - Gem::Atom_Asset_Shader.Builders - Gem::Atom_Component_DebugCamera - Gem::AtomImGuiTools - Gem::AtomLyIntegration_CommonFeatures.Editor - Gem::EMotionFX_Atom.Editor - Gem::ImageProcessingAtom.Editor - Gem::Atom_AtomBridge.Editor - Gem::ImguiAtom - Gem::AtomFont - Gem::AtomToolsFramework.Editor - Gem::GradientSignal.Editor - Gem::WhiteBox.Editor -) diff --git a/Templates/DefaultProject/Template/EngineFinder.cmake b/Templates/DefaultProject/Template/EngineFinder.cmake new file mode 100644 index 0000000000..fbbe3d8cfe --- /dev/null +++ b/Templates/DefaultProject/Template/EngineFinder.cmake @@ -0,0 +1,68 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# +# This file is copied during engine registration. Edits to this file will be lost next +# time a registration happens. + +include_guard() + +# Read the engine name from the project_json file +file(READ ${CMAKE_CURRENT_LIST_DIR}/project.json project_json) +string(JSON LY_ENGINE_NAME_TO_USE ERROR_VARIABLE json_error GET ${project_json} engine) +if(json_error) + message(FATAL_ERROR "Unable to read key 'engine' from 'project.json', error: ${json_error}") +endif() + +if(DEFINED ENV{USERPROFILE} AND EXISTS $ENV{USERPROFILE}) + set(manifest_path $ENV{USERPROFILE}/.o3de/o3de_manifest.json) # Windows +else() + set(manifest_path $ENV{HOME}/.o3de/o3de_manifest.json) # Unix +endif() + +# Read the ~/.o3de/o3de_manifest.json file and look through the 'engines_path' object. +# Find a key that matches LY_ENGINE_NAME_TO_USE and use that as the engine path. +if(EXISTS ${manifest_path}) + file(READ ${manifest_path} manifest_json) + + string(JSON engines_path_count ERROR_VARIABLE json_error LENGTH ${manifest_json} engines_path) + if(json_error) + message(FATAL_ERROR "Unable to read key 'engines_path' from '${manifest_path}', error: ${json_error}") + endif() + + string(JSON engines_path_type ERROR_VARIABLE json_error TYPE ${manifest_json} engines_path) + if(json_error OR NOT ${engines_path_type} STREQUAL "OBJECT") + message(FATAL_ERROR "Type of 'engines_path' in '${manifest_path}' is not a JSON Object, error: ${json_error}") + endif() + + math(EXPR engines_path_count "${engines_path_count}-1") + foreach(engine_path_index RANGE ${engines_path_count}) + string(JSON engine_name ERROR_VARIABLE json_error MEMBER ${manifest_json} engines_path ${engine_path_index}) + if(json_error) + message(FATAL_ERROR "Unable to read 'engines_path/${engine_path_index}' from '${manifest_path}', error: ${json_error}") + endif() + + if(LY_ENGINE_NAME_TO_USE STREQUAL engine_name) + string(JSON engine_path ERROR_VARIABLE json_error GET ${manifest_json} engines_path ${engine_name}) + if(json_error) + message(FATAL_ERROR "Unable to read value from 'engines_path/${engine_name}', error: ${json_error}") + endif() + + if(engine_path) + list(APPEND CMAKE_MODULE_PATH "${engine_path}/cmake") + break() + endif() + endif() + endforeach() +else() + # If the user is passing CMAKE_MODULE_PATH we assume thats where we will find the engine + if(NOT CMAKE_MODULE_PATH) + message(FATAL_ERROR "Engine registration is required before configuring a project. Please register an engine by running 'scripts/o3de register --this-engine'") + endif() +endif() diff --git a/Templates/DefaultProject/Template/ShaderLib/README.md b/Templates/DefaultProject/Template/ShaderLib/README.md new file mode 100644 index 0000000000..034550163d --- /dev/null +++ b/Templates/DefaultProject/Template/ShaderLib/README.md @@ -0,0 +1,5 @@ +# Customizing Shader Resource Groups + +Please read: +*\/Gems/Atom/Feature/Common/Assets/ShaderResourceGroups/README.md* +for details on how to customize scenesrg.srgi and viewsrg.srgi. diff --git a/Templates/DefaultProject/Template/ShaderLib/raytracingscenesrg.srgi b/Templates/DefaultProject/Template/ShaderLib/raytracingscenesrg.srgi deleted file mode 100644 index ac27571828..0000000000 --- a/Templates/DefaultProject/Template/ShaderLib/raytracingscenesrg.srgi +++ /dev/null @@ -1,30 +0,0 @@ -// {BEGIN_LICENSE} -/* -* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -* its licensors. -* -* For complete copyright and license terms please see the LICENSE at the root of this -* distribution (the "License"). All use of this software is governed by the License, -* or, if provided, by the license below or the license accompanying this file. Do not -* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* -*/ -// {END_LICENSE} - -#pragma once - -// Please read README.md for an explanation on why scenesrg.srgi and viewsrg.srgi are -// located in this folder (And how you can optionally customize your own scenesrg.srgi -// and viewsrg.srgi in your game project). - -#include - -partial ShaderResourceGroup RayTracingSceneSrg : SRG_RayTracingScene -{ -/* Intentionally Empty. Helps define the SrgSemantic for RayTracingSceneSrg once.*/ -}; - -#define AZ_COLLECTING_PARTIAL_SRGS -#include -#undef AZ_COLLECTING_PARTIAL_SRGS diff --git a/Templates/DefaultProject/Template/ShaderLib/scenesrg.srgi b/Templates/DefaultProject/Template/ShaderLib/scenesrg.srgi index 9b4803b7dc..0a8cec5963 100644 --- a/Templates/DefaultProject/Template/ShaderLib/scenesrg.srgi +++ b/Templates/DefaultProject/Template/ShaderLib/scenesrg.srgi @@ -26,5 +26,6 @@ partial ShaderResourceGroup SceneSrg : SRG_PerScene }; #define AZ_COLLECTING_PARTIAL_SRGS -#include +#include +#include #undef AZ_COLLECTING_PARTIAL_SRGS diff --git a/Gems/ScriptCanvasDiagnosticLibrary/Code/Source/precompiled.h b/Templates/DefaultProject/Template/Shaders/ShaderResourceGroups/SceneSrg.azsli similarity index 62% rename from Gems/ScriptCanvasDiagnosticLibrary/Code/Source/precompiled.h rename to Templates/DefaultProject/Template/Shaders/ShaderResourceGroups/SceneSrg.azsli index 01688d8dc7..4c962fbbcd 100644 --- a/Gems/ScriptCanvasDiagnosticLibrary/Code/Source/precompiled.h +++ b/Templates/DefaultProject/Template/Shaders/ShaderResourceGroups/SceneSrg.azsli @@ -1,3 +1,4 @@ +// {BEGIN_LICENSE} /* * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or * its licensors. @@ -9,20 +10,15 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * */ +// {END_LICENSE} -#pragma once - -#include -#include -#include -#include -#include -#include - -#if !defined(SCRIPTCANVASDIAGNOSTICSLIBRARY_EDITOR) - -#include - -#else - +#ifndef AZ_COLLECTING_PARTIAL_SRGS +#error Do not include this file directly. Include the main .srgi file instead. #endif + +partial ShaderResourceGroup SceneSrg +{ + float m_time; + float m_deltaTime; +} + diff --git a/Templates/DefaultProject/Template/preview.png b/Templates/DefaultProject/Template/preview.png index 3d4fe78063..a3e13481c9 100644 --- a/Templates/DefaultProject/Template/preview.png +++ b/Templates/DefaultProject/Template/preview.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:40949893ed7009eeaa90b7ce6057cb6be9dfaf7b162e3c26ba9dadf985939d7d -size 2038 +oid sha256:4a5881b8d6cfbc4ceefb14ab96844484fe19407ee030824768f9fcce2f729d35 +size 2949 diff --git a/Templates/DefaultProject/template.json b/Templates/DefaultProject/template.json index 56278a6b04..26b868d315 100644 --- a/Templates/DefaultProject/template.json +++ b/Templates/DefaultProject/template.json @@ -1,6 +1,6 @@ { "template_name": "DefaultProject", - "restricted": "o3de", + "restricted_name": "o3de", "restricted_platform_relative_path": "Templates", "origin": "The primary repo for DefaultProject goes here: i.e. http://www.mydomain.com", "license": "What license DefaultProject uses goes here: i.e. https://opensource.org/licenses/MIT", @@ -24,6 +24,12 @@ "isTemplated": true, "isOptional": false }, + { + "file": "EngineFinder.cmake", + "origin": "EngineFinder.cmake", + "isTemplated": false, + "isOptional": false + }, { "file": "Code/${NameLower}_files.cmake", "origin": "Code/${NameLower}_files.cmake", @@ -42,6 +48,12 @@ "isTemplated": true, "isOptional": false }, + { + "file": "Code/gem.json", + "origin": "Code/gem.json", + "isTemplated": true, + "isOptional": true + }, { "file": "Code/Include/${Name}/${Name}Bus.h", "origin": "Code/Include/${Name}/${Name}Bus.h", @@ -66,24 +78,6 @@ "isTemplated": true, "isOptional": false }, - { - "file": "Code/Platform/Android/android_runtime_dependencies.cmake", - "origin": "Code/Platform/Android/android_runtime_dependencies.cmake", - "isTemplated": true, - "isOptional": false - }, - { - "file": "Code/Platform/Android/android_server_dependencies.cmake", - "origin": "Code/Platform/Android/android_server_dependencies.cmake", - "isTemplated": true, - "isOptional": false - }, - { - "file": "Code/Platform/Android/android_tool_dependencies.cmake", - "origin": "Code/Platform/Android/android_tool_dependencies.cmake", - "isTemplated": true, - "isOptional": false - }, { "file": "Code/Platform/Linux/${NameLower}_linux_files.cmake", "origin": "Code/Platform/Linux/${NameLower}_linux_files.cmake", @@ -102,24 +96,6 @@ "isTemplated": true, "isOptional": false }, - { - "file": "Code/Platform/Linux/linux_runtime_dependencies.cmake", - "origin": "Code/Platform/Linux/linux_runtime_dependencies.cmake", - "isTemplated": true, - "isOptional": false - }, - { - "file": "Code/Platform/Linux/linux_server_dependencies.cmake", - "origin": "Code/Platform/Linux/linux_server_dependencies.cmake", - "isTemplated": true, - "isOptional": false - }, - { - "file": "Code/Platform/Linux/linux_tool_dependencies.cmake", - "origin": "Code/Platform/Linux/linux_tool_dependencies.cmake", - "isTemplated": true, - "isOptional": false - }, { "file": "Code/Platform/Mac/${NameLower}_mac_files.cmake", "origin": "Code/Platform/Mac/${NameLower}_mac_files.cmake", @@ -138,24 +114,6 @@ "isTemplated": true, "isOptional": false }, - { - "file": "Code/Platform/Mac/mac_runtime_dependencies.cmake", - "origin": "Code/Platform/Mac/mac_runtime_dependencies.cmake", - "isTemplated": true, - "isOptional": false - }, - { - "file": "Code/Platform/Mac/mac_server_dependencies.cmake", - "origin": "Code/Platform/Mac/mac_server_dependencies.cmake", - "isTemplated": true, - "isOptional": false - }, - { - "file": "Code/Platform/Mac/mac_tool_dependencies.cmake", - "origin": "Code/Platform/Mac/mac_tool_dependencies.cmake", - "isTemplated": true, - "isOptional": false - }, { "file": "Code/Platform/Windows/${NameLower}_shared_windows_files.cmake", "origin": "Code/Platform/Windows/${NameLower}_shared_windows_files.cmake", @@ -174,24 +132,6 @@ "isTemplated": true, "isOptional": false }, - { - "file": "Code/Platform/Windows/windows_runtime_dependencies.cmake", - "origin": "Code/Platform/Windows/windows_runtime_dependencies.cmake", - "isTemplated": true, - "isOptional": false - }, - { - "file": "Code/Platform/Windows/windows_server_dependencies.cmake", - "origin": "Code/Platform/Windows/windows_server_dependencies.cmake", - "isTemplated": true, - "isOptional": false - }, - { - "file": "Code/Platform/Windows/windows_tool_dependencies.cmake", - "origin": "Code/Platform/Windows/windows_tool_dependencies.cmake", - "isTemplated": true, - "isOptional": false - }, { "file": "Code/Platform/iOS/${NameLower}_ios_files.cmake", "origin": "Code/Platform/iOS/${NameLower}_ios_files.cmake", @@ -210,24 +150,6 @@ "isTemplated": true, "isOptional": false }, - { - "file": "Code/Platform/iOS/ios_runtime_dependencies.cmake", - "origin": "Code/Platform/iOS/ios_runtime_dependencies.cmake", - "isTemplated": true, - "isOptional": false - }, - { - "file": "Code/Platform/iOS/ios_server_dependencies.cmake", - "origin": "Code/Platform/iOS/ios_server_dependencies.cmake", - "isTemplated": true, - "isOptional": false - }, - { - "file": "Code/Platform/iOS/ios_tool_dependencies.cmake", - "origin": "Code/Platform/iOS/ios_tool_dependencies.cmake", - "isTemplated": true, - "isOptional": false - }, { "file": "Code/Source/${Name}Module.cpp", "origin": "Code/Source/${Name}Module.cpp", @@ -247,20 +169,8 @@ "isOptional": false }, { - "file": "Code/runtime_dependencies.cmake", - "origin": "Code/runtime_dependencies.cmake", - "isTemplated": true, - "isOptional": false - }, - { - "file": "Code/server_dependencies.cmake", - "origin": "Code/server_dependencies.cmake", - "isTemplated": true, - "isOptional": false - }, - { - "file": "Code/tool_dependencies.cmake", - "origin": "Code/tool_dependencies.cmake", + "file": "Code/enabled_gems.cmake", + "origin": "Code/enabled_gems.cmake", "isTemplated": true, "isOptional": false }, @@ -565,10 +475,10 @@ "isOptional": false }, { - "file": "ShaderLib/raytracingscenesrg.srgi", - "origin": "ShaderLib/raytracingscenesrg.srgi", + "file": "ShaderLib/README.md", + "origin": "ShaderLib/README.md", "isTemplated": true, - "isOptional": false + "isOptional": true }, { "file": "ShaderLib/scenesrg.srgi", @@ -588,6 +498,12 @@ "isTemplated": true, "isOptional": false }, + { + "file": "Shaders/ShaderResourceGroups/SceneSrg.azsli", + "origin": "Shaders/ShaderResourceGroups/SceneSrg.azsli", + "isTemplated": true, + "isOptional": false + }, { "file": "autoexec.cfg", "origin": "autoexec.cfg", diff --git a/cmake/3rdParty.cmake b/cmake/3rdParty.cmake index 7385f34e5a..eb11404237 100644 --- a/cmake/3rdParty.cmake +++ b/cmake/3rdParty.cmake @@ -313,6 +313,9 @@ list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/3rdParty) ly_get_absolute_pal_filename(pal_dir ${CMAKE_CURRENT_LIST_DIR}/3rdParty/Platform/${PAL_PLATFORM_NAME}) list(APPEND CMAKE_MODULE_PATH ${pal_dir}) -ly_include_cmake_file_list(cmake/3rdParty/cmake_files.cmake) -ly_get_absolute_pal_filename(pal_3rdparty_dir ${CMAKE_CURRENT_SOURCE_DIR}/cmake/3rdParty/Platform/${PAL_PLATFORM_NAME}) -ly_include_cmake_file_list(${pal_3rdparty_dir}/cmake_${PAL_PLATFORM_NAME_LOWERCASE}_files.cmake) +if(NOT INSTALLED_ENGINE) + # Add the 3rdParty cmake files to the IDE + ly_include_cmake_file_list(cmake/3rdParty/cmake_files.cmake) + ly_get_absolute_pal_filename(pal_3rdparty_dir ${CMAKE_CURRENT_SOURCE_DIR}/cmake/3rdParty/Platform/${PAL_PLATFORM_NAME}) + ly_include_cmake_file_list(${pal_3rdparty_dir}/cmake_${PAL_PLATFORM_NAME_LOWERCASE}_files.cmake) +endif() \ No newline at end of file diff --git a/cmake/3rdParty/FindClang.cmake b/cmake/3rdParty/FindClang.cmake deleted file mode 100644 index 8062e6ebb2..0000000000 --- a/cmake/3rdParty/FindClang.cmake +++ /dev/null @@ -1,15 +0,0 @@ -# -# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -# its licensors. -# -# For complete copyright and license terms please see the LICENSE at the root of this -# distribution (the "License"). All use of this software is governed by the License, -# or, if provided, by the license below or the license accompanying this file. Do not -# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# - -ly_add_external_target( - NAME Clang - VERSION 6.0.1-az -) diff --git a/cmake/3rdParty/cmake_files.cmake b/cmake/3rdParty/cmake_files.cmake index ebbad6e156..f7a315686f 100644 --- a/cmake/3rdParty/cmake_files.cmake +++ b/cmake/3rdParty/cmake_files.cmake @@ -11,7 +11,6 @@ set(FILES BuiltInPackages.cmake - FindClang.cmake FindOpenGLInterface.cmake FindRadTelemetry.cmake FindVkValidation.cmake diff --git a/cmake/CMakeFiles.cmake b/cmake/CMakeFiles.cmake index 952f9b5eb8..77c2eb75e1 100644 --- a/cmake/CMakeFiles.cmake +++ b/cmake/CMakeFiles.cmake @@ -9,8 +9,10 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # -# Add all cmake files in a project so they can be handled from within the IDE -ly_include_cmake_file_list(cmake/cmake_files.cmake) -add_custom_target(CMakeFiles SOURCES ${ALLFILES}) -ly_source_groups_from_folders("${ALLFILES}") -unset(ALLFILES) \ No newline at end of file +if(NOT INSTALLED_ENGINE) + # Add all cmake files in a project so they can be handled from within the IDE + ly_include_cmake_file_list(cmake/cmake_files.cmake) + add_custom_target(CMakeFiles SOURCES ${ALLFILES}) + ly_source_groups_from_folders("${ALLFILES}") + unset(ALLFILES) +endif() \ No newline at end of file diff --git a/cmake/EngineFinder.cmake b/cmake/EngineFinder.cmake deleted file mode 100644 index 9ff8ce4d66..0000000000 --- a/cmake/EngineFinder.cmake +++ /dev/null @@ -1,52 +0,0 @@ -# -# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -# its licensors. -# -# For complete copyright and license terms please see the LICENSE at the root of this -# distribution (the "License"). All use of this software is governed by the License, -# or, if provided, by the license below or the license accompanying this file. Do not -# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# -# This file is copied during engine registration. Edits to this file will be lost next -# time a registration happens. - -include_guard() - -# Read the engine name from the project_json file -file(READ ${CMAKE_CURRENT_LIST_DIR}/project.json project_json) -string(JSON LY_ENGINE_NAME_TO_USE ERROR_VARIABLE json_error GET ${project_json} engine) -if(json_error) - message(FATAL_ERROR "Unable to read key 'engine' from 'project.json', error: ${json_error}") -endif() - -# Read the list of paths from ~.o3de/o3de_manifest.json -file(TO_CMAKE_PATH "$ENV{USERPROFILE}" home_directory) # Windows -if((NOT home_directory) OR (NOT EXISTS ${home_directory})) - file(TO_CMAKE_PATH "$ENV{HOME}" home_directory)# Unix -endif() - -if (NOT home_directory) - message(FATAL_ERROR "Cannot find user home directory, the o3de manifest cannot be found") -endif() -# Set manifest path to path in the user home directory -set(manifest_path ${home_directory}/.o3de/o3de_manifest.json) - -if(EXISTS ${manifest_path}) - file(READ ${manifest_path} manifest_json) - string(JSON engines_count ERROR_VARIABLE json_error LENGTH ${manifest_json} engines) - if(json_error) - message(FATAL_ERROR "Unable to read key 'engines' from '${manifest_path}', error: ${json_error}") - endif() - - math(EXPR engines_count "${engines_count}-1") - foreach(engine_path_index RANGE ${engines_count}) - string(JSON engine_path ERROR_VARIABLE json_error GET ${manifest_json} engines ${engine_path_index}) - if(${json_error}) - message(FATAL_ERROR "Unable to read engines[${engine_path_index}] '${manifest_path}', error: ${json_error}") - endif() - if(engine_path) - list(APPEND CMAKE_MODULE_PATH "${engine_path}/cmake") - endif() - endforeach() -endif() diff --git a/cmake/EngineJson.cmake b/cmake/EngineJson.cmake new file mode 100644 index 0000000000..c3ab29d09e --- /dev/null +++ b/cmake/EngineJson.cmake @@ -0,0 +1,47 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# +# This file is copied during engine registration. Edits to this file will be lost next +# time a registration happens. + +include_guard() + +set(LY_EXTERNAL_SUBDIRS "" CACHE STRING "List of subdirectories to recurse into when running cmake against the engine's CMakeLists.txt") + +#! read_engine_external_subdirs +# Read the external subdirectories from the engine.json file +# External subdirectories are any folders with CMakeLists.txt in them +# This could be regular subdirectories, Gems(contains an additional gem.json), +# Restricted folders(contains an additional restricted.json), etc... +# \arg:output_external_subdirs name of output variable to store external subdirectories into +function(read_engine_external_subdirs output_external_subdirs) + file(READ ${LY_ROOT_FOLDER}/engine.json engine_json_data) + string(JSON external_subdirs_count ERROR_VARIABLE engine_json_error + LENGTH ${engine_json_data} "external_subdirectories") + if(engine_json_error) + message(FATAL_ERROR "Error querying number of elements in JSON array \"external_subdirectories\": ${engine_json_error}") + endif() + + if(external_subdirs_count GREATER 0) + math(EXPR external_subdir_range "${external_subdirs_count}-1") + # Convert the paths the relative paths to absolute paths using the engine root + # as the base directory + foreach(external_subdir_index RANGE ${external_subdir_range}) + string(JSON external_subdir ERROR_VARIABLE engine_json_error + GET ${engine_json_data} "external_subdirectories" "${external_subdir_index}") + if(engine_json_error) + message(FATAL_ERROR "Error reading field at index ${external_subdir_index} in \"external_subdirectories\" JSON array: ${engine_json_error}") + endif() + file(REAL_PATH ${external_subdir} real_external_subdir BASE_DIRECTORY ${CMAKE_SOURCE_DIR}) + list(APPEND external_subdirs ${real_external_subdir}) + endforeach() + endif() + set(${output_external_subdirs} ${external_subdirs} PARENT_SCOPE) +endfunction() diff --git a/cmake/FindTarget.cmake.in b/cmake/FindTarget.cmake.in deleted file mode 100644 index 8ad9822dae..0000000000 --- a/cmake/FindTarget.cmake.in +++ /dev/null @@ -1,34 +0,0 @@ -# -# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -# its licensors. -# -# For complete copyright and license terms please see the LICENSE at the root of this -# distribution (the "License"). All use of this software is governed by the License, -# or, if provided, by the license below or the license accompanying this file. Do not -# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# - -# Generated by O3DE - -include(FindPackageHandleStandardArgs) - -ly_add_target( - NAME @NAME_PLACEHOLDER@ UNKNOWN IMPORTED - @NAMESPACE_PLACEHOLDER@ - COMPILE_DEFINITIONS - INTERFACE -@COMPILE_DEFINITIONS_PLACEHOLDER@ - INCLUDE_DIRECTORIES - INTERFACE -@INCLUDE_DIRECTORIES_PLACEHOLDER@ - BUILD_DEPENDENCIES - INTERFACE -@BUILD_DEPENDENCIES_PLACEHOLDER@ - RUNTIME_DEPENDENCIES -@RUNTIME_DEPENDENCIES_PLACEHOLDER@ -) - -foreach(config @CMAKE_CONFIGURATION_TYPES@) - include("${LY_ROOT_FOLDER}/cmake_autogen/@NAME_PLACEHOLDER@/@NAME_PLACEHOLDER@_${config}.cmake" OPTIONAL) -endforeach() diff --git a/cmake/Gems.cmake b/cmake/Gems.cmake new file mode 100644 index 0000000000..d418d5dcd1 --- /dev/null +++ b/cmake/Gems.cmake @@ -0,0 +1,217 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +# This file contains utility wrappers for dealing with the Gems system. + +# ly_create_alias +# given an alias to create, and a list of one or more targets, +# this creates an alias that depends on all of the given targets. +function(ly_create_alias) + set(options) + set(oneValueArgs NAME NAMESPACE) + set(multiValueArgs TARGETS) + + cmake_parse_arguments(ly_create_alias "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if (NOT ly_create_alias_NAME) + message(FATAL_ERROR "Provide the name of the alias to create using the NAME keyword") + endif() + + if (NOT ly_create_alias_NAMESPACE) + message(FATAL_ERROR "Provide the namespace of the alias to create using the NAMESPACE keyword") + endif() + + if (NOT ly_create_alias_TARGETS) + message(FATAL_ERROR "Provide the name of the targets the alias be associated with, using the TARGETS keyword") + endif() + + if(TARGET ${ly_create_alias_NAMESPACE}::${ly_create_alias_NAME}) + message(FATAL_ERROR "Target already exists, cannot create an alias for it: ${ly_create_alias_NAMESPACE}::${ly_create_alias_NAME}\n" + "Make sure the target wasn't copy and pasted here or elsewhere.") + endif() + + # easy version - if its juts one target, we can directly get the target, and make both aliases, + # the namespaced and non namespaced one, point at it. + list(LENGTH ly_create_alias_TARGETS number_of_targets) + if (number_of_targets EQUAL 1) + ly_de_alias_target(${ly_create_alias_TARGETS} de_aliased_target_name) + add_library(${ly_create_alias_NAMESPACE}::${ly_create_alias_NAME} ALIAS ${de_aliased_target_name}) + if (NOT TARGET ${ly_create_alias_NAME}) + add_library(${ly_create_alias_NAME} ALIAS ${de_aliased_target_name}) + endif() + # Store off the arguments needed used ly_create_alias into a DIRECTORY property + # This will be used to re-create the calls in the generated CMakeLists.txt in the INSTALL step + string(REPLACE ";" " " create_alias_args "${ly_create_alias_NAME},${ly_create_alias_NAMESPACE},${ly_create_alias_TARGETS}") + set_property(DIRECTORY APPEND PROPERTY LY_CREATE_ALIAS_ARGUMENTS "${ly_create_alias_NAME},${ly_create_alias_NAMESPACE},${ly_create_alias_TARGETS}") + return() + endif() + + # more complex version - one alias to multiple targets. To actually achieve this + # we have to create an interface library with those dependencies, then we have to create an alias to that target. + # by convention we create one without a namespace then alias the namespaced one. + + if(TARGET ${ly_create_alias_NAME}) + message(FATAL_ERROR "Internal alias target already exists, cannot create an alias for it: ${ly_create_alias_NAME}\n" + "This could be a copy-paste error, where some part of the ly_create_alias call was changed but the other") + endif() + + add_library(${ly_create_alias_NAME} INTERFACE IMPORTED GLOBAL) + set_target_properties(${ly_create_alias_NAME} PROPERTIES GEM_MODULE TRUE) + + foreach(target_name ${ly_create_alias_TARGETS}) + if(TARGET ${target_name}) + ly_de_alias_target(${target_name} de_aliased_target_name) + if(NOT de_aliased_target_name) + message(FATAL_ERROR "Target not found in ly_create_alias call: ${target_name} - check your spelling of the target name") + endif() + else() + set(de_aliased_target_name ${target_name}) + endif() + list(APPEND final_targets ${de_aliased_target_name}) + endforeach() + + ly_parse_third_party_dependencies("${final_targets}") + ly_add_dependencies(${ly_create_alias_NAME} ${final_targets}) + + # now add the final alias: + add_library(${ly_create_alias_NAMESPACE}::${ly_create_alias_NAME} ALIAS ${ly_create_alias_NAME}) + + # Store off the arguments needed used ly_create_alias into a DIRECTORY property + # This will be used to re-create the calls in the generated CMakeLists.txt in the INSTALL step + + # Replace the CMake list separator with a space to replicate the space separated TARGETS arguments + string(REPLACE ";" " " create_alias_args "${ly_create_alias_NAME},${ly_create_alias_NAMESPACE},${ly_create_alias_TARGETS}") + set_property(DIRECTORY APPEND PROPERTY LY_CREATE_ALIAS_ARGUMENTS "${create_alias_args}") +endfunction() + +# ly_enable_gems +# this function makes sure that the given gems, or gems listed in the variable ENABLED_GEMS +# in the GEM_FILE name, are set as runtime dependencies (and thus loaded) for the given targets +# in the context of the given project. +# note that it can't do this immediately, so it saves the data for later processing. +# Note: If you don't supply a project name, it will apply it across the board to all projects. +# this is useful in the case of "ly_add_gems being called for so called 'mandatory gems' inside the engine. +# if you specify a gem name with a namespace, it will be used, otherwise it will assume Gem:: +function(ly_enable_gems) + set(options) + set(oneValueArgs PROJECT_NAME GEM_FILE) + set(multiValueArgs GEMS TARGETS VARIANTS) + + cmake_parse_arguments(ly_enable_gems "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if (NOT ly_enable_gems_TARGETS) + message(FATAL_ERROR "You must provide the targets to add gems to using the TARGETS keyword") + endif() + + + if (NOT ly_enable_gems_PROJECT_NAME) + message(VERBOSE "Note: ly_enable_gems called with no PROJECT_NAME name, applying to all projects: \n" + " - VARIANTS ${ly_enable_gems_VARIANTS} \n" + " - GEMS ${ly_enable_gems_GEMS} \n" + " - TARGETS ${ly_enable_gems_TARGETS} \n" + " - GEM_FILE ${ly_enable_gems_GEM_FILE}") + set(ly_enable_gems_PROJECT_NAME "__NOPROJECT__") # so that the token is not blank + endif() + + if (NOT ly_enable_gems_VARIANTS) + message(FATAL_ERROR "You must provide at least 1 variant of the gem modules (Editor, Server, Client, Builder) to " + "add to your targets, using the VARIANTS keyword") + endif() + + if ((NOT ly_enable_gems_GEMS AND NOT ly_enable_gems_GEM_FILE) OR (ly_enable_gems_GEMS AND ly_enable_gems_GEM_FILE)) + message(FATAL_ERROR "Provide exactly one of either GEM_FILE (filename) or GEMS (list of gems) keywords.") + endif() + + if (ly_enable_gems_GEM_FILE) + set(store_temp ${ENABLED_GEMS}) + include(${ly_enable_gems_GEM_FILE} RESULT_VARIABLE was_able_to_load_the_file) + if(NOT was_able_to_load_the_file) + message(FATAL_ERROR "could not load the GEM_FILE ${ly_enable_gems_GEM_FILE}") + endif() + if(NOT ENABLED_GEMS) + message(FATAL_ERROR "GEM_FILE ${ly_enable_gems_GEM_FILE} did not set the value of ENABLED_GEMS.\n" + "Gem Files should contain set(ENABLED_GEMS ... )") + endif() + set(ly_enable_gems_GEMS ${ENABLED_GEMS}) + set(ENABLED_GEMS ${store_temp}) # restore value of ENABLED_GEMS just in case... + endif() + + # all the actual work has to be done later. + foreach(target_name ${ly_enable_gems_TARGETS}) + foreach(variant_name ${ly_enable_gems_VARIANTS}) + set_property(GLOBAL APPEND PROPERTY LY_DELAYED_ENABLE_GEMS "${ly_enable_gems_PROJECT_NAME},${target_name},${variant_name}") + set_property(GLOBAL APPEND PROPERTY LY_DELAYED_ENABLE_GEMS_"${ly_enable_gems_PROJECT_NAME},${target_name},${variant_name}" ${ly_enable_gems_GEMS}) + endforeach() + endforeach() +endfunction() + +# call this before runtime dependencies are used to add any relevant targets +# saved by the above function +function(ly_enable_gems_delayed) + get_property(ly_delayed_enable_gems GLOBAL PROPERTY LY_DELAYED_ENABLE_GEMS) + foreach(project_target_variant ${ly_delayed_enable_gems}) + # we expect a colon separated list of + # PROJECT_NAME,target_name,variant_name + string(REPLACE "," ";" project_target_variant_list "${project_target_variant}") + list(LENGTH project_target_variant_list project_target_variant_length) + if(project_target_variant_length EQUAL 0) + continue() + endif() + + if(NOT project_target_variant_length EQUAL 3) + message(FATAL_ERROR "Invalid specification of gems, expected 'project','target','variant' and got ${project_target_variant}") + endif() + + list(POP_BACK project_target_variant_list variant) + list(POP_BACK project_target_variant_list target) + list(POP_BACK project_target_variant_list project) + + get_property(gem_dependencies GLOBAL PROPERTY LY_DELAYED_ENABLE_GEMS_"${project_target_variant}") + if (NOT gem_dependencies) + continue() + endif() + + if(${project} STREQUAL "__NOPROJECT__") + # special case, apply to all + unset(PREFIX_CLAUSE) + else() + set(PREFIX_CLAUSE "PREFIX;${project}") + endif() + + if (NOT TARGET ${target}) + message(FATAL_ERROR "ly_enable_gems specified TARGET '${target}' but no such target was found.") + endif() + + # apply the list of gem targets. Adding a gem really just means adding the appropriate dependency. + foreach(gem_name ${gem_dependencies}) + # the gem name may already have a namespace. If it does, we use that one + ly_strip_target_namespace(TARGET ${gem_name} OUTPUT_VARIABLE unaliased_gem_name) + if (${unaliased_gem_name} STREQUAL ${gem_name}) + # if stripping a namespace had no effect, it had no namespace + # and we supply the default Gem:: namespace. + set(gem_name_with_namespace Gem::${gem_name}) + else() + # if stripping the namespace had an effect then we use the original + # with the namespace, instead of assuming Gem:: + set(gem_name_with_namespace ${gem_name}) + endif() + + # if the target exists, add it. + if (TARGET ${gem_name_with_namespace}.${variant}) + ly_add_target_dependencies( + ${PREFIX_CLAUSE} + TARGETS ${target} + DEPENDENT_TARGETS ${gem_name_with_namespace}.${variant} + ) + endif() + endforeach() + endforeach() +endfunction() \ No newline at end of file diff --git a/cmake/Install.cmake b/cmake/Install.cmake index b56f5ced85..205277f0e5 100644 --- a/cmake/Install.cmake +++ b/cmake/Install.cmake @@ -9,5 +9,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # -ly_get_absolute_pal_filename(pal_dir ${CMAKE_CURRENT_SOURCE_DIR}/cmake/Platform/${PAL_PLATFORM_NAME}) -include(${pal_dir}/Install_${PAL_PLATFORM_NAME_LOWERCASE}.cmake) \ No newline at end of file +if(NOT INSTALLED_ENGINE) + ly_get_absolute_pal_filename(pal_dir ${CMAKE_CURRENT_SOURCE_DIR}/cmake/Platform/${PAL_PLATFORM_NAME}) + include(${pal_dir}/Install_${PAL_PLATFORM_NAME_LOWERCASE}.cmake) +endif() \ No newline at end of file diff --git a/cmake/LYPython.cmake b/cmake/LYPython.cmake index ff5132097c..238889d829 100644 --- a/cmake/LYPython.cmake +++ b/cmake/LYPython.cmake @@ -81,7 +81,7 @@ function(update_pip_requirements requirements_file_path unique_name) set(ENV{PYTHONNOUSERSITE} 1) execute_process(COMMAND - ${LY_PYTHON_CMD} -m pip install --no-deps -r "${requirements_file_path}" --disable-pip-version-check --no-warn-script-location + ${LY_PYTHON_CMD} -m pip install -r "${requirements_file_path}" --disable-pip-version-check --no-warn-script-location WORKING_DIRECTORY ${Python_BINFOLDER} RESULT_VARIABLE PIP_RESULT OUTPUT_VARIABLE PIP_OUT @@ -265,10 +265,13 @@ if (NOT CMAKE_SCRIPT_MODE_FILE) # we also need to make sure any custom packages are installed. # this costs a moment of time though, so we'll only do it based on stamp files. + if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND NOT INSTALLED_ENGINE) + ly_pip_install_local_package_editable(${LY_ROOT_FOLDER}/Tools/LyTestTools ly-test-tools) + ly_pip_install_local_package_editable(${LY_ROOT_FOLDER}/Tools/RemoteConsole/ly_remote_console ly-remote-console) + ly_pip_install_local_package_editable(${LY_ROOT_FOLDER}/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools editor-python-test-tools) + endif() - ly_pip_install_local_package_editable(${LY_ROOT_FOLDER}/Tools/LyTestTools ly-test-tools) - ly_pip_install_local_package_editable(${LY_ROOT_FOLDER}/Tools/RemoteConsole/ly_remote_console ly-remote-console) - ly_pip_install_local_package_editable(${LY_ROOT_FOLDER}/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools editor-python-test-tools) + ly_pip_install_local_package_editable(${LY_ROOT_FOLDER}/scripts/o3de o3de) endif() endif() diff --git a/cmake/LYWrappers.cmake b/cmake/LYWrappers.cmake index 8aba6ccb99..d7f88f12ec 100644 --- a/cmake/LYWrappers.cmake +++ b/cmake/LYWrappers.cmake @@ -53,7 +53,6 @@ define_property(TARGET PROPERTY GEM_MODULE # \arg:HEADERONLY (bool) defines this target to be a header only library. A ${NAME}_HEADERS project will be created for the IDE # \arg:EXECUTABLE (bool) defines this target to be an executable # \arg:APPLICATION (bool) defines this target to be an application (executable that is not a console) -# \arg:UNKNOWN (bool) defines this target to be unknown. This is used when importing installed targets from Find files # \arg:IMPORTED (bool) defines this target to be imported. # \arg:NAMESPACE namespace declaration for this target. It will be used for IDE and dependencies # \arg:OUTPUT_NAME (optional) overrides the name of the output target. If not specified, the name will be used. @@ -77,7 +76,7 @@ define_property(TARGET PROPERTY GEM_MODULE # \arg:AUTOGEN_RULES a set of AutoGeneration rules to be passed to the AzAutoGen expansion system function(ly_add_target) - set(options STATIC GEM_STATIC SHARED MODULE GEM_MODULE HEADERONLY EXECUTABLE APPLICATION UNKNOWN IMPORTED AUTOMOC AUTOUIC AUTORCC NO_UNITY) + set(options STATIC SHARED MODULE GEM_STATIC GEM_MODULE HEADERONLY EXECUTABLE APPLICATION IMPORTED AUTOMOC AUTOUIC AUTORCC NO_UNITY) set(oneValueArgs NAME NAMESPACE OUTPUT_SUBDIRECTORY OUTPUT_NAME) set(multiValueArgs FILES_CMAKE GENERATED_FILES INCLUDE_DIRECTORIES COMPILE_DEFINITIONS BUILD_DEPENDENCIES RUNTIME_DEPENDENCIES PLATFORM_INCLUDE_FILES TARGET_PROPERTIES AUTOGEN_RULES) @@ -87,7 +86,7 @@ function(ly_add_target) if(NOT ly_add_target_NAME) message(FATAL_ERROR "You must provide a name for the target") endif() - if(NOT ly_add_target_IMPORTED) + if(NOT ly_add_target_IMPORTED AND NOT ly_add_target_HEADERONLY) if(NOT ly_add_target_FILES_CMAKE) message(FATAL_ERROR "You must provide a list of _files.cmake files for the target") endif() @@ -106,23 +105,27 @@ function(ly_add_target) ly_include_cmake_file_list(${file_cmake}) endforeach() - set(linking_options) - set(linking_count) + unset(linking_options) + unset(linking_count) + unset(target_type_options) if(ly_add_target_STATIC) set(linking_options STATIC) + set(target_type_options STATIC) set(linking_count "${linking_count}1") endif() if(ly_add_target_SHARED) set(linking_options SHARED) + set(target_type_options SHARED) set(linking_count "${linking_count}1") endif() if(ly_add_target_MODULE) set(linking_options ${PAL_LINKOPTION_MODULE}) + set(target_type_options ${PAL_LINKOPTION_MODULE}) set(linking_count "${linking_count}1") endif() - if(ly_add_target_HEADERONLY) set(linking_options INTERFACE) + set(target_type_options INTERFACE) set(linking_count "${linking_count}1") endif() if(ly_add_target_EXECUTABLE) @@ -133,12 +136,11 @@ function(ly_add_target) set(linking_options APPLICATION) set(linking_count "${linking_count}1") endif() - if(ly_add_target_UNKNOWN) - set(linking_options UNKNOWN) - set(linking_count "${linking_count}1") - endif() if(NOT ("${linking_count}" STREQUAL "1")) - message(FATAL_ERROR "More than one of the following options [STATIC | SHARED | MODULE | HEADERONLY | EXECUTABLE | APPLICATION | UNKNOWN] was specified and they are mutually exclusive") + message(FATAL_ERROR "More than one of the following options [STATIC | SHARED | MODULE | HEADERONLY | EXECUTABLE | APPLICATION ] was specified and they are mutually exclusive") + endif() + if(ly_add_target_IMPORTED) + list(APPEND target_type_options IMPORTED GLOBAL) endif() if(ly_add_target_NAMESPACE) @@ -149,29 +151,32 @@ function(ly_add_target) set(project_NAME ${ly_add_target_NAME}) if(ly_add_target_EXECUTABLE) - add_executable(${ly_add_target_NAME} + add_executable(${ly_add_target_NAME} + ${target_type_options} ${ALLFILES} ${ly_add_target_GENERATED_FILES} ) ly_apply_platform_properties(${ly_add_target_NAME}) + if(ly_add_target_IMPORTED) + set_target_properties(${ly_add_target_NAME} PROPERTIES LINKER_LANGUAGE CXX) + endif() elseif(ly_add_target_APPLICATION) - add_executable(${ly_add_target_NAME} + add_executable(${ly_add_target_NAME} + ${target_type_options} ${PAL_EXECUTABLE_APPLICATION_FLAG} ${ALLFILES} ${ly_add_target_GENERATED_FILES} ) ly_apply_platform_properties(${ly_add_target_NAME}) + if(ly_add_target_IMPORTED) + set_target_properties(${ly_add_target_NAME} PROPERTIES LINKER_LANGUAGE CXX) + endif() elseif(ly_add_target_HEADERONLY) add_library(${ly_add_target_NAME} - ${linking_options} + ${target_type_options} ${ALLFILES} ${ly_add_target_GENERATED_FILES} ) - elseif(ly_add_target_UNKNOWN) - add_library(${ly_add_target_NAME} - ${linking_options} - IMPORTED - ) else() add_library(${ly_add_target_NAME} - ${linking_options} + ${target_type_options} ${ALLFILES} ${ly_add_target_GENERATED_FILES} ) ly_apply_platform_properties(${ly_add_target_NAME}) @@ -209,7 +214,7 @@ function(ly_add_target) endif() if (ly_add_target_INCLUDE_DIRECTORIES) - ly_target_include_directories(${ly_add_target_NAME} + target_include_directories(${ly_add_target_NAME} ${ly_add_target_INCLUDE_DIRECTORIES} ) endif() @@ -306,10 +311,19 @@ function(ly_add_target) endif() # Store the target so we can walk through all of them in LocationDependencies.cmake - set_property(GLOBAL APPEND PROPERTY LY_ALL_TARGETS ${ly_add_target_NAME}) + set_property(GLOBAL APPEND PROPERTY LY_ALL_TARGETS ${interface_name}) + + # Store the aliased target into a DIRECTORY property + set_property(DIRECTORY APPEND PROPERTY LY_DIRECTORY_TARGETS ${interface_name}) + # Store the directory path in a GLOBAL property so that it can be accessed + # in the layout install logic. Skip if the directory has already been added + get_property(ly_all_target_directories GLOBAL PROPERTY LY_ALL_TARGET_DIRECTORIES) + if(NOT CMAKE_CURRENT_SOURCE_DIR IN_LIST ly_all_target_directories) + set_property(GLOBAL APPEND PROPERTY LY_ALL_TARGET_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR}) + endif() set(runtime_dependencies_list SHARED MODULE EXECUTABLE APPLICATION) - if(linking_options IN_LIST runtime_dependencies_list) + if(NOT ly_add_target_IMPORTED AND linking_options IN_LIST runtime_dependencies_list) add_custom_command(TARGET ${ly_add_target_NAME} POST_BUILD COMMAND ${CMAKE_COMMAND} -P ${CMAKE_BINARY_DIR}/runtime_dependencies/$/${ly_add_target_NAME}.cmake @@ -337,22 +351,6 @@ function(ly_add_target) ) endif() - if(NOT ly_add_target_IMPORTED) - if(NOT ly_add_target_INSTALL_COMPONENT) - set(ly_add_target_INSTALL_COMPONENT ${LY_DEFAULT_INSTALL_COMPONENT}) - endif() - - ly_install_target( - ${ly_add_target_NAME} - NAMESPACE ${ly_add_target_NAMESPACE} - INCLUDE_DIRECTORIES ${ly_add_target_INCLUDE_DIRECTORIES} - BUILD_DEPENDENCIES ${ly_add_target_BUILD_DEPENDENCIES} - RUNTIME_DEPENDENCIES ${ly_add_target_RUNTIME_DEPENDENCIES} - COMPILE_DEFINITIONS ${ly_add_target_COMPILE_DEFINITIONS} - COMPONENT ${ly_add_target_INSTALL_COMPONENT} - ) - endif() - endfunction() #! ly_target_link_libraries: wraps target_link_libraries handling also MODULE linkage. @@ -412,12 +410,10 @@ function(ly_delayed_target_link_libraries) endif() if(item_type STREQUAL MODULE_LIBRARY) - ly_target_include_directories(${target} ${visibility} $) + target_include_directories(${target} ${visibility} $) target_link_libraries(${target} ${visibility} $) target_compile_definitions(${target} ${visibility} $) target_compile_options(${target} ${visibility} $) - # Add it also as a manual dependency so runtime_dependencies walks it through - ly_add_dependencies(${target} ${item}) else() ly_parse_third_party_dependencies(${item}) target_link_libraries(${target} ${visibility} ${item}) @@ -513,7 +509,7 @@ endfunction() # Looks at the the following variables within the platform include file to set the equivalent target properties # LY_FILES_CMAKE -> extract list of files -> target_sources # LY_FILES -> target_source -# LY_INCLUDE_DIRECTORIES -> ly_target_include_directories +# LY_INCLUDE_DIRECTORIES -> target_include_directories # LY_COMPILE_DEFINITIONS -> target_compile_definitions # LY_COMPILE_OPTIONS -> target_compile_options # LY_LINK_OPTIONS -> target_link_options @@ -539,7 +535,11 @@ macro(ly_configure_target_platform_properties) message(FATAL_ERROR "The supplied PLATFORM_INCLUDE_FILE(${platform_include_file}) cannot be included.\ Parsing of target will halt") endif() - target_sources(${ly_add_target_NAME} PRIVATE ${platform_include_file}) + if(ly_add_target_HEADERONLY) + target_sources(${ly_add_target_NAME} INTERFACE ${platform_include_file}) + else() + target_sources(${ly_add_target_NAME} PRIVATE ${platform_include_file}) + endif() ly_source_groups_from_folders("${platform_include_file}") if(LY_FILES_CMAKE) @@ -555,7 +555,7 @@ macro(ly_configure_target_platform_properties) target_sources(${ly_add_target_NAME} PRIVATE ${LY_FILES}) endif() if (LY_INCLUDE_DIRECTORIES) - ly_target_include_directories(${ly_add_target_NAME} ${LY_INCLUDE_DIRECTORIES}) + target_include_directories(${ly_add_target_NAME} ${LY_INCLUDE_DIRECTORIES}) endif() if(LY_COMPILE_DEFINITIONS) target_compile_definitions(${ly_add_target_NAME} ${LY_COMPILE_DEFINITIONS}) @@ -658,42 +658,6 @@ function(ly_add_source_properties) endfunction() -function(ly_target_include_directories TARGET) - - # Add the includes to the build and install interface - set(reserved_keywords PRIVATE PUBLIC INTERFACE) - unset(last_keyword) - foreach(include ${ARGN}) - if(${include} IN_LIST reserved_keywords) - list(APPEND adapted_includes ${include}) - elseif(IS_ABSOLUTE ${include}) - list(APPEND adapted_includes - $ - ) - else() - string(GENEX_STRIP ${include} include_genex_expr) - if(include_genex_expr STREQUAL include) # only for cases where there are no generation expressions - # We will be installing the includes using the same directory structure used in our source tree. - # The INSTALL_INTERFACE path tells CMake the location of the includes relative to the install prefix. - # When the target is imported into an external project, cmake will find these includes at /include/ - # where is the location of the lumberyard install on disk. - file(REAL_PATH ${include} include_real) - file(RELATIVE_PATH install_dir ${CMAKE_SOURCE_DIR} ${include_real}) - list(APPEND adapted_includes - $ - $ - ) - else() - list(APPEND adapted_includes - ${include} - ) - endif() - endif() - endforeach() - target_include_directories(${TARGET} ${adapted_includes}) - -endfunction() - #! ly_project_add_subdirectory: calls add_subdirectory() if the project name is in the project list # @@ -735,7 +699,6 @@ function(ly_de_alias_target target_name output_variable_name) while(target_name) set(de_aliased_target_name ${target_name}) - get_target_property(target_name ${target_name} ALIASED_TARGET) endwhile() diff --git a/cmake/LyAutoGen.cmake b/cmake/LyAutoGen.cmake index 16a8a8de55..aa0e7f8d5a 100644 --- a/cmake/LyAutoGen.cmake +++ b/cmake/LyAutoGen.cmake @@ -26,7 +26,7 @@ function(ly_add_autogen) if(ly_add_autogen_AUTOGEN_RULES) set(AZCG_INPUTFILES ${ly_add_autogen_ALLFILES}) list(FILTER AZCG_INPUTFILES INCLUDE REGEX ".*\.(xml|json|jinja)$") - ly_target_include_directories(${ly_add_autogen_NAME} PUBLIC "${CMAKE_CURRENT_BINARY_DIR}/Azcg/Generated") + target_include_directories(${ly_add_autogen_NAME} PUBLIC "${CMAKE_CURRENT_BINARY_DIR}/Azcg/Generated") execute_process( COMMAND ${LY_PYTHON_CMD} "${LY_ROOT_FOLDER}/Code/Framework/AzAutoGen/AzAutoGen.py" "${CMAKE_BINARY_DIR}/Azcg/TemplateCache/" "${CMAKE_CURRENT_BINARY_DIR}/Azcg/Generated/" "${CMAKE_CURRENT_SOURCE_DIR}" "${AZCG_INPUTFILES}" "${ly_add_autogen_AUTOGEN_RULES}" "-n" OUTPUT_VARIABLE AUTOGEN_OUTPUTS diff --git a/cmake/O3DEJson.cmake b/cmake/O3DEJson.cmake new file mode 100644 index 0000000000..ab5f95bc8c --- /dev/null +++ b/cmake/O3DEJson.cmake @@ -0,0 +1,62 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +include_guard() + +set(LY_EXTERNAL_SUBDIRS "" CACHE STRING "List of subdirectories to recurse into when running cmake against the engine's CMakeLists.txt") + +#! read_json_external_subdirs +# Read the "external_subdirectories" array from a *.json file +# External subdirectories are any folders with CMakeLists.txt in them +# This could be regular subdirectories, Gems(contains an additional gem.json), +# Restricted folders(contains an additional restricted.json), etc... +# +# \arg:output_external_subdirs name of output variable to store external subdirectories into +# \arg:input_json_path path to the *.json file to load and read the external subdirectories from +# \return: external subdirectories as is from the json file. +function(read_json_external_subdirs output_external_subdirs input_json_path) + o3de_read_json_array(json_array ${input_json_path} "external_subdirectories") + set(${output_external_subdirs} ${json_array} PARENT_SCOPE) +endfunction() + +#! read_json_array +# Reads the a json array field into a cmake list variable +function(o3de_read_json_array read_output_array input_json_path array_key) + file(READ ${input_json_path} manifest_json_data) + string(JSON array_count ERROR_VARIABLE manifest_json_error + LENGTH ${manifest_json_data} ${array_key}) + if(manifest_json_error) + # There is no key, return + return() + endif() + + if(array_count GREATER 0) + math(EXPR array_range "${array_count}-1") + foreach(array_index RANGE ${array_range}) + string(JSON array_element ERROR_VARIABLE manifest_json_error + GET ${manifest_json_data} ${array_key} "${array_index}") + if(manifest_json_error) + message(FATAL_ERROR "Error reading field at index ${array_index} in \"${array_key}\" JSON array: ${manifest_json_error}") + endif() + list(APPEND array_elements ${array_element}) + endforeach() + endif() + set(${read_output_array} ${array_elements} PARENT_SCOPE) +endfunction() + +function(o3de_read_json_key output_value input_json_path key) + file(READ ${input_json_path} manifest_json_data) + string(JSON value ERROR_VARIABLE manifest_json_error GET ${manifest_json_data} ${key}) + if(manifest_json_error) + message(FATAL_ERROR "Error reading field at key ${key} in file \"${input_json_path}\" : ${manifest_json_error}") + endif() + set(${output_value} ${value} PARENT_SCOPE) +endfunction() diff --git a/cmake/OutputDirectory.cmake b/cmake/OutputDirectory.cmake index 9055802d39..5fe5c7a957 100644 --- a/cmake/OutputDirectory.cmake +++ b/cmake/OutputDirectory.cmake @@ -13,4 +13,8 @@ set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib CACHE PATH "Build directory for static libraries and import libraries") set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin CACHE PATH "Build directory for shared libraries") set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin CACHE PATH "Build directory for executables") -set(CMAKE_INSTALL_PREFIX ${CMAKE_BINARY_DIR}/install CACHE PATH "Installation prefix") + +# We install outside of the binary dir because our install support muliple platforms to +# be installed together. We also have an exclusion rule in the AP that filters out the +# "build" folder which is a common binary dir +set(CMAKE_INSTALL_PREFIX ${CMAKE_SOURCE_DIR}/install CACHE PATH "Installation prefix") diff --git a/cmake/PAL.cmake b/cmake/PAL.cmake index 7802ac8c58..dca54e4731 100644 --- a/cmake/PAL.cmake +++ b/cmake/PAL.cmake @@ -22,7 +22,105 @@ file(GLOB detection_files "cmake/Platform/*/PALDetection_*.cmake") foreach(detection_file ${detection_files}) include(${detection_file}) endforeach() -file(GLOB detection_files ${o3de_engine_restricted_path}/*/cmake/PALDetection_*.cmake) + + +#! o3de_restricted_id: Reads the "restricted" key from the o3de manifest +# +# \arg:o3de_json_file name of the o3de json file to read the "restricted_name" key from +# \arg:restricted returns the restricted association element from an o3de json, otherwise engine 'o3de' is assumed +# \arg:o3de_json_file name of the o3de json file +function(o3de_restricted_id o3de_json_file restricted) + file(READ ${o3de_json_file} json_data) + string(JSON restricted_entry ERROR_VARIABLE json_error GET ${json_data} "restricted_name") + if(json_error) + message(WARNING "Unable to read restricted from '${o3de_json_file}', error: ${json_error}") + endif() + if(restricted_entry) + set(${restricted} ${restricted_entry} PARENT_SCOPE) + endif() +endfunction() + +#! o3de_find_restricted_folder: +# +# \arg:restricted_path returns the path of the o3de restricted folder with name restricted_name +# \arg:restricted_name name of the restricted +function(o3de_find_restricted_folder restricted_name restricted_path) + # Read the restricted path from engine.json if one EXISTS + file(READ ${LY_ROOT_FOLDER}/engine.json engine_json_data) + string(JSON restricted_subdirs_count ERROR_VARIABLE engine_json_error LENGTH ${engine_json_data} "restricted") + if(restricted_subdirs_count GREATER 0) + string(JSON restricted_subdir ERROR_VARIABLE engine_json_error GET ${engine_json_data} "restricted" "0") + set(${restricted_path} ${restricted_subdir} PARENT_SCOPE) + return() + endif() + + + file(TO_CMAKE_PATH "$ENV{USERPROFILE}" home_directory) # Windows + if(NOT EXISTS ${home_directory}) + file(TO_CMAKE_PATH "$ENV{HOME}" home_directory) # Unix + if (NOT EXISTS ${home_directory}) + return() + endif() + endif() + + # Examine the o3de manifest file for the list of restricted directories + set(o3de_manifest_path ${home_directory}/.o3de/o3de_manifest.json) + if(EXISTS ${o3de_manifest_path}) + file(READ ${o3de_manifest_path} o3de_manifest_json_data) + string(JSON restricted_subdirs_count ERROR_VARIABLE engine_json_error LENGTH ${o3de_manifest_json_data} "restricted") + if(restricted_subdirs_count GREATER 0) + math(EXPR restricted_subdirs_range "${restricted_subdirs_count}-1") + foreach(restricted_subdir_index RANGE ${restricted_subdirs_range}) + string(JSON restricted_subdir ERROR_VARIABLE engine_json_error GET ${o3de_manifest_json_data} "restricted" "${restricted_subdir_index}") + list(APPEND restricted_subdirs ${restricted_subdir}) + endforeach() + endif() + endif() + # Iterate over the restricted directories from the manifest file + foreach(restricted_entry ${restricted_subdirs}) + set(restricted_json_file ${restricted_entry}/restricted.json) + file(READ ${restricted_json_file} restricted_json) + string(JSON this_restricted_name ERROR_VARIABLE json_error GET ${restricted_json} "restricted_name") + if(json_error) + message(WARNING "Unable to read restricted_name from '${restricted_json_file}', error: ${json_error}") + else() + if(this_restricted_name STREQUAL restricted_name) + set(${restricted_path} ${restricted_entry} PARENT_SCOPE) + return() + endif() + endif() + endforeach() +endfunction() + + +#! o3de_restricted_path: +# +# \arg:o3de_json_file json file to read restricted id from +# \arg:restricted_name name of the restricted object +function(o3de_restricted_path o3de_json_file restricted_path) + o3de_restricted_id(${o3de_json_file} restricted_name) + if(restricted_name) + o3de_find_restricted_folder(${restricted_name} restricted_folder) + if(restricted_folder) + set(${restricted_path} ${restricted_folder} PARENT_SCOPE) + endif() + endif() +endfunction() + +#! read_engine_restricted_path: Locates the restricted path within the engine from a json file +# +# \arg:output_restricted_path returns the path of the o3de restricted folder with name restricted_name +function(read_engine_restricted_path output_restricted_path) + # Set manifest path to path in the user home directory + set(manifest_path ${LY_ROOT_FOLDER}/engine.json) + if(EXISTS ${manifest_path}) + o3de_restricted_path(${manifest_path} output_restricted_path) + endif() +endfunction() + +read_engine_restricted_path(O3DE_ENGINE_RESTRICTED_PATH) + +file(GLOB detection_files ${O3DE_ENGINE_RESTRICTED_PATH}/*/cmake/PALDetection_*.cmake) foreach(detection_file ${detection_files}) include(${detection_file}) endforeach() @@ -37,8 +135,8 @@ ly_set(PAL_HOST_PLATFORM_NAME_LOWERCASE ${PAL_HOST_PLATFORM_NAME_LOWERCASE}) set(PAL_RESTRICTED_PLATFORMS) -string(LENGTH ${o3de_engine_restricted_path} engine_restricted_length) -file(GLOB pal_restricted_files ${o3de_engine_restricted_path}/*/cmake/PAL_*.cmake) +string(LENGTH "${O3DE_ENGINE_RESTRICTED_PATH}" engine_restricted_length) +file(GLOB pal_restricted_files ${O3DE_ENGINE_RESTRICTED_PATH}/*/cmake/PAL_*.cmake) foreach(pal_restricted_file ${pal_restricted_files}) string(FIND ${pal_restricted_file} "/cmake/PAL" end) if(${end} GREATER -1) @@ -109,18 +207,18 @@ function(ly_get_absolute_pal_filename out_name in_name) else() # The user has not supplied any path so we must assume it is the o3de engine restricted and o3de engine path # if the file is not in the o3de engine path then we cannot determine a PAL file for it - file(RELATIVE_PATH relative_path ${o3de_engine_path} ${full_name}) + file(RELATIVE_PATH relative_path ${LY_ROOT_FOLDER} ${full_name}) if (NOT (IS_ABSOLUTE relative_path OR relative_path MATCHES [[^(\.\./)+(.*)]])) if (NOT EXISTS ${full_name}) - string(REGEX MATCH "${o3de_engine_path}/(.*)/Platform/([^/]*)/?(.*)$" match ${full_name}) + string(REGEX MATCH "${LY_ROOT_FOLDER}/(.*)/Platform/([^/]*)/?(.*)$" match ${full_name}) if(NOT CMAKE_MATCH_1) - string(REGEX MATCH "${o3de_engine_path}/Platform/([^/]*)/?(.*)$" match ${full_name}) - set(full_name ${o3de_engine_restricted_path}/${CMAKE_MATCH_1}) + string(REGEX MATCH "${LY_ROOT_FOLDER}/Platform/([^/]*)/?(.*)$" match ${full_name}) + set(full_name ${O3DE_ENGINE_RESTRICTED_PATH}/${CMAKE_MATCH_1}) if(CMAKE_MATCH_2) string(APPEND full_name "/" ${CMAKE_MATCH_2}) endif() elseif("${CMAKE_MATCH_2}" IN_LIST PAL_RESTRICTED_PLATFORMS) - set(full_name ${o3de_engine_restricted_path}/${CMAKE_MATCH_2}/${CMAKE_MATCH_1}) + set(full_name ${O3DE_ENGINE_RESTRICTED_PATH}/${CMAKE_MATCH_2}/${CMAKE_MATCH_1}) if(CMAKE_MATCH_3) string(APPEND full_name "/" ${CMAKE_MATCH_3}) endif() @@ -149,25 +247,3 @@ set(LY_DISABLE_TEST_MODULES FALSE CACHE BOOL "Option to forcibly disable the inc if(LY_DISABLE_TEST_MODULES) ly_set(PAL_TRAIT_BUILD_TESTS_SUPPORTED FALSE) endif() - -################################################################################ -# Add each restricted platform in the engines restricted folder -# If the enabled restricted platform does not have a folder add one. -# If the restricted platform folder does not have a CMakeLists.txt, create one -# so the add_subdirectory on the external folder does not fail. -################################################################################ -function(o3de_add_engine_restricted_platform_external_subdirs) - foreach(restricted_platform ${PAL_RESTRICTED_PLATFORMS}) - if(restricted_platform IN_LIST enabled_platforms) - set(o3de_engine_restricted_platform_folder ${o3de_engine_restricted_path}/${restricted_platform}) - if(NOT EXISTS ${o3de_engine_restricted_platform_folder}) - file(MAKE_DIRECTORY ${o3de_engine_restricted_platform_folder}) - endif() - set(o3de_engine_restricted_platform_folder_cmakelists ${o3de_engine_restricted_platform_folder}/CMakeLists.txt) - if(NOT EXISTS ${o3de_engine_restricted_platform_folder_cmakelists}) - file(TOUCH ${o3de_engine_restricted_platform_folder_cmakelists}) - endif() - list(APPEND LY_EXTERNAL_SUBDIRS ${o3de_engine_restricted_platform_folder}) - endif() - endforeach() -endfunction() diff --git a/cmake/Platform/Common/Install_common.cmake b/cmake/Platform/Common/Install_common.cmake index 8fe2fe2c1c..b18aed6fb4 100644 --- a/cmake/Platform/Common/Install_common.cmake +++ b/cmake/Platform/Common/Install_common.cmake @@ -11,52 +11,57 @@ set(CMAKE_INSTALL_MESSAGE NEVER) # Simplify messages to reduce output noise -ly_set(LY_DEFAULT_INSTALL_COMPONENT "Core") +ly_set(LY_DEFAULT_INSTALL_COMPONENT Core) -#! ly_install_target: registers the target to be installed by cmake install. -# -# \arg:NAME name of the target -# \arg:COMPONENT the grouping string of the target used for splitting up the install -# into smaller packages. -# All other parameters are forwarded to ly_generate_target_find_file -function(ly_install_target ly_install_target_NAME) +file(RELATIVE_PATH runtime_output_directory ${CMAKE_BINARY_DIR} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) +file(RELATIVE_PATH library_output_directory ${CMAKE_BINARY_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}) +set(install_output_folder "${CMAKE_INSTALL_PREFIX}/${runtime_output_directory}/${PAL_PLATFORM_NAME}/$") - set(options) - set(oneValueArgs NAMESPACE COMPONENT) - set(multiValueArgs INCLUDE_DIRECTORIES BUILD_DEPENDENCIES RUNTIME_DEPENDENCIES COMPILE_DEFINITIONS) - cmake_parse_arguments(ly_install_target "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) +#! ly_setup_target: Setup the data needed to re-create the cmake target commands for a single target +function(ly_setup_target OUTPUT_CONFIGURED_TARGET ALIAS_TARGET_NAME) + # De-alias target name + ly_de_alias_target(${ALIAS_TARGET_NAME} TARGET_NAME) - # All include directories marked PUBLIC or INTERFACE will be installed + # All include directories marked PUBLIC or INTERFACE will be installed. We dont use PUBLIC_HEADER because in order to do that + # we need to set the PUBLIC_HEADER property of the target for all the headers we are exporting. After doing that, installing the + # headers end up in one folder instead of duplicating the folder structure of the public/interface include directory. + # Instead, we install them with install(DIRECTORY) set(include_location "include") - get_target_property(include_directories ${ly_install_target_NAME} INTERFACE_INCLUDE_DIRECTORIES) - + get_target_property(include_directories ${TARGET_NAME} INTERFACE_INCLUDE_DIRECTORIES) if (include_directories) - set_target_properties(${ly_install_target_NAME} PROPERTIES PUBLIC_HEADER "${include_directories}") - # The include directories are specified relative to the CMakeLists.txt file that adds the target. - # We need to install the includes relative to our source tree root because that's where INSTALL_INTERFACE - # will point CMake when it looks for headers - file(RELATIVE_PATH relative_path ${CMAKE_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}) - string(APPEND include_location "/${relative_path}") + unset(public_headers) + foreach(include_directory ${include_directories}) + string(GENEX_STRIP ${include_directory} include_genex_expr) + if(include_genex_expr STREQUAL include_directory) # only for cases where there are no generation expressions + unset(current_public_headers) + install(DIRECTORY ${include_directory} + DESTINATION ${include_location}/${target_source_dir} + COMPONENT ${ly_install_target_COMPONENT} + FILES_MATCHING + PATTERN *.h + PATTERN *.hpp + PATTERN *.inl + ) + endif() + endforeach() endif() # Get the output folders, archive is always the same, but runtime/library can be in subfolders defined per target file(RELATIVE_PATH archive_output_directory ${CMAKE_BINARY_DIR} ${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}) - get_target_property(target_runtime_output_directory ${ly_install_target_NAME} RUNTIME_OUTPUT_DIRECTORY) + get_target_property(target_runtime_output_directory ${TARGET_NAME} RUNTIME_OUTPUT_DIRECTORY) if(target_runtime_output_directory) file(RELATIVE_PATH target_runtime_output_subdirectory ${CMAKE_RUNTIME_OUTPUT_DIRECTORY} ${target_runtime_output_directory}) endif() - file(RELATIVE_PATH runtime_output_directory ${CMAKE_BINARY_DIR} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) - get_target_property(target_library_output_directory ${ly_install_target_NAME} LIBRARY_OUTPUT_DIRECTORY) + get_target_property(target_library_output_directory ${TARGET_NAME} LIBRARY_OUTPUT_DIRECTORY) if(target_library_output_directory) file(RELATIVE_PATH target_library_output_subdirectory ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${target_library_output_directory}) endif() - file(RELATIVE_PATH library_output_directory ${CMAKE_BINARY_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}) install( - TARGETS ${ly_install_target_NAME} + TARGETS ${TARGET_NAME} ARCHIVE DESTINATION ${archive_output_directory}/${PAL_PLATFORM_NAME}/$ COMPONENT ${ly_install_target_COMPONENT} @@ -66,151 +71,184 @@ function(ly_install_target ly_install_target_NAME) RUNTIME DESTINATION ${runtime_output_directory}/${PAL_PLATFORM_NAME}/$/${target_runtime_output_subdirectory} COMPONENT ${ly_install_target_COMPONENT} - PUBLIC_HEADER - DESTINATION ${include_location} - COMPONENT ${ly_install_target_COMPONENT} ) - ly_generate_target_find_file( - NAME ${ly_install_target_NAME} - ${ARGN} - ) - ly_generate_target_config_file(${ly_install_target_NAME}) - install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${ly_install_target_NAME}_$.cmake" - DESTINATION cmake_autogen/${ly_install_target_NAME} - COMPONENT ${ly_install_target_COMPONENT} - ) - install(FILES "${CMAKE_CURRENT_BINARY_DIR}/Find${ly_install_target_NAME}.cmake" - DESTINATION cmake - COMPONENT ${ly_install_target_COMPONENT} - ) - -endfunction() - - -#! ly_generate_target_find_file: generates the Find${target}.cmake file which is used when importing installed packages. -# -# \arg:NAME name of the target -# \arg:NAMESPACE namespace declaration for this target. It will be used for IDE and dependencies -# \arg:INCLUDE_DIRECTORIES paths to the include directories -# \arg:BUILD_DEPENDENCIES list of interfaces this target depends on (could be a compilation dependency -# if the dependency is only exposing an include path, or could be a linking -# dependency is exposing a lib) -# \arg:RUNTIME_DEPENDENCIES list of dependencies this target depends on at runtime -# \arg:COMPILE_DEFINITIONS list of compilation definitions this target will use to compile -function(ly_generate_target_find_file) - - set(options) - set(oneValueArgs NAME NAMESPACE) - set(multiValueArgs INCLUDE_DIRECTORIES COMPILE_DEFINITIONS BUILD_DEPENDENCIES RUNTIME_DEPENDENCIES) - cmake_parse_arguments(ly_generate_target_find_file "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) - - set(NAME_PLACEHOLDER ${ly_generate_target_find_file_NAME}) - unset(NAMESPACE_PLACEHOLDER) - unset(COMPILE_DEFINITIONS_PLACEHOLDER) - unset(include_directories_interface_props) - unset(INCLUDE_DIRECTORIES_PLACEHOLDER) - set(RUNTIME_DEPENDENCIES_PLACEHOLDER ${ly_generate_target_find_file_RUNTIME_DEPENDENCIES}) - - # These targets will be imported. We will expose PUBLIC and INTERFACE properties as INTERFACE properties since - # only INTERFACE properties can be exposed on imported targets - ly_strip_private_properties(COMPILE_DEFINITIONS_PLACEHOLDER ${ly_generate_target_find_file_COMPILE_DEFINITIONS}) - ly_strip_private_properties(include_directories_interface_props ${ly_generate_target_find_file_INCLUDE_DIRECTORIES}) - ly_strip_private_properties(BUILD_DEPENDENCIES_PLACEHOLDER ${ly_generate_target_find_file_BUILD_DEPENDENCIES}) - - if(ly_generate_target_find_file_NAMESPACE) - set(NAMESPACE_PLACEHOLDER "NAMESPACE ${ly_generate_target_find_file_NAMESPACE}") + # CMakeLists.txt file + string(REGEX MATCH "(.*)::(.*)$" match ${ALIAS_TARGET_NAME}) + if(match) + set(NAMESPACE_PLACEHOLDER "NAMESPACE ${CMAKE_MATCH_1}") + set(NAME_PLACEHOLDER ${CMAKE_MATCH_2}) + else() + set(NAMESPACE_PLACEHOLDER "") + set(NAME_PLACEHOLDER ${TARGET_NAME}) endif() - string(REPLACE ";" "\n" COMPILE_DEFINITIONS_PLACEHOLDER "${COMPILE_DEFINITIONS_PLACEHOLDER}") + set(TARGET_TYPE_PLACEHOLDER "") + get_target_property(target_type ${NAME_PLACEHOLDER} TYPE) + # Remove the _LIBRARY since we dont need to pass that to ly_add_targets + string(REPLACE "_LIBRARY" "" TARGET_TYPE_PLACEHOLDER ${target_type}) + # For HEADER_ONLY libs we end up generating "INTERFACE" libraries, need to specify HEADERONLY instead + string(REPLACE "INTERFACE" "HEADERONLY" TARGET_TYPE_PLACEHOLDER ${TARGET_TYPE_PLACEHOLDER}) + if(TARGET_TYPE_PLACEHOLDER STREQUAL "MODULE") + get_target_property(gem_module ${NAME_PLACEHOLDER} GEM_MODULE) + if(gem_module) + set(TARGET_TYPE_PLACEHOLDER "GEM_MODULE") + endif() + endif() + + get_target_property(COMPILE_DEFINITIONS_PLACEHOLDER ${TARGET_NAME} INTERFACE_COMPILE_DEFINITIONS) + if(COMPILE_DEFINITIONS_PLACEHOLDER) + string(REPLACE ";" "\n" COMPILE_DEFINITIONS_PLACEHOLDER "${COMPILE_DEFINITIONS_PLACEHOLDER}") + else() + unset(COMPILE_DEFINITIONS_PLACEHOLDER) + endif() # Includes need additional processing to add the install root - foreach(include ${include_directories_interface_props}) - set(installed_include_prefix "\${LY_ROOT_FOLDER}/include/") - file(RELATIVE_PATH relative_path ${CMAKE_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/${include}) - list(APPEND INCLUDE_DIRECTORIES_PLACEHOLDER "include/${relative_path}") - endforeach() - string(REPLACE ";" "\n" INCLUDE_DIRECTORIES_PLACEHOLDER "${INCLUDE_DIRECTORIES_PLACEHOLDER}") + if(include_directories) + foreach(include ${include_directories}) + string(GENEX_STRIP ${include} include_genex_expr) + if(include_genex_expr STREQUAL include) # only for cases where there are no generation expressions + file(RELATIVE_PATH relative_include ${absolute_target_source_dir} ${include}) + string(APPEND INCLUDE_DIRECTORIES_PLACEHOLDER "\${LY_ROOT_FOLDER}/include/${target_source_dir}/${relative_include}\n") + endif() + endforeach() + endif() - string(REPLACE ";" "\n" BUILD_DEPENDENCIES_PLACEHOLDER "${BUILD_DEPENDENCIES_PLACEHOLDER}") - string(REPLACE ";" "\n" RUNTIME_DEPENDENCIES_PLACEHOLDER "${RUNTIME_DEPENDENCIES_PLACEHOLDER}") + 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 + string(REPLACE ";" "\n" RUNTIME_DEPENDENCIES_PLACEHOLDER "${RUNTIME_DEPENDENCIES_PLACEHOLDER}") + else() + unset(RUNTIME_DEPENDENCIES_PLACEHOLDER) + endif() - configure_file(${LY_ROOT_FOLDER}/cmake/FindTarget.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/Find${ly_generate_target_find_file_NAME}.cmake @ONLY) - -endfunction() - - -#! ly_generate_target_config_file: generates the ${target}_$.cmake files for a target -# -# The generated file will set the location of the target binary per configuration -# These per config files will be included by the target's find file to set the location of the binary/ -# \arg:NAME name of the target -function(ly_generate_target_config_file NAME) - - get_target_property(target_type ${NAME} TYPE) + get_target_property(inteface_build_dependencies_props ${TARGET_NAME} INTERFACE_LINK_LIBRARIES) + unset(INTERFACE_BUILD_DEPENDENCIES_PLACEHOLDER) + if(inteface_build_dependencies_props) + foreach(build_dependency ${inteface_build_dependencies_props}) + # Skip wrapping produced when targets are not created in the same directory + if(NOT ${build_dependency} MATCHES "^::@") + list(APPEND INTERFACE_BUILD_DEPENDENCIES_PLACEHOLDER "${build_dependency}") + endif() + endforeach() + endif() + # We also need to pass the private link libraries since we will use that to generate the runtime dependencies + get_target_property(private_build_dependencies_props ${TARGET_NAME} LINK_LIBRARIES) + if(private_build_dependencies_props) + foreach(build_dependency ${private_build_dependencies_props}) + # Skip wrapping produced when targets are not created in the same directory + if(NOT ${build_dependency} MATCHES "^::@") + list(APPEND INTERFACE_BUILD_DEPENDENCIES_PLACEHOLDER "${build_dependency}") + endif() + endforeach() + endif() + list(REMOVE_DUPLICATES INTERFACE_BUILD_DEPENDENCIES_PLACEHOLDER) + string(REPLACE ";" "\n" INTERFACE_BUILD_DEPENDENCIES_PLACEHOLDER "${INTERFACE_BUILD_DEPENDENCIES_PLACEHOLDER}") + # Config file set(target_file_contents "# Generated by O3DE install\n\n") if(NOT target_type STREQUAL INTERFACE_LIBRARY) unset(target_location) set(runtime_types EXECUTABLE APPLICATION) if(target_type IN_LIST runtime_types) - string(APPEND target_location "\"\${LY_ROOT_FOLDER}/${runtime_output_directory}/${PAL_PLATFORM_NAME}/$/${target_runtime_output_subdirectory}/$\"") + set(target_location "\${LY_ROOT_FOLDER}/${runtime_output_directory}/${PAL_PLATFORM_NAME}/$/${target_runtime_output_subdirectory}/$") elseif(target_type STREQUAL MODULE_LIBRARY) - string(APPEND target_location "\"\${LY_ROOT_FOLDER}/${library_output_directory}/${PAL_PLATFORM_NAME}/$/${target_library_output_subdirectory}/$\"") + set(target_location "\${LY_ROOT_FOLDER}/${library_output_directory}/${PAL_PLATFORM_NAME}/$/${target_library_output_subdirectory}/$") elseif(target_type STREQUAL SHARED_LIBRARY) - string(APPEND target_location "\"\${LY_ROOT_FOLDER}/${archive_output_directory}/${PAL_PLATFORM_NAME}/$/$\"") - string(APPEND target_file_contents "ly_add_dependencies(${NAME} \"\${LY_ROOT_FOLDER}/${library_output_directory}/${PAL_PLATFORM_NAME}/$/${target_library_output_subdirectory}/$\")\n") + string(APPEND target_file_contents "set_property(TARGET ${TARGET_NAME} PROPERTY IMPORTED_IMPLIB_$ \"\${LY_ROOT_FOLDER}/${archive_output_directory}/${PAL_PLATFORM_NAME}/$/$\")\n") + set(target_location "\${LY_ROOT_FOLDER}/${library_output_directory}/${PAL_PLATFORM_NAME}/$/${target_library_output_subdirectory}/$") else() # STATIC_LIBRARY, OBJECT_LIBRARY, INTERFACE_LIBRARY - string(APPEND target_location "\"\${LY_ROOT_FOLDER}/${archive_output_directory}/${PAL_PLATFORM_NAME}/$/$\"") + set(target_location "\${LY_ROOT_FOLDER}/${archive_output_directory}/${PAL_PLATFORM_NAME}/$/$") endif() - string(APPEND target_file_contents -"set(target_location ${target_location}) -set_target_properties(${NAME} - PROPERTIES - $<$:IMPORTED_LOCATION \"\${target_location}\"> - IMPORTED_LOCATION_$> \"\${target_location}\" + if(target_location) + string(APPEND target_file_contents +"set_property(TARGET ${TARGET_NAME} + APPEND_STRING PROPERTY IMPORTED_LOCATION + $<$$:${target_location}$ +) +set_property(TARGET ${TARGET_NAME} + PROPERTY IMPORTED_LOCATION_$> + ${target_location} ) -if(EXISTS \"\${target_location}\") - set(${NAME}_$_FOUND TRUE) -else() - set(${NAME}_$_FOUND FALSE) -endif() ") + endif() endif() - file(GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${NAME}_$.cmake" CONTENT "${target_file_contents}") + file(GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/install/${target_source_dir}/${NAME_PLACEHOLDER}_$.cmake" CONTENT "${target_file_contents}") + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/install/${target_source_dir}/${NAME_PLACEHOLDER}_$.cmake" + DESTINATION ${target_source_dir} + COMPONENT ${ly_install_target_COMPONENT} + ) + # Since a CMakeLists.txt could contain multiple targets, we generate it in a folder per target + file(READ ${LY_ROOT_FOLDER}/cmake/install/InstalledTarget.in target_cmakelists_template) + string(CONFIGURE ${target_cmakelists_template} output_cmakelists_data @ONLY) + set(${OUTPUT_CONFIGURED_TARGET} ${output_cmakelists_data} PARENT_SCOPE) +endfunction() + +#! ly_setup_subdirectories: setups all targets on a per directory basis +function(ly_setup_subdirectories) + get_property(all_subdirectories GLOBAL PROPERTY LY_ALL_TARGET_DIRECTORIES) + foreach(target IN LISTS all_subdirectories) + ly_setup_subdirectory(${target}) + endforeach() endfunction() -#! ly_strip_private_properties: strips private properties since we're exporting an interface target -# -# \arg:INTERFACE_PROPERTIES list of interface properties to be returned -function(ly_strip_private_properties INTERFACE_PROPERTIES) - set(reserved_keywords PRIVATE PUBLIC INTERFACE) - unset(last_keyword) - unset(stripped_props) - foreach(prop ${ARGN}) - if(${prop} IN_LIST reserved_keywords) - set(last_keyword ${prop}) - else() - if (NOT last_keyword STREQUAL "PRIVATE") - list(APPEND stripped_props ${prop}) - endif() - endif() +#! ly_setup_subdirectory: setup all targets in the subdirectory +function(ly_setup_subdirectory absolute_target_source_dir) + + # The builtin BUILDSYSTEM_TARGETS property isn't being used here as that returns the de-alised + # TARGET and we need the alias namespace for recreating the CMakeLists.txt in the install layout + get_property(ALIAS_TARGETS_NAME DIRECTORY ${absolute_target_source_dir} PROPERTY LY_DIRECTORY_TARGETS) + file(RELATIVE_PATH target_source_dir ${LY_ROOT_FOLDER} ${absolute_target_source_dir}) + foreach(ALIAS_TARGET_NAME IN LISTS ALIAS_TARGETS_NAME) + ly_setup_target(configured_target ${ALIAS_TARGET_NAME}) + string(APPEND all_configured_targets "${configured_target}") endforeach() - set(${INTERFACE_PROPERTIES} ${stripped_props} PARENT_SCOPE) -endfunction() + # Replicate the ly_create_alias() calls based on the SOURCE_DIR for each target that generates an installed CMakeLists.txt + string(JOIN "\n" create_alias_template + "if(NOT TARGET @ALIAS_NAME@)" + " ly_create_alias(NAME @ALIAS_NAME@ NAMESPACE @ALIAS_NAMESPACE@ TARGETS @ALIAS_TARGETS@)" + "endif()" + "" + ) + get_property(create_alias_commands_arg_list DIRECTORY ${absolute_target_source_dir} PROPERTY LY_CREATE_ALIAS_ARGUMENTS) + foreach(create_alias_single_command_arg_list ${create_alias_commands_arg_list}) + # Split the ly_create_alias arguments back out based on commas + string(REPLACE "," ";" create_alias_single_command_arg_list "${create_alias_single_command_arg_list}") + list(POP_FRONT create_alias_single_command_arg_list ALIAS_NAME) + list(POP_FRONT create_alias_single_command_arg_list ALIAS_NAMESPACE) + # The rest of the list are the target dependencies + set(ALIAS_TARGETS ${create_alias_single_command_arg_list}) + string(CONFIGURE "${create_alias_template}" create_alias_command @ONLY) + string(APPEND CREATE_ALIASES_PLACEHOLDER ${create_alias_command}) + endforeach() + file(READ ${LY_ROOT_FOLDER}/cmake/install/Copyright.in cmake_copyright_comment) + # Write out all the agreegated ly_add_target function calls and the final ly_create_alias() calls to the target CMakeList.txt + file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/install/${target_source_dir}/CMakeLists.txt + "${cmake_copyright_comment}" + "${all_configured_targets}" + "\n" + "${CREATE_ALIASES_PLACEHOLDER}" + ) + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/install/${target_source_dir}/CMakeLists.txt" + DESTINATION ${target_source_dir} + COMPONENT ${ly_install_target_COMPONENT} + ) + +endfunction() #! ly_setup_o3de_install: orchestrates the installation of the different parts. This is the entry point from the root CMakeLists.txt function(ly_setup_o3de_install) + ly_setup_subdirectories() ly_setup_cmake_install() ly_setup_target_generator() + ly_setup_runtime_dependencies() ly_setup_others() endfunction() @@ -218,16 +256,35 @@ endfunction() #! ly_setup_cmake_install: install the "cmake" folder function(ly_setup_cmake_install) - install(DIRECTORY "${CMAKE_SOURCE_DIR}/cmake" + install(DIRECTORY "${LY_ROOT_FOLDER}/cmake" DESTINATION . COMPONENT ${LY_DEFAULT_INSTALL_COMPONENT} + PATTERN "__pycache__" EXCLUDE REGEX "Findo3de.cmake" EXCLUDE REGEX "Platform\/.*\/BuiltInPackages_.*\.cmake" EXCLUDE ) + + # Transform the LY_EXTERNAL_SUBDIRS list into a json array + set(indent " ") + foreach(external_subdir ${LY_EXTERNAL_SUBDIRS}) + file(RELATIVE_PATH engine_rel_external_subdir ${LY_ROOT_FOLDER} ${external_subdir}) + list(APPEND relative_external_subdirs "\"${engine_rel_external_subdir}\"") + endforeach() + list(JOIN relative_external_subdirs ",\n${indent}" LY_INSTALL_EXTERNAL_SUBDIRS) + + # Read the "templates" key from the source engine.json + o3de_read_json_array(engine_templates ${LY_ROOT_FOLDER}/engine.json "templates") + foreach(template_path ${engine_templates}) + list(APPEND relative_templates "\"${template_path}\"") + endforeach() + list(JOIN relative_templates ",\n${indent}" LY_INSTALL_TEMPLATES) + + configure_file(${LY_ROOT_FOLDER}/cmake/install/engine.json.in ${CMAKE_CURRENT_BINARY_DIR}/cmake/engine.json @ONLY) + install( FILES - "${CMAKE_SOURCE_DIR}/CMakeLists.txt" - "${CMAKE_SOURCE_DIR}/engine.json" + "${LY_ROOT_FOLDER}/CMakeLists.txt" + "${CMAKE_CURRENT_BINARY_DIR}/cmake/engine.json" DESTINATION . COMPONENT ${LY_DEFAULT_INSTALL_COMPONENT} ) @@ -247,14 +304,16 @@ function(ly_setup_cmake_install) # Findo3de.cmake file: we generate a different Findo3de.camke file than the one we have in cmake. This one is going to expose all # targets that are pre-built - get_property(all_targets GLOBAL PROPERTY LY_ALL_TARGETS) unset(FIND_PACKAGES_PLACEHOLDER) - foreach(target IN LISTS all_targets) - string(APPEND FIND_PACKAGES_PLACEHOLDER " find_package(${target})\n") + + # Add to the FIND_PACKAGES_PLACEHOLDER all directories in which ly_add_target were called in + get_property(all_subdirectories GLOBAL PROPERTY LY_ALL_TARGET_DIRECTORIES) + foreach(target_subdirectory IN LISTS all_subdirectories) + file(RELATIVE_PATH target_source_dir_relative ${LY_ROOT_FOLDER} ${target_subdirectory}) + string(APPEND FIND_PACKAGES_PLACEHOLDER " add_subdirectory(${target_source_dir_relative})\n") endforeach() - configure_file(${LY_ROOT_FOLDER}/cmake/Findo3de.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/cmake/Findo3de.cmake @ONLY) - + configure_file(${LY_ROOT_FOLDER}/cmake/install/Findo3de.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/cmake/Findo3de.cmake @ONLY) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/cmake/Findo3de.cmake" DESTINATION cmake COMPONENT ${LY_DEFAULT_INSTALL_COMPONENT} @@ -282,6 +341,74 @@ function(ly_setup_cmake_install) endfunction() +#! ly_setup_runtime_dependencies: install runtime dependencies +function(ly_setup_runtime_dependencies) + + # Common functions used by the bellow code + install(CODE +"function(ly_deploy_qt_install target_output) + execute_process(COMMAND \"${WINDEPLOYQT_EXECUTABLE}\" --verbose 0 --no-compiler-runtime \"\${target_output}\" ERROR_VARIABLE deploy_error RESULT_VARIABLE deploy_result) + if (NOT \${deploy_result} EQUAL 0) + if(NOT deploy_error MATCHES \"does not seem to be a Qt executable\" ) + message(SEND_ERROR \"Deploying qt for \${target_output} returned \${deploy_result}: \${deploy_error}\") + endif() + endif() +endfunction() + +function(ly_copy source_file target_directory) + file(COPY \"\${source_file}\" DESTINATION \"\${target_directory}\" FILE_PERMISSIONS ${LY_COPY_PERMISSIONS}) +endfunction()" + COMPONENT ${LY_DEFAULT_INSTALL_COMPONENT} + ) + + unset(runtime_commands) + get_property(all_targets GLOBAL PROPERTY LY_ALL_TARGETS) + foreach(alias_target IN LISTS all_targets) + ly_de_alias_target(${alias_target} target) + + # Exclude targets that dont produce runtime outputs + get_target_property(target_type ${target} TYPE) + if(NOT target_type IN_LIST LY_TARGET_TYPES_WITH_RUNTIME_OUTPUTS) + continue() + endif() + + get_target_property(target_runtime_output_directory ${target} RUNTIME_OUTPUT_DIRECTORY) + if(target_runtime_output_directory) + file(RELATIVE_PATH target_runtime_output_subdirectory ${CMAKE_RUNTIME_OUTPUT_DIRECTORY} ${target_runtime_output_directory}) + endif() + + # Qt + get_property(has_qt_dependency GLOBAL PROPERTY LY_DETECT_QT_DEPENDENCY_${target}) + if(has_qt_dependency) + # Qt deploy needs to be done after the binary is copied to the output, so we do a install(CODE) which effectively + # puts it as a postbuild step of the "install" target. Binaries are copied at that point. + if(NOT EXISTS ${WINDEPLOYQT_EXECUTABLE}) + message(FATAL_ERROR "Qt deploy executable not found: ${WINDEPLOYQT_EXECUTABLE}") + endif() + set(target_output "${install_output_folder}/${target_runtime_output_subdirectory}/$") + list(APPEND runtime_commands "ly_deploy_qt_install(\"${target_output}\")\n") + endif() + + # runtime dependencies that need to be copied to the output + set(target_file_dir "${install_output_folder}/${target_runtime_output_subdirectory}") + ly_get_runtime_dependencies(runtime_dependencies ${target}) + foreach(runtime_dependency ${runtime_dependencies}) + unset(runtime_command) + ly_get_runtime_dependency_command(runtime_command ${runtime_dependency}) + string(CONFIGURE "${runtime_command}" runtime_command @ONLY) + list(APPEND runtime_commands ${runtime_command}) + endforeach() + + endforeach() + + list(REMOVE_DUPLICATES runtime_commands) + list(JOIN runtime_commands " " runtime_commands_str) # the spaces are just to see the right identation in the cmake_install.cmake file + install(CODE "${runtime_commands_str}" + COMPONENT ${LY_DEFAULT_INSTALL_COMPONENT} + ) + +endfunction() + #! ly_setup_others: install directories required by the engine function(ly_setup_others) @@ -294,15 +421,16 @@ function(ly_setup_others) set(install_path .) endif() - install(DIRECTORY "${CMAKE_SOURCE_DIR}/${dir}" + install(DIRECTORY "${LY_ROOT_FOLDER}/${dir}" DESTINATION ${install_path} COMPONENT ${LY_DEFAULT_INSTALL_COMPONENT} + PATTERN "__pycache__" EXCLUDE ) endforeach() # Scripts - file(GLOB o3de_scripts "${CMAKE_SOURCE_DIR}/scripts/o3de.*") + file(GLOB o3de_scripts "${LY_ROOT_FOLDER}/scripts/o3de.*") install(FILES ${o3de_scripts} DESTINATION ./scripts @@ -310,8 +438,9 @@ function(ly_setup_others) ) install(DIRECTORY - ${CMAKE_SOURCE_DIR}/scripts/bundler - ${CMAKE_SOURCE_DIR}/scripts/project_manager + ${LY_ROOT_FOLDER}/scripts/bundler + ${LY_ROOT_FOLDER}/scripts/project_manager + ${LY_ROOT_FOLDER}/scripts/o3de DESTINATION ./scripts COMPONENT ${LY_DEFAULT_INSTALL_COMPONENT} PATTERN "__pycache__" EXCLUDE @@ -319,7 +448,7 @@ function(ly_setup_others) PATTERN "tests" EXCLUDE ) - install(DIRECTORY "${CMAKE_SOURCE_DIR}/python" + install(DIRECTORY "${LY_ROOT_FOLDER}/python" DESTINATION . COMPONENT ${LY_DEFAULT_INSTALL_COMPONENT} REGEX "downloaded_packages" EXCLUDE @@ -328,36 +457,35 @@ function(ly_setup_others) # Registry install(DIRECTORY - ${CMAKE_CURRENT_BINARY_DIR}/bin/$/Registry - DESTINATION ./bin/${PAL_PLATFORM_NAME}/$ + ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/$/Registry + DESTINATION ./${runtime_output_directory}/${PAL_PLATFORM_NAME}/$ COMPONENT ${LY_DEFAULT_INSTALL_COMPONENT} ) install(DIRECTORY - ${CMAKE_SOURCE_DIR}/Registry + ${LY_ROOT_FOLDER}/Registry DESTINATION . COMPONENT ${LY_DEFAULT_INSTALL_COMPONENT} ) # Engine Source Assets install(DIRECTORY - ${CMAKE_SOURCE_DIR}/Assets + ${LY_ROOT_FOLDER}/Assets DESTINATION . COMPONENT ${LY_DEFAULT_INSTALL_COMPONENT} ) # Gem Source Assets and Registry # Find all gem directories relative to the CMake Source Dir - file( - GLOB_RECURSE + file(GLOB_RECURSE gems_assets_path LIST_DIRECTORIES TRUE - RELATIVE "${CMAKE_SOURCE_DIR}/" + RELATIVE "${LY_ROOT_FOLDER}/" "Gems/*" ) list(FILTER gems_assets_path INCLUDE REGEX "/(Assets|Registry)$") foreach (gem_assets_path ${gems_assets_path}) - set(gem_abs_assets_path ${CMAKE_SOURCE_DIR}/${gem_assets_path}/) + set(gem_abs_assets_path ${LY_ROOT_FOLDER}/${gem_assets_path}/) if (EXISTS ${gem_abs_assets_path}) # The trailing slash is IMPORTANT here as that is needed to prevent # the "Assets" folder from being copied underneath the /Assets folder @@ -368,50 +496,62 @@ function(ly_setup_others) endif() endforeach() - # Qt Binaries - set(QT_DIRS bearer iconengines imageformats platforms styles translations) - list(TRANSFORM QT_DIRS PREPEND "${CMAKE_CURRENT_BINARY_DIR}/bin/$/" OUTPUT_VARIABLE QT_BIN_DIRS) + # gem.json files + file(GLOB_RECURSE + gems_json_path + LIST_DIRECTORIES FALSE + RELATIVE "${LY_ROOT_FOLDER}" + "Gems/*/gem.json" + ) + foreach(gem_json_path ${gems_json_path}) + get_filename_component(gem_relative_path ${gem_json_path} DIRECTORY) + install(FILES ${gem_json_path} + DESTINATION ${gem_relative_path} + COMPONENT ${LY_DEFAULT_INSTALL_COMPONENT} + ) + endforeach() + + # Additional files needed by gems install(DIRECTORY - ${QT_BIN_DIRS} - DESTINATION ./bin/${PAL_PLATFORM_NAME}/$ + ${LY_ROOT_FOLDER}/Gems/Atom/Asset/ImageProcessingAtom/Config + DESTINATION Gems/Atom/Asset/ImageProcessingAtom COMPONENT ${LY_DEFAULT_INSTALL_COMPONENT} ) # Templates install(DIRECTORY - ${CMAKE_SOURCE_DIR}/Templates + ${LY_ROOT_FOLDER}/Templates DESTINATION . COMPONENT ${LY_DEFAULT_INSTALL_COMPONENT} ) # Misc install(FILES - ${CMAKE_SOURCE_DIR}/ctest_pytest.ini - ${CMAKE_SOURCE_DIR}/LICENSE.txt - ${CMAKE_SOURCE_DIR}/README.md + ${LY_ROOT_FOLDER}/ctest_pytest.ini + ${LY_ROOT_FOLDER}/LICENSE.txt + ${LY_ROOT_FOLDER}/README.md DESTINATION . COMPONENT ${LY_DEFAULT_INSTALL_COMPONENT} ) endfunction() - #! ly_setup_target_generator: install source files needed for project launcher generation function(ly_setup_target_generator) install(FILES - ${CMAKE_SOURCE_DIR}/Code/LauncherUnified/launcher_generator.cmake - ${CMAKE_SOURCE_DIR}/Code/LauncherUnified/launcher_project_files.cmake - ${CMAKE_SOURCE_DIR}/Code/LauncherUnified/LauncherProject.cpp - ${CMAKE_SOURCE_DIR}/Code/LauncherUnified/StaticModules.in + ${LY_ROOT_FOLDER}/Code/LauncherUnified/launcher_generator.cmake + ${LY_ROOT_FOLDER}/Code/LauncherUnified/launcher_project_files.cmake + ${LY_ROOT_FOLDER}/Code/LauncherUnified/LauncherProject.cpp + ${LY_ROOT_FOLDER}/Code/LauncherUnified/StaticModules.in DESTINATION LauncherGenerator COMPONENT ${LY_DEFAULT_INSTALL_COMPONENT} ) - install(DIRECTORY ${CMAKE_SOURCE_DIR}/Code/LauncherUnified/Platform + install(DIRECTORY ${LY_ROOT_FOLDER}/Code/LauncherUnified/Platform DESTINATION LauncherGenerator COMPONENT ${LY_DEFAULT_INSTALL_COMPONENT} ) - install(FILES ${CMAKE_SOURCE_DIR}/Code/LauncherUnified/FindLauncherGenerator.cmake + install(FILES ${LY_ROOT_FOLDER}/Code/LauncherUnified/FindLauncherGenerator.cmake DESTINATION cmake COMPONENT ${LY_DEFAULT_INSTALL_COMPONENT} ) diff --git a/cmake/Platform/Common/RuntimeDependencies_common.cmake b/cmake/Platform/Common/RuntimeDependencies_common.cmake index 6ac5215a01..4ae914744f 100644 --- a/cmake/Platform/Common/RuntimeDependencies_common.cmake +++ b/cmake/Platform/Common/RuntimeDependencies_common.cmake @@ -10,7 +10,7 @@ # set(LY_COPY_PERMISSIONS "OWNER_READ OWNER_WRITE OWNER_EXECUTE") -set(LY_TARGET_TYPES_WITH_RUNTIME_OUTPUTS MODULE_LIBRARY SHARED_LIBRARY EXECUTABLE) +set(LY_TARGET_TYPES_WITH_RUNTIME_OUTPUTS MODULE_LIBRARY SHARED_LIBRARY EXECUTABLE APPLICATION) # There are several runtime dependencies to handle: # 1. Dependencies to 3rdparty libraries. This involves copying IMPORTED_LOCATION to the folder where the target is. @@ -61,7 +61,7 @@ function(ly_get_runtime_dependencies ly_RUNTIME_DEPENDENCIES ly_TARGET) if(dependencies) list(APPEND link_dependencies ${dependencies}) endif() - if(NOT target_type MATCHES "INTERFACE") + if(NOT target_type STREQUAL "INTERFACE_LIBRARY") unset(dependencies) get_target_property(dependencies ${ly_TARGET} LINK_LIBRARIES) if(dependencies) @@ -105,11 +105,15 @@ function(ly_get_runtime_dependencies ly_RUNTIME_DEPENDENCIES ly_TARGET) set(skip_imported TRUE) endif() endif() + if(target_type MATCHES "(STATIC_LIBRARY)") + # No need to copy these dependencies since the outputs are not used at runtime + set(skip_imported TRUE) + endif() if(NOT skip_imported) # Add imported locations - if(target_type MATCHES "INTERFACE") + if(target_type STREQUAL "INTERFACE_LIBRARY") set(imported_property INTERFACE_IMPORTED_LOCATION) else() set(imported_property IMPORTED_LOCATION) @@ -183,7 +187,7 @@ function(ly_get_runtime_dependency_command ly_RUNTIME_COMMAND ly_TARGET) # To support platforms where the binaries end in different places, we are going to assume that all dependencies, # including the ones we are building, need to be copied over. However, we add a check to prevent copying something # over itself. This detection cannot happen now because the target we are copying for varies. - set(runtime_command "ly_copy(\"${source_file}\" \"$${target_directory}\")\n") + set(runtime_command "ly_copy(\"${source_file}\" \"@target_file_dir@${target_directory}\")\n") # Tentative optimization: this is an attempt to solve the first "if" at generation time, making the runtime_dependencies # file smaller and faster to run. In platforms where the built target and the dependencies targets end up in the same @@ -206,20 +210,25 @@ function(ly_get_runtime_dependency_command ly_RUNTIME_COMMAND ly_TARGET) endfunction() -get_property(additional_module_paths GLOBAL PROPERTY LY_ADDITIONAL_MODULE_PATH) -list(APPEND CMAKE_MODULE_PATH ${additional_module_paths}) +function(ly_delayed_generate_runtime_dependencies) -get_property(all_targets GLOBAL PROPERTY LY_ALL_TARGETS) -foreach(target IN LISTS all_targets) + get_property(additional_module_paths GLOBAL PROPERTY LY_ADDITIONAL_MODULE_PATH) + list(APPEND CMAKE_MODULE_PATH ${additional_module_paths}) - # Exclude targets that dont produce runtime outputs - get_target_property(target_type ${target} TYPE) - if(NOT target_type IN_LIST LY_TARGET_TYPES_WITH_RUNTIME_OUTPUTS) - continue() - endif() + get_property(all_targets GLOBAL PROPERTY LY_ALL_TARGETS) + foreach(aliased_target IN LISTS all_targets) - unset(runtime_dependencies) - set(runtime_commands " + unset(target) + ly_de_alias_target(${aliased_target} target) + + # Exclude targets that dont produce runtime outputs + get_target_property(target_type ${target} TYPE) + if(NOT target_type IN_LIST LY_TARGET_TYPES_WITH_RUNTIME_OUTPUTS) + continue() + endif() + + unset(runtime_dependencies) + set(runtime_commands " function(ly_copy source_file target_directory) get_filename_component(target_filename \"\${source_file}\" NAME) if(NOT \"\${source_file}\" STREQUAL \"\${target_directory}/\${target_filename}\") @@ -232,21 +241,23 @@ function(ly_copy source_file target_directory) endif() endif() endfunction() -\n") + \n") + + ly_get_runtime_dependencies(runtime_dependencies ${target}) + foreach(runtime_dependency ${runtime_dependencies}) + unset(runtime_command) + ly_get_runtime_dependency_command(runtime_command ${runtime_dependency}) + string(APPEND runtime_commands ${runtime_command}) + endforeach() + + # Generate the output file + set(target_file_dir "$") + string(CONFIGURE "${runtime_commands}" generated_commands @ONLY) + file(GENERATE + OUTPUT ${CMAKE_BINARY_DIR}/runtime_dependencies/$/${target}.cmake + CONTENT "${generated_commands}" + ) - ly_get_runtime_dependencies(runtime_dependencies ${target}) - foreach(runtime_dependency ${runtime_dependencies}) - unset(runtime_command) - ly_get_runtime_dependency_command(runtime_command ${runtime_dependency}) - string(APPEND runtime_commands ${runtime_command}) endforeach() - - # Generate the output file - string(CONFIGURE "${runtime_commands}" generated_commands @ONLY) - file(GENERATE - OUTPUT ${CMAKE_BINARY_DIR}/runtime_dependencies/$/${target}.cmake - CONTENT "${generated_commands}" - ) - -endforeach() +endfunction() diff --git a/cmake/Platform/Mac/Install_mac.cmake b/cmake/Platform/Mac/Install_mac.cmake index 8c96c199de..5c7959bf77 100644 --- a/cmake/Platform/Mac/Install_mac.cmake +++ b/cmake/Platform/Mac/Install_mac.cmake @@ -11,11 +11,5 @@ # Empty implementations for untested platforms to fix build errors. -function(ly_install_target ly_install_target_NAME) - -endfunction() - - function(ly_setup_o3de_install) - endfunction() \ No newline at end of file diff --git a/cmake/Platform/Windows/Configurations_windows.cmake b/cmake/Platform/Windows/Configurations_windows.cmake index 6ab376ed0b..9ef535e455 100644 --- a/cmake/Platform/Windows/Configurations_windows.cmake +++ b/cmake/Platform/Windows/Configurations_windows.cmake @@ -106,7 +106,7 @@ if(NOT CMAKE_GENERATOR MATCHES "Visual Studio") endforeach() if(NOT version VERSION_EQUAL CMAKE_SYSTEM_VERSION) - message(STATUS "Selecting Windows SDK version ${version} to target Windows ${CMAKE_SYSTEM_VERSION}.") + message(STATUS "Using Windows SDK version ${version} to target Windows ${CMAKE_SYSTEM_VERSION}") endif() ly_set(CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION "${version}") @@ -116,4 +116,3 @@ endif() if(NOT CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION MATCHES "10.0") message(FATAL_ERROR "Unsupported version of Windows SDK ${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION}, specify \"-DCMAKE_SYSTEM_VERSION=10.0\" when invoking cmake") endif() -message(STATUS "Using Windows target SDK ${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION}") diff --git a/cmake/Platform/iOS/Install_ios.cmake b/cmake/Platform/iOS/Install_ios.cmake index 8c96c199de..5c7959bf77 100644 --- a/cmake/Platform/iOS/Install_ios.cmake +++ b/cmake/Platform/iOS/Install_ios.cmake @@ -11,11 +11,5 @@ # Empty implementations for untested platforms to fix build errors. -function(ly_install_target ly_install_target_NAME) - -endfunction() - - function(ly_setup_o3de_install) - endfunction() \ No newline at end of file diff --git a/cmake/Platform/iOS/RuntimeDependencies_ios.cmake b/cmake/Platform/iOS/RuntimeDependencies_ios.cmake index d558c6f12a..a2a6d30593 100644 --- a/cmake/Platform/iOS/RuntimeDependencies_ios.cmake +++ b/cmake/Platform/iOS/RuntimeDependencies_ios.cmake @@ -118,57 +118,65 @@ function(ios_get_dependencies_recursive ios_DEPENDENCIES ly_TARGET) endfunction() -# For each (non-monolithic) game project, find runtime dependencies and tell XCode to embed/sign them -if(NOT LY_MONOLITHIC_GAME) +function(ly_delayed_generate_runtime_dependencies) - foreach(game_project ${LY_PROJECTS}) + # For each (non-monolithic) game project, find runtime dependencies and tell XCode to embed/sign them + if(NOT LY_MONOLITHIC_GAME) - # Recursively get all dependent frameworks for the game project. - unset(dependencies) - ios_get_dependencies_recursive(dependencies ${game_project}.GameLauncher) - if(dependencies) - set_target_properties(${game_project}.GameLauncher - PROPERTIES - XCODE_EMBED_FRAMEWORKS "${dependencies}" - XCODE_EMBED_FRAMEWORKS_CODE_SIGN_ON_COPY TRUE - XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/Frameworks" - ) + foreach(game_project ${LY_PROJECTS}) + + # Recursively get all dependent frameworks for the game project. + unset(dependencies) + ios_get_dependencies_recursive(dependencies ${game_project}.GameLauncher) + if(dependencies) + set_target_properties(${game_project}.GameLauncher + PROPERTIES + XCODE_EMBED_FRAMEWORKS "${dependencies}" + XCODE_EMBED_FRAMEWORKS_CODE_SIGN_ON_COPY TRUE + XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/Frameworks" + ) + endif() + + endforeach() + + endif() + + get_property(all_targets GLOBAL PROPERTY LY_ALL_TARGETS) + unset(test_runner_dependencies) + foreach(aliased_target IN LISTS all_targets) + + unset(target) + ly_de_alias_target(${aliased_target} target) + + # Exclude targets that dont produce runtime outputs + get_target_property(target_type ${target} TYPE) + if(NOT target_type IN_LIST LY_TARGET_TYPES_WITH_RUNTIME_OUTPUTS) + continue() endif() + + file(GENERATE + OUTPUT ${CMAKE_BINARY_DIR}/runtime_dependencies/$/${target}.cmake + CONTENT "" + ) + if(target_type IN_LIST IOS_FRAMEWORK_TARGET_TYPES) + list(APPEND test_runner_dependencies ${target}) + endif() endforeach() -endif() + if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) + add_dependencies("AzTestRunner" ${test_runner_dependencies}) + + # We still need to add indirect dependencies(eg. 3rdParty) + unset(all_dependencies) + ios_get_dependencies_recursive(all_dependencies AzTestRunner) -get_property(all_targets GLOBAL PROPERTY LY_ALL_TARGETS) -unset(test_runner_dependencies) -foreach(target IN LISTS all_targets) - # Exclude targets that dont produce runtime outputs - get_target_property(target_type ${target} TYPE) - if(NOT target_type IN_LIST LY_TARGET_TYPES_WITH_RUNTIME_OUTPUTS) - continue() + set_target_properties("AzTestRunner" + PROPERTIES + XCODE_EMBED_FRAMEWORKS "${all_dependencies}" + XCODE_EMBED_FRAMEWORKS_CODE_SIGN_ON_COPY TRUE + XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/Frameworks" + ) endif() - - file(GENERATE - OUTPUT ${CMAKE_BINARY_DIR}/runtime_dependencies/$/${target}.cmake - CONTENT "" - ) - if(target_type IN_LIST IOS_FRAMEWORK_TARGET_TYPES) - list(APPEND test_runner_dependencies ${target}) - endif() -endforeach() - -if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) - add_dependencies("AzTestRunner" ${test_runner_dependencies}) - - # We still need to add indirect dependencies(eg. 3rdParty) - unset(all_dependencies) - ios_get_dependencies_recursive(all_dependencies AzTestRunner) - - set_target_properties("AzTestRunner" - PROPERTIES - XCODE_EMBED_FRAMEWORKS "${all_dependencies}" - XCODE_EMBED_FRAMEWORKS_CODE_SIGN_ON_COPY TRUE - XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/Frameworks" - ) -endif() \ No newline at end of file +endfunction() \ No newline at end of file diff --git a/cmake/Projects.cmake b/cmake/Projects.cmake index 781fee3711..adaf7ee15f 100644 --- a/cmake/Projects.cmake +++ b/cmake/Projects.cmake @@ -105,6 +105,7 @@ function(ly_add_project_dependencies) ) endfunction() + #template for generating the project build_path setreg set(project_build_path_template [[ { @@ -120,7 +121,6 @@ set(project_build_path_template [[ }]] ) - #! ly_generate_project_build_path_setreg: Generates a .setreg file that contains an absolute path to the ${CMAKE_BINARY_DIR} # This allows locate the directory where the project it's binaries are built to be located within the engine. # Which are the shared libraries and launcher executables @@ -136,23 +136,40 @@ set(project_build_path_template [[ # can only run on the host platform # \arg:project_real_path Full path to the o3de project directory function(ly_generate_project_build_path_setreg project_real_path) - # The build path isn't needed on non-monolithic platforms - # Nor on any non-host platforms - if (LY_MONOLITHIC_GAME OR NOT PAL_TRAIT_BUILD_HOST_TOOLS) - return() - endif() + # The build path isn't needed on non-monolithic platforms + # Nor on any non-host platforms + if (LY_MONOLITHIC_GAME OR NOT PAL_TRAIT_BUILD_HOST_TOOLS) + return() + endif() - # Set the project_bin_path to the ${CMAKE_BINARY_DIR} to provide the configure template - # with the project build directory - set(project_bin_path ${CMAKE_BINARY_DIR}) - string(CONFIGURE ${project_build_path_template} project_build_path_setreg_content @ONLY) - set(project_user_build_path_setreg_file ${project_real_path}/user/Registry/Platform/${PAL_PLATFORM_NAME}/build_path.setreg) - file(GENERATE OUTPUT ${project_user_build_path_setreg_file} CONTENT ${project_build_path_setreg_content}) + # Set the project_bin_path to the ${CMAKE_BINARY_DIR} to provide the configure template + # with the project build directory + set(project_bin_path ${CMAKE_BINARY_DIR}) + string(CONFIGURE ${project_build_path_template} project_build_path_setreg_content @ONLY) + set(project_user_build_path_setreg_file ${project_real_path}/user/Registry/Platform/${PAL_PLATFORM_NAME}/build_path.setreg) + file(GENERATE OUTPUT ${project_user_build_path_setreg_file} CONTENT ${project_build_path_setreg_content}) +endfunction() + + +function(add_project_json_external_subdirectories project_path) + set(project_json_path ${project_path}/project.json) + if(EXISTS ${project_json_path}) + read_json_external_subdirs(external_subdirs ${project_path}/project.json) + foreach(external_subdir ${external_subdirs}) + file(REAL_PATH ${external_subdir} real_external_subdir BASE_DIRECTORY ${project_path}) + list(APPEND project_external_subdirs ${real_external_subdir}) + endforeach() + + set_property(GLOBAL APPEND PROPERTY LY_EXTERNAL_SUBDIRS ${project_external_subdirs}) + endif() endfunction() # Add the projects here so the above function is found foreach(project ${LY_PROJECTS}) file(REAL_PATH ${project} full_directory_path BASE_DIRECTORY ${CMAKE_SOURCE_DIR}) + if(NOT LY_FIRST_PROJECT) + ly_set(LY_FIRST_PROJECT_PATH ${full_directory_path}) + endif() string(SHA256 full_directory_hash ${full_directory_path}) # Truncate the full_directory_hash down to 8 characters to avoid hitting the Windows 260 character path limit @@ -163,5 +180,6 @@ foreach(project ${LY_PROJECTS}) list(APPEND LY_PROJECTS_FOLDER_NAME ${project_folder_name}) add_subdirectory(${project} "${project_folder_name}-${full_directory_hash}") ly_generate_project_build_path_setreg(${full_directory_path}) + add_project_json_external_subdirectories(${full_directory_path}) endforeach() ly_set(LY_PROJECTS_FOLDER_NAME ${LY_PROJECTS_FOLDER_NAME}) diff --git a/cmake/SettingsRegistry.cmake b/cmake/SettingsRegistry.cmake index e1c07f4492..4d932601b4 100644 --- a/cmake/SettingsRegistry.cmake +++ b/cmake/SettingsRegistry.cmake @@ -31,7 +31,7 @@ set(gem_module_template [[ "@stripped_gem_target@": { "Modules":["$"], - "SourcePaths":["@gem_relative_source_dir@"] + "SourcePaths":["@gem_module_root_relative_to_engine_root@"] }]] ) @@ -81,39 +81,78 @@ function(ly_get_gem_load_dependencies ly_GEM_LOAD_DEPENDENCIES ly_TARGET) list(REMOVE_DUPLICATES all_gem_load_dependencies) set_property(GLOBAL PROPERTY LY_GEM_LOAD_DEPENDENCIES_${ly_TARGET} "${all_gem_load_dependencies}") set(${ly_GEM_LOAD_DEPENDENCIES} ${all_gem_load_dependencies} PARENT_SCOPE) + message(VERBOSE "Gem Target \"${ly_TARGET}\" has load dependencies of: ${all_gem_load_dependencies}") + endfunction() +#!ly_get_gem_module_root: Uses the supplied gem_target to lookup the nearest gem.json file above the SOURCE_DIR +# +# \arg:gem_target(TARGET) - Target to look upwards from using its SOURCE_DIR property +function(ly_get_gem_module_root output_gem_module_root gem_target) + unset(gem_module_roots) + get_property(gem_source_dir TARGET ${gem_target} PROPERTY SOURCE_DIR) + + if(gem_source_dir) + set(candidate_gem_dir ${gem_source_dir}) + # Locate the root of the gem by finding the gem.json location + while(NOT EXISTS ${candidate_gem_dir}/gem.json) + get_filename_component(parent_dir ${candidate_gem_dir} DIRECTORY) + if (${parent_dir} STREQUAL ${candidate_gem_dir}) + message(WARNING "Did not find a gem.json while processing GEM_MODULE target ${gem_target}!") + break() + endif() + set(candidate_gem_dir ${parent_dir}) + endwhile() + endif() + + if (EXISTS ${candidate_gem_dir}/gem.json) + set(gem_source_dir ${candidate_gem_dir}) + endif() + + # Set the gem module root output directory to the location with the gem.json file within it or + # the supplied gem_target SOURCE_DIR location if no gem.json file was found + set(${output_gem_module_root} ${gem_source_dir} PARENT_SCOPE) +endfunction() + + #! ly_delayed_generate_settings_registry: Generates a .setreg file for each target with dependencies # added to it via ly_add_target_dependencies # The generated file contains the file to the each dependent targets # This can be used for example to determine which list of gems to load with an application function(ly_delayed_generate_settings_registry) get_property(ly_delayed_load_targets GLOBAL PROPERTY LY_DELAYED_LOAD_DEPENDENCIES) - foreach(prefix_target ${ly_delayed_load_targets}) string(REPLACE "," ";" prefix_target_list "${prefix_target}") list(LENGTH prefix_target_list prefix_target_length) if(prefix_target_length EQUAL 0) - message(SEND_ERROR "Delayed load target is missing target name") continue() endif() # Retrieve the target name from the back of the list list(POP_BACK prefix_target_list target) - # Retreives the prefix if available from the remaining element of the list + # Retrieves the prefix if available from the remaining element of the list list(POP_BACK prefix_target_list prefix) # Get the gem dependencies for the given project and target combination get_property(gem_dependencies GLOBAL PROPERTY LY_DELAYED_LOAD_"${prefix_target}") list(REMOVE_DUPLICATES gem_dependencies) # Strip out any duplicate gem targets - set(all_gem_dependencies ${gem_dependencies}) + unset(all_gem_dependencies) foreach(gem_target ${gem_dependencies}) ly_get_gem_load_dependencies(gem_load_gem_dependencies ${gem_target}) - list(APPEND all_gem_dependencies ${gem_load_gem_dependencies}) + list(APPEND all_gem_dependencies ${gem_load_gem_dependencies} ${gem_target}) endforeach() list(REMOVE_DUPLICATES all_gem_dependencies) + # de-namespace them + unset(new_gem_dependencies) + foreach(gem_target ${all_gem_dependencies}) + ly_de_alias_target(${gem_target} stripped_gem_target) + list(APPEND new_gem_dependencies ${stripped_gem_target}) + endforeach() + set(all_gem_dependencies ${new_gem_dependencies}) + list(REMOVE_DUPLICATES all_gem_dependencies) + unset(target_gem_dependencies_names) foreach(gem_target ${all_gem_dependencies}) unset(gem_relative_source_dir) @@ -121,16 +160,18 @@ function(ly_delayed_generate_settings_registry) if (NOT TARGET ${gem_target}) message(FATAL_ERROR "Dependency ${gem_target} from ${target} does not exist") endif() - get_property(gem_relative_source_dir TARGET ${gem_target} PROPERTY SOURCE_DIR) - if(gem_relative_source_dir) - # Most gems CMakeLists.txt files reside in the /Code/ so remove "Code/" from the path - if(gem_relative_source_dir MATCHES ".*/Code$") - get_filename_component(gem_relative_source_dir ${gem_relative_source_dir} DIRECTORY) - endif() - file(TO_CMAKE_PATH ${LY_ROOT_FOLDER} ly_root_folder_cmake) - file(RELATIVE_PATH gem_relative_source_dir ${ly_root_folder_cmake} ${gem_relative_source_dir}) + + get_target_property(target_type ${gem_target} TYPE) + if (target_type STREQUAL "INTERFACE_LIBRARY") + # don't use interface libraries here, we only want ones which produce actual binaries. + # we have still already recursed into their dependencies - they'll show up later. + continue() endif() + + ly_get_gem_module_root(gem_module_root ${gem_target}) + file(RELATIVE_PATH gem_module_root_relative_to_engine_root ${LY_ROOT_FOLDER} ${gem_module_root}) + # De-alias namespace from gem targets before configuring them into the json template ly_de_alias_target(${gem_target} stripped_gem_target) string(CONFIGURE ${gem_module_template} gem_module_json @ONLY) @@ -144,7 +185,12 @@ function(ly_delayed_generate_settings_registry) list(JOIN target_gem_dependencies_names ",\n" target_gem_dependencies_names) string(CONFIGURE ${gems_json_template} gem_json @ONLY) - set(dependencies_setreg $/Registry/cmake_dependencies.${specialization_name}.setreg) + if(prefix) + set(target_dir $) + else() + set(target_dir $) + endif() + set(dependencies_setreg ${target_dir}/Registry/cmake_dependencies.${specialization_name}.setreg) file(GENERATE OUTPUT ${dependencies_setreg} CONTENT ${gem_json}) set_property(TARGET ${target} APPEND PROPERTY INTERFACE_LY_TARGET_FILES "${dependencies_setreg}\nRegistry") diff --git a/cmake/TestImpactFramework/LYTestImpactFramework.cmake b/cmake/TestImpactFramework/LYTestImpactFramework.cmake index c10c5bf637..d46b16bca5 100644 --- a/cmake/TestImpactFramework/LYTestImpactFramework.cmake +++ b/cmake/TestImpactFramework/LYTestImpactFramework.cmake @@ -204,7 +204,10 @@ function(ly_test_impact_export_source_target_mappings MAPPING_TEMPLATE_FILE) get_property(LY_ALL_TARGETS GLOBAL PROPERTY LY_ALL_TARGETS) # Walk the build targets - foreach(target ${LY_ALL_TARGETS}) + foreach(aliased_target ${LY_ALL_TARGETS}) + + unset(target) + ly_de_alias_target(${aliased_target} target) message(TRACE "Exporting static source file mappings for ${target}") # Target name and path relative to root diff --git a/cmake/Tools/Platform/Android/android_support.py b/cmake/Tools/Platform/Android/android_support.py index 6443077457..75e0cd2970 100755 --- a/cmake/Tools/Platform/Android/android_support.py +++ b/cmake/Tools/Platform/Android/android_support.py @@ -10,7 +10,9 @@ # import imghdr +import configparser import datetime +import fnmatch import logging import os import json @@ -33,6 +35,13 @@ if ROOT_DEV_PATH not in sys.path: from cmake.Tools import common +ANDROID_GRADLE_PLUGIN_COMPATIBILITY_MAP = { + '4.2.0': {'min_gradle_version': '6.7.1', + 'sdk_build': '30.0.2', + 'default_ndk': '21.4.7075529', + 'min_cmake_version': '3.20'} +} + APP_NAME = 'app' ANDROID_MANIFEST_FILE = 'AndroidManifest.xml' ANDROID_LIBRARIES_JSON_FILE = 'android_libraries.json' @@ -86,83 +95,93 @@ PYTHON_SCRIPT = 'python.cmd' if platform.system() == 'Windows' else 'python.sh' ANDROID_LAUNCHER_NAME_PATTERN = "{project_name}.GameLauncher" + class AndroidProjectManifestEnvironment(object): """ - This class manages the environment for the AndroidManifiest.xml template file, based on project settings and environments + This class manages the environment for the AndroidManifest.xml template file, based on project settings and environments that were passed in or calculated from the command line arguments. """ - def __init__(self, engine_root, project_path, android_sdk_version_number, android_ndk_platform_number, is_test:bool): + def __init__(self, engine_root, project_path, android_sdk_version_number, is_test:bool): """ Initialize the object with the project specific parameters and values for the game project :param engine_root: The path where the engine is located :param project_path: The path were the project is located :param android_sdk_version_number: The android SDK platform version - :param android_ndk_platform_number: The android NDK platform version :param is_test: Indicates if theAzTestRunner application should be run """ - if is_test: - # The AzTestRunner project.json is located under {engine_root}/Code/Tools/AzTestRunner/Platform/Android/android_project.json - project_properties_path = engine_root / 'Code' / 'Tools' / 'AzTestRunner' / 'Platform' / 'Android' / 'android_project.json' - else: - # The project.json file is located under the game name folder - project_properties_path = project_path / 'project.json' - # Read and parse the project.json file into a dictionary to process the specific attributes needed for the manifest template - project_properties_content = project_properties_path.resolve(strict=True)\ - .read_text(encoding=common.DEFAULT_TEXT_READ_ENCODING, - errors=common.ENCODING_ERROR_HANDLINGS) - self.project_path = project_path + try: + if is_test: + # The AzTestRunner project.json is located under {engine_root}/Code/Tools/AzTestRunner/Platform/Android/android_project.json + project_properties_path = engine_root / 'Code' / 'Tools' / 'AzTestRunner' / 'Platform' / 'Android' / 'android_project.json' + assert project_properties_path.is_file(), f'Missing required android settings file {project_properties_path.resolve()}' + project_properties_content = project_properties_path.read_text(encoding=common.DEFAULT_TEXT_READ_ENCODING, + errors=common.ENCODING_ERROR_HANDLINGS) + project_json = json.loads(project_properties_content) - # Extract the key attributes we need to process and build up our environment table - project_json = json.loads(project_properties_content) + android_settings = project_json['android_settings'] - project_name = project_json.get('project_name') - if not project_name: - raise common.LmbrCmdError(f"Missing required 'project_name' from project.json for project at '{str(project_path)}'") - product_name = project_json.get('product_name', project_name) + else: + # O3DE projects have both a project.json and an android_project.json files (unless its internal) + project_properties_path = project_path / 'project.json' + assert project_properties_path.is_file(), f'Missing required project settings file {project_properties_path.resolve()}' + project_properties_content = project_properties_path.read_text(encoding=common.DEFAULT_TEXT_READ_ENCODING, + errors=common.ENCODING_ERROR_HANDLINGS) + project_json = json.loads(project_properties_content) - game_project_android_settings = project_json['android_settings'] + android_project_properties_path = project_path / 'Platform' / 'Android' / 'android_project.json' + if android_project_properties_path.is_file(): + android_project_properties_content = android_project_properties_path.read_text(encoding=common.DEFAULT_TEXT_READ_ENCODING, + errors=common.ENCODING_ERROR_HANDLINGS) + android_project_json = json.loads(android_project_properties_content) + android_settings = android_project_json['android_settings'] + else: + android_settings = project_json['android_settings'] - package_name = game_project_android_settings["package_name"] + self.project_path = project_path - package_path = package_name.replace('.', '/') + project_name = project_json['project_name'] + product_name = project_json.get('product_name', project_name) + package_name = android_settings["package_name"] + package_path = package_name.replace('.', '/') - project_activity = f'{TEST_RUNNER_PROJECT}Activity' if is_test else f'{project_name}Activity' + project_activity = f'{TEST_RUNNER_PROJECT}Activity' if is_test else f'{project_name}Activity' - # Multiview options require special processing - multi_window_options = AndroidProjectManifestEnvironment.process_android_multi_window_options(game_project_android_settings) + # Multiview options require special processing + multi_window_options = AndroidProjectManifestEnvironment.process_android_multi_window_options(android_settings) - self.internal_dict = { - 'ANDROID_PACKAGE': package_name, - 'ANDROID_PACKAGE_PATH': package_path, - 'ANDROID_VERSION_NUMBER': game_project_android_settings["version_number"], - "ANDROID_VERSION_NAME": game_project_android_settings["version_name"], - "ANDROID_SCREEN_ORIENTATION": game_project_android_settings["orientation"], - 'ANDROID_APP_NAME': TEST_RUNNER_PROJECT if is_test else product_name, # external facing name - 'ANDROID_PROJECT_NAME': TEST_RUNNER_PROJECT if is_test else project_name, # internal facing name - 'ANDROID_PROJECT_ACTIVITY': project_activity, - 'ANDROID_LAUNCHER_NAME': TEST_RUNNER_PROJECT if is_test else ANDROID_LAUNCHER_NAME_PATTERN.format(project_name=project_name), - 'ANDROID_CONFIG_CHANGES': multi_window_options['ANDROID_CONFIG_CHANGES'], - 'ANDROID_APP_PUBLIC_KEY': game_project_android_settings.get('app_public_key', 'NoKey'), - 'ANDROID_APP_OBFUSCATOR_SALT': game_project_android_settings.get('app_obfuscator_salt', ''), - 'ANDROID_USE_MAIN_OBB': game_project_android_settings.get('use_main_obb', 'false'), - 'ANDROID_USE_PATCH_OBB': game_project_android_settings.get('use_patch_obb', 'false'), - 'ANDROID_ENABLE_KEEP_SCREEN_ON': game_project_android_settings.get('enable_keep_screen_on', 'false'), - 'ANDROID_DISABLE_IMMERSIVE_MODE': game_project_android_settings.get('disable_immersive_mode', 'false'), - 'ANDROID_MIN_SDK_VERSION': android_ndk_platform_number, - 'ANDROID_TARGET_SDK_VERSION': android_sdk_version_number, - 'ICONS': game_project_android_settings.get('icons', None), - 'SPLASH_SCREEN': game_project_android_settings.get('splash_screen', None), + self.internal_dict = { + 'ANDROID_PACKAGE': package_name, + 'ANDROID_PACKAGE_PATH': package_path, + 'ANDROID_VERSION_NUMBER': android_settings["version_number"], + "ANDROID_VERSION_NAME": android_settings["version_name"], + "ANDROID_SCREEN_ORIENTATION": android_settings["orientation"], + 'ANDROID_APP_NAME': TEST_RUNNER_PROJECT if is_test else product_name, # external facing name + 'ANDROID_PROJECT_NAME': TEST_RUNNER_PROJECT if is_test else project_name, # internal facing name + 'ANDROID_PROJECT_ACTIVITY': project_activity, + 'ANDROID_LAUNCHER_NAME': TEST_RUNNER_PROJECT if is_test else ANDROID_LAUNCHER_NAME_PATTERN.format(project_name=project_name), + 'ANDROID_CONFIG_CHANGES': multi_window_options['ANDROID_CONFIG_CHANGES'], + 'ANDROID_APP_PUBLIC_KEY': android_settings.get('app_public_key', 'NoKey'), + 'ANDROID_APP_OBFUSCATOR_SALT': android_settings.get('app_obfuscator_salt', ''), + 'ANDROID_USE_MAIN_OBB': android_settings.get('use_main_obb', 'false'), + 'ANDROID_USE_PATCH_OBB': android_settings.get('use_patch_obb', 'false'), + 'ANDROID_ENABLE_KEEP_SCREEN_ON': android_settings.get('enable_keep_screen_on', 'false'), + 'ANDROID_DISABLE_IMMERSIVE_MODE': android_settings.get('disable_immersive_mode', 'false'), + 'ANDROID_TARGET_SDK_VERSION': android_sdk_version_number, + 'ICONS': android_settings.get('icons', None), + 'SPLASH_SCREEN': android_settings.get('splash_screen', None), - 'ANDROID_MULTI_WINDOW': multi_window_options['ANDROID_MULTI_WINDOW'], - 'ANDROID_MULTI_WINDOW_PROPERTIES': multi_window_options['ANDROID_MULTI_WINDOW_PROPERTIES'], + 'ANDROID_MULTI_WINDOW': multi_window_options['ANDROID_MULTI_WINDOW'], + 'ANDROID_MULTI_WINDOW_PROPERTIES': multi_window_options['ANDROID_MULTI_WINDOW_PROPERTIES'], - 'SAMSUNG_DEX_KEEP_ALIVE': multi_window_options['SAMSUNG_DEX_KEEP_ALIVE'], - 'SAMSUNG_DEX_LAUNCH_WIDTH': multi_window_options['SAMSUNG_DEX_LAUNCH_WIDTH'], - 'SAMSUNG_DEX_LAUNCH_HEIGHT': multi_window_options['SAMSUNG_DEX_LAUNCH_HEIGHT'] - } + 'SAMSUNG_DEX_KEEP_ALIVE': multi_window_options['SAMSUNG_DEX_KEEP_ALIVE'], + 'SAMSUNG_DEX_LAUNCH_WIDTH': multi_window_options['SAMSUNG_DEX_LAUNCH_WIDTH'], + 'SAMSUNG_DEX_LAUNCH_HEIGHT': multi_window_options['SAMSUNG_DEX_LAUNCH_HEIGHT'] + } + except KeyError as e: + raise common.LmbrCmdError(f"Missing key from android project settings for project at {project_path}:'{e}' ") def __getitem__(self, item): return self.internal_dict.get(item) @@ -306,6 +325,7 @@ asset_deploy_type={asset_type} android_sdk_path={android_sdk_path} embed_assets_in_apk={embed_assets_in_apk} is_unit_test={is_unit_test} +android_gradle_plugin={android_gradle_plugin_version} """ NATIVE_CMAKE_SECTION_ANDROID_FORMAT = """ @@ -425,26 +445,28 @@ class AndroidProjectGenerator(object): Class the manages the process to generate an android project folder in order to build with gradle/android studio """ - def __init__(self, engine_root, build_dir, android_ndk_path, android_sdk_path, android_sdk_version, android_ndk_platform, - project_path, third_party_path, cmake_version, override_cmake_path, override_gradle_path, override_ninja_path, - android_sdk_build_tool_version, include_assets_in_apk, asset_mode, asset_type, signing_config, is_test_project=False, + def __init__(self, engine_root, build_dir, android_sdk_path, build_tool, android_sdk_platform, android_native_api_level, android_ndk, + project_path, third_party_path, cmake_version, override_cmake_path, override_gradle_path, gradle_version, gradle_plugin_version, + override_ninja_path, include_assets_in_apk, asset_mode, asset_type, signing_config, is_test_project=False, overwrite_existing=True): """ Initialize the object with all the required parameters needed to create an Android Project. The parameters should be verified before initializing this object - + :param engine_root: The engine root that contains the engine :param build_dir: The target folder under the where the android project folder will be created - :param android_ndk_path: The path to the ANDROID_NDK used for building the native android code :param android_sdk_path: The path to the ANDROID_SDK used for building the android java code - :param android_sdk_version: The android platform version number to use for the Android SDK related builds - :param android_ndk_platform: The android platform version number to use for the Android NDK related builds + :param build_tool: The android SDK build-tool version. + :param android_sdk_platform: The android sdk platform version number to use for the Android SDK related builds + :param android_native_api_level:The android native API level (ANDROID_NATIVE_API_LEVEL) to set + :param android_ndk: The android ndk version number to use for the native builds :param project_path: The path to the project :param third_party_path: The required path to the lumberyard 3rd party path :param cmake_version: The version number of cmake that will be used by gradle :param override_cmake_path: The override path to cmake if it does not exists in the system path :param override_gradle_path: The override path to gradle if it does not exists in the system path + :param gradle_version: The detected version of gradle being used + :param gradle_plugin_version: The android gradle plugin version :param override_ninja_path: The override path to ninja if it does not exists in the system path - :param android_sdk_build_tool_version: The preferred android SDK build-tool version. Will default to the first one detected in the android sdk path :param include_assets_in_apk: :param asset_mode: :param asset_type: @@ -458,17 +480,16 @@ class AndroidProjectGenerator(object): self.build_dir = build_dir - self.android_ndk_path = android_ndk_path - self.android_sdk_path = android_sdk_path self.android_project_builder_path = self.engine_root / 'Code/Tools/Android/ProjectBuilder' - self.android_sdk_version = android_sdk_version + self.android_sdk_platform = android_sdk_platform + self.android_sdk_build_tool_version = build_tool.version - self.android_sdk_build_tool_version = android_sdk_build_tool_version - - self.android_ndk_platform = android_ndk_platform + self.android_ndk = android_ndk + self.android_ndk_version = android_ndk.version + self.android_native_api_level = android_native_api_level self.project_path = project_path @@ -480,6 +501,10 @@ class AndroidProjectGenerator(object): self.override_gradle_path = override_gradle_path + self.gradle_version = gradle_version + + self.gradle_plugin_version = gradle_plugin_version + self.override_ninja_path = override_ninja_path self.include_assets_in_apk = include_assets_in_apk @@ -511,8 +536,10 @@ class AndroidProjectGenerator(object): project_names.extend(self.create_lumberyard_app(project_names)) root_gradle_env = { - 'SDK_VER': self.android_sdk_version, - 'NDK_PLATFORM_VER': self.android_ndk_platform, + 'ANDROID_GRADLE_PLUGIN_VERSION': str(self.gradle_plugin_version), + 'SDK_VER': self.android_sdk_platform, + 'MIN_SDK_VER': self.android_sdk_platform, + 'NDK_VERSION': self.android_ndk_version, 'SDK_BUILD_TOOL_VER': self.android_sdk_build_tool_version, 'LY_ENGINE_ROOT': common.normalize_path_for_settings(self.engine_root) } @@ -557,7 +584,7 @@ class AndroidProjectGenerator(object): if self.override_gradle_path: gradle_wrapper_cmd = [self.override_gradle_path] else: - gradle_wrapper_cmd = ['gradle.bat' if platform.system() == 'Windows' else 'gradle'] + gradle_wrapper_cmd = ['gradle'] gradle_wrapper_cmd.extend(['wrapper', '-p', str(self.build_dir.resolve())]) @@ -580,7 +607,8 @@ class AndroidProjectGenerator(object): asset_type='', android_sdk_path=str(self.android_sdk_path), embed_assets_in_apk=True, - is_unit_test=True) + is_unit_test=True, + android_gradle_plugin_version=self.gradle_plugin_version) else: platform_settings_content = PLATFORM_SETTINGS_FORMAT.format(generation_timestamp=str(datetime.datetime.now().strftime("%c")), platform='android', @@ -589,16 +617,28 @@ class AndroidProjectGenerator(object): asset_type=self.asset_type, android_sdk_path=str(self.android_sdk_path), embed_assets_in_apk=str(self.include_assets_in_apk), - is_unit_test=False) + is_unit_test=False, + android_gradle_plugin_version=self.gradle_plugin_version) platform_settings_file = self.build_dir / 'platform.settings' + + # Check if there already exists the build folder and a 'platform.settings' file. If there is an android gradle + # plugin version set and it is different than the one configured here, we will always overwrite it since + # there could be significant differences from one plug-in to the next + if platform_settings_file.is_file(): + config = configparser.ConfigParser() + config.read([str(platform_settings_file.resolve(strict=True))]) + if config.has_option('android', 'android_gradle_plugin'): + exist_agp_version = config.get('android', 'android_gradle_plugin') + if exist_agp_version != self.gradle_plugin_version: + self.overwrite_existing = True + platform_settings_file.open('w').write(platform_settings_content) def create_default_local_properties(self): """ Create the default 'local.properties' file in the build folder """ - template_android_ndk_path = common.normalize_path_for_settings(self.android_ndk_path, True) template_android_sdk_path = common.normalize_path_for_settings(self.android_sdk_path, True) if self.override_cmake_path: # The cmake dir references the base cmake folder, not the executable path itself, so resolve to the base folder @@ -608,7 +648,6 @@ class AndroidProjectGenerator(object): local_properties_env = { "GENERATION_TIMESTAMP": str(datetime.datetime.now().strftime("%c")), - "ANDROID_NDK_PATH": template_android_ndk_path, "ANDROID_SDK_PATH": template_android_sdk_path, "CMAKE_DIR_LINE": f'cmake.dir={template_cmake_path}' if template_cmake_path else '' } @@ -626,8 +665,7 @@ class AndroidProjectGenerator(object): # before we can process it. android_libraries_substitution_table = { "ANDROID_SDK_HOME": common.normalize_path_for_settings(self.android_sdk_path, False), - "ANDROID_NDK_HOME": common.normalize_path_for_settings(self.android_ndk_path, False), - "ANDROID_SDK_VERSION": "android-".format(self.android_sdk_version) + "ANDROID_SDK_VERSION": f"android-{self.android_sdk_platform}" } android_libraries_template_json_path = self.android_project_builder_path / ANDROID_LIBRARIES_JSON_FILE @@ -717,7 +755,7 @@ class AndroidProjectGenerator(object): template_engine_root = common.normalize_path_for_settings(self.engine_root) template_third_party_path = common.normalize_path_for_settings(self.third_party_path) - template_ndk_path = common.normalize_path_for_settings(self.android_ndk_path) + template_ndk_path = common.normalize_path_for_settings(os.path.join(self.android_sdk_path, self.android_ndk.location)) gradle_build_env = dict() @@ -733,7 +771,6 @@ class AndroidProjectGenerator(object): gradle_build_env['OVERRIDE_JAVA_SOURCESET'] = OVERRIDE_JAVA_SOURCESET_STR.format(absolute_azandroid_path=absolute_azandroid_path) - gradle_build_env['OPTIONAL_JNI_SRC_LIB_SET'] = ', "outputs/native-lib"' for native_config in BUILD_CONFIGURATIONS: @@ -755,7 +792,7 @@ class AndroidProjectGenerator(object): cmake_argument_list.append('"-DLY_TEST_PROJECT=1"') cmake_argument_list.extend([ - f'"-DANDROID_NATIVE_API_LEVEL={self.android_ndk_platform}"', + f'"-DANDROID_NATIVE_API_LEVEL={self.android_native_api_level}"', f'"-DLY_NDK_DIR={template_ndk_path}"', '"-DANDROID_STL=c++_shared"', '"-Wno-deprecated"', @@ -835,8 +872,7 @@ class AndroidProjectGenerator(object): dest_src_main_path.mkdir(parents=True) az_android_package_env = AndroidProjectManifestEnvironment(engine_root=self.engine_root, project_path=self.project_path, - android_sdk_version_number=self.android_sdk_version, - android_ndk_platform_number=self.android_ndk_platform, + android_sdk_version_number=self.android_sdk_platform, is_test=self.is_test_project) self.create_file_from_project_template(src_template_file=ANDROID_MANIFEST_FILE, template_env=az_android_package_env, @@ -1304,218 +1340,7 @@ class AndroidProjectGenerator(object): self.new = new -ANDROID_PLATFORM_PATTERN = re.compile(r'([\w\d]*-)?(\d+\d*)') # Regex to handle android platform naming for both SDKs and NDKs - - -def validate_android_platform_input(input_android_platform, platform_variable_type, min_version, max_version): - """ - Helper tool to support android platform number inputs and perform min/max version validation - - :param input_android_platform: The inpuit argument to evaluate - :param platform_variable_type: The type of platform version to validate (android sdk / android ndk) - :param min_version: The minimum version to validate against - :param max_version: The maximum version to validate against - :return: The int version of the extracted platform number from the input - """ - # Validate the platform number's format and against the supported versions - platform_number_match = ANDROID_PLATFORM_PATTERN.search(input_android_platform) - if not platform_number_match or not platform_number_match.group(2) or (platform_number_match.group(1) and platform_number_match.group(1) != 'android-'): - raise common.LmbrCmdError(f"Invalid {platform_variable_type} version value ({input_android_platform}). It must be " - f"either 'XX' or android-'XX' where 'XX' is a platform number.", - common.ERROR_CODE_INVALID_PARAMETER) - - android_platform_number = int(platform_number_match.group(2)) - if android_platform_number < min_version: - raise common.LmbrCmdError(f"Invalid {platform_variable_type} version value ({input_android_platform}) is less than the minimum " - f"supported version ({min_version}).", - common.ERROR_CODE_INVALID_PARAMETER) - if android_platform_number > max_version: - raise common.LmbrCmdError(f"Invalid {platform_variable_type} version value ({input_android_platform}) is greater than the maximum " - f"supported version ({max_version}).", - common.ERROR_CODE_INVALID_PARAMETER) - return android_platform_number - - ANDROID_SDK_ENV_NAME = 'ANDROID_SDK' -ANDROID_SDK_MIN_PLATFORM = 28 -ANDROID_SDK_MAX_PLATFORM = 29 - - -def verify_android_sdk(android_sdk_platform, argument_name, override_android_sdk_path=None, preferred_sdk_build_tools_ver=None): - """ - Verify the android sdk and the requested platform platform against the android sdk path - - :param android_sdk_platform: The android sdk platform to use (e.g. '28' or 'android-28') - :param argument_name: The name of the argument for descriptive errors to present - :param override_android_sdk_path: The location of the android SDK path if not set through the environment variable - :param preferred_sdk_build_tools_ver: Option prefered built tool version under the android SDK if available. Will fallback to the first one discovered - :returns tuple of the verified android sdk platform number, path to the Android SDK path and the build tool version - """ - android_sdk_platform_number = validate_android_platform_input(input_android_platform=android_sdk_platform, - platform_variable_type='android sdk', - min_version=ANDROID_SDK_MIN_PLATFORM, - max_version=ANDROID_SDK_MAX_PLATFORM) - - # Get the candidate android sdk path from either the override argument or the system environment variable - if override_android_sdk_path: - check_android_sdk_path = override_android_sdk_path - else: - check_android_sdk_path = os.environ.get(ANDROID_SDK_ENV_NAME) - if not check_android_sdk_path: - raise common.LmbrCmdError(f"Android SDK path not set. Make sure that either the '{ANDROID_SDK_ENV_NAME}' environment is " - f"set or it is passed in through the {argument_name} argument") - - # The android sdk folder structure is expected to have a 'platforms' sub folder based on the android sdk-platform number - check_android_sdk_path = pathlib.Path(check_android_sdk_path) - android_sdk_platforms_path = check_android_sdk_path / 'platforms' - if not android_sdk_platforms_path.is_dir(): - raise common.LmbrCmdError(f"Invalid Android SDK path '{str(check_android_sdk_path)}': Missing 'platforms' directory.") - - # Collect the available platform numbers from the platforms subdirectory - validated_android_platforms = [] - for dir_item in android_sdk_platforms_path.iterdir(): - if not dir_item.is_dir(): - continue - check_file = dir_item / 'package.xml' - if check_file.is_file(): - validated_android_platforms.append(dir_item.name) - - if not validated_android_platforms: - raise common.LmbrCmdError(f"Invalid Android SDK path '{str(check_android_sdk_path)}': Unable to find any android platforms.") - - # Normalize the android_sdk argument to fit the same folder name pattern - android_sdk_platform_name = f'android-{android_sdk_platform_number}' - if android_sdk_platform_name not in validated_android_platforms: - raise common.LmbrCmdError(f"Android SDK platform {android_sdk_platform_name} is not a valid for the android SDK located under '{str(check_android_sdk_path)}'") - - # Enumerate through the build tools under android sdk - android_sdk_build_tools_dir = check_android_sdk_path / 'build-tools' - if not android_sdk_build_tools_dir.is_dir(): - raise common.LmbrCmdError(f"Invalid Android SDK path '{str(check_android_sdk_path)}': Unable to find any built-tools folder.") - supported_build_tools = [str(build_tool.name) for build_tool in android_sdk_build_tools_dir.iterdir() if build_tool.is_dir()] - if not supported_build_tools: - raise common.LmbrCmdError(f"Invalid Android SDK path '{str(check_android_sdk_path)}': Unable to find any built-tools.") - if preferred_sdk_build_tools_ver: - if preferred_sdk_build_tools_ver in supported_build_tools: - validated_build_tool = preferred_sdk_build_tools_ver - else: - validated_build_tool = supported_build_tools[0] - logging.warning("Unable to locate android sdk build tool version {preferred_sdk_build_tools_ver}. Defaulting to version {validated_build_tool}") - - else: - validated_build_tool = supported_build_tools[0] - - return android_sdk_platform_number, check_android_sdk_path, validated_build_tool - - -ANDROID_NDK_ENV_NAME = 'ANDROID_NDK' -ANDROID_NDK_MIN_PLATFORM = 21 -ANDROID_NDK_MAX_PLATFORM = 29 -ANDROID_NDK_SOURCE_PROPERTIES_REVISION_PATTERN = re.compile(r'Pkg.Revision\s*=\s*(\d+.\d+.\d+)') - - -def verify_android_ndk(android_ndk_platform, argument_name, override_android_ndk_path=None): - """ - Verify the android ndk and requested platform against the android ndk path - - :param android_ndk_platform: The android ndk platform to use (e.g. '21' or 'android-21') - :param argument_name: The name of the argument for descriptive errors to present - :param override_android_ndk_path: The location of the android NDK path if not set through the environment variable - :returns tuple of the verified android ndk platform number and the Path to the Android SDK path and the - """ - - android_ndk_platform_number = validate_android_platform_input(input_android_platform=android_ndk_platform, - platform_variable_type='android ndk', - min_version=ANDROID_NDK_MIN_PLATFORM, - max_version=ANDROID_NDK_MAX_PLATFORM) - - # Get the candidate android ndk path from either the override argument or the system environment variable - if override_android_ndk_path: - check_android_ndk_path = str(override_android_ndk_path) - else: - check_android_ndk_path = os.environ.get(ANDROID_NDK_ENV_NAME) - if not check_android_ndk_path: - raise common.LmbrCmdError(f"Android NDK path not set. Make sure that either the {ANDROID_NDK_ENV_NAME} environment " - f"is set or it is passed in through the {argument_name} argument") - check_android_ndk_path = pathlib.Path(check_android_ndk_path) - - # Validate the android ndk path - - # Determine the NDK revision by reading the source.properties file - ndk_source_properties_file = check_android_ndk_path / 'source.properties' - if not ndk_source_properties_file.is_file(): - raise common.LmbrCmdError(f"Invalid Android NDK path '{str(check_android_ndk_path)}'. Missing 'source.properties' file.", - common.ERROR_CODE_INVALID_PARAMETER) - ndk_source_properties_file_content = ndk_source_properties_file.read_text(encoding=common.DEFAULT_TEXT_READ_ENCODING, - errors=common.ENCODING_ERROR_HANDLINGS) - - ndk_revision_match = ANDROID_NDK_SOURCE_PROPERTIES_REVISION_PATTERN.search(ndk_source_properties_file_content) - if not ndk_revision_match: - raise common.LmbrCmdError(f"Invalid Android NDK path '{str(check_android_ndk_path)}'. Unable to extract version from 'source.properties' file.", - common.ERROR_CODE_INVALID_PARAMETER) - ndk_revision_number = LooseVersion(ndk_revision_match.group(1)) - logging.info(f"Detected Android NDK Revision {str(ndk_revision_number)}") - - # Collect the supported android platforms from the required 'platforms' folder under the ndk path - android_ndk_platforms_path = check_android_ndk_path / 'platforms' - if not android_ndk_platforms_path.is_dir(): - raise common.LmbrCmdError(f"Invalid Android NDK path '{str(check_android_ndk_path)}'. Missing 'platforms' folder.", - common.ERROR_CODE_INVALID_PARAMETER) - - validated_android_platforms = [] - for dir_item in android_ndk_platforms_path.iterdir(): - if not dir_item.is_dir(): - continue - api_version_match = ANDROID_PLATFORM_PATTERN.search(dir_item.name) - if not api_version_match or api_version_match.group(1) != 'android-': - continue - - check_lib_path = dir_item / 'arch-arm64/usr/lib' - if check_lib_path.is_dir(): - validated_android_platforms.append(dir_item.name) - - # For NDK revisions 19 and up, there is a mapping file for version numbers that map to other version. - platforms_map_aliases = {} - if ndk_revision_number >= LooseVersion('19.0.0'): - platforms_map_file = check_android_ndk_path / 'meta/platforms.json' - if platforms_map_file.exists(): - with open(platforms_map_file, 'r') as platforms_map_file_handle: - platforms_map_file_json = json.load(platforms_map_file_handle) - platforms_map_aliases = platforms_map_file_json['aliases'] - elif validated_android_platforms: - # Revisions before 19 does not have a mapping file for API versions, they fall back to the previous one - # So we need to make a mapping file that does the same - platforms_map_aliases = {} - validated_android_platforms.sort() - max_supported_api_number = int(ANDROID_PLATFORM_PATTERN.search(validated_android_platforms[-1]).group(2)) - for validated_android_platform in validated_android_platforms: - current_api_version = int(ANDROID_PLATFORM_PATTERN.search(validated_android_platform).group(2)) - next_api_version = current_api_version + 1 - while f'android-{next_api_version}' not in validated_android_platforms and next_api_version <= max_supported_api_number: - platforms_map_aliases[str(next_api_version)] = current_api_version - next_api_version += 1 - - # Go through the aliases and add to the validated platforms if it is mapped to an existing platform - for alias_key, alias_value in platforms_map_aliases.items(): - if not ANDROID_PLATFORM_PATTERN.search(f'android-{alias_key}'): - # Skip any non android-XX (XX = number) aliases - continue - aliased_platform_key = f'android-{alias_value}' - if aliased_platform_key in validated_android_platforms: - validated_android_platforms.append(f'android-{alias_key}') - - if not validated_android_platforms: - raise common.LmbrCmdError(f"Invalid Android NDK path {str(check_android_ndk_path)}") - - # Verify the ndk platform against the ndk path - android_ndk_platform_name = f'android-{android_ndk_platform_number}' - if android_ndk_platform_name not in validated_android_platforms: - raise common.LmbrCmdError(f"Android NDK platform {android_ndk_platform_name} is not a valid for the Android NDK located under '{str(check_android_ndk_path)}'") - - return android_ndk_platform_number, check_android_ndk_path - - -ADB_TARGET = 'adb.exe' if platform.system() == 'Windows' else 'adb' def resolve_adb_tool(android_sdk_path): @@ -1528,9 +1353,16 @@ def resolve_adb_tool(android_sdk_path): if isinstance(android_sdk_path, str): android_sdk_path = pathlib.Path(android_sdk_path) - check_adb_target = android_sdk_path / 'platform-tools' / ADB_TARGET - if not check_adb_target.exists(): - raise common.LmbrCmdError(f"Invalid Android SDK path '{str(android_sdk_path)}': Unable to locate '{ADB_TARGET}'.") + file_found = False + for executable_path_ext in common.PLATFORM_EXECUTABLE_EXTENSIONS: + check_adb_target = android_sdk_path / 'platform-tools' / f'adb{executable_path_ext}' + if check_adb_target.is_file(): + file_found = True + break + + if not file_found: + raise common.LmbrCmdError(f"Invalid Android SDK path '{str(android_sdk_path)}': Unable to locate 'adb'.") + return check_adb_target @@ -1633,3 +1465,196 @@ class AdbTool(common.CommandLineExec): else: adb_params = arguments return super().popen(adb_params, cwd) + + +class AndroidGradlePluginInfo(object): + + def __init__(self, android_gradle_plugin_version): + + if android_gradle_plugin_version not in ANDROID_GRADLE_PLUGIN_COMPATIBILITY_MAP.keys(): + raise common.LmbrCmdError(f"Android Gradle Plugin version {android_gradle_plugin_version} is not supported. " + f"Only the following version(s) are supported: {','.join(ANDROID_GRADLE_PLUGIN_COMPATIBILITY_MAP.keys())}") + + details = ANDROID_GRADLE_PLUGIN_COMPATIBILITY_MAP[android_gradle_plugin_version] + self.default_sdk_build_tools_version = LooseVersion(details.get('sdk_build')) + + self.default_ndk_version = LooseVersion(details.get('default_ndk')) + + self.min_gradle_version = LooseVersion(details.get('min_gradle_version')) + + self.min_cmake_version = LooseVersion(details.get('min_cmake_version')) + + max_cmake_version_number = details.get('max_cmake_version') + self.max_cmake_version = None if max_cmake_version_number is None else LooseVersion(max_cmake_version_number) + + +class AndroidSDKResolver(object): + """ + Class that manages the Android SDK tool to validate, install packages (e.g. built tools, sdk platforms, ndk, etc) + """ + + class InstalledPackage(object): + def __init__(self, installed_package_components): + assert len(installed_package_components) == 4, '4 sections expected for installed package components (path, version, description, location)' + self.path = installed_package_components[0] + self.version = LooseVersion(installed_package_components[1]) + self.description = installed_package_components[2] + self.location = installed_package_components[3] + + class AvailablePackage(object): + def __init__(self, available_package_components): + assert len(available_package_components) == 3, '3 sections expected for installed package components (path, version, description)' + self.path = available_package_components[0] + self.version = LooseVersion(available_package_components[1]) + self.description = available_package_components[2] + + class AvailableUpdate(object): + def __init__(self, available_update_components): + assert len(available_update_components) == 3, '3 sections expected for installed package components (path, version, available)' + self.path = available_update_components[0] + self.version = LooseVersion(available_update_components[1]) + self.available = available_update_components[2] + + def __init__(self, android_sdk_path): + + self.android_sdk_path = android_sdk_path or os.environ.get(ANDROID_SDK_ENV_NAME) + if not self.android_sdk_path: + raise common.LmbrCmdError(f"Android SDK path not set or it was not passed into the command to generate the android project") + if not os.path.isdir(self.android_sdk_path): + raise common.LmbrCmdError(f"Android SDK path {self.android_sdk_path} is not valid") + if platform.system() == 'Windows': + self.sdk_manager_path = pathlib.Path(self.android_sdk_path) / 'tools' / 'bin' / 'sdkmanager.bat' + else: + raise common.LmbrCmdError(f"This tool is not supported on the current platform {platform.system()}") + if not self.sdk_manager_path.is_file(): + raise common.LmbrCmdError(f"Android SDK path {self.android_sdk_path} is not valid or complete. Missing {self.sdk_manager_path}") + + self.sdk_manager = common.CommandLineExec(str(self.sdk_manager_path.resolve())) + + self.installed_packages = {} + self.available_packages = {} + self.available_updates = {} + self.refresh_sdk_installation() + + def refresh_sdk_installation(self): + """ + Utilize the sdk_manager command line tool from the Android SDK to collect / refresh the list of + installed, available, and updateable packages that are managed by the android SDK. + """ + self.installed_packages = {} + self.available_packages = {} + self.available_updates = {} + + def _factory_installed_package(package_map, item_components): + package_map[item_components[0]] = AndroidSDKResolver.InstalledPackage(item_components) + + def _factory_available_package(package_map, item_components): + package_map[item_components[0]] = AndroidSDKResolver.AvailablePackage(item_components) + + def _factory_available_update(package_map, item_components): + package_map[item_components[0]] = AndroidSDKResolver.AvailableUpdate(item_components) + + # Use the SDK manager to collect the available and installed packages + result_code, result_stdout, result_stderr = self.sdk_manager.exec(['--list'], capture_stdout=True, suppress_stderr=True) + + current_append_map = None + current_item_factory = None + for package_item in result_stdout.split('\n'): + package_item_stripped = package_item.strip() + if not package_item_stripped: + continue + if '|' not in package_item_stripped: + if package_item_stripped.upper() == 'INSTALLED PACKAGES:': + current_append_map = self.installed_packages + current_item_factory = _factory_installed_package + elif package_item_stripped.upper() == 'AVAILABLE PACKAGES:': + current_append_map = self.available_packages + current_item_factory = _factory_available_package + elif package_item_stripped.upper() == 'AVAILABLE UPDATES:': + current_append_map = self.available_updates + current_item_factory = _factory_available_update + else: + current_append_map = None + current_item_factory = None + continue + item_parts = [split.strip() for split in package_item_stripped.split('|')] + if len(item_parts) < 3: + continue + elif item_parts[1].upper() in ('VERSION', 'INSTALLED', '-------'): + continue + elif current_append_map is None: + continue + if current_append_map is not None and current_item_factory is not None: + current_item_factory(current_append_map, item_parts) + + def is_package_installed(self, search_package_path): + """ + Check if a package path to see if its a package that is installed. The path can use wildcard '*'s + The function will return a list of the results that match the package paths, ordered by the newest version first + """ + def _package_sort(package): + return package.version + package_detail_result_list = [] + for installed_package_path, installed_package_details in self.installed_packages.items(): + if fnmatch.fnmatch(installed_package_path, search_package_path): + package_detail_result_list.append(installed_package_details) + package_detail_result_list.sort(reverse=True, key=_package_sort) + return package_detail_result_list + + def is_package_available(self, search_package_path): + """ + Check if a package path to see if its an available package to install. The path can use wildcard '*'s + The function will return a list of the results that match the package paths, ordered by the newest version first + """ + def _package_sort(package): + return package.version + package_detail_result_list = [] + for available_package_path, available_package_details in self.available_packages.items(): + if fnmatch.fnmatch(available_package_path, search_package_path): + package_detail_result_list.append(available_package_details) + package_detail_result_list.sort(reverse=True, key=_package_sort) + return package_detail_result_list + + def install_package(self, package_install_path, package_description): + """ + Install a package based on the path of an available android sdk package + """ + + # Skip installation if the package is already installed + package_result_list = self.is_package_installed(package_install_path) + if package_result_list: + installed_package_detail = package_result_list[0] + logging.info(f"{installed_package_detail.description} (version {installed_package_detail.version}) Detected") + return installed_package_detail + + # Make sure the package name is available + package_result_list = self.is_package_available(package_install_path) + if not package_result_list: + raise common.LmbrCmdError(f"Invalid Android SDK Package {package_description}: Bad package path {package_install_path}") + + # Reverse sort and pick the first item, which should be the latest (if the install path contains wildcards) + def _available_sort(item): + return item.path + + package_result_list.sort(reverse=True, key=_available_sort) + + available_package_to_install = package_result_list[0] # For multiple hits, resolve to the first item which will be the latest version + + # Perform the package installation + logging.info(f"Installing {available_package_to_install.description} ...") + result_code, result_stdout, result_stderr = self.sdk_manager.exec(['--install', available_package_to_install.path], capture_stdout=True, suppress_stderr=True) + if result_code != 0: + raise common.LmbrCmdError(f"Error installing package {available_package_to_install.path}: \n{result_stderr}") + + # Refresh the tracked SDK Contents + self.refresh_sdk_installation() + + # Get the package details to verify + package_result_list = self.is_package_installed(package_install_path) + if package_result_list: + installed_package_detail = package_result_list[0] + logging.info(f"{installed_package_detail.description} (version {installed_package_detail.version}) Installed") + return installed_package_detail + else: + raise common.LmbrCmdError(f"Error installing package {available_package_to_install.path}: \n{result_stderr}") + diff --git a/cmake/Tools/Platform/Android/generate_android_project.py b/cmake/Tools/Platform/Android/generate_android_project.py index 9a0e2760f5..d25b62dde8 100755 --- a/cmake/Tools/Platform/Android/generate_android_project.py +++ b/cmake/Tools/Platform/Android/generate_android_project.py @@ -27,7 +27,7 @@ from cmake.Tools import common from cmake.Tools.Platform.Android import android_support GRADLE_ARGUMENT_NAME = '--gradle-install-path' -GRADLE_MIN_VERSION = LooseVersion('4.10.1') +GRADLE_MIN_VERSION = LooseVersion('6.5') GRADLE_MAX_VERSION = LooseVersion('7.0.0') GRADLE_VERSION_REGEX = re.compile(r"Gradle\s(\d+.\d+.?\d*)") GRADLE_EXECUTABLE = 'gradle.bat' if platform.system() == 'Windows' else 'gradle' @@ -48,9 +48,9 @@ def verify_gradle(override_gradle_path=None): CMAKE_ARGUMENT_NAME = '--cmake-install-path' -CMAKE_MIN_VERSION = LooseVersion('3.17.0') +CMAKE_MIN_VERSION = LooseVersion('3.19.0') CMAKE_VERSION_REGEX = re.compile(r'cmake version (\d+.\d+.?\d*)') -CMAKE_EXECUTABLE = 'cmake.exe' if platform.system() == 'Windows' else 'cmake' +CMAKE_EXECUTABLE = 'cmake' def verify_cmake(override_cmake_path=None): @@ -69,7 +69,7 @@ def verify_cmake(override_cmake_path=None): NINJA_ARGUMENT_NAME = '--ninja-install-path' NINJA_VERSION_REGEX = re.compile(r'(\d+.\d+.?\d*)') -NINJA_EXECUTABLE = 'ninja.exe' if platform.system() == 'Windows' else 'ninja' +NINJA_EXECUTABLE = 'ninja' def verify_ninja(override_ninja_path=None): @@ -78,7 +78,7 @@ def verify_ninja(override_ninja_path=None): """ return common.verify_tool(override_tool_path=override_ninja_path, tool_name='ninja', - tool_filename='ninja.exe' if platform.system() == 'Windows' else 'ninja', + tool_filename='ninja', argument_name=NINJA_ARGUMENT_NAME, tool_version_argument='--version', tool_version_regex=NINJA_VERSION_REGEX, @@ -103,13 +103,21 @@ def build_optional_signing_profile(store_file, store_password, key_alias, key_pa ANDROID_SDK_ARGUMENT_NAME = '--android-sdk-path' -ANDROID_SDK_PLATFORM_ARGUMENT_NAME = '--android-sdk-version' +ANDROID_SDK_PLATFORM_ARGUMENT_NAME = '--android-sdk-platform' ANDROID_SDK_PREFERRED_TOOL_VER = '--android-sdk-build-tool-version' +ANDROID_NATIVE_API_LEVEL = '--android-native-api-level' + + +MIN_ANDROID_SDK_PLATFORM = 28 # The minimum platform/api level that is supported for the SDK Platform +MIN_NATIVE_API_LEVEL = 24 # The minimum Native API level that is supported for the NDK + -ANDROID_NDK_ARGUMENT_NAME = '--android-ndk-path' ANDROID_NDK_PLATFORM_ARGUMENT_NAME = '--android-ndk-version' +ANDROID_GRADLE_PLUGIN_ARGUMENT_NAME = '--gradle-plugin-version' +ANDROID_GRADLE_MIN_PLUGIN_VERSION = LooseVersion("4.2.0") + # Constants for asset-related options for APK generation INCLUDE_APK_ASSETS_ARGUMENT_NAME = "--include-apk-assets" ASSET_MODE_ARGUMENT_NAME = "--asset-mode" @@ -147,6 +155,7 @@ def main(args): parser = argparse.ArgumentParser(description="Prepare the android studio subfolder") + # Required Arguments parser.add_argument('--engine-root', help='The path to the engine root. Defaults to the current working directory.', default=os.getcwd()) @@ -160,32 +169,42 @@ def main(args): help='The path to the 3rd Party root directory', required=True) - parser.add_argument(ANDROID_NDK_ARGUMENT_NAME, - help='The path to the android NDK', - required=True) - parser.add_argument(ANDROID_SDK_ARGUMENT_NAME, help='The path to the android SDK', required=True) - parser.add_argument(ANDROID_SDK_PLATFORM_ARGUMENT_NAME, - help='The android SDK version', + parser.add_argument('-g', '--project-path', + help='The project path to generate an android project', required=True) + parser.add_argument(ANDROID_SDK_PLATFORM_ARGUMENT_NAME, + help=f'The android SDK platform number version to use for the APK. (Minimum {MIN_ANDROID_SDK_PLATFORM})', + type=int, + default=-1) + + parser.add_argument(ANDROID_NATIVE_API_LEVEL, + help=f'The android native API level to use for the APK. If not set, this will default to the android SDK platform. (Minimum {MIN_ANDROID_SDK_PLATFORM})', + type=int, + default=-1) + + # Override arguments parser.add_argument(ANDROID_SDK_PREFERRED_TOOL_VER, - help='The preferred android sdk build version (i.e. 28.0.3). Will default to the first one detected under the android sdk', - default=None, + help='The android SDK build tools version.', required=False) parser.add_argument(ANDROID_NDK_PLATFORM_ARGUMENT_NAME, help='The android NDK version', - required=True) + required=False) parser.add_argument(GRADLE_ARGUMENT_NAME, help=f'The path to installed gradle. The version of gradle must fall in between {str(GRADLE_MIN_VERSION)} and {str(GRADLE_MAX_VERSION)}.', default=None, required=False) + parser.add_argument(ANDROID_GRADLE_PLUGIN_ARGUMENT_NAME, + help=f'The version of the android gradle plugin to use. Defaults to the minimum version ({ANDROID_GRADLE_MIN_PLUGIN_VERSION})', + default=str(ANDROID_GRADLE_MIN_PLUGIN_VERSION)) + parser.add_argument(CMAKE_ARGUMENT_NAME, help=f'The path to cmake build tool if not installed on the system path. The version of cmake must be at least version {str(CMAKE_MIN_VERSION)}.', default=None, @@ -196,9 +215,6 @@ def main(args): default=None, required=False) - parser.add_argument('-g', '--project-path', - help='The project path to generate an android project') - # Asset Options parser.add_argument(INCLUDE_APK_ASSETS_ARGUMENT_NAME, action='store_true', @@ -207,11 +223,11 @@ def main(args): parser.add_argument(ASSET_MODE_ARGUMENT_NAME, choices=ALL_ASSET_MODES, default=ASSET_MODE_LOOSE, - help='Asset Mode (vfs|pak|loose) to use when including assets into the APK') + help=f'Asset Mode (vfs|pak|loose) to use when including assets into the APK. (Defaults to {ASSET_MODE_LOOSE})') parser.add_argument(ASSET_TYPE_ARGUMENT_NAME, default=DEFAULT_ASSET_TYPE, - help='Asset Type to use when including assets into the APK') + help=f'Asset Type to use when including assets into the APK. (Defaults to {DEFAULT_ASSET_TYPE})') parser.add_argument('--debug', action='store_true', @@ -260,16 +276,81 @@ def main(args): ninja_version, override_ninja_path = verify_ninja(override_ninja_path=parsed_args.get_argument(NINJA_ARGUMENT_NAME)) logging.info("Detected Ninja version %s", str(ninja_version)) - # Verify the android sdk path and sdk version - verified_android_sdk_platform, verified_android_sdk_path, android_sdk_build_tool_ver = android_support.verify_android_sdk(android_sdk_platform=parsed_args.get_argument(ANDROID_SDK_PLATFORM_ARGUMENT_NAME), - argument_name=ANDROID_SDK_ARGUMENT_NAME, - override_android_sdk_path=parsed_args.get_argument(ANDROID_SDK_ARGUMENT_NAME), - preferred_sdk_build_tools_ver=parsed_args.get_argument(ANDROID_SDK_PREFERRED_TOOL_VER)) + # Get the android sdk platform version to use from the arguments, but also handle the deprecated argument name + android_sdk_platform_version = parsed_args.get_argument(ANDROID_SDK_PLATFORM_ARGUMENT_NAME) - # Verify the android ndk path and ndk version - verified_android_ndk_platform, verified_android_ndk_path = android_support.verify_android_ndk(android_ndk_platform=parsed_args.get_argument(ANDROID_NDK_PLATFORM_ARGUMENT_NAME), - argument_name=ANDROID_NDK_ARGUMENT_NAME, - override_android_ndk_path=parsed_args.get_argument(ANDROID_NDK_ARGUMENT_NAME)) + # Get the gradle plugin details and validate against the current environment + android_gradle_plugin_version = parsed_args.get_argument(ANDROID_GRADLE_PLUGIN_ARGUMENT_NAME) + android_gradle_plugin = android_support.AndroidGradlePluginInfo(android_gradle_plugin_version) + logging.info(f"Generating Android Gradle Plugin version {android_gradle_plugin_version} based project") + + if gradle_version < android_gradle_plugin.min_gradle_version: + raise common.LmbrCmdError(f"The current version of gradle ({gradle_version}) does not satisfy the minimum version " + f"({android_gradle_plugin.min_gradle_version}) needed for the android gradle plugin " + f"({android_gradle_plugin_version}). Please upgrade your gradle.") + if cmake_version < android_gradle_plugin.min_cmake_version: + raise common.LmbrCmdError(f"The current version of cmake ({cmake_version}) does not satisfy the minimum version " + f"({android_gradle_plugin.min_cmake_version}) needed for the android gradle plugin " + f"({android_gradle_plugin_version}). Please upgrade your cmake.") + if android_gradle_plugin.max_cmake_version and cmake_version > android_gradle_plugin.max_cmake_version: + raise common.LmbrCmdError(f"The current version of cmake ({cmake_version}) exceeds the maximum version " + f"({android_gradle_plugin.max_cmake_version}) of the android gradle plugin " + f"({android_gradle_plugin_version}).") + + # Use the SDK Resolver to make sure the build tools and ndk + android_sdk = android_support.AndroidSDKResolver(android_sdk_path=parsed_args.get_argument(ANDROID_SDK_ARGUMENT_NAME)) + + # If no SDK platform is provided, check for any installed one + if android_sdk_platform_version < 0: + android_sdk_platform_version = MIN_ANDROID_SDK_PLATFORM + installed_android_sdk_platforms = android_sdk.is_package_installed('platforms;*') + if installed_android_sdk_platforms: + # If there are installed platforms, check the most recent one + latest_platform_version = -1 + for installed_android_sdk_platform in installed_android_sdk_platforms: + platform_number_match = re.match(r'platforms;android-([0-9]*)', installed_android_sdk_platform.path) + if not platform_number_match: + continue + check_platform_version = int(platform_number_match.group(1)) + if check_platform_version > latest_platform_version: + latest_platform_version = check_platform_version + if latest_platform_version >= MIN_ANDROID_SDK_PLATFORM: + android_sdk_platform_version = latest_platform_version + else: + if android_sdk_platform_version < MIN_ANDROID_SDK_PLATFORM: + raise common.LmbrCmdError(f"Invalid argument for {ANDROID_SDK_PLATFORM_ARGUMENT_NAME} ({android_sdk_platform_version}). Must be greater than the minimum value supported {MIN_ANDROID_SDK_PLATFORM}.") + + # Get the android native api level from the arguments. Default to the sdk platform version if not provided + android_native_api_level = parsed_args.get_argument(ANDROID_NATIVE_API_LEVEL) + if android_native_api_level < 0: + android_native_api_level = android_sdk_platform_version + else: + if android_native_api_level < MIN_NATIVE_API_LEVEL: + raise common.LmbrCmdError(f"Invalid argument for {ANDROID_NATIVE_API_LEVEL} ({android_native_api_level}). Must be greater than the minimum value supported {MIN_NATIVE_API_LEVEL}.") + + # Check and make sure that the requested sdk platform exists, download if necessary + platform_package_name = f"platforms;android-{android_sdk_platform_version}" + android_sdk.install_package(package_install_path=platform_package_name, + package_description=f'Android SDK Platform {android_sdk_platform_version}') + + # Make sure we have the extra android packages "market_apk_expansion" and "market_licensing" which is needed by the APK + android_sdk.install_package(package_install_path='extras;google;market_apk_expansion', + package_description='Google APK Expansion Library') + + android_sdk.install_package(package_install_path='extras;google;market_licensing', + package_description='Google Play Licensing Library') + + # Install either the requested SDK build tools or the default one for the android gradle plugin version + build_tools_version = parsed_args.get_argument(ANDROID_SDK_PREFERRED_TOOL_VER) or android_gradle_plugin.default_sdk_build_tools_version + build_tools_package_name = f'build-tools;{build_tools_version}' + build_tools_package = android_sdk.install_package(package_install_path=build_tools_package_name, + package_description='Android SDK Build Tools') + + # Install either the requested NDK version or the default one for the android gradle plugin version + android_ndk_version = parsed_args.get_argument(ANDROID_NDK_PLATFORM_ARGUMENT_NAME) or android_gradle_plugin.default_ndk_version + android_ndk_package_name = f'ndk;{android_ndk_version}' + android_ndk_package = android_sdk.install_package(package_install_path=android_ndk_package_name, + package_description='Android NDK') # Verify the engine root path and project path verified_project_path, verified_engine_root = common.verify_project_and_engine_root(project_root=parsed_args.project_path, @@ -277,10 +358,9 @@ def main(args): is_test_project = parsed_args.unit_test # Verify the 3rd Party Root Path - third_party_path = pathlib.Path(parsed_args.third_party_path) / '3rdParty.txt' - if not third_party_path.is_file(): - raise common.LmbrCmdError("Invalid --third-party-path '{}'. Make sure it exists and contains " - "3rdParty.txt".format(parsed_args.third_party_path), + third_party_path = pathlib.Path(parsed_args.third_party_path) + if not third_party_path.is_dir(): + raise common.LmbrCmdError(f"Invalid --third-party-path '{parsed_args.third_party_path}'.", common.ERROR_CODE_INVALID_PARAMETER) third_party_path = third_party_path.parent @@ -293,23 +373,23 @@ def main(args): logging.debug("Engine Root : %s", str(verified_engine_root.resolve())) logging.debug("Build Path : %s", str(build_dir.resolve())) - logging.debug("Android NDK Path : %s", str(verified_android_ndk_path.resolve())) - logging.debug("Android SDK Path : %s", str(verified_android_sdk_path.resolve())) # Prepare the generator and execute generator = android_support.AndroidProjectGenerator(engine_root=verified_engine_root, - project_path=verified_project_path, build_dir=build_dir, - android_sdk_path=verified_android_sdk_path, - android_ndk_path=verified_android_ndk_path, - android_sdk_version=verified_android_sdk_platform, - android_ndk_platform=verified_android_ndk_platform, + android_sdk_path=android_sdk.android_sdk_path, + build_tool=build_tools_package, + android_sdk_platform=android_sdk_platform_version, + android_native_api_level=android_native_api_level, + android_ndk=android_ndk_package, + project_path=verified_project_path, third_party_path=third_party_path, cmake_version=cmake_version, override_cmake_path=override_cmake_path, override_gradle_path=override_gradle_path, + gradle_version=gradle_version, + gradle_plugin_version=android_gradle_plugin_version, override_ninja_path=override_ninja_path, - android_sdk_build_tool_version=android_sdk_build_tool_ver, include_assets_in_apk=parsed_args.get_argument(INCLUDE_APK_ASSETS_ARGUMENT_NAME), asset_mode=parsed_args.get_argument(ASSET_MODE_ARGUMENT_NAME), asset_type=parsed_args.get_argument(ASSET_TYPE_ARGUMENT_NAME), diff --git a/cmake/Tools/Platform/Android/unit_test_generate_android_project.py b/cmake/Tools/Platform/Android/unit_test_generate_android_project.py index 5598942046..0cd0f16eaf 100755 --- a/cmake/Tools/Platform/Android/unit_test_generate_android_project.py +++ b/cmake/Tools/Platform/Android/unit_test_generate_android_project.py @@ -170,117 +170,3 @@ def test_verify_ninja(tmpdir, from_override, version_str, expected_result): finally: subprocess.check_output = orig_check_output - -TEST_VALIDATE_VERSION_MIN = 19 -TEST_VALIDATE_VERSION_MAX = 21 - - -@pytest.mark.parametrize( - "test_input, expected", [ - pytest.param('20', 20), - pytest.param('android-20', 20), - pytest.param('bad-21', "android-'XX'"), - pytest.param('10', "minimum"), - pytest.param('30', "maximum") - ] -) -def test_validate_android_platform_input(test_input, expected): - try: - result = android_support.validate_android_platform_input(input_android_platform=test_input, - platform_variable_type='test', - min_version=TEST_VALIDATE_VERSION_MIN, - max_version=TEST_VALIDATE_VERSION_MAX) - assert isinstance(expected, int) - assert result == expected - except Exception as e: - assert expected in str(e) - - -def test_verify_android_sdk_success(tmpdir): - - test_android_path = 'android_sdk' - sdk_version_number = 28 - sdk_version = f'android-{sdk_version_number}' - - tmpdir.ensure(f'{test_android_path}/platforms/{sdk_version}/package.xml') - - tmpdir.ensure(f'{test_android_path}/build-tools/28.0.3/package.xml') - tmpdir.ensure(f'{test_android_path}/build-tools/29.0.3/package.xml') - - input_sdk_path = tmpdir.join(test_android_path).realpath() - argument_name = '--android-sdk' - - requested_build_tool_version = '29.0.3' - - result_sdk_version, result_sdk_path, result_build_tool_version = android_support.verify_android_sdk(android_sdk_platform=sdk_version, - argument_name=argument_name, - override_android_sdk_path=input_sdk_path, - preferred_sdk_build_tools_ver=requested_build_tool_version) - assert result_sdk_version == sdk_version_number - assert result_sdk_path == input_sdk_path - assert result_build_tool_version == requested_build_tool_version - - sdk_version_number_only = str(sdk_version_number) - result_sdk_version, result_sdk_path, result_build_tool_version = android_support.verify_android_sdk(android_sdk_platform=sdk_version_number_only, - argument_name=argument_name, - override_android_sdk_path=input_sdk_path) - assert result_sdk_version == sdk_version_number - assert result_sdk_path == input_sdk_path - assert result_build_tool_version == '28.0.3' - - requested_build_tool_version = '30.0.3' - result_sdk_version, result_sdk_path, result_build_tool_version = android_support.verify_android_sdk(android_sdk_platform=sdk_version, - argument_name=argument_name, - override_android_sdk_path=input_sdk_path, - preferred_sdk_build_tools_ver=requested_build_tool_version) - assert result_sdk_version == sdk_version_number - assert result_sdk_path == input_sdk_path - assert result_build_tool_version == '28.0.3' - - -@pytest.mark.parametrize( - "desired_ndk_version_number, available_ndk_revisions, pkg_revision, mappings, expect_error", [ - pytest.param(21, [21, 22, 24], '15.2.4203891', None, False, id='preNdk19ExactMatch'), - pytest.param(23, [21, 22, 24], '15.2.4203891', None, False, id='preNdk19FallbackMatch'), - pytest.param(22, [21, 22, 24], '19.2.4203891', {'23': 21}, False, id='postNdk19ExactMatch'), - pytest.param(23, [21, 22, 24], '21.2.4203891', {'23': 21}, False, id='postNdk19MappingMatch'), - pytest.param(android_support.ANDROID_NDK_MIN_PLATFORM-1, [21, 22, 24], '15.2.4203891', None, True, id='preNdk19BelowMinVer'), - pytest.param(android_support.ANDROID_NDK_MAX_PLATFORM+1, [21, 22, 24], '15.2.4203891', None, True, id='preNdk19AboveMaxVer'), - pytest.param(25, [21, 22, 24], '19.2.4203891', {'23': 21}, True, id='postNdk19NoMatch') - ] -) -def test_verify_android_ndk_success(tmpdir, desired_ndk_version_number, available_ndk_revisions, pkg_revision, mappings, expect_error): - - test_android_path = 'android_ndk' - for ndk_number in available_ndk_revisions: - tmpdir.ensure(f'{test_android_path}/platforms/android-{ndk_number}/arch-arm64/usr/lib/libc.so') - - tmpdir.ensure(f'{test_android_path}/source.properties') - test_ndk_source_properties_file = tmpdir / test_android_path / 'source.properties' - test_ndk_source_properties_file.write_text(f'Pkg.Desc = Android NDK\nPkg.Revision = {pkg_revision}\n', encoding='ASCII') - - if mappings: - platform_mapping = { - # min and max are arbitrary for now since we dont use it during evaluation, but if we do, parameterize it here as well - "min": 16, # - "max": 29, - "aliases": {} - } - for key, value in mappings.items(): - platform_mapping['aliases'][key] = value - tmpdir.ensure(f'{test_android_path}/meta/platforms.json') - platform_mapping_file = tmpdir / test_android_path / 'meta/platforms.json' - platform_mapping_file.write_text(json.dumps(platform_mapping), encoding='ASCII') - - input_ndk_path = tmpdir.join(test_android_path).realpath() - - try: - android_ndk_platform_number, android_ndk_path = android_support.verify_android_ndk(android_ndk_platform=str(desired_ndk_version_number), - argument_name="--android-ndk", - override_android_ndk_path=input_ndk_path) - assert not expect_error - assert android_ndk_platform_number == desired_ndk_version_number - assert android_ndk_path == input_ndk_path - except Exception: - assert expect_error - diff --git a/cmake/Tools/common.py b/cmake/Tools/common.py index 8189bb3ca3..1990041c86 100755 --- a/cmake/Tools/common.py +++ b/cmake/Tools/common.py @@ -55,6 +55,7 @@ ENGINE_ROOT_CHECK_FILE = 'engine.json' HASH_CHUNK_SIZE = 200000 + class LmbrCmdError(Exception): """ Wrapper class to the general exception class where will absorb and prevent the printing of stack. @@ -149,7 +150,7 @@ def get_bootstrap_values(bootstrap_dir, keys_to_extract): raise logging.error(f'Bootstrap.setreg file {bootstrap_file} does not exist.') result_map = {} - with bootstrap_file.open('r') as f: + with open(bootstrap_file, 'r') as f: try: json_data = json.load(f) except Exception as e: @@ -157,9 +158,9 @@ def get_bootstrap_values(bootstrap_dir, keys_to_extract): else: for search_key in keys_to_extract: try: - search_result = json_data["Amazon"]["AzCore"]["Bootstrap"][f'"{search_key}"'] + search_result = json_data["Amazon"]["AzCore"]["Bootstrap"][search_key] except KeyError as e: - logging.error(f'Bootstrap.setreg cannot find Amazon:AzCore:Bootstrap:{search_result}: {str(e)}') + logging.warning(f'Bootstrap.setreg cannot find /Amazon/AzCore/Bootstrap/{search_key}: {str(e)}') else: result_map[search_key] = search_result @@ -244,6 +245,19 @@ def load_template_file(template_file_path, template_env): raise FileNotFoundError(f"Invalid file path. Cannot find template file located at {str(template_file_path)}") +# Determine the possible file extensions for executable files based on the host platform +PLATFORM_EXECUTABLE_EXTENSIONS = [''] # Files without extensions are always considered + +if platform.system() == 'Windows': + # Windows manages its executable extensions through the %PATHEXT% environment variable + path_extensions_str = os.environ.get('PATHEXT', default='.EXE;.COM;.BAT;.CMD') + PLATFORM_EXECUTABLE_EXTENSIONS.extend([pathext.lower() for pathext in path_extensions_str.split(';')]) +elif platform.system() == 'Linux': + PLATFORM_EXECUTABLE_EXTENSIONS = ['', '.out'] +else: + PLATFORM_EXECUTABLE_EXTENSIONS = [''] + + def verify_tool(override_tool_path, tool_name, tool_filename, argument_name, tool_version_argument, tool_version_regex, min_version, max_version): """ Support method to validate a required system tool needed for the build either through an installed tool in the @@ -270,12 +284,21 @@ def verify_tool(override_tool_path, tool_name, tool_filename, argument_name, too elif not isinstance(override_tool_path, pathlib.Path): raise LmbrCmdError(f"Invalid {tool_name} path argument. '{override_tool_path}' must be a string or Path", ERROR_CODE_INVALID_PARAMETER) - check_tool_path = override_tool_path / tool_filename - if not check_tool_path.is_file(): - check_tool_path = pathlib.Path(override_tool_path) / 'bin' / tool_filename + file_found = False + for executable_path_ext in PLATFORM_EXECUTABLE_EXTENSIONS: + check_tool_filename = f'{tool_filename}{executable_path_ext}' - if not check_tool_path.is_file(): + check_tool_path = override_tool_path / check_tool_filename + if check_tool_path.is_file(): + file_found = True + break + check_tool_path = override_tool_path / 'bin' / check_tool_filename + if check_tool_path.is_file(): + file_found = True + break + + if not file_found: raise LmbrCmdError(f"Invalid {tool_name} path argument. '{override_tool_path}' is not a valid {tool_name} path", ERROR_CODE_INVALID_PARAMETER) resolved_override_tool_path = str(check_tool_path.resolve()) @@ -284,7 +307,7 @@ def verify_tool(override_tool_path, tool_name, tool_filename, argument_name, too else: resolved_override_tool_path = None tool_source = tool_name - tool_desc = "installed gradle in the system path" + tool_desc = f"installed {tool_name} in the system path" # Extract the version and verify version_output = subprocess.check_output([tool_source, tool_version_argument], @@ -293,13 +316,17 @@ def verify_tool(override_tool_path, tool_name, tool_filename, argument_name, too version_match = tool_version_regex.search(version_output) if not version_match: raise RuntimeError() - result_version = LooseVersion(str(version_match.group(1)).strip()) + + + # Since we are doing a compare, strip out any non-numeric and non . character from the version otherwise we will get a TypeError on the LooseVersion comparison + result_version_str = re.sub(r"[^\.0-9]", "", str(version_match.group(1)).strip()) + result_version = LooseVersion(result_version_str) if min_version and result_version < min_version: - raise LmbrCmdError(f"The {tool_desc} does not meet the minimum version of gradle required ({str(min_version)}).", + raise LmbrCmdError(f"The {tool_desc} does not meet the minimum version of {tool_name} required ({str(min_version)}).", ERROR_CODE_ENVIRONMENT_ERROR) elif max_version and result_version > max_version: - raise LmbrCmdError(f"The {tool_desc} exceeds maximum version of gradle supported ({str(max_version)}).", + raise LmbrCmdError(f"The {tool_desc} exceeds maximum version of {tool_name} supported ({str(max_version)}).", ERROR_CODE_ENVIRONMENT_ERROR) return result_version, resolved_override_tool_path diff --git a/cmake/Tools/global_project.py b/cmake/Tools/global_project.py deleted file mode 100644 index 1d84d9dcfb..0000000000 --- a/cmake/Tools/global_project.py +++ /dev/null @@ -1,168 +0,0 @@ -# -# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -# its licensors. -# -# For complete copyright and license terms please see the LICENSE at the root of this -# distribution (the "License"). All use of this software is governed by the License, -# or, if provided, by the license below or the license accompanying this file. Do not -# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# - -import argparse -import logging -import os -import sys -import re -import pathlib -import json -import cmake.Tools.registration as registration - -logger = logging.getLogger() -logging.basicConfig() - - -def set_global_project(project_name: str or None, - project_path: str or pathlib.Path or None) -> int: - """ - set what the current project is - :param project_name: the name of the project you want to set, resolves project_path - :param project_path: the path of the project you want to set - :return: 0 for success or non 0 failure code - """ - if project_path and project_name: - logger.error(f'Project Name and Project Path provided, these are mutually exclusive.') - return 1 - - if not project_name and not project_path: - logger.error('Must specify either a Project name or Project Path.') - return 1 - - if project_name and not project_path: - project_path = registration.get_registered(project_name=project_name) - - if not project_path: - logger.error(f'Project Path {project_path} has not been registered.') - return 1 - - project_path = pathlib.Path(project_path).resolve() - - bootstrap_setreg_file = registration.get_o3de_registry_folder() / 'bootstrap.setreg' - if bootstrap_setreg_file.is_file(): - with bootstrap_setreg_file.open('r') as f: - try: - json_data = json.load(f) - except Exception as e: - logger.error(f'Bootstrap.setreg failed to load: {str(e)}') - else: - try: - json_data["Amazon"]["AzCore"]["Bootstrap"]["project_path"] = project_path - except Exception as e: - logger.error(f'Bootstrap.setreg failed to load: {str(e)}') - else: - try: - os.unlink(bootstrap_setreg_file) - except Exception as e: - logger.error(f'Failed to unlink bootstrap file {bootstrap_setreg_file}: {str(e)}') - return 1 - else: - json_data = {} - json_data.update({"Amazon":{"AzCore":{"Bootstrap":{"project_path":project_path.as_posix()}}}}) - - with bootstrap_setreg_file.open('w') as s: - s.write(json.dumps(json_data, indent=4)) - - return 0 - - -def get_global_project() -> pathlib.Path or None: - """ - get what the current project set is - :return: project_path or None on failure - """ - bootstrap_setreg_file = registration.get_o3de_registry_folder() / 'bootstrap.setreg' - if not bootstrap_setreg_file.is_file(): - logger.error(f'Bootstrap.setreg file {bootstrap_setreg_file} does not exist.') - return None - - with bootstrap_setreg_file.open('r') as f: - try: - json_data = json.load(f) - except Exception as e: - logger.error(f'Bootstrap.setreg failed to load: {str(e)}') - else: - try: - project_path = json_data["Amazon"]["AzCore"]["Bootstrap"]["project_path"] - except Exception as e: - logger.error(f'Bootstrap.setreg cannot find Amazon:AzCore:Bootstrap:project_path: {str(e)}') - else: - return pathlib.Path(project_path).resolve() - return None - -def _run_get_global_project(args: argparse) -> int: - if args.override_home_folder: - registration.override_home_folder = args.override_home_folder - - project_path = get_global_project() - if project_path: - print(project_path.as_posix()) - return 0 - return 1 - - -def _run_set_global_project(args: argparse) -> int: - if args.override_home_folder: - registration.override_home_folder = args.override_home_folder - - return set_global_project(args.project_name, - args.project_path) - - -def add_args(parser, subparsers) -> None: - """ - add_args is called to add expected parser arguments and subparsers arguments to each command such that it can be - invoked locally or aggregated by a central python file. - Ex. Directly run from this file alone with: python global_project.py set_global_project --project-name TestProject - OR - o3de.py can aggregate commands by importing global_project, call add_args and - execute: python o3de.py set_global_project --project-path C:/TestProject - :param parser: the caller instantiates a parser and passes it in here - :param subparsers: the caller instantiates subparsers and passes it in here - """ - get_global_project_subparser = subparsers.add_parser('get-global-project') - get_global_project_subparser.add_argument('-ohf', '--override-home-folder', type=str, required=False, - help='By default the home folder is the user folder, override it to this folder.') - - get_global_project_subparser.set_defaults(func=_run_get_global_project) - - set_global_project_subparser = subparsers.add_parser('set-global-project') - group = set_global_project_subparser.add_mutually_exclusive_group(required=True) - group.add_argument('-pn', '--project-name', required=False, - help='The name of the project. If supplied this will resolve the --project-path.') - group.add_argument('-pp', '--project-path', required=False, - help='The path to the project') - - set_global_project_subparser.add_argument('-ohf', '--override-home-folder', type=str, required=False, - help='By default the home folder is the user folder, override it to this folder.') - - set_global_project_subparser.set_defaults(func=_run_set_global_project) - - -if __name__ == "__main__": - # parse the command line args - the_parser = argparse.ArgumentParser() - - # add subparsers - the_subparsers = the_parser.add_subparsers(help='sub-command help') - - # add args to the parser - add_args(the_parser, the_subparsers) - - # parse args - the_args = the_parser.parse_args() - - # run - ret = the_args.func(the_args) - - # return - sys.exit(ret) diff --git a/cmake/Tools/layout_tool.py b/cmake/Tools/layout_tool.py index 69f5b34ae7..3fcab241d7 100755 --- a/cmake/Tools/layout_tool.py +++ b/cmake/Tools/layout_tool.py @@ -107,7 +107,7 @@ def verify_layout(layout_dir, platform_name, project_path, asset_mode, asset_typ project_name_lower = project_path.lower() layout_path = pathlib.Path(layout_dir) - bootstrap_path = layout_path / 'Registry' + bootstrap_path = pathlib.Path(ROOT_ENGINE_PATH) / 'Registry' bootstrap_values = common.get_bootstrap_values(str(bootstrap_path), [f'{platform_name_lower}_remote_filesystem', f'{platform_name_lower}_connect_to_remote', f'{platform_name_lower}_wait_for_connect', diff --git a/cmake/Tools/registration.py b/cmake/Tools/registration.py deleted file mode 100755 index b13419414b..0000000000 --- a/cmake/Tools/registration.py +++ /dev/null @@ -1,4439 +0,0 @@ -# -# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -# its licensors. -# -# For complete copyright and license terms please see the LICENSE at the root of this -# distribution (the "License"). All use of this software is governed by the License, -# or, if provided, by the license below or the license accompanying this file. Do not -# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# -""" -This file contains all the code that has to do with registering engines, projects, gems and templates -""" - -import argparse -import logging -import os -import sys -import json -import pathlib -import hashlib -import shutil -import zipfile -import urllib.parse -import urllib.request - -logger = logging.getLogger() -logging.basicConfig() - - -def backup_file(file_name: str or pathlib.Path) -> None: - index = 0 - renamed = False - while not renamed: - backup_file_name = pathlib.Path(str(file_name) + '.bak' + str(index)).resolve() - index += 1 - if not backup_file_name.is_file(): - file_name = pathlib.Path(file_name).resolve() - file_name.rename(backup_file_name) - if backup_file_name.is_file(): - renamed = True - - -def backup_folder(folder: str or pathlib.Path) -> None: - index = 0 - renamed = False - while not renamed: - backup_folder_name = pathlib.Path(str(folder) + '.bak' + str(index)).resolve() - index += 1 - if not backup_folder_name.is_dir(): - folder = pathlib.Path(folder).resolve() - folder.rename(backup_folder_name) - if backup_folder_name.is_dir(): - renamed = True - - -def get_this_engine_path() -> pathlib.Path: - return pathlib.Path(os.path.realpath(__file__)).parents[2].resolve() - - -override_home_folder = None - - -def get_home_folder() -> pathlib.Path: - if override_home_folder: - return pathlib.Path(override_home_folder).resolve() - else: - return pathlib.Path(os.path.expanduser("~")).resolve() - - -def get_o3de_folder() -> pathlib.Path: - o3de_folder = get_home_folder() / '.o3de' - o3de_folder.mkdir(parents=True, exist_ok=True) - return o3de_folder - - -def get_o3de_registry_folder() -> pathlib.Path: - registry_folder = get_o3de_folder() / 'Registry' - registry_folder.mkdir(parents=True, exist_ok=True) - return registry_folder - - -def get_o3de_cache_folder() -> pathlib.Path: - cache_folder = get_o3de_folder() / 'Cache' - cache_folder.mkdir(parents=True, exist_ok=True) - return cache_folder - - -def get_o3de_download_folder() -> pathlib.Path: - download_folder = get_o3de_folder() / 'Download' - download_folder.mkdir(parents=True, exist_ok=True) - return download_folder - - -def get_o3de_engines_folder() -> pathlib.Path: - engines_folder = get_o3de_folder() / 'Engines' - engines_folder.mkdir(parents=True, exist_ok=True) - return engines_folder - - -def get_o3de_projects_folder() -> pathlib.Path: - projects_folder = get_o3de_folder() / 'Projects' - projects_folder.mkdir(parents=True, exist_ok=True) - return projects_folder - - -def get_o3de_gems_folder() -> pathlib.Path: - gems_folder = get_o3de_folder() / 'Gems' - gems_folder.mkdir(parents=True, exist_ok=True) - return gems_folder - - -def get_o3de_templates_folder() -> pathlib.Path: - templates_folder = get_o3de_folder() / 'Templates' - templates_folder.mkdir(parents=True, exist_ok=True) - return templates_folder - - -def get_o3de_restricted_folder() -> pathlib.Path: - restricted_folder = get_o3de_folder() / 'Restricted' - restricted_folder.mkdir(parents=True, exist_ok=True) - return restricted_folder - - -def get_o3de_logs_folder() -> pathlib.Path: - restricted_folder = get_o3de_folder() / 'Logs' - restricted_folder.mkdir(parents=True, exist_ok=True) - return restricted_folder - - -def register_shipped_engine_o3de_objects() -> int: - engine_path = get_this_engine_path() - - ret_val = 0 - - # directories with engines - starting_engines_directories = [ - ] - for engines_directory in sorted(starting_engines_directories, reverse=True): - error_code = register_all_engines_in_folder(engines_path=engines_directory) - if error_code: - ret_val = error_code - - # specific engines - starting_engines = [ - ] - for engine_path in sorted(starting_engines): - error_code = register(engine_path=engine_path) - if error_code: - ret_val = error_code - - # directories with projects - starting_projects_directories = [ - ] - for projects_directory in sorted(starting_projects_directories, reverse=True): - error_code = register_all_projects_in_folder(engine_path=engine_path, projects_path=projects_directory) - if error_code: - ret_val = error_code - - # specific projects - starting_projects = [ - f'{engine_path}/AutomatedTesting' - ] - for project_path in sorted(starting_projects, reverse=True): - error_code = register(engine_path=engine_path, project_path=project_path) - if error_code: - ret_val = error_code - - # directories with gems - starting_gems_directories = [ - f'{engine_path}/Gems' - ] - for gems_directory in sorted(starting_gems_directories, reverse=True): - error_code = register_all_gems_in_folder(engine_path=engine_path, gems_path=gems_directory) - if error_code: - ret_val = error_code - - # specific gems - starting_gems = [ - ] - for gem_path in sorted(starting_gems, reverse=True): - error_code = register(engine_path=engine_path, gem_path=gem_path) - if error_code: - ret_val = error_code - - # directories with templates - starting_templates_directories = [ - f'{engine_path}/Templates' - ] - for templates_directory in sorted(starting_templates_directories, reverse=True): - error_code = register_all_templates_in_folder(engine_path=engine_path, templates_path=templates_directory) - if error_code: - ret_val = error_code - - # specific templates - starting_templates = [ - ] - for template_path in sorted(starting_templates, reverse=True): - error_code = register(engine_path=engine_path, template_path=template_path) - if error_code: - ret_val = error_code - - # directories with restricted - starting_restricted_directories = [ - ] - for restricted_directory in sorted(starting_restricted_directories, reverse=True): - error_code = register_all_restricted_in_folder(engine_path=engine_path, restricted_path=restricted_directory) - if error_code: - ret_val = error_code - - # specific restricted - starting_restricted = [ - ] - for restricted_path in sorted(starting_restricted, reverse=True): - error_code = register(engine_path=engine_path, restricted_path=restricted_path) - if error_code: - ret_val = error_code - - # directories with repos - starting_repo_directories = [ - ] - for repos_directory in sorted(starting_repo_directories, reverse=True): - error_code = register_all_repos_in_folder(engine_path=engine_path, repos_path=repos_directory) - if error_code: - ret_val = error_code - - # specific repos - starting_repos = [ - ] - for repo_uri in sorted(starting_repos, reverse=True): - error_code = register(repo_uri=repo_uri) - if error_code: - ret_val = error_code - - # register anything in the users default folders globally - error_code = register_all_engines_in_folder(get_registered(default_folder='engines')) - if error_code: - ret_val = error_code - error_code = register_all_projects_in_folder(get_registered(default_folder='projects')) - if error_code: - ret_val = error_code - error_code = register_all_gems_in_folder(get_registered(default_folder='gems')) - if error_code: - ret_val = error_code - error_code = register_all_templates_in_folder(get_registered(default_folder='templates')) - if error_code: - ret_val = error_code - error_code = register_all_restricted_in_folder(get_registered(default_folder='restricted')) - if error_code: - ret_val = error_code - error_code = register_all_restricted_in_folder(get_registered(default_folder='projects')) - if error_code: - ret_val = error_code - error_code = register_all_restricted_in_folder(get_registered(default_folder='gems')) - if error_code: - ret_val = error_code - error_code = register_all_restricted_in_folder(get_registered(default_folder='templates')) - if error_code: - ret_val = error_code - - json_data = load_o3de_manifest() - engine_object = find_engine_data(json_data) - gems = json_data['gems'].copy() - gems.extend(engine_object['gems']) - for gem_path in sorted(gems, key=len): - gem_path = pathlib.Path(gem_path).resolve() - gem_cmake_lists_txt = gem_path / 'CMakeLists.txt' - if gem_cmake_lists_txt.is_file(): - add_gem_to_cmake(engine_path=engine_path, gem_path=gem_path, supress_errors=True) # don't care about errors - - return ret_val - - -def register_all_in_folder(folder_path: str or pathlib.Path, - remove: bool = False, - engine_path: str or pathlib.Path = None, - exclude: list = None) -> int: - if not folder_path: - logger.error(f'Folder path cannot be empty.') - return 1 - - folder_path = pathlib.Path(folder_path).resolve() - if not folder_path.is_dir(): - logger.error(f'Folder path is not dir.') - return 1 - - engines_set = set() - projects_set = set() - gems_set = set() - templates_set = set() - restricted_set = set() - repo_set = set() - - ret_val = 0 - for root, dirs, files in os.walk(folder_path): - if root in exclude: - continue - - for name in files: - if name == 'engine.json': - engines_set.add(root) - elif name == 'project.json': - projects_set.add(root) - elif name == 'gem.json': - gems_set.add(root) - elif name == 'template.json': - templates_set.add(root) - elif name == 'restricted.json': - restricted_set.add(root) - elif name == 'repo.json': - repo_set.add(root) - - for engine in sorted(engines_set, reverse=True): - error_code = register(engine_path=engine, remove=remove) - if error_code: - ret_val = error_code - - for project in sorted(projects_set, reverse=True): - error_code = register(engine_path=engine_path, project_path=project, remove=remove) - if error_code: - ret_val = error_code - - for gem in sorted(gems_set, reverse=True): - error_code = register(engine_path=engine_path, gem_path=gem, remove=remove) - if error_code: - ret_val = error_code - - for template in sorted(templates_set, reverse=True): - error_code = register(engine_path=engine_path, template_path=template, remove=remove) - if error_code: - ret_val = error_code - - for restricted in sorted(restricted_set, reverse=True): - error_code = register(engine_path=engine_path, restricted_path=restricted, remove=remove) - if error_code: - ret_val = error_code - - for repo in sorted(repo_set, reverse=True): - error_code = register(engine_path=engine_path, repo_uri=repo, remove=remove) - if error_code: - ret_val = error_code - - return ret_val - - -def register_all_engines_in_folder(engines_path: str or pathlib.Path, - remove: bool = False) -> int: - if not engines_path: - logger.error(f'Engines path cannot be empty.') - return 1 - - engines_path = pathlib.Path(engines_path).resolve() - if not engines_path.is_dir(): - logger.error(f'Engines path is not dir.') - return 1 - - engines_set = set() - - ret_val = 0 - for root, dirs, files in os.walk(engines_path): - for name in files: - if name == 'engine.json': - engines_set.add(name) - - for engine in sorted(engines_set, reverse=True): - error_code = register(engine_path=engine, remove=remove) - if error_code: - ret_val = error_code - - return ret_val - - -def register_all_projects_in_folder(projects_path: str or pathlib.Path, - remove: bool = False, - engine_path: str or pathlib.Path = None) -> int: - if not projects_path: - logger.error(f'Projects path cannot be empty.') - return 1 - - projects_path = pathlib.Path(projects_path).resolve() - if not projects_path.is_dir(): - logger.error(f'Projects path is not dir.') - return 1 - - projects_set = set() - - ret_val = 0 - for root, dirs, files in os.walk(projects_path): - for name in files: - if name == 'project.json': - projects_set.add(root) - - for project in sorted(projects_set, reverse=True): - error_code = register(engine_path=engine_path, project_path=project, remove=remove) - if error_code: - ret_val = error_code - - return ret_val - - -def register_all_gems_in_folder(gems_path: str or pathlib.Path, - remove: bool = False, - engine_path: str or pathlib.Path = None) -> int: - if not gems_path: - logger.error(f'Gems path cannot be empty.') - return 1 - - gems_path = pathlib.Path(gems_path).resolve() - if not gems_path.is_dir(): - logger.error(f'Gems path is not dir.') - return 1 - - gems_set = set() - - ret_val = 0 - for root, dirs, files in os.walk(gems_path): - for name in files: - if name == 'gem.json': - gems_set.add(root) - - for gem in sorted(gems_set, reverse=True): - error_code = register(engine_path=engine_path, gem_path=gem, remove=remove) - if error_code: - ret_val = error_code - - return ret_val - - -def register_all_templates_in_folder(templates_path: str or pathlib.Path, - remove: bool = False, - engine_path: str or pathlib.Path = None) -> int: - if not templates_path: - logger.error(f'Templates path cannot be empty.') - return 1 - - templates_path = pathlib.Path(templates_path).resolve() - if not templates_path.is_dir(): - logger.error(f'Templates path is not dir.') - return 1 - - templates_set = set() - - ret_val = 0 - for root, dirs, files in os.walk(templates_path): - for name in files: - if name == 'template.json': - templates_set.add(root) - - for template in sorted(templates_set, reverse=True): - error_code = register(engine_path=engine_path, template_path=template, remove=remove) - if error_code: - ret_val = error_code - - return ret_val - - -def register_all_restricted_in_folder(restricted_path: str or pathlib.Path, - remove: bool = False, - engine_path: str or pathlib.Path = None) -> int: - if not restricted_path: - logger.error(f'Restricted path cannot be empty.') - return 1 - - restricted_path = pathlib.Path(restricted_path).resolve() - if not restricted_path.is_dir(): - logger.error(f'Restricted path is not dir.') - return 1 - - restricted_set = set() - - ret_val = 0 - for root, dirs, files in os.walk(restricted_path): - for name in files: - if name == 'restricted.json': - restricted_set.add(root) - - for restricted in sorted(restricted_set, reverse=True): - error_code = register(engine_path=engine_path, restricted_path=restricted, remove=remove) - if error_code: - ret_val = error_code - - return ret_val - - -def register_all_repos_in_folder(repos_path: str or pathlib.Path, - remove: bool = False, - engine_path: str or pathlib.Path = None) -> int: - if not repos_path: - logger.error(f'Repos path cannot be empty.') - return 1 - - repos_path = pathlib.Path(repos_path).resolve() - if not repos_path.is_dir(): - logger.error(f'Repos path is not dir.') - return 1 - - repo_set = set() - - ret_val = 0 - for root, dirs, files in os.walk(repos_path): - for name in files: - if name == 'repo.json': - repo_set.add(root) - - for repo in sorted(repo_set, reverse=True): - error_code = register(engine_path=engine_path, repo_uri=repo, remove=remove) - if error_code: - ret_val = error_code - - return ret_val - - -def get_o3de_manifest() -> pathlib.Path: - manifest_path = get_o3de_folder() / 'o3de_manifest.json' - if not manifest_path.is_file(): - username = os.path.split(get_home_folder())[-1] - - o3de_folder = get_o3de_folder() - default_registry_folder = get_o3de_registry_folder() - default_cache_folder = get_o3de_cache_folder() - default_downloads_folder = get_o3de_download_folder() - default_logs_folder = get_o3de_logs_folder() - default_engines_folder = get_o3de_engines_folder() - default_projects_folder = get_o3de_projects_folder() - default_gems_folder = get_o3de_gems_folder() - default_templates_folder = get_o3de_templates_folder() - default_restricted_folder = get_o3de_restricted_folder() - - default_projects_restricted_folder = default_projects_folder / 'Restricted' - default_projects_restricted_folder.mkdir(parents=True, exist_ok=True) - default_gems_restricted_folder = default_gems_folder / 'Restricted' - default_gems_restricted_folder.mkdir(parents=True, exist_ok=True) - default_templates_restricted_folder = default_templates_folder / 'Restricted' - default_templates_restricted_folder.mkdir(parents=True, exist_ok=True) - - json_data = {} - json_data.update({'o3de_manifest_name': f'{username}'}) - json_data.update({'origin': o3de_folder.as_posix()}) - json_data.update({'default_engines_folder': default_engines_folder.as_posix()}) - json_data.update({'default_projects_folder': default_projects_folder.as_posix()}) - json_data.update({'default_gems_folder': default_gems_folder.as_posix()}) - json_data.update({'default_templates_folder': default_templates_folder.as_posix()}) - json_data.update({'default_restricted_folder': default_restricted_folder.as_posix()}) - - json_data.update({'projects': []}) - json_data.update({'gems': []}) - json_data.update({'templates': []}) - json_data.update({'restricted': []}) - json_data.update({'repos': []}) - json_data.update({'engines': []}) - - default_restricted_folder_json = default_restricted_folder / 'restricted.json' - if not default_restricted_folder_json.is_file(): - with default_restricted_folder_json.open('w') as s: - restricted_json_data = {} - restricted_json_data.update({'restricted_name': 'o3de'}) - s.write(json.dumps(restricted_json_data, indent=4)) - json_data.update({'default_restricted_folder': default_restricted_folder.as_posix()}) - - default_projects_restricted_folder_json = default_projects_restricted_folder / 'restricted.json' - if not default_projects_restricted_folder_json.is_file(): - with default_projects_restricted_folder_json.open('w') as s: - restricted_json_data = {} - restricted_json_data.update({'restricted_name': 'projects'}) - s.write(json.dumps(restricted_json_data, indent=4)) - - default_gems_restricted_folder_json = default_gems_restricted_folder / 'restricted.json' - if not default_gems_restricted_folder_json.is_file(): - with default_gems_restricted_folder_json.open('w') as s: - restricted_json_data = {} - restricted_json_data.update({'restricted_name': 'gems'}) - s.write(json.dumps(restricted_json_data, indent=4)) - - default_templates_restricted_folder_json = default_templates_restricted_folder / 'restricted.json' - if not default_templates_restricted_folder_json.is_file(): - with default_templates_restricted_folder_json.open('w') as s: - restricted_json_data = {} - restricted_json_data.update({'restricted_name': 'templates'}) - s.write(json.dumps(restricted_json_data, indent=4)) - - with manifest_path.open('w') as s: - s.write(json.dumps(json_data, indent=4)) - - return manifest_path - - -def load_o3de_manifest() -> dict: - with get_o3de_manifest().open('r') as f: - try: - json_data = json.load(f) - except Exception as e: - logger.error(f'Manifest json failed to load: {str(e)}') - else: - return json_data - - -def save_o3de_manifest(json_data: dict) -> None: - with get_o3de_manifest().open('w') as s: - try: - s.write(json.dumps(json_data, indent=4)) - except Exception as e: - logger.error(f'Manifest json failed to save: {str(e)}') - - -def register_engine_path(json_data: dict, - engine_path: str or pathlib.Path, - remove: bool = False) -> int: - if not engine_path: - logger.error(f'Engine path cannot be empty.') - return 1 - engine_path = pathlib.Path(engine_path).resolve() - - for engine_object in json_data['engines']: - engine_object_path = pathlib.Path(engine_object['path']).resolve() - if engine_object_path == engine_path: - json_data['engines'].remove(engine_object) - - if remove: - return 0 - - if not engine_path.is_dir(): - logger.error(f'Engine path {engine_path} does not exist.') - return 1 - - engine_json = engine_path / 'engine.json' - if not valid_o3de_engine_json(engine_json): - logger.error(f'Engine json {engine_json} is not valid.') - return 1 - - engine_object = {} - engine_object.update({'path': engine_path.as_posix()}) - engine_object.update({'projects': []}) - engine_object.update({'gems': []}) - engine_object.update({'templates': []}) - engine_object.update({'restricted': []}) - engine_object.update({'external_subdirectories': []}) - - json_data['engines'].insert(0, engine_object) - - return 0 - - -def register_gem_path(json_data: dict, - gem_path: str or pathlib.Path, - remove: bool = False, - engine_path: str or pathlib.Path = None) -> int: - if not gem_path: - logger.error(f'Gem path cannot be empty.') - return 1 - gem_path = pathlib.Path(gem_path).resolve() - - if engine_path: - engine_data = find_engine_data(json_data, engine_path) - if not engine_data: - logger.error(f'Engine path {engine_path} is not registered.') - return 1 - - while gem_path in engine_data['gems']: - engine_data['gems'].remove(gem_path) - - while gem_path.as_posix() in engine_data['gems']: - engine_data['gems'].remove(gem_path.as_posix()) - - if remove: - logger.warn(f'Removing Gem path {gem_path}.') - return 0 - else: - while gem_path in json_data['gems']: - json_data['gems'].remove(gem_path) - - while gem_path.as_posix() in json_data['gems']: - json_data['gems'].remove(gem_path.as_posix()) - - if remove: - logger.warn(f'Removing Gem path {gem_path}.') - return 0 - - if not gem_path.is_dir(): - logger.error(f'Gem path {gem_path} does not exist.') - return 1 - - gem_json = gem_path / 'gem.json' - if not valid_o3de_gem_json(gem_json): - logger.error(f'Gem json {gem_json} is not valid.') - return 1 - - if engine_path: - engine_data['gems'].insert(0, gem_path.as_posix()) - else: - json_data['gems'].insert(0, gem_path.as_posix()) - - return 0 - - -def register_project_path(json_data: dict, - project_path: str or pathlib.Path, - remove: bool = False, - engine_path: str or pathlib.Path = None) -> int: - if not project_path: - logger.error(f'Project path cannot be empty.') - return 1 - project_path = pathlib.Path(project_path).resolve() - - if engine_path: - engine_data = find_engine_data(json_data, engine_path) - if not engine_data: - logger.error(f'Engine path {engine_path} is not registered.') - return 1 - - while project_path in engine_data['projects']: - engine_data['projects'].remove(project_path) - - while project_path.as_posix() in engine_data['projects']: - engine_data['projects'].remove(project_path.as_posix()) - - if remove: - logger.warn(f'Engine {engine_path} removing Project path {project_path}.') - return 0 - else: - while project_path in json_data['projects']: - json_data['projects'].remove(project_path) - - while project_path.as_posix() in json_data['projects']: - json_data['projects'].remove(project_path.as_posix()) - - if remove: - logger.warn(f'Removing Project path {project_path}.') - return 0 - - if not project_path.is_dir(): - logger.error(f'Project path {project_path} does not exist.') - return 1 - - project_json = project_path / 'project.json' - if not valid_o3de_project_json(project_json): - logger.error(f'Project json {project_json} is not valid.') - return 1 - - if engine_path: - engine_data['projects'].insert(0, project_path.as_posix()) - else: - json_data['projects'].insert(0, project_path.as_posix()) - - # registering a project has the additional step of setting the project.json 'engine' field - this_engine_json = get_this_engine_path() / 'engine.json' - with this_engine_json.open('r') as f: - try: - this_engine_json = json.load(f) - except Exception as e: - logger.error(f'Engine json failed to load: {str(e)}') - return 1 - with project_json.open('r') as f: - try: - project_json_data = json.load(f) - except Exception as e: - logger.error(f'Project json failed to load: {str(e)}') - return 1 - - update_project_json = False - try: - update_project_json = project_json_data['engine'] != this_engine_json['engine_name'] - except Exception as e: - update_project_json = True - - if update_project_json: - project_json_data['engine'] = this_engine_json['engine_name'] - backup_file(project_json) - with project_json.open('w') as s: - try: - s.write(json.dumps(project_json_data, indent=4)) - except Exception as e: - logger.error(f'Project json failed to save: {str(e)}') - return 1 - - return 0 - - -def register_template_path(json_data: dict, - template_path: str or pathlib.Path, - remove: bool = False, - engine_path: str or pathlib.Path = None) -> int: - if not template_path: - logger.error(f'Template path cannot be empty.') - return 1 - template_path = pathlib.Path(template_path).resolve() - - if engine_path: - engine_data = find_engine_data(json_data, engine_path) - if not engine_data: - logger.error(f'Engine path {engine_path} is not registered.') - return 1 - - while template_path in engine_data['templates']: - engine_data['templates'].remove(template_path) - - while template_path.as_posix() in engine_data['templates']: - engine_data['templates'].remove(template_path.as_posix()) - - if remove: - logger.warn(f'Engine {engine_path} removing Template path {template_path}.') - return 0 - else: - while template_path in json_data['templates']: - json_data['templates'].remove(template_path) - - while template_path.as_posix() in json_data['templates']: - json_data['templates'].remove(template_path.as_posix()) - - if remove: - logger.warn(f'Removing Template path {template_path}.') - return 0 - - if not template_path.is_dir(): - logger.error(f'Template path {template_path} does not exist.') - return 1 - - template_json = template_path / 'template.json' - if not valid_o3de_template_json(template_json): - logger.error(f'Template json {template_json} is not valid.') - return 1 - - if engine_path: - engine_data['templates'].insert(0, template_path.as_posix()) - else: - json_data['templates'].insert(0, template_path.as_posix()) - - return 0 - - -def register_restricted_path(json_data: dict, - restricted_path: str or pathlib.Path, - remove: bool = False, - engine_path: str or pathlib.Path = None) -> int: - if not restricted_path: - logger.error(f'Restricted path cannot be empty.') - return 1 - restricted_path = pathlib.Path(restricted_path).resolve() - - if engine_path: - engine_data = find_engine_data(json_data, engine_path) - if not engine_data: - logger.error(f'Engine path {engine_path} is not registered.') - return 1 - - while restricted_path in engine_data['restricted']: - engine_data['restricted'].remove(restricted_path) - - while restricted_path.as_posix() in engine_data['restricted']: - engine_data['restricted'].remove(restricted_path.as_posix()) - - if remove: - logger.warn(f'Engine {engine_path} removing Restricted path {restricted_path}.') - return 0 - else: - while restricted_path in json_data['restricted']: - json_data['restricted'].remove(restricted_path) - - while restricted_path.as_posix() in json_data['restricted']: - json_data['restricted'].remove(restricted_path.as_posix()) - - if remove: - logger.warn(f'Removing Restricted path {restricted_path}.') - return 0 - - if not restricted_path.is_dir(): - logger.error(f'Restricted path {restricted_path} does not exist.') - return 1 - - restricted_json = restricted_path / 'restricted.json' - if not valid_o3de_restricted_json(restricted_json): - logger.error(f'Restricted json {restricted_json} is not valid.') - return 1 - - if engine_path: - engine_data['restricted'].insert(0, restricted_path.as_posix()) - else: - json_data['restricted'].insert(0, restricted_path.as_posix()) - - return 0 - - -def valid_o3de_repo_json(file_name: str or pathlib.Path) -> bool: - file_name = pathlib.Path(file_name).resolve() - if not file_name.is_file(): - return False - - with file_name.open('r') as f: - try: - json_data = json.load(f) - test = json_data['repo_name'] - test = json_data['origin'] - except Exception as e: - return False - - return True - - -def valid_o3de_engine_json(file_name: str or pathlib.Path) -> bool: - file_name = pathlib.Path(file_name).resolve() - if not file_name.is_file(): - return False - - with file_name.open('r') as f: - try: - json_data = json.load(f) - test = json_data['engine_name'] - # test = json_data['origin'] # will be required soon - except Exception as e: - return False - return True - - -def valid_o3de_project_json(file_name: str or pathlib.Path) -> bool: - file_name = pathlib.Path(file_name).resolve() - if not file_name.is_file(): - return False - - with file_name.open('r') as f: - try: - json_data = json.load(f) - test = json_data['project_name'] - # test = json_data['origin'] # will be required soon - except Exception as e: - return False - return True - - -def valid_o3de_gem_json(file_name: str or pathlib.Path) -> bool: - file_name = pathlib.Path(file_name).resolve() - if not file_name.is_file(): - return False - - with file_name.open('r') as f: - try: - json_data = json.load(f) - test = json_data['gem_name'] - # test = json_data['origin'] # will be required soon - except Exception as e: - return False - return True - - -def valid_o3de_template_json(file_name: str or pathlib.Path) -> bool: - file_name = pathlib.Path(file_name).resolve() - if not file_name.is_file(): - return False - with file_name.open('r') as f: - try: - json_data = json.load(f) - test = json_data['template_name'] - # test = json_data['origin'] # will be required soon - except Exception as e: - return False - return True - - -def valid_o3de_restricted_json(file_name: str or pathlib.Path) -> bool: - file_name = pathlib.Path(file_name).resolve() - if not file_name.is_file(): - return False - with file_name.open('r') as f: - try: - json_data = json.load(f) - test = json_data['restricted_name'] - # test = json_data['origin'] # will be required soon - except Exception as e: - return False - return True - - -def process_add_o3de_repo(file_name: str or pathlib.Path, - repo_set: set) -> int: - file_name = pathlib.Path(file_name).resolve() - if not valid_o3de_repo_json(file_name): - return 1 - - cache_folder = get_o3de_cache_folder() - - with file_name.open('r') as f: - try: - repo_data = json.load(f) - except Exception as e: - logger.error(f'{file_name} failed to load: {str(e)}') - return 1 - - for engine_uri in repo_data['engines']: - engine_uri = f'{engine_uri}/engine.json' - engine_sha256 = hashlib.sha256(engine_uri.encode()) - cache_file = cache_folder / str(engine_sha256.hexdigest() + '.json') - if not cache_file.is_file(): - parsed_uri = urllib.parse.urlparse(engine_uri) - if parsed_uri.scheme == 'http' or \ - parsed_uri.scheme == 'https' or \ - parsed_uri.scheme == 'ftp' or \ - parsed_uri.scheme == 'ftps': - with urllib.request.urlopen(engine_uri) as s: - with cache_file.open('wb') as f: - shutil.copyfileobj(s, f) - else: - engine_json = pathlib.Path(engine_uri).resolve() - if not engine_json.is_file(): - return 1 - shutil.copy(engine_json, cache_file) - - for project_uri in repo_data['projects']: - project_uri = f'{project_uri}/project.json' - project_sha256 = hashlib.sha256(project_uri.encode()) - cache_file = cache_folder / str(project_sha256.hexdigest() + '.json') - if not cache_file.is_file(): - parsed_uri = urllib.parse.urlparse(project_uri) - if parsed_uri.scheme == 'http' or \ - parsed_uri.scheme == 'https' or \ - parsed_uri.scheme == 'ftp' or \ - parsed_uri.scheme == 'ftps': - with urllib.request.urlopen(project_uri) as s: - with cache_file.open('wb') as f: - shutil.copyfileobj(s, f) - else: - project_json = pathlib.Path(project_uri).resolve() - if not project_json.is_file(): - return 1 - shutil.copy(project_json, cache_file) - - for gem_uri in repo_data['gems']: - gem_uri = f'{gem_uri}/gem.json' - gem_sha256 = hashlib.sha256(gem_uri.encode()) - cache_file = cache_folder / str(gem_sha256.hexdigest() + '.json') - if not cache_file.is_file(): - parsed_uri = urllib.parse.urlparse(gem_uri) - if parsed_uri.scheme == 'http' or \ - parsed_uri.scheme == 'https' or \ - parsed_uri.scheme == 'ftp' or \ - parsed_uri.scheme == 'ftps': - with urllib.request.urlopen(gem_uri) as s: - with cache_file.open('wb') as f: - shutil.copyfileobj(s, f) - else: - gem_json = pathlib.Path(gem_uri).resolve() - if not gem_json.is_file(): - return 1 - shutil.copy(gem_json, cache_file) - - for template_uri in repo_data['templates']: - template_uri = f'{template_uri}/template.json' - template_sha256 = hashlib.sha256(template_uri.encode()) - cache_file = cache_folder / str(template_sha256.hexdigest() + '.json') - if not cache_file.is_file(): - parsed_uri = urllib.parse.urlparse(template_uri) - if parsed_uri.scheme == 'http' or \ - parsed_uri.scheme == 'https' or \ - parsed_uri.scheme == 'ftp' or \ - parsed_uri.scheme == 'ftps': - with urllib.request.urlopen(template_uri) as s: - with cache_file.open('wb') as f: - shutil.copyfileobj(s, f) - else: - template_json = pathlib.Path(template_uri).resolve() - if not template_json.is_file(): - return 1 - shutil.copy(template_json, cache_file) - - for repo_uri in repo_data['repos']: - if repo_uri not in repo_set: - repo_set.add(repo_uri) - repo_uri = f'{repo_uri}/repo.json' - repo_sha256 = hashlib.sha256(repo_uri.encode()) - cache_file = cache_folder / str(repo_sha256.hexdigest() + '.json') - if not cache_file.is_file(): - if parsed_uri.scheme == 'http' or \ - parsed_uri.scheme == 'https' or \ - parsed_uri.scheme == 'ftp' or \ - parsed_uri.scheme == 'ftps': - with urllib.request.urlopen(repo_uri) as s: - with cache_file.open('wb') as f: - shutil.copyfileobj(s, f) - else: - repo_json = pathlib.Path(repo_uri).resolve() - if not repo_json.is_file(): - return 1 - shutil.copy(repo_json, cache_file) - return 0 - - -def register_repo(json_data: dict, - repo_uri: str or pathlib.Path, - remove: bool = False) -> int: - if not repo_uri: - logger.error(f'Repo URI cannot be empty.') - return 1 - - url = f'{repo_uri}/repo.json' - parsed_uri = urllib.parse.urlparse(url) - - if parsed_uri.scheme == 'http' or \ - parsed_uri.scheme == 'https' or \ - parsed_uri.scheme == 'ftp' or \ - parsed_uri.scheme == 'ftps': - while repo_uri in json_data['repos']: - json_data['repos'].remove(repo_uri) - else: - repo_uri = pathlib.Path(repo_uri).resolve() - while repo_uri.as_posix() in json_data['repos']: - json_data['repos'].remove(repo_uri.as_posix()) - - if remove: - logger.warn(f'Removing repo uri {repo_uri}.') - return 0 - - repo_sha256 = hashlib.sha256(url.encode()) - cache_file = get_o3de_cache_folder() / str(repo_sha256.hexdigest() + '.json') - - result = 0 - if parsed_uri.scheme == 'http' or \ - parsed_uri.scheme == 'https' or \ - parsed_uri.scheme == 'ftp' or \ - parsed_uri.scheme == 'ftps': - if not cache_file.is_file(): - with urllib.request.urlopen(url) as s: - with cache_file.open('wb') as f: - shutil.copyfileobj(s, f) - json_data['repos'].insert(0, repo_uri) - else: - if not cache_file.is_file(): - origin_file = pathlib.Path(url).resolve() - if not origin_file.is_file(): - return 1 - shutil.copy(origin_file, origin_file) - json_data['repos'].insert(0, repo_uri.as_posix()) - - repo_set = set() - result = process_add_o3de_repo(cache_file, repo_set) - - return result - - -def register_default_engines_folder(json_data: dict, - default_engines_folder: str or pathlib.Path, - remove: bool = False) -> int: - if remove: - default_engines_folder = get_o3de_engines_folder() - - # make sure the path exists - default_engines_folder = pathlib.Path(default_engines_folder).resolve() - if not default_engines_folder.is_dir(): - logger.error(f'Default engines folder {default_engines_folder} does not exist.') - return 1 - - default_engines_folder = default_engines_folder.as_posix() - json_data['default_engines_folder'] = default_engines_folder - - return 0 - - -def register_default_projects_folder(json_data: dict, - default_projects_folder: str or pathlib.Path, - remove: bool = False) -> int: - if remove: - default_projects_folder = get_o3de_projects_folder() - - # make sure the path exists - default_projects_folder = pathlib.Path(default_projects_folder).resolve() - if not default_projects_folder.is_dir(): - logger.error(f'Default projects folder {default_projects_folder} does not exist.') - return 1 - - default_projects_folder = default_projects_folder.as_posix() - json_data['default_projects_folder'] = default_projects_folder - - return 0 - - -def register_default_gems_folder(json_data: dict, - default_gems_folder: str or pathlib.Path, - remove: bool = False) -> int: - if remove: - default_gems_folder = get_o3de_gems_folder() - - # make sure the path exists - default_gems_folder = pathlib.Path(default_gems_folder).resolve() - if not default_gems_folder.is_dir(): - logger.error(f'Default gems folder {default_gems_folder} does not exist.') - return 1 - - default_gems_folder = default_gems_folder.as_posix() - json_data['default_gems_folder'] = default_gems_folder - - return 0 - - -def register_default_templates_folder(json_data: dict, - default_templates_folder: str or pathlib.Path, - remove: bool = False) -> int: - if remove: - default_templates_folder = get_o3de_templates_folder() - - # make sure the path exists - default_templates_folder = pathlib.Path(default_templates_folder).resolve() - if not default_templates_folder.is_dir(): - logger.error(f'Default templates folder {default_templates_folder} does not exist.') - return 1 - - default_templates_folder = default_templates_folder.as_posix() - json_data['default_templates_folder'] = default_templates_folder - - return 0 - - -def register_default_restricted_folder(json_data: dict, - default_restricted_folder: str or pathlib.Path, - remove: bool = False) -> int: - if remove: - default_restricted_folder = get_o3de_restricted_folder() - - # make sure the path exists - default_restricted_folder = pathlib.Path(default_restricted_folder).resolve() - if not default_restricted_folder.is_dir(): - logger.error(f'Default restricted folder {default_restricted_folder} does not exist.') - return 1 - - default_restricted_folder = default_restricted_folder.as_posix() - json_data['default_restricted_folder'] = default_restricted_folder - - return 0 - - -def register(engine_path: str or pathlib.Path = None, - project_path: str or pathlib.Path = None, - gem_path: str or pathlib.Path = None, - template_path: str or pathlib.Path = None, - restricted_path: str or pathlib.Path = None, - repo_uri: str or pathlib.Path = None, - default_engines_folder: str or pathlib.Path = None, - default_projects_folder: str or pathlib.Path = None, - default_gems_folder: str or pathlib.Path = None, - default_templates_folder: str or pathlib.Path = None, - default_restricted_folder: str or pathlib.Path = None, - remove: bool = False - ) -> int: - """ - Adds/Updates entries to the .o3de/o3de_manifest.json - - :param engine_path: if engine folder is supplied the path will be added to the engine if it can, if not global - :param project_path: project folder - :param gem_path: gem folder - :param template_path: template folder - :param restricted_path: restricted folder - :param repo_uri: repo uri - :param default_engines_folder: default engines folder - :param default_projects_folder: default projects folder - :param default_gems_folder: default gems folder - :param default_templates_folder: default templates folder - :param default_restricted_folder: default restricted code folder - :param remove: add/remove the entries - - :return: 0 for success or non 0 failure code - """ - - json_data = load_o3de_manifest() - - result = 0 - - # do anything that could require a engine context first - if isinstance(project_path, str) or isinstance(project_path, pathlib.PurePath): - if not project_path: - logger.error(f'Project path cannot be empty.') - return 1 - result = register_project_path(json_data, project_path, remove, engine_path) - - elif isinstance(gem_path, str) or isinstance(gem_path, pathlib.PurePath): - if not gem_path: - logger.error(f'Gem path cannot be empty.') - return 1 - result = register_gem_path(json_data, gem_path, remove, engine_path) - - elif isinstance(template_path, str) or isinstance(template_path, pathlib.PurePath): - if not template_path: - logger.error(f'Template path cannot be empty.') - return 1 - result = register_template_path(json_data, template_path, remove, engine_path) - - elif isinstance(restricted_path, str) or isinstance(restricted_path, pathlib.PurePath): - if not restricted_path: - logger.error(f'Restricted path cannot be empty.') - return 1 - result = register_restricted_path(json_data, restricted_path, remove, engine_path) - - elif isinstance(repo_uri, str) or isinstance(repo_uri, pathlib.PurePath): - if not repo_uri: - logger.error(f'Repo URI cannot be empty.') - return 1 - result = register_repo(json_data, repo_uri, remove) - - elif isinstance(default_engines_folder, str) or isinstance(default_engines_folder, pathlib.PurePath): - result = register_default_engines_folder(json_data, default_engines_folder, remove) - - elif isinstance(default_projects_folder, str) or isinstance(default_projects_folder, pathlib.PurePath): - result = register_default_projects_folder(json_data, default_projects_folder, remove) - - elif isinstance(default_gems_folder, str) or isinstance(default_gems_folder, pathlib.PurePath): - result = register_default_gems_folder(json_data, default_gems_folder, remove) - - elif isinstance(default_templates_folder, str) or isinstance(default_templates_folder, pathlib.PurePath): - result = register_default_templates_folder(json_data, default_templates_folder, remove) - - elif isinstance(default_restricted_folder, str) or isinstance(default_restricted_folder, pathlib.PurePath): - result = register_default_restricted_folder(json_data, default_restricted_folder, remove) - - # engine is done LAST - # Now that everything that could have an engine context is done, if the engine is supplied that means this is - # registering the engine itself - elif isinstance(engine_path, str) or isinstance(engine_path, pathlib.PurePath): - if not engine_path: - logger.error(f'Engine path cannot be empty.') - return 1 - result = register_engine_path(json_data, engine_path, remove) - - if not result: - save_o3de_manifest(json_data) - - return result - - -def remove_invalid_o3de_objects() -> None: - json_data = load_o3de_manifest() - - for engine_object in json_data['engines']: - engine_path = engine_object['path'] - if not valid_o3de_engine_json(pathlib.Path(engine_path).resolve() / 'engine.json'): - logger.warn(f"Engine path {engine_path} is invalid.") - register(engine_path=engine_path, remove=True) - else: - for project in engine_object['projects']: - if not valid_o3de_project_json(pathlib.Path(project).resolve() / 'project.json'): - logger.warn(f"Project path {project} is invalid.") - register(engine_path=engine_path, project_path=project, remove=True) - - for gem_path in engine_object['gems']: - if not valid_o3de_gem_json(pathlib.Path(gem_path).resolve() / 'gem.json'): - logger.warn(f"Gem path {gem_path} is invalid.") - register(engine_path=engine_path, gem_path=gem_path, remove=True) - - for template_path in engine_object['templates']: - if not valid_o3de_template_json(pathlib.Path(template_path).resolve() / 'template.json'): - logger.warn(f"Template path {template_path} is invalid.") - register(engine_path=engine_path, template_path=template_path, remove=True) - - for restricted in engine_object['restricted']: - if not valid_o3de_restricted_json(pathlib.Path(restricted).resolve() / 'restricted.json'): - logger.warn(f"Restricted path {restricted} is invalid.") - register(engine_path=engine_path, restricted_path=restricted, remove=True) - - for external in engine_object['external_subdirectories']: - external = pathlib.Path(external).resolve() - if not external.is_dir(): - logger.warn(f"External subdirectory {external} is invalid.") - remove_external_subdirectory(external) - - for project in json_data['projects']: - if not valid_o3de_project_json(pathlib.Path(project).resolve() / 'project.json'): - logger.warn(f"Project path {project} is invalid.") - register(project_path=project, remove=True) - - for gem in json_data['gems']: - if not valid_o3de_gem_json(pathlib.Path(gem).resolve() / 'gem.json'): - logger.warn(f"Gem path {gem} is invalid.") - register(gem_path=gem, remove=True) - - for template in json_data['templates']: - if not valid_o3de_template_json(pathlib.Path(template).resolve() / 'template.json'): - logger.warn(f"Template path {template} is invalid.") - register(template_path=template, remove=True) - - for restricted in json_data['restricted']: - if not valid_o3de_restricted_json(pathlib.Path(restricted).resolve() / 'restricted.json'): - logger.warn(f"Restricted path {restricted} is invalid.") - register(restricted_path=restricted, remove=True) - - default_engines_folder = pathlib.Path(json_data['default_engines_folder']).resolve() - if not default_engines_folder.is_dir(): - new_default_engines_folder = get_o3de_folder() / 'Engines' - new_default_engines_folder.mkdir(parents=True, exist_ok=True) - logger.warn( - f"Default engines folder {default_engines_folder} is invalid. Set default {new_default_engines_folder}") - register(default_engines_folder=new_default_engines_folder.as_posix()) - - default_projects_folder = pathlib.Path(json_data['default_projects_folder']).resolve() - if not default_projects_folder.is_dir(): - new_default_projects_folder = get_o3de_folder() / 'Projects' - new_default_projects_folder.mkdir(parents=True, exist_ok=True) - logger.warn( - f"Default projects folder {default_projects_folder} is invalid. Set default {new_default_projects_folder}") - register(default_projects_folder=new_default_projects_folder.as_posix()) - - default_gems_folder = pathlib.Path(json_data['default_gems_folder']).resolve() - if not default_gems_folder.is_dir(): - new_default_gems_folder = get_o3de_folder() / 'Gems' - new_default_gems_folder.mkdir(parents=True, exist_ok=True) - logger.warn(f"Default gems folder {default_gems_folder} is invalid." - f" Set default {new_default_gems_folder}") - register(default_gems_folder=new_default_gems_folder.as_posix()) - - default_templates_folder = pathlib.Path(json_data['default_templates_folder']).resolve() - if not default_templates_folder.is_dir(): - new_default_templates_folder = get_o3de_folder() / 'Templates' - new_default_templates_folder.mkdir(parents=True, exist_ok=True) - logger.warn( - f"Default templates folder {default_templates_folder} is invalid." - f" Set default {new_default_templates_folder}") - register(default_templates_folder=new_default_templates_folder.as_posix()) - - default_restricted_folder = pathlib.Path(json_data['default_restricted_folder']).resolve() - if not default_restricted_folder.is_dir(): - default_restricted_folder = get_o3de_folder() / 'Restricted' - default_restricted_folder.mkdir(parents=True, exist_ok=True) - logger.warn( - f"Default restricted folder {default_restricted_folder} is invalid." - f" Set default {default_restricted_folder}") - register(default_restricted_folder=default_restricted_folder.as_posix()) - - -def refresh_repos() -> int: - json_data = load_o3de_manifest() - - # clear the cache - cache_folder = get_o3de_cache_folder() - shutil.rmtree(cache_folder) - cache_folder = get_o3de_cache_folder() # will recreate it - - result = 0 - - # set will stop circular references - repo_set = set() - - for repo_uri in json_data['repos']: - if repo_uri not in repo_set: - repo_set.add(repo_uri) - - repo_uri = f'{repo_uri}/repo.json' - repo_sha256 = hashlib.sha256(repo_uri.encode()) - cache_file = cache_folder / str(repo_sha256.hexdigest() + '.json') - if not cache_file.is_file(): - parsed_uri = urllib.parse.urlparse(repo_uri) - if parsed_uri.scheme == 'http' or \ - parsed_uri.scheme == 'https' or \ - parsed_uri.scheme == 'ftp' or \ - parsed_uri.scheme == 'ftps': - with urllib.request.urlopen(repo_uri) as s: - with cache_file.open('wb') as f: - shutil.copyfileobj(s, f) - else: - origin_file = pathlib.Path(repo_uri).resolve() - if not origin_file.is_file(): - return 1 - shutil.copy(origin_file, cache_file) - - if not valid_o3de_repo_json(cache_file): - logger.error(f'Repo json {repo_uri} is not valid.') - cache_file.unlink() - return 1 - - last_failure = process_add_o3de_repo(cache_file, repo_set) - if last_failure: - result = last_failure - - return result - - -def search_repo(repo_set: set, - repo_json_data: dict, - engine_name: str = None, - project_name: str = None, - gem_name: str = None, - template_name: str = None, - restricted_name: str = None) -> dict or None: - cache_folder = get_o3de_cache_folder() - - if isinstance(engine_name, str) or isinstance(engine_name, pathlib.PurePath): - for engine_uri in repo_json_data['engines']: - engine_uri = f'{engine_uri}/engine.json' - engine_sha256 = hashlib.sha256(engine_uri.encode()) - engine_cache_file = cache_folder / str(engine_sha256.hexdigest() + '.json') - if engine_cache_file.is_file(): - with engine_cache_file.open('r') as f: - try: - engine_json_data = json.load(f) - except Exception as e: - logger.warn(f'{engine_cache_file} failed to load: {str(e)}') - else: - if engine_json_data['engine_name'] == engine_name: - return engine_json_data - - elif isinstance(project_name, str) or isinstance(project_name, pathlib.PurePath): - for project_uri in repo_json_data['projects']: - project_uri = f'{project_uri}/project.json' - project_sha256 = hashlib.sha256(project_uri.encode()) - project_cache_file = cache_folder / str(project_sha256.hexdigest() + '.json') - if project_cache_file.is_file(): - with project_cache_file.open('r') as f: - try: - project_json_data = json.load(f) - except Exception as e: - logger.warn(f'{project_cache_file} failed to load: {str(e)}') - else: - if project_json_data['project_name'] == project_name: - return project_json_data - - elif isinstance(gem_name, str) or isinstance(gem_name, pathlib.PurePath): - for gem_uri in repo_json_data['gems']: - gem_uri = f'{gem_uri}/gem.json' - gem_sha256 = hashlib.sha256(gem_uri.encode()) - gem_cache_file = cache_folder / str(gem_sha256.hexdigest() + '.json') - if gem_cache_file.is_file(): - with gem_cache_file.open('r') as f: - try: - gem_json_data = json.load(f) - except Exception as e: - logger.warn(f'{gem_cache_file} failed to load: {str(e)}') - else: - if gem_json_data['gem_name'] == gem_name: - return gem_json_data - - elif isinstance(template_name, str) or isinstance(template_name, pathlib.PurePath): - for template_uri in repo_json_data['templates']: - template_uri = f'{template_uri}/template.json' - template_sha256 = hashlib.sha256(template_uri.encode()) - template_cache_file = cache_folder / str(template_sha256.hexdigest() + '.json') - if template_cache_file.is_file(): - with template_cache_file.open('r') as f: - try: - template_json_data = json.load(f) - except Exception as e: - logger.warn(f'{template_cache_file} failed to load: {str(e)}') - else: - if template_json_data['template_name'] == template_name: - return template_json_data - - elif isinstance(restricted_name, str) or isinstance(restricted_name, pathlib.PurePath): - for restricted_uri in repo_json_data['restricted']: - restricted_uri = f'{restricted_uri}/restricted.json' - restricted_sha256 = hashlib.sha256(restricted_uri.encode()) - restricted_cache_file = cache_folder / str(restricted_sha256.hexdigest() + '.json') - if restricted_cache_file.is_file(): - with restricted_cache_file.open('r') as f: - try: - restricted_json_data = json.load(f) - except Exception as e: - logger.warn(f'{restricted_cache_file} failed to load: {str(e)}') - else: - if restricted_json_data['restricted_name'] == restricted_name: - return restricted_json_data - # recurse - else: - for repo_repo_uri in repo_json_data['repos']: - if repo_repo_uri not in repo_set: - repo_set.add(repo_repo_uri) - repo_repo_uri = f'{repo_repo_uri}/repo.json' - repo_repo_sha256 = hashlib.sha256(repo_repo_uri.encode()) - repo_repo_cache_file = cache_folder / str(repo_repo_sha256.hexdigest() + '.json') - if repo_repo_cache_file.is_file(): - with repo_repo_cache_file.open('r') as f: - try: - repo_repo_json_data = json.load(f) - except Exception as e: - logger.warn(f'{repo_repo_cache_file} failed to load: {str(e)}') - else: - item = search_repo(repo_set, - repo_repo_json_data, - engine_name, - project_name, - gem_name, - template_name) - if item: - return item - return None - - -def get_downloadable(engine_name: str = None, - project_name: str = None, - gem_name: str = None, - template_name: str = None, - restricted_name: str = None) -> dict or None: - json_data = load_o3de_manifest() - cache_folder = get_o3de_cache_folder() - repo_set = set() - for repo_uri in json_data['repos']: - if repo_uri not in repo_set: - repo_set.add(repo_uri) - repo_uri = f'{repo_uri}/repo.json' - repo_sha256 = hashlib.sha256(repo_uri.encode()) - repo_cache_file = cache_folder / str(repo_sha256.hexdigest() + '.json') - if repo_cache_file.is_file(): - with repo_cache_file.open('r') as f: - try: - repo_json_data = json.load(f) - except Exception as e: - logger.warn(f'{repo_cache_file} failed to load: {str(e)}') - else: - item = search_repo(repo_set, - repo_json_data, - engine_name, - project_name, - gem_name, - template_name, - restricted_name) - if item: - return item - return None - - -def get_registered(engine_name: str = None, - project_name: str = None, - gem_name: str = None, - template_name: str = None, - default_folder: str = None, - repo_name: str = None, - restricted_name: str = None) -> pathlib.Path or None: - json_data = load_o3de_manifest() - - # check global first then this engine - if isinstance(engine_name, str): - for engine in json_data['engines']: - engine_path = pathlib.Path(engine['path']).resolve() - engine_json = engine_path / 'engine.json' - with engine_json.open('r') as f: - try: - engine_json_data = json.load(f) - except Exception as e: - logger.warn(f'{engine_json} failed to load: {str(e)}') - else: - this_engines_name = engine_json_data['engine_name'] - if this_engines_name == engine_name: - return engine_path - - elif isinstance(project_name, str): - engine_object = find_engine_data(json_data) - projects = json_data['projects'].copy() - projects.extend(engine_object['projects']) - for project_path in projects: - project_path = pathlib.Path(project_path).resolve() - project_json = project_path / 'project.json' - with project_json.open('r') as f: - try: - project_json_data = json.load(f) - except Exception as e: - logger.warn(f'{project_json} failed to load: {str(e)}') - else: - this_projects_name = project_json_data['project_name'] - if this_projects_name == project_name: - return project_path - - elif isinstance(gem_name, str): - engine_object = find_engine_data(json_data) - gems = json_data['gems'].copy() - gems.extend(engine_object['gems']) - for gem_path in gems: - gem_path = pathlib.Path(gem_path).resolve() - gem_json = gem_path / 'gem.json' - with gem_json.open('r') as f: - try: - gem_json_data = json.load(f) - except Exception as e: - logger.warn(f'{gem_json} failed to load: {str(e)}') - else: - this_gems_name = gem_json_data['gem_name'] - if this_gems_name == gem_name: - return gem_path - - elif isinstance(template_name, str): - engine_object = find_engine_data(json_data) - templates = json_data['templates'].copy() - templates.extend(engine_object['templates']) - for template_path in templates: - template_path = pathlib.Path(template_path).resolve() - template_json = template_path / 'template.json' - with template_json.open('r') as f: - try: - template_json_data = json.load(f) - except Exception as e: - logger.warn(f'{template_path} failed to load: {str(e)}') - else: - this_templates_name = template_json_data['template_name'] - if this_templates_name == template_name: - return template_path - - elif isinstance(restricted_name, str): - engine_object = find_engine_data(json_data) - restricted = json_data['restricted'].copy() - restricted.extend(engine_object['restricted']) - for restricted_path in restricted: - restricted_path = pathlib.Path(restricted_path).resolve() - restricted_json = restricted_path / 'restricted.json' - with restricted_json.open('r') as f: - try: - restricted_json_data = json.load(f) - except Exception as e: - logger.warn(f'{restricted_json} failed to load: {str(e)}') - else: - this_restricted_name = restricted_json_data['restricted_name'] - if this_restricted_name == restricted_name: - return restricted_path - - elif isinstance(default_folder, str): - if default_folder == 'engines': - default_engines_folder = pathlib.Path(json_data['default_engines_folder']).resolve() - return default_engines_folder - elif default_folder == 'projects': - default_projects_folder = pathlib.Path(json_data['default_projects_folder']).resolve() - return default_projects_folder - elif default_folder == 'gems': - default_gems_folder = pathlib.Path(json_data['default_gems_folder']).resolve() - return default_gems_folder - elif default_folder == 'templates': - default_templates_folder = pathlib.Path(json_data['default_templates_folder']).resolve() - return default_templates_folder - elif default_folder == 'restricted': - default_restricted_folder = pathlib.Path(json_data['default_restricted_folder']).resolve() - return default_restricted_folder - - elif isinstance(repo_name, str): - cache_folder = get_o3de_cache_folder() - for repo_uri in json_data['repos']: - repo_uri = pathlib.Path(repo_uri).resolve() - repo_sha256 = hashlib.sha256(repo_uri.encode()) - cache_file = cache_folder / str(repo_sha256.hexdigest() + '.json') - if cache_file.is_file(): - repo = pathlib.Path(cache_file).resolve() - with repo.open('r') as f: - try: - repo_json_data = json.load(f) - except Exception as e: - logger.warn(f'{cache_file} failed to load: {str(e)}') - else: - this_repos_name = repo_json_data['repo_name'] - if this_repos_name == repo_name: - return repo_uri - return None - - -def print_engines_data(engines_data: dict) -> None: - print('\n') - print("Engines================================================") - for engine_object in engines_data: - # if it's not local it should be in the cache - engine_uri = engine_object['path'] - parsed_uri = urllib.parse.urlparse(engine_uri) - if parsed_uri.scheme == 'http' or \ - parsed_uri.scheme == 'https' or \ - parsed_uri.scheme == 'ftp' or \ - parsed_uri.scheme == 'ftps': - repo_sha256 = hashlib.sha256(engine_uri.encode()) - cache_folder = get_o3de_cache_folder() - engine = cache_folder / str(repo_sha256.hexdigest() + '.json') - print(f'{engine_uri}/engine.json cached as:') - else: - engine_json = pathlib.Path(engine_uri).resolve() / 'engine.json' - - with engine_json.open('r') as f: - try: - engine_json_data = json.load(f) - except Exception as e: - logger.warn(f'{engine_json} failed to load: {str(e)}') - else: - print(engine_json) - print(json.dumps(engine_json_data, indent=4)) - print('\n') - - -def print_projects_data(projects_data: dict) -> None: - print('\n') - print("Projects================================================") - for project_uri in projects_data: - # if it's not local it should be in the cache - parsed_uri = urllib.parse.urlparse(project_uri) - if parsed_uri.scheme == 'http' or \ - parsed_uri.scheme == 'https' or \ - parsed_uri.scheme == 'ftp' or \ - parsed_uri.scheme == 'ftps': - repo_sha256 = hashlib.sha256(project_uri.encode()) - cache_folder = get_o3de_cache_folder() - project_json = cache_folder / str(repo_sha256.hexdigest() + '.json') - else: - project_json = pathlib.Path(project_uri).resolve() / 'project.json' - - with project_json.open('r') as f: - try: - project_json_data = json.load(f) - except Exception as e: - logger.warn(f'{project_json} failed to load: {str(e)}') - else: - print(project_json) - print(json.dumps(project_json_data, indent=4)) - print('\n') - - -def print_gems_data(gems_data: dict) -> None: - print('\n') - print("Gems================================================") - for gem_uri in gems_data: - # if it's not local it should be in the cache - parsed_uri = urllib.parse.urlparse(gem_uri) - if parsed_uri.scheme == 'http' or \ - parsed_uri.scheme == 'https' or \ - parsed_uri.scheme == 'ftp' or \ - parsed_uri.scheme == 'ftps': - repo_sha256 = hashlib.sha256(gem_uri.encode()) - cache_folder = get_o3de_cache_folder() - gem_json = cache_folder / str(repo_sha256.hexdigest() + '.json') - else: - gem_json = pathlib.Path(gem_uri).resolve() / 'gem.json' - - with gem_json.open('r') as f: - try: - gem_json_data = json.load(f) - except Exception as e: - logger.warn(f'{gem_json} failed to load: {str(e)}') - else: - print(gem_json) - print(json.dumps(gem_json_data, indent=4)) - print('\n') - - -def print_templates_data(templates_data: dict) -> None: - print('\n') - print("Templates================================================") - for template_uri in templates_data: - # if it's not local it should be in the cache - parsed_uri = urllib.parse.urlparse(template_uri) - if parsed_uri.scheme == 'http' or \ - parsed_uri.scheme == 'https' or \ - parsed_uri.scheme == 'ftp' or \ - parsed_uri.scheme == 'ftps': - repo_sha256 = hashlib.sha256(template_uri.encode()) - cache_folder = get_o3de_cache_folder() - template_json = cache_folder / str(repo_sha256.hexdigest() + '.json') - else: - template_json = pathlib.Path(template_uri).resolve() / 'template.json' - - with template_json.open('r') as f: - try: - template_json_data = json.load(f) - except Exception as e: - logger.warn(f'{template_json} failed to load: {str(e)}') - else: - print(template_json) - print(json.dumps(template_json_data, indent=4)) - print('\n') - - -def print_repos_data(repos_data: dict) -> None: - print('\n') - print("Repos================================================") - cache_folder = get_o3de_cache_folder() - for repo_uri in repos_data: - repo_sha256 = hashlib.sha256(repo_uri.encode()) - cache_file = cache_folder / str(repo_sha256.hexdigest() + '.json') - if valid_o3de_repo_json(cache_file): - with cache_file.open('r') as s: - try: - repo_json_data = json.load(s) - except Exception as e: - logger.warn(f'{cache_file} failed to load: {str(e)}') - else: - print(f'{repo_uri}/repo.json cached as:') - print(cache_file) - print(json.dumps(repo_json_data, indent=4)) - print('\n') - - -def print_restricted_data(restricted_data: dict) -> None: - print('\n') - print("Restricted================================================") - for restricted_path in restricted_data: - restricted_json = pathlib.Path(restricted_path).resolve() / 'restricted.json' - with restricted_json.open('r') as f: - try: - restricted_json_data = json.load(f) - except Exception as e: - logger.warn(f'{restricted_json} failed to load: {str(e)}') - else: - print(restricted_json) - print(json.dumps(restricted_json_data, indent=4)) - print('\n') - - -def get_this_engine() -> dict: - json_data = load_o3de_manifest() - engine_data = find_engine_data(json_data) - return engine_data - - -def get_engines() -> dict: - json_data = load_o3de_manifest() - return json_data['engines'] - - -def get_projects() -> dict: - json_data = load_o3de_manifest() - return json_data['projects'] - - -def get_gems() -> dict: - json_data = load_o3de_manifest() - return json_data['gems'] - - -def get_templates() -> dict: - json_data = load_o3de_manifest() - return json_data['templates'] - - -def get_restricted() -> dict: - json_data = load_o3de_manifest() - return json_data['restricted'] - - -def get_repos() -> dict: - json_data = load_o3de_manifest() - return json_data['repos'] - - -def get_engine_projects() -> dict: - json_data = load_o3de_manifest() - engine_object = find_engine_data(json_data) - return engine_object['projects'] - - -def get_engine_gems() -> dict: - json_data = load_o3de_manifest() - engine_object = find_engine_data(json_data) - return engine_object['gems'] - - -def get_engine_templates() -> dict: - json_data = load_o3de_manifest() - engine_object = find_engine_data(json_data) - return engine_object['templates'] - - -def get_engine_restricted() -> dict: - json_data = load_o3de_manifest() - engine_object = find_engine_data(json_data) - return engine_object['restricted'] - - -def get_external_subdirectories() -> dict: - json_data = load_o3de_manifest() - engine_object = find_engine_data(json_data) - return engine_object['external_subdirectories'] - - -def get_all_projects() -> dict: - json_data = load_o3de_manifest() - engine_object = find_engine_data(json_data) - projects_data = json_data['projects'].copy() - projects_data.extend(engine_object['projects']) - return projects_data - - -def get_all_gems() -> dict: - json_data = load_o3de_manifest() - engine_object = find_engine_data(json_data) - gems_data = json_data['gems'].copy() - gems_data.extend(engine_object['gems']) - return gems_data - - -def get_all_templates() -> dict: - json_data = load_o3de_manifest() - engine_object = find_engine_data(json_data) - templates_data = json_data['templates'].copy() - templates_data.extend(engine_object['templates']) - return templates_data - - -def get_all_restricted() -> dict: - json_data = load_o3de_manifest() - engine_object = find_engine_data(json_data) - restricted_data = json_data['restricted'].copy() - restricted_data.extend(engine_object['restricted']) - return restricted_data - - -def print_this_engine(verbose: int) -> None: - engine_data = get_this_engine() - print(json.dumps(engine_data, indent=4)) - if verbose > 0: - print_engines_data(engine_data) - - -def print_engines(verbose: int) -> None: - engines_data = get_engines() - print(json.dumps(engines_data, indent=4)) - if verbose > 0: - print_engines_data(engines_data) - - -def print_projects(verbose: int) -> None: - projects_data = get_projects() - print(json.dumps(projects_data, indent=4)) - if verbose > 0: - print_projects_data(projects_data) - - -def print_gems(verbose: int) -> None: - gems_data = get_gems() - print(json.dumps(gems_data, indent=4)) - if verbose > 0: - print_gems_data(gems_data) - - -def print_templates(verbose: int) -> None: - templates_data = get_templates() - print(json.dumps(templates_data, indent=4)) - if verbose > 0: - print_templates_data(templates_data) - - -def print_restricted(verbose: int) -> None: - restricted_data = get_restricted() - print(json.dumps(restricted_data, indent=4)) - if verbose > 0: - print_restricted_data(restricted_data) - - -def register_show_repos(verbose: int) -> None: - repos_data = get_repos() - print(json.dumps(repos_data, indent=4)) - if verbose > 0: - print_repos_data(repos_data) - - -def print_engine_projects(verbose: int) -> None: - engine_projects_data = get_engine_projects() - print(json.dumps(engine_projects_data, indent=4)) - if verbose > 0: - print_projects_data(engine_projects_data) - - -def print_engine_gems(verbose: int) -> None: - engine_gems_data = get_engine_gems() - print(json.dumps(engine_gems_data, indent=4)) - if verbose > 0: - print_gems_data(engine_gems_data) - - -def print_engine_templates(verbose: int) -> None: - engine_templates_data = get_engine_templates() - print(json.dumps(engine_templates_data, indent=4)) - if verbose > 0: - print_templates_data(engine_templates_data) - - -def print_engine_restricted(verbose: int) -> None: - engine_restricted_data = get_engine_restricted() - print(json.dumps(engine_restricted_data, indent=4)) - if verbose > 0: - print_restricted_data(engine_restricted_data) - - -def print_external_subdirectories(verbose: int) -> None: - external_subdirs_data = get_external_subdirectories() - print(json.dumps(external_subdirs_data, indent=4)) - - -def print_all_projects(verbose: int) -> None: - all_projects_data = get_all_projects() - print(json.dumps(all_projects_data, indent=4)) - if verbose > 0: - print_projects_data(all_projects_data) - - -def print_all_gems(verbose: int) -> None: - all_gems_data = get_all_gems() - print(json.dumps(all_gems_data, indent=4)) - if verbose > 0: - print_gems_data(all_gems_data) - - -def print_all_templates(verbose: int) -> None: - all_templates_data = get_all_templates() - print(json.dumps(all_templates_data, indent=4)) - if verbose > 0: - print_templates_data(all_templates_data) - - -def print_all_restricted(verbose: int) -> None: - all_restricted_data = get_all_restricted() - print(json.dumps(all_restricted_data, indent=4)) - if verbose > 0: - print_restricted_data(all_restricted_data) - - -def register_show(verbose: int) -> None: - json_data = load_o3de_manifest() - print(f"{get_o3de_manifest()}:") - print(json.dumps(json_data, indent=4)) - - if verbose > 0: - print_engines_data(get_engines()) - print_projects_data(get_all_projects()) - print_gems_data(get_gems()) - print_templates_data(get_all_templates()) - print_restricted_data(get_all_restricted()) - print_repos_data(get_repos()) - - -def find_engine_data(json_data: dict, - engine_path: str or pathlib.Path = None) -> dict or None: - if not engine_path: - engine_path = get_this_engine_path() - engine_path = pathlib.Path(engine_path).resolve() - - for engine_object in json_data['engines']: - engine_object_path = pathlib.Path(engine_object['path']).resolve() - if engine_path == engine_object_path: - return engine_object - - return None - - -def _validate_engine_name_and_path(engine_name: str = None, - engine_path: str or pathlib.Path = None) -> pathlib.Path or None: - if not engine_name and not engine_path: - logger.error('Must specify either a Engine name or Engine Path.') - return None - - if engine_name and not engine_path: - engine_path = get_registered(engine_name=engine_name) - - if not engine_path: - logger.error(f'Engine Path {engine_path} has not been registered.') - return None - - engine_path = pathlib.Path(engine_path).resolve() - engine_json = engine_path / 'engine.json' - if not engine_json.is_file(): - logger.error(f'Engine json {engine_json} is not present.') - return None - if not valid_o3de_engine_json(engine_json): - logger.error(f'Engine json {engine_json} is not valid.') - return None - - return engine_json - -def get_engine_data(engine_name: str = None, - engine_path: str or pathlib.Path = None ) -> dict or None: - engine_json = _validate_engine_name_and_path(engine_name, engine_path) - if not engine_json: - return None - - with engine_json.open('r') as f: - try: - engine_json_data = json.load(f) - except Exception as e: - logger.warn(f'{engine_json} failed to load: {str(e)}') - else: - return engine_json_data - - return None - -def set_engine_data(engine_name: str = None, - engine_path: str or pathlib.Path = None, - engine_data: dict = None ) -> int: - if not engine_data: - logger.error('Must provide engine data.') - return 1 - - engine_json = _validate_engine_name_and_path(engine_name, engine_path) - if not engine_json: - return 1 - - with engine_json.open('w') as f: - try: - json.dump(engine_data, f, indent=4) - except Exception as e: - logger.warn(f'Failed to load or write {engine_json}: {str(e)}') - return 1 - - return 0 - - -def get_project_data(project_name: str = None, - project_path: str or pathlib.Path = None, ) -> dict or None: - if not project_name and not project_path: - logger.error('Must specify either a Project name or Project Path.') - return 1 - - if project_name and not project_path: - project_path = get_registered(project_name=project_name) - - if not project_path: - logger.error(f'Project Path {project_path} has not been registered.') - return 1 - - project_path = pathlib.Path(project_path).resolve() - project_json = project_path / 'project.json' - if not project_json.is_file(): - logger.error(f'Project json {project_json} is not present.') - return 1 - if not valid_o3de_project_json(project_json): - logger.error(f'Project json {project_json} is not valid.') - return 1 - - with project_json.open('r') as f: - try: - project_json_data = json.load(f) - except Exception as e: - logger.warn(f'{project_json} failed to load: {str(e)}') - else: - return project_json_data - - return None - - -def _validate_gem_name_and_path(gem_name: str = None, - gem_path: str or pathlib.Path = None) -> pathlib.Path or None: - if not gem_name and not gem_path: - logger.error('Must specify either a Gem name or Gem Path.') - return None - - if gem_name and not gem_path: - gem_path = get_registered(gem_name=gem_name) - - if not gem_path: - logger.error(f'Gem Path {gem_path} has not been registered.') - return None - - gem_path = pathlib.Path(gem_path).resolve() - gem_json = gem_path / 'gem.json' - if not gem_json.is_file(): - logger.error(f'Gem json {gem_json} is not present.') - return None - if not valid_o3de_gem_json(gem_json): - logger.error(f'Gem json {gem_json} is not valid.') - return None - - return gem_json - - -def get_gem_data(gem_name: str = None, - gem_path: str or pathlib.Path = None) -> dict or None: - gem_json = _validate_gem_name_and_path(gem_name, gem_path) - if not gem_json: - return None - - with gem_json.open('r') as f: - try: - gem_json_data = json.load(f) - except Exception as e: - logger.warn(f'{gem_json} failed to load: {str(e)}') - else: - return gem_json_data - - return None - - -def set_gem_data(gem_name: str = None, - gem_path: str or pathlib.Path = None, - gem_data: dict = None) -> int: - if not gem_data: - logger.error('Must provide Gem data.') - return 1 - - gem_json = _validate_gem_name_and_path(gem_name, gem_path) - if not gem_json: - return 1 - - with gem_json.open('w') as f: - try: - json.dump(gem_data, f, indent=4) - except Exception as e: - logger.warn(f'Failed to load and write {gem_json}: {str(e)}') - return 1 - - return 0 - - -def get_template_data(template_name: str = None, - template_path: str or pathlib.Path = None, ) -> dict or None: - if not template_name and not template_path: - logger.error('Must specify either a Template name or Template Path.') - return 1 - - if template_name and not template_path: - template_path = get_registered(template_name=template_name) - - if not template_path: - logger.error(f'Template Path {template_path} has not been registered.') - return 1 - - template_path = pathlib.Path(template_path).resolve() - template_json = template_path / 'template.json' - if not template_json.is_file(): - logger.error(f'Template json {template_json} is not present.') - return 1 - if not valid_o3de_template_json(template_json): - logger.error(f'Template json {template_json} is not valid.') - return 1 - - with template_json.open('r') as f: - try: - template_json_data = json.load(f) - except Exception as e: - logger.warn(f'{template_json} failed to load: {str(e)}') - else: - return template_json_data - - return None - - -def get_restricted_data(restricted_name: str = None, - restricted_path: str or pathlib.Path = None, ) -> dict or None: - if not restricted_name and not restricted_path: - logger.error('Must specify either a Restricted name or Restricted Path.') - return 1 - - if restricted_name and not restricted_path: - restricted_path = get_registered(restricted_name=restricted_name) - - if not restricted_path: - logger.error(f'Restricted Path {restricted_path} has not been registered.') - return 1 - - restricted_path = pathlib.Path(restricted_path).resolve() - restricted_json = restricted_path / 'restricted.json' - if not restricted_json.is_file(): - logger.error(f'Restricted json {restricted_json} is not present.') - return 1 - if not valid_o3de_restricted_json(restricted_json): - logger.error(f'Restricted json {restricted_json} is not valid.') - return 1 - - with restricted_json.open('r') as f: - try: - restricted_json_data = json.load(f) - except Exception as e: - logger.warn(f'{restricted_json} failed to load: {str(e)}') - else: - return restricted_json_data - - return None - - -def get_downloadables() -> dict: - json_data = load_o3de_manifest() - downloadable_data = {} - downloadable_data.update({'engines': []}) - downloadable_data.update({'projects': []}) - downloadable_data.update({'gems': []}) - downloadable_data.update({'templates': []}) - downloadable_data.update({'restricted': []}) - - def recurse_downloadables(repo_uri: str or pathlib.Path) -> None: - cache_folder = get_o3de_cache_folder() - repo_sha256 = hashlib.sha256(repo_uri.encode()) - cache_file = cache_folder / str(repo_sha256.hexdigest() + '.json') - if valid_o3de_repo_json(cache_file): - with cache_file.open('r') as s: - try: - repo_json_data = json.load(s) - except Exception as e: - logger.warn(f'{cache_file} failed to load: {str(e)}') - else: - for engine in repo_json_data['engines']: - if engine not in downloadable_data['engines']: - downloadable_data['engines'].append(engine) - - for project in repo_json_data['projects']: - if project not in downloadable_data['projects']: - downloadable_data['projects'].append(project) - - for gem in repo_json_data['gems']: - if gem not in downloadable_data['gems']: - downloadable_data['gems'].append(gem) - - for template in repo_json_data['templates']: - if template not in downloadable_data['templates']: - downloadable_data['templates'].append(template) - - for restricted in repo_json_data['restricted']: - if restricted not in downloadable_data['restricted']: - downloadable_data['restricted'].append(restricted) - - for repo in repo_json_data['repos']: - if repo not in downloadable_data['repos']: - downloadable_data['repos'].append(repo) - - for repo in downloadable_data['repos']: - recurse_downloadables(repo) - - for repo_entry in json_data['repos']: - recurse_downloadables(repo_entry) - return downloadable_data - - -def get_downloadable_engines() -> dict: - downloadable_data = get_downloadables() - return downloadable_data['engines'] - - -def get_downloadable_projects() -> dict: - downloadable_data = get_downloadables() - return downloadable_data['projects'] - - -def get_downloadable_gems() -> dict: - downloadable_data = get_downloadables() - return downloadable_data['gems'] - - -def get_downloadable_templates() -> dict: - downloadable_data = get_downloadables() - return downloadable_data['templates'] - - -def get_downloadable_restricted() -> dict: - downloadable_data = get_downloadables() - return downloadable_data['restricted'] - - -def print_downloadable_engines(verbose: int) -> None: - downloadable_engines = get_downloadable_engines() - for engine_data in downloadable_engines: - print(json.dumps(engine_data, indent=4)) - if verbose > 0: - print_engines_data(downloadable_engines) - - -def print_downloadable_projects(verbose: int) -> None: - downloadable_projects = get_downloadable_projects() - for projects_data in downloadable_projects: - print(json.dumps(projects_data, indent=4)) - if verbose > 0: - print_projects_data(downloadable_projects) - - -def print_downloadable_gems(verbose: int) -> None: - downloadable_gems = get_downloadable_gems() - for gem_data in downloadable_gems: - print(json.dumps(gem_data, indent=4)) - if verbose > 0: - print_gems_data(downloadable_gems) - - -def print_downloadable_templates(verbose: int) -> None: - downloadable_templates = get_downloadable_templates() - for template_data in downloadable_templates: - print(json.dumps(template_data, indent=4)) - if verbose > 0: - print_engines_data(downloadable_templates) - - -def print_downloadable_restricted(verbose: int) -> None: - downloadable_restricted = get_downloadable_restricted() - for restricted_data in downloadable_restricted: - print(json.dumps(restricted_data, indent=4)) - if verbose > 0: - print_engines_data(downloadable_restricted) - - -def print_downloadables(verbose: int) -> None: - downloadable_data = get_downloadables() - print(json.dumps(downloadable_data, indent=4)) - if verbose > 0: - print_engines_data(downloadable_data['engines']) - print_projects_data(downloadable_data['projects']) - print_gems_data(downloadable_data['gems']) - print_templates_data(downloadable_data['templates']) - print_restricted_data(downloadable_data['templates']) - - -def download_engine(engine_name: str, - dest_path: str) -> int: - if not dest_path: - dest_path = get_registered(default_folder='engines') - if not dest_path: - logger.error(f'Destination path not cannot be empty.') - return 1 - - dest_path = pathlib.Path(dest_path).resolve() - dest_path.mkdir(exist_ok=True) - - download_path = get_o3de_download_folder() / 'engines' / engine_name - download_path.mkdir(exist_ok=True) - download_zip_path = download_path / 'engine.zip' - - downloadable_engine_data = get_downloadable(engine_name=engine_name) - if not downloadable_engine_data: - logger.error(f'Downloadable engine {engine_name} not found.') - return 1 - - origin = downloadable_engine_data['origin'] - url = f'{origin}/project.zip' - parsed_uri = urllib.parse.urlparse(url) - - if download_zip_path.is_file(): - logger.warn(f'Project already downloaded to {download_zip_path}.') - elif parsed_uri.scheme == 'http' or \ - parsed_uri.scheme == 'https' or \ - parsed_uri.scheme == 'ftp' or \ - parsed_uri.scheme == 'ftps': - with urllib.request.urlopen(url) as s: - with download_zip_path.open('wb') as f: - shutil.copyfileobj(s, f) - else: - origin_file = pathlib.Path(url).resolve() - if not origin_file.is_file(): - return 1 - shutil.copy(origin_file, download_zip_path) - - if not zipfile.is_zipfile(download_zip_path): - logger.error(f"Engine zip {download_zip_path} is invalid.") - download_zip_path.unlink() - return 1 - - # if the engine.json has a sha256 check it against a sha256 of the zip - try: - sha256A = downloadable_engine_data['sha256'] - except Exception as e: - logger.warn(f'SECURITY WARNING: The advertised engine you downloaded has no "sha256"!!! Be VERY careful!!!' - f' We cannot verify this is the actually the advertised engine!!!') - else: - sha256B = hashlib.sha256(download_zip_path.open('rb').read()).hexdigest() - if sha256A != sha256B: - logger.error(f'SECURITY VIOLATION: Downloaded engine.zip sha256 {sha256B} does not match' - f' the advertised "sha256":{sha256A} in the engine.json. Deleting unzipped files!!!') - shutil.rmtree(dest_path) - return 1 - - dest_engine_folder = dest_path / engine_name - if dest_engine_folder.is_dir(): - backup_folder(dest_engine_folder) - with zipfile.ZipFile(download_zip_path, 'r') as project_zip: - try: - project_zip.extractall(dest_path) - except Exception as e: - logger.error(f'UnZip exception:{str(e)}') - shutil.rmtree(dest_path) - return 1 - - unzipped_engine_json = dest_engine_folder / 'engine.json' - if not unzipped_engine_json.is_file(): - logger.error(f'Engine json {unzipped_engine_json} is missing.') - return 1 - - if not valid_o3de_engine_json(unzipped_engine_json): - logger.error(f'Engine json {unzipped_engine_json} is invalid.') - return 1 - - # remove the sha256 if present in the advertised downloadable engine.json - # then compare it to the engine.json in the zip, they should now be identical - try: - del downloadable_engine_data['sha256'] - except Exception as e: - pass - - sha256A = hashlib.sha256(json.dumps(downloadable_engine_data, indent=4).encode('utf8')).hexdigest() - with unzipped_engine_json.open('r') as s: - try: - unzipped_engine_json_data = json.load(s) - except Exception as e: - logger.error(f'Failed to read engine json {unzipped_engine_json}. Unable to confirm this' - f' is the same template that was advertised.') - return 1 - sha256B = hashlib.sha256(json.dumps(unzipped_engine_json_data, indent=4).encode('utf8')).hexdigest() - if sha256A != sha256B: - logger.error(f'SECURITY VIOLATION: Downloaded engine.json does not match' - f' the advertised engine.json. Deleting unzipped files!!!') - shutil.rmtree(dest_path) - return 1 - - return 0 - - -def download_project(project_name: str, - dest_path: str or pathlib.Path) -> int: - if not dest_path: - dest_path = get_registered(default_folder='projects') - if not dest_path: - logger.error(f'Destination path not specified and not default projects path.') - return 1 - - dest_path = pathlib.Path(dest_path).resolve() - dest_path.mkdir(exist_ok=True, parents=True) - - download_path = get_o3de_download_folder() / 'projects' / project_name - download_path.mkdir(exist_ok=True, parents=True) - download_zip_path = download_path / 'project.zip' - - downloadable_project_data = get_downloadable(project_name=project_name) - if not downloadable_project_data: - logger.error(f'Downloadable project {project_name} not found.') - return 1 - - origin = downloadable_project_data['origin'] - url = f'{origin}/project.zip' - parsed_uri = urllib.parse.urlparse(url) - - if download_zip_path.is_file(): - logger.warn(f'Project already downloaded to {download_zip_path}.') - elif parsed_uri.scheme == 'http' or \ - parsed_uri.scheme == 'https' or \ - parsed_uri.scheme == 'ftp' or \ - parsed_uri.scheme == 'ftps': - with urllib.request.urlopen(url) as s: - with download_zip_path.open('wb') as f: - shutil.copyfileobj(s, f) - else: - origin_file = pathlib.Path(url).resolve() - if not origin_file.is_file(): - return 1 - shutil.copy(origin_file, download_zip_path) - - if not zipfile.is_zipfile(download_zip_path): - logger.error(f"Project zip {download_zip_path} is invalid.") - download_zip_path.unlink() - return 1 - - # if the project.json has a sha256 check it against a sha256 of the zip - try: - sha256A = downloadable_project_data['sha256'] - except Exception as e: - logger.warn(f'SECURITY WARNING: The advertised project you downloaded has no "sha256"!!! Be VERY careful!!!' - f' We cannot verify this is the actually the advertised project!!!') - else: - sha256B = hashlib.sha256(download_zip_path.open('rb').read()).hexdigest() - if sha256A != sha256B: - logger.error(f'SECURITY VIOLATION: Downloaded project.zip sha256 {sha256B} does not match' - f' the advertised "sha256":{sha256A} in the project.json. Deleting unzipped files!!!') - shutil.rmtree(dest_path) - return 1 - - dest_project_folder = dest_path / project_name - if dest_project_folder.is_dir(): - backup_folder(dest_project_folder) - with zipfile.ZipFile(download_zip_path, 'r') as project_zip: - try: - project_zip.extractall(dest_project_folder) - except Exception as e: - logger.error(f'UnZip exception:{str(e)}') - shutil.rmtree(dest_path) - return 1 - - unzipped_project_json = dest_project_folder / 'project.json' - if not unzipped_project_json.is_file(): - logger.error(f'Project json {unzipped_project_json} is missing.') - return 1 - - if not valid_o3de_project_json(unzipped_project_json): - logger.error(f'Project json {unzipped_project_json} is invalid.') - return 1 - - # remove the sha256 if present in the advertised downloadable project.json - # then compare it to the project.json in the zip, they should now be identical - try: - del downloadable_project_data['sha256'] - except Exception as e: - pass - - sha256A = hashlib.sha256(json.dumps(downloadable_project_data, indent=4).encode('utf8')).hexdigest() - with unzipped_project_json.open('r') as s: - try: - unzipped_project_json_data = json.load(s) - except Exception as e: - logger.error(f'Failed to read Project json {unzipped_project_json}. Unable to confirm this' - f' is the same project that was advertised.') - return 1 - sha256B = hashlib.sha256(json.dumps(unzipped_project_json_data, indent=4).encode('utf8')).hexdigest() - if sha256A != sha256B: - logger.error(f'SECURITY VIOLATION: Downloaded project.json does not match' - f' the advertised project.json. Deleting unzipped files!!!') - shutil.rmtree(dest_path) - return 1 - - return 0 - - -def download_gem(gem_name: str, - dest_path: str or pathlib.Path) -> int: - if not dest_path: - dest_path = get_registered(default_folder='gems') - if not dest_path: - logger.error(f'Destination path not cannot be empty.') - return 1 - - dest_path = pathlib.Path(dest_path).resolve() - dest_path.mkdir(exist_ok=True, parents=True) - - download_path = get_o3de_download_folder() / 'gems' / gem_name - download_path.mkdir(exist_ok=True, parents=True) - download_zip_path = download_path / 'gem.zip' - - downloadable_gem_data = get_downloadable(gem_name=gem_name) - if not downloadable_gem_data: - logger.error(f'Downloadable gem {gem_name} not found.') - return 1 - - origin = downloadable_gem_data['origin'] - url = f'{origin}/gem.zip' - parsed_uri = urllib.parse.urlparse(url) - - if download_zip_path.is_file(): - logger.warn(f'Project already downloaded to {download_zip_path}.') - elif parsed_uri.scheme == 'http' or \ - parsed_uri.scheme == 'https' or \ - parsed_uri.scheme == 'ftp' or \ - parsed_uri.scheme == 'ftps': - with urllib.request.urlopen(url) as s: - with download_zip_path.open('wb') as f: - shutil.copyfileobj(s, f) - else: - origin_file = pathlib.Path(url).resolve() - if not origin_file.is_file(): - return 1 - shutil.copy(origin_file, download_zip_path) - - if not zipfile.is_zipfile(download_zip_path): - logger.error(f"Gem zip {download_zip_path} is invalid.") - download_zip_path.unlink() - return 1 - - # if the gem.json has a sha256 check it against a sha256 of the zip - try: - sha256A = downloadable_gem_data['sha256'] - except Exception as e: - logger.warn(f'SECURITY WARNING: The advertised gem you downloaded has no "sha256"!!! Be VERY careful!!!' - f' We cannot verify this is the actually the advertised gem!!!') - else: - sha256B = hashlib.sha256(download_zip_path.open('rb').read()).hexdigest() - if sha256A != sha256B: - logger.error(f'SECURITY VIOLATION: Downloaded gem.zip sha256 {sha256B} does not match' - f' the advertised "sha256":{sha256A} in the gem.json. Deleting unzipped files!!!') - shutil.rmtree(dest_path) - return 1 - - dest_gem_folder = dest_path / gem_name - if dest_gem_folder.is_dir(): - backup_folder(dest_gem_folder) - with zipfile.ZipFile(download_zip_path, 'r') as gem_zip: - try: - gem_zip.extractall(dest_path) - except Exception as e: - logger.error(f'UnZip exception:{str(e)}') - shutil.rmtree(dest_path) - return 1 - - unzipped_gem_json = dest_gem_folder / 'gem.json' - if not unzipped_gem_json.is_file(): - logger.error(f'Engine json {unzipped_gem_json} is missing.') - return 1 - - if not valid_o3de_engine_json(unzipped_gem_json): - logger.error(f'Engine json {unzipped_gem_json} is invalid.') - return 1 - - # remove the sha256 if present in the advertised downloadable gem.json - # then compare it to the gem.json in the zip, they should now be identical - try: - del downloadable_gem_data['sha256'] - except Exception as e: - pass - - sha256A = hashlib.sha256(json.dumps(downloadable_gem_data, indent=4).encode('utf8')).hexdigest() - with unzipped_gem_json.open('r') as s: - try: - unzipped_gem_json_data = json.load(s) - except Exception as e: - logger.error(f'Failed to read gem json {unzipped_gem_json}. Unable to confirm this' - f' is the same gem that was advertised.') - return 1 - sha256B = hashlib.sha256(json.dumps(unzipped_gem_json_data, indent=4).encode('utf8')).hexdigest() - if sha256A != sha256B: - logger.error(f'SECURITY VIOLATION: Downloaded gem.json does not match' - f' the advertised gem.json. Deleting unzipped files!!!') - shutil.rmtree(dest_path) - return 1 - - return 0 - - -def download_template(template_name: str, - dest_path: str or pathlib.Path) -> int: - if not dest_path: - dest_path = get_registered(default_folder='templates') - if not dest_path: - logger.error(f'Destination path not cannot be empty.') - return 1 - - dest_path = pathlib.Path(dest_path).resolve() - dest_path.mkdir(exist_ok=True, parents=True) - - download_path = get_o3de_download_folder() / 'templates' / template_name - download_path.mkdir(exist_ok=True, parents=True) - download_zip_path = download_path / 'template.zip' - - downloadable_template_data = get_downloadable(template_name=template_name) - if not downloadable_template_data: - logger.error(f'Downloadable template {template_name} not found.') - return 1 - - origin = downloadable_template_data['origin'] - url = f'{origin}/project.zip' - parsed_uri = urllib.parse.urlparse(url) - - result = 0 - - if download_zip_path.is_file(): - logger.warn(f'Project already downloaded to {download_zip_path}.') - elif parsed_uri.scheme == 'http' or \ - parsed_uri.scheme == 'https' or \ - parsed_uri.scheme == 'ftp' or \ - parsed_uri.scheme == 'ftps': - with urllib.request.urlopen(url) as s: - with download_zip_path.open('wb') as f: - shutil.copyfileobj(s, f) - else: - origin_file = pathlib.Path(url).resolve() - if not origin_file.is_file(): - return 1 - shutil.copy(origin_file, download_zip_path) - - if not zipfile.is_zipfile(download_zip_path): - logger.error(f"Template zip {download_zip_path} is invalid.") - download_zip_path.unlink() - return 1 - - # if the template.json has a sha256 check it against a sha256 of the zip - try: - sha256A = downloadable_template_data['sha256'] - except Exception as e: - logger.warn(f'SECURITY WARNING: The advertised template you downloaded has no "sha256"!!! Be VERY careful!!!' - f' We cannot verify this is the actually the advertised template!!!') - else: - sha256B = hashlib.sha256(download_zip_path.open('rb').read()).hexdigest() - if sha256A != sha256B: - logger.error(f'SECURITY VIOLATION: Downloaded template.zip sha256 {sha256B} does not match' - f' the advertised "sha256":{sha256A} in the template.json. Deleting unzipped files!!!') - shutil.rmtree(dest_path) - return 1 - - dest_template_folder = dest_path / template_name - if dest_template_folder.is_dir(): - backup_folder(dest_template_folder) - with zipfile.ZipFile(download_zip_path, 'r') as project_zip: - try: - project_zip.extractall(dest_path) - except Exception as e: - logger.error(f'UnZip exception:{str(e)}') - shutil.rmtree(dest_path) - return 1 - - unzipped_template_json = dest_template_folder / 'template.json' - if not unzipped_template_json.is_file(): - logger.error(f'Template json {unzipped_template_json} is missing.') - return 1 - - if not valid_o3de_engine_json(unzipped_template_json): - logger.error(f'Template json {unzipped_template_json} is invalid.') - return 1 - - # remove the sha256 if present in the advertised downloadable template.json - # then compare it to the template.json in the zip, they should now be identical - try: - del downloadable_template_data['sha256'] - except Exception as e: - pass - - sha256A = hashlib.sha256(json.dumps(downloadable_template_data, indent=4).encode('utf8')).hexdigest() - with unzipped_template_json.open('r') as s: - try: - unzipped_template_json_data = json.load(s) - except Exception as e: - logger.error(f'Failed to read Template json {unzipped_template_json}. Unable to confirm this' - f' is the same template that was advertised.') - return 1 - sha256B = hashlib.sha256(json.dumps(unzipped_template_json_data, indent=4).encode('utf8')).hexdigest() - if sha256A != sha256B: - logger.error(f'SECURITY VIOLATION: Downloaded template.json does not match' - f' the advertised template.json. Deleting unzipped files!!!') - shutil.rmtree(dest_path) - return 1 - - return 0 - - -def download_restricted(restricted_name: str, - dest_path: str or pathlib.Path) -> int: - if not dest_path: - dest_path = get_registered(default_folder='restricted') - if not dest_path: - logger.error(f'Destination path not cannot be empty.') - return 1 - - dest_path = pathlib.Path(dest_path).resolve() - dest_path.mkdir(exist_ok=True, parents=True) - - download_path = get_o3de_download_folder() / 'restricted' / restricted_name - download_path.mkdir(exist_ok=True, parents=True) - download_zip_path = download_path / 'restricted.zip' - - downloadable_restricted_data = get_downloadable(restricted_name=restricted_name) - if not downloadable_restricted_data: - logger.error(f'Downloadable Restricted {restricted_name} not found.') - return 1 - - origin = downloadable_restricted_data['origin'] - url = f'{origin}/restricted.zip' - parsed_uri = urllib.parse.urlparse(url) - - if download_zip_path.is_file(): - logger.warn(f'Restricted already downloaded to {download_zip_path}.') - elif parsed_uri.scheme == 'http' or \ - parsed_uri.scheme == 'https' or \ - parsed_uri.scheme == 'ftp' or \ - parsed_uri.scheme == 'ftps': - with urllib.request.urlopen(url) as s: - with download_zip_path.open('wb') as f: - shutil.copyfileobj(s, f) - else: - origin_file = pathlib.Path(url).resolve() - if not origin_file.is_file(): - return 1 - shutil.copy(origin_file, download_zip_path) - - if not zipfile.is_zipfile(download_zip_path): - logger.error(f"Restricted zip {download_zip_path} is invalid.") - download_zip_path.unlink() - return 1 - - # if the restricted.json has a sha256 check it against a sha256 of the zip - try: - sha256A = downloadable_restricted_data['sha256'] - except Exception as e: - logger.warn(f'SECURITY WARNING: The advertised restricted you downloaded has no "sha256"!!! Be VERY careful!!!' - f' We cannot verify this is the actually the advertised restricted!!!') - else: - sha256B = hashlib.sha256(download_zip_path.open('rb').read()).hexdigest() - if sha256A != sha256B: - logger.error(f'SECURITY VIOLATION: Downloaded restricted.zip sha256 {sha256B} does not match' - f' the advertised "sha256":{sha256A} in the restricted.json. Deleting unzipped files!!!') - shutil.rmtree(dest_path) - return 1 - - dest_restricted_folder = dest_path / restricted_name - if dest_restricted_folder.is_dir(): - backup_folder(dest_restricted_folder) - with zipfile.ZipFile(download_zip_path, 'r') as project_zip: - try: - project_zip.extractall(dest_path) - except Exception as e: - logger.error(f'UnZip exception:{str(e)}') - shutil.rmtree(dest_path) - return 1 - - unzipped_restricted_json = dest_restricted_folder / 'restricted.json' - if not unzipped_restricted_json.is_file(): - logger.error(f'Restricted json {unzipped_restricted_json} is missing.') - return 1 - - if not valid_o3de_engine_json(unzipped_restricted_json): - logger.error(f'Restricted json {unzipped_restricted_json} is invalid.') - return 1 - - # remove the sha256 if present in the advertised downloadable restricted.json - # then compare it to the restricted.json in the zip, they should now be identical - try: - del downloadable_restricted_data['sha256'] - except Exception as e: - pass - - sha256A = hashlib.sha256(json.dumps(downloadable_restricted_data, indent=4).encode('utf8')).hexdigest() - with unzipped_restricted_json.open('r') as s: - try: - unzipped_restricted_json_data = json.load(s) - except Exception as e: - logger.error( - f'Failed to read Restricted json {unzipped_restricted_json}. Unable to confirm this' - f' is the same restricted that was advertised.') - return 1 - sha256B = hashlib.sha256( - json.dumps(unzipped_restricted_json_data, indent=4).encode('utf8')).hexdigest() - if sha256A != sha256B: - logger.error(f'SECURITY VIOLATION: Downloaded restricted.json does not match' - f' the advertised restricted.json. Deleting unzipped files!!!') - shutil.rmtree(dest_path) - return 1 - - return 0 - - -def add_gem_dependency(cmake_file: str or pathlib.Path, - gem_target: str) -> int: - """ - adds a gem dependency to a cmake file - :param cmake_file: path to the cmake file - :param gem_target: name of the cmake target - :return: 0 for success or non 0 failure code - """ - if not os.path.isfile(cmake_file): - logger.error(f'Failed to locate cmake file {cmake_file}') - return 1 - - # on a line by basis, see if there already is Gem::{gem_name} - # find the first occurrence of a gem, copy its formatting and replace - # the gem name with the new one and append it - # if the gem is already present fail - t_data = [] - added = False - with open(cmake_file, 'r') as s: - for line in s: - if f'Gem::{gem_target}' in line: - logger.warning(f'{gem_target} is already a gem dependency.') - return 0 - if not added and r'Gem::' in line: - new_gem = ' ' * line.find(r'Gem::') + f'Gem::{gem_target}\n' - t_data.append(new_gem) - added = True - t_data.append(line) - - # if we didn't add it the set gem dependencies could be empty so - # add a new gem, if empty the correct format is 1 tab=4spaces - if not added: - index = 0 - for line in t_data: - index = index + 1 - if r'set(GEM_DEPENDENCIES' in line: - t_data.insert(index, f' Gem::{gem_target}\n') - added = True - break - - # if we didn't add it then it's not here, add a whole new one - if not added: - t_data.append('\n') - t_data.append('set(GEM_DEPENDENCIES\n') - t_data.append(f' Gem::{gem_target}\n') - t_data.append(')\n') - - # write the cmake - os.unlink(cmake_file) - with open(cmake_file, 'w') as s: - s.writelines(t_data) - - return 0 - - -def get_project_runtime_gem_targets(project_path: str or pathlib.Path, - platform: str = 'Common') -> set: - return get_gem_targets_from_cmake_file(get_dependencies_cmake_file(project_path=project_path, dependency_type='runtime', platform=platform)) - - -def get_project_tool_gem_targets(project_path: str or pathlib.Path, - platform: str = 'Common') -> set: - return get_gem_targets_from_cmake_file(get_dependencies_cmake_file(project_path=project_path, dependency_type='tool', platform=platform)) - - -def get_project_server_gem_targets(project_path: str or pathlib.Path, - platform: str = 'Common') -> set: - return get_gem_targets_from_cmake_file(get_dependencies_cmake_file(project_path=project_path, dependency_type='server', platform=platform)) - - -def get_project_gem_targets(project_path: str or pathlib.Path, - platform: str = 'Common') -> set: - runtime_gems = get_gem_targets_from_cmake_file(get_dependencies_cmake_file(project_path=project_path, dependency_type='runtime', platform=platform)) - tool_gems = get_gem_targets_from_cmake_file(get_dependencies_cmake_file(project_path=project_path, dependency_type='tool', platform=platform)) - server_gems = get_gem_targets_from_cmake_file(get_dependencies_cmake_file(project_path=project_path, dependency_type='server', platform=platform)) - return runtime_gems.union(tool_gems.union(server_gems)) - - -def get_gem_targets_from_cmake_file(cmake_file: str or pathlib.Path) -> set: - """ - Gets a list of declared gem targets dependencies of a cmake file - :param cmake_file: path to the cmake file - :return: set of gem targets found - """ - cmake_file = pathlib.Path(cmake_file).resolve() - - if not cmake_file.is_file(): - logger.error(f'Failed to locate cmake file {cmake_file}') - return set() - - gem_target_set = set() - with cmake_file.open('r') as s: - for line in s: - gem_name = line.split('Gem::') - if len(gem_name) > 1: - # Only take the name as everything leading up to the first '.' if found - # Gem naming conventions will have GemName.Editor, GemName.Server, and GemName - # as different targets of the GemName Gem - gem_target_set.add(gem_name[1].replace('\n', '')) - return gem_target_set - - -def get_project_runtime_gem_names(project_path: str or pathlib.Path, - platform: str = 'Common') -> set: - return get_gem_names_from_cmake_file(get_dependencies_cmake_file(project_path=project_path, dependency_type='runtime', platform=platform)) - - -def get_project_tool_gem_names(project_path: str or pathlib.Path, - platform: str = 'Common') -> set: - return get_gem_names_from_cmake_file(get_dependencies_cmake_file(project_path=project_path, dependency_type='tool', platform=platform)) - - -def get_project_server_gem_names(project_path: str or pathlib.Path, - platform: str = 'Common') -> set: - return get_gem_names_from_cmake_file(get_dependencies_cmake_file(project_path=project_path, dependency_type='server', platform=platform)) - - -def get_project_gem_names(project_path: str or pathlib.Path, - platform: str = 'Common') -> set: - runtime_gem_names = get_gem_names_from_cmake_file(get_dependencies_cmake_file(project_path=project_path, dependency_type='runtime', platform=platform)) - tool_gem_names = get_gem_names_from_cmake_file(get_dependencies_cmake_file(project_path=project_path, dependency_type='tool', platform=platform)) - server_gem_names = get_gem_names_from_cmake_file(get_dependencies_cmake_file(project_path=project_path, dependency_type='server', platform=platform)) - return runtime_gem_names.union(tool_gem_names.union(server_gem_names)) - - -def get_gem_names_from_cmake_file(cmake_file: str or pathlib.Path) -> set: - """ - Gets a list of declared gem dependencies of a cmake file - :param cmake_file: path to the cmake file - :return: set of gems found - """ - cmake_file = pathlib.Path(cmake_file).resolve() - - if not cmake_file.is_file(): - logger.error(f'Failed to locate cmake file {cmake_file}') - return set() - - gem_set = set() - with cmake_file.open('r') as s: - for line in s: - gem_name = line.split('Gem::') - if len(gem_name) > 1: - # Only take the name as everything leading up to the first '.' if found - # Gem naming conventions will have GemName.Editor, GemName.Server, and GemName - # as different targets of the GemName Gem - gem_set.add(gem_name[1].split('.')[0].replace('\n', '')) - return gem_set - - -def get_project_runtime_gem_paths(project_path: str or pathlib.Path, - platform: str = 'Common') -> set: - gem_names = get_project_runtime_gem_names(project_path, platform) - gem_paths = set() - for gem_name in gem_names: - gem_paths.add(get_registered(gem_name=gem_name)) - return gem_paths - - -def get_project_tool_gem_paths(project_path: str or pathlib.Path, - platform: str = 'Common') -> set: - gem_names = get_project_tool_gem_names(project_path, platform) - gem_paths = set() - for gem_name in gem_names: - gem_paths.add(get_registered(gem_name=gem_name)) - return gem_paths - - -def get_project_server_gem_paths(project_path: str or pathlib.Path, - platform: str = 'Common') -> set: - gem_names = get_project_server_gem_names(project_path, platform) - gem_paths = set() - for gem_name in gem_names: - gem_paths.add(get_registered(gem_name=gem_name)) - return gem_paths - - -def get_project_gem_paths(project_path: str or pathlib.Path, - platform: str = 'Common') -> set: - gem_names = get_project_gem_names(project_path, platform) - gem_paths = set() - for gem_name in gem_names: - gem_paths.add(get_registered(gem_name=gem_name)) - return gem_paths - - -def remove_gem_dependency(cmake_file: str or pathlib.Path, - gem_target: str) -> int: - """ - removes a gem dependency from a cmake file - :param cmake_file: path to the cmake file - :param gem_target: cmake target name - :return: 0 for success or non 0 failure code - """ - if not os.path.isfile(cmake_file): - logger.error(f'Failed to locate cmake file {cmake_file}') - return 1 - - # on a line by basis, remove any line with Gem::{gem_name} - t_data = [] - # Remove the gem from the cmake_dependencies file by skipping the gem name entry - removed = False - with open(cmake_file, 'r') as s: - for line in s: - if f'Gem::{gem_target}' in line: - removed = True - else: - t_data.append(line) - - if not removed: - logger.error(f'Failed to remove Gem::{gem_target} from cmake file {cmake_file}') - return 1 - - # write the cmake - os.unlink(cmake_file) - with open(cmake_file, 'w') as s: - s.writelines(t_data) - - return 0 - - -def get_project_templates(): # temporary until we have a better way to do this... maybe template_type element - project_templates = [] - for template in get_all_templates(): - if 'Project' in template: - project_templates.append(template) - return project_templates - - -def get_gem_templates(): # temporary until we have a better way to do this... maybe template_type element - gem_templates = [] - for template in get_all_templates(): - if 'Gem' in template: - gem_templates.append(template) - return gem_templates - - -def get_generic_templates(): # temporary until we have a better way to do this... maybe template_type element - generic_templates = [] - for template in get_all_templates(): - if 'Project' not in template and 'Gem' not in template: - generic_templates.append(template) - return generic_templates - - -def get_dependencies_cmake_file(project_name: str = None, - project_path: str or pathlib.Path = None, - dependency_type: str = 'runtime', - platform: str = 'Common') -> str or None: - """ - get the standard cmake file name for a particular type of dependency - :param gem_name: name of the gem, resolves gem_path - :param gem_path: path of the gem - :return: list of gem targets - """ - if not project_name and not project_path: - logger.error(f'Must supply either a Project Name or Project Path.') - return None - - if project_name and not project_path: - project_path = get_registered(project_name=project_name) - - project_path = pathlib.Path(project_path).resolve() - - if platform == 'Common': - dependencies_file = f'{dependency_type}_dependencies.cmake' - dependencies_file_path = project_path / 'Gem/Code' / dependencies_file - if dependencies_file_path.is_file(): - return dependencies_file_path - return project_path / 'Code' / dependencies_file - else: - dependencies_file = f'{platform.lower()}_{dependency_type}_dependencies.cmake' - dependencies_file_path = project_path / 'Gem/Code/Platform' / platform / dependencies_file - if dependencies_file_path.is_file(): - return dependencies_file_path - return project_path / 'Code/Platform' / platform / dependencies_file - - -def get_all_gem_targets() -> list: - modules = [] - for gem_path in get_all_gems(): - this_gems_targets = get_gem_targets(gem_path=gem_path) - modules.extend(this_gems_targets) - return modules - - -def get_gem_targets(gem_name: str = None, - gem_path: str or pathlib.Path = None) -> list: - """ - Finds gem targets in a gem - :param gem_name: name of the gem, resolves gem_path - :param gem_path: path of the gem - :return: list of gem targets - """ - if not gem_name and not gem_path: - return [] - - if gem_name and not gem_path: - gem_path = get_registered(gem_name=gem_name) - - if not gem_path: - return [] - - gem_path = pathlib.Path(gem_path).resolve() - gem_json = gem_path / 'gem.json' - if not valid_o3de_gem_json(gem_json): - return [] - - module_identifiers = [ - 'MODULE', - 'GEM_MODULE', - '${PAL_TRAIT_MONOLITHIC_DRIVEN_MODULE_TYPE}' - ] - modules = [] - for root, dirs, files in os.walk(gem_path): - for file in files: - if file == 'CMakeLists.txt': - with open(os.path.join(root, file), 'r') as s: - for line in s: - trimmed = line.lstrip() - if trimmed.startswith('NAME '): - trimmed = trimmed.rstrip(' \n') - split_trimmed = trimmed.split(' ') - if len(split_trimmed) == 3 and split_trimmed[2] in module_identifiers: - modules.append(split_trimmed[1]) - return modules - - -def add_external_subdirectory(external_subdir: str or pathlib.Path, - engine_path: str or pathlib.Path = None, - supress_errors: bool = False) -> int: - """ - add external subdirectory to a cmake - :param external_subdir: external subdirectory to add to cmake - :param engine_path: optional engine path, defaults to this engine - :param supress_errors: optional silence errors - :return: 0 for success or non 0 failure code - """ - external_subdir = pathlib.Path(external_subdir).resolve() - if not external_subdir.is_dir(): - if not supress_errors: - logger.error(f'Add External Subdirectory Failed: {external_subdir} does not exist.') - return 1 - - external_subdir_cmake = external_subdir / 'CMakeLists.txt' - if not external_subdir_cmake.is_file(): - if not supress_errors: - logger.error(f'Add External Subdirectory Failed: {external_subdir} does not contain a CMakeLists.txt.') - return 1 - - json_data = load_o3de_manifest() - engine_object = find_engine_data(json_data, engine_path) - if not engine_object: - if not supress_errors: - logger.error(f'Add External Subdirectory Failed: {engine_path} not registered.') - return 1 - - while external_subdir.as_posix() in engine_object['external_subdirectories']: - engine_object['external_subdirectories'].remove(external_subdir.as_posix()) - - def parse_cmake_file(cmake: str or pathlib.Path, - files: set()): - cmake_path = pathlib.Path(cmake).resolve() - cmake_file = cmake_path - if cmake_path.is_dir(): - files.add(cmake_path) - cmake_file = cmake_path / 'CMakeLists.txt' - elif cmake_path.is_file(): - cmake_path = cmake_path.parent - else: - return - - with cmake_file.open('r') as s: - lines = s.readlines() - for line in lines: - line = line.strip() - start = line.find('include(') - if start == 0: - end = line.find(')', start) - if end > start + len('include('): - try: - include_cmake_file = pathlib.Path(engine_path / line[start + len('include('): end]).resolve() - except Exception as e: - pass - else: - parse_cmake_file(include_cmake_file, files) - else: - start = line.find('add_subdirectory(') - if start == 0: - end = line.find(')', start) - if end > start + len('add_subdirectory('): - try: - include_cmake_file = pathlib.Path( - cmake_path / line[start + len('add_subdirectory('): end]).resolve() - except Exception as e: - pass - else: - parse_cmake_file(include_cmake_file, files) - - cmake_files = set() - parse_cmake_file(engine_path, cmake_files) - for external in engine_object["external_subdirectories"]: - parse_cmake_file(external, cmake_files) - - if external_subdir in cmake_files: - save_o3de_manifest(json_data) - if not supress_errors: - logger.error(f'External subdirectory {external_subdir.as_posix()} already included by add_subdirectory().') - return 1 - - engine_object['external_subdirectories'].insert(0, external_subdir.as_posix()) - engine_object['external_subdirectories'] = sorted(engine_object['external_subdirectories']) - - save_o3de_manifest(json_data) - - return 0 - - -def remove_external_subdirectory(external_subdir: str or pathlib.Path, - engine_path: str or pathlib.Path = None) -> int: - """ - remove external subdirectory from cmake - :param external_subdir: external subdirectory to add to cmake - :param engine_path: optional engine path, defaults to this engine - :return: 0 for success or non 0 failure code - """ - json_data = load_o3de_manifest() - engine_object = find_engine_data(json_data, engine_path) - if not engine_object: - logger.error(f'Remove External Subdirectory Failed: {engine_path} not registered.') - return 1 - - external_subdir = pathlib.Path(external_subdir).resolve() - while external_subdir.as_posix() in engine_object['external_subdirectories']: - engine_object['external_subdirectories'].remove(external_subdir.as_posix()) - - save_o3de_manifest(json_data) - - return 0 - - -def add_gem_to_cmake(gem_name: str = None, - gem_path: str or pathlib.Path = None, - engine_name: str = None, - engine_path: str or pathlib.Path = None, - supress_errors: bool = False) -> int: - """ - add a gem to a cmake as an external subdirectory for an engine - :param gem_name: name of the gem to add to cmake - :param gem_path: the path of the gem to add to cmake - :param engine_name: name of the engine to add to cmake - :param engine_path: the path of the engine to add external subdirectory to, default to this engine - :param supress_errors: optional silence errors - :return: 0 for success or non 0 failure code - """ - if not gem_name and not gem_path: - if not supress_errors: - logger.error('Must specify either a Gem name or Gem Path.') - return 1 - - if gem_name and not gem_path: - gem_path = get_registered(gem_name=gem_name) - - if not gem_path: - if not supress_errors: - logger.error(f'Gem Path {gem_path} has not been registered.') - return 1 - - gem_path = pathlib.Path(gem_path).resolve() - gem_json = gem_path / 'gem.json' - if not gem_json.is_file(): - if not supress_errors: - logger.error(f'Gem json {gem_json} is not present.') - return 1 - if not valid_o3de_gem_json(gem_json): - if not supress_errors: - logger.error(f'Gem json {gem_json} is not valid.') - return 1 - - if not engine_name and not engine_path: - engine_path = get_this_engine_path() - - if engine_name and not engine_path: - engine_path = get_registered(engine_name=engine_name) - - if not engine_path: - if not supress_errors: - logger.error(f'Engine Path {engine_path} has not been registered.') - return 1 - - engine_json = engine_path / 'engine.json' - if not engine_json.is_file(): - if not supress_errors: - logger.error(f'Engine json {engine_json} is not present.') - return 1 - if not valid_o3de_engine_json(engine_json): - if not supress_errors: - logger.error(f'Engine json {engine_json} is not valid.') - return 1 - - return add_external_subdirectory(external_subdir=gem_path, engine_path=engine_path, supress_errors=supress_errors) - - -def remove_gem_from_cmake(gem_name: str = None, - gem_path: str or pathlib.Path = None, - engine_name: str = None, - engine_path: str or pathlib.Path = None) -> int: - """ - remove a gem to cmake as an external subdirectory - :param gem_name: name of the gem to remove from cmake - :param gem_path: the path of the gem to add to cmake - :param engine_name: optional name of the engine to remove from cmake - :param engine_path: the path of the engine to remove external subdirectory from, defaults to this engine - :return: 0 for success or non 0 failure code - """ - if not gem_name and not gem_path: - logger.error('Must specify either a Gem name or Gem Path.') - return 1 - - if gem_name and not gem_path: - gem_path = get_registered(gem_name=gem_name) - - if not gem_path: - logger.error(f'Gem Path {gem_path} has not been registered.') - return 1 - - if not engine_name and not engine_path: - engine_path = get_this_engine_path() - - if engine_name and not engine_path: - engine_path = get_registered(engine_name=engine_name) - - if not engine_path: - logger.error(f'Engine Path {engine_path} is not registered.') - return 1 - - return remove_external_subdirectory(external_subdir=gem_path, engine_path=engine_path) - - -def add_gem_to_project(gem_name: str = None, - gem_path: str or pathlib.Path = None, - gem_target: str = None, - project_name: str = None, - project_path: str or pathlib.Path = None, - dependencies_file: str or pathlib.Path = None, - runtime_dependency: bool = False, - tool_dependency: bool = False, - server_dependency: bool = False, - platforms: str = 'Common', - add_to_cmake: bool = True) -> int: - """ - add a gem to a project - :param gem_name: name of the gem to add - :param gem_path: path to the gem to add - :param gem_target: the name of the cmake gem module - :param project_name: name of to the project to add the gem to - :param project_path: path to the project to add the gem to - :param dependencies_file: if this dependency goes/is in a specific file - :param runtime_dependency: bool to specify this is a runtime gem for the game - :param tool_dependency: bool to specify this is a tool gem for the editor - :param server_dependency: bool to specify this is a server gem for the server - :param platforms: str to specify common or which specific platforms - :param add_to_cmake: bool to specify that this gem should be added to cmake - :return: 0 for success or non 0 failure code - """ - # we need either a project name or path - if not project_name and not project_path: - logger.error(f'Must either specify a Project path or Project Name.') - return 1 - - # if project name resolve it into a path - if project_name and not project_path: - project_path = get_registered(project_name=project_name) - project_path = pathlib.Path(project_path).resolve() - if not project_path.is_dir(): - logger.error(f'Project path {project_path} is not a folder.') - return 1 - - # get the engine name this project is associated with - # and resolve that engines path - project_json = project_path / 'project.json' - if not valid_o3de_project_json(project_json): - logger.error(f'Project json {project_json} is not valid.') - return 1 - with project_json.open('r') as s: - try: - project_json_data = json.load(s) - except Exception as e: - logger.error(f'Error loading Project json {project_json}: {str(e)}') - return 1 - else: - try: - engine_name = project_json_data['engine'] - except Exception as e: - logger.error(f'Project json {project_json} "engine" not found: {str(e)}') - return 1 - else: - engine_path = get_registered(engine_name=engine_name) - if not engine_path: - logger.error(f'Engine {engine_name} is not registered.') - return 1 - - # we need either a gem name or path - if not gem_name and not gem_path: - logger.error(f'Must either specify a Gem path or Gem Name.') - return 1 - - # if gem name resolve it into a path - if gem_name and not gem_path: - gem_path = get_registered(gem_name=gem_name) - gem_path = pathlib.Path(gem_path).resolve() - # make sure this gem already exists if we're adding. We can always remove a gem. - if not gem_path.is_dir(): - logger.error(f'Gem Path {gem_path} does not exist.') - return 1 - - # if add to cmake, make sure the gem.json exists and valid before we proceed - if add_to_cmake: - gem_json = gem_path / 'gem.json' - if not gem_json.is_file(): - logger.error(f'Gem json {gem_json} is not present.') - return 1 - if not valid_o3de_gem_json(gem_json): - logger.error(f'Gem json {gem_json} is not valid.') - return 1 - - # find all available modules in this gem_path - modules = get_gem_targets(gem_path=gem_path) - if len(modules) == 0: - logger.error(f'No gem modules found under {gem_path}.') - return 1 - - # if the gem has no modules and the user has specified a target fail - if gem_target and not modules: - logger.error(f'Gem has no targets, but gem target {gem_target} was specified.') - return 1 - - # if the gem target is not in the modules - if gem_target not in modules: - logger.error(f'Gem target not in gem modules: {modules}') - return 1 - - if gem_target: - # if the user has not specified either we will assume they meant the most common which is runtime - if not runtime_dependency and not tool_dependency and not server_dependency and not dependencies_file: - logger.warning("Dependency type not specified: Assuming '--runtime-dependency'") - runtime_dependency = True - - ret_val = 0 - - # if the user has specified the dependencies file then ignore the runtime_dependency and tool_dependency flags - if dependencies_file: - dependencies_file = pathlib.Path(dependencies_file).resolve() - # make sure this is a project has a dependencies_file - if not dependencies_file.is_file(): - logger.error(f'Dependencies file {dependencies_file} is not present.') - return 1 - # add the dependency - ret_val = add_gem_dependency(dependencies_file, gem_target) - - else: - if ',' in platforms: - platforms = platforms.split(',') - else: - platforms = [platforms] - for platform in platforms: - if runtime_dependency: - # make sure this is a project has a runtime_dependencies.cmake file - project_runtime_dependencies_file = pathlib.Path( - get_dependencies_cmake_file(project_path=project_path, dependency_type='runtime', platform=platform)).resolve() - if not project_runtime_dependencies_file.is_file(): - logger.error(f'Runtime dependencies file {project_runtime_dependencies_file} is not present.') - return 1 - # add the dependency - ret_val = add_gem_dependency(project_runtime_dependencies_file, gem_target) - - if (ret_val == 0) and tool_dependency: - # make sure this is a project has a tool_dependencies.cmake file - project_tool_dependencies_file = pathlib.Path( - get_dependencies_cmake_file(project_path=project_path, dependency_type='tool', platform=platform)).resolve() - if not project_tool_dependencies_file.is_file(): - logger.error(f'Tool dependencies file {project_tool_dependencies_file} is not present.') - return 1 - # add the dependency - ret_val = add_gem_dependency(project_tool_dependencies_file, gem_target) - - if (ret_val == 0) and server_dependency: - # make sure this is a project has a tool_dependencies.cmake file - project_server_dependencies_file = pathlib.Path( - get_dependencies_cmake_file(project_path=project_path, dependency_type='server', platform=platform)).resolve() - if not project_server_dependencies_file.is_file(): - logger.error(f'Server dependencies file {project_server_dependencies_file} is not present.') - return 1 - # add the dependency - ret_val = add_gem_dependency(project_server_dependencies_file, gem_target) - - if not ret_val and add_to_cmake: - ret_val = add_gem_to_cmake(gem_path=gem_path, engine_path=engine_path) - - return ret_val - - -def remove_gem_from_project(gem_name: str = None, - gem_path: str or pathlib.Path = None, - gem_target: str = None, - project_name: str = None, - project_path: str or pathlib.Path = None, - dependencies_file: str or pathlib.Path = None, - runtime_dependency: bool = False, - tool_dependency: bool = False, - server_dependency: bool = False, - platforms: str = 'Common', - remove_from_cmake: bool = False) -> int: - """ - remove a gem from a project - :param gem_name: name of the gem to add - :param gem_path: path to the gem to add - :param gem_target: the name of teh cmake gem module - :param project_name: name of the project to add the gem to - :param project_path: path to the project to add the gem to - :param dependencies_file: if this dependency goes/is in a specific file - :param runtime_dependency: bool to specify this is a runtime gem for the game - :param tool_dependency: bool to specify this is a tool gem for the editor - :param server_dependency: bool to specify this is a server gem for the server - :param platforms: str to specify common or which specific platforms - :param remove_from_cmake: bool to specify that this gem should be removed from cmake - :return: 0 for success or non 0 failure code - """ - - # we need either a project name or path - if not project_name and not project_path: - logger.error(f'Must either specify a Project path or Project Name.') - return 1 - - # if project name resolve it into a path - if project_name and not project_path: - project_path = get_registered(project_name=project_name) - project_path = pathlib.Path(project_path).resolve() - if not project_path.is_dir(): - logger.error(f'Project path {project_path} is not a folder.') - return 1 - - # We need either a gem name or path - if not gem_name and not gem_path: - logger.error(f'Must either specify a Gem path or Gem Name.') - return 1 - - # if gem name resolve it into a path - if gem_name and not gem_path: - gem_path = get_registered(gem_name=gem_name) - gem_path = pathlib.Path(gem_path).resolve() - # make sure this gem already exists if we're adding. We can always remove a gem. - if not gem_path.is_dir(): - logger.error(f'Gem Path {gem_path} does not exist.') - return 1 - - # find all available modules in this gem_path - modules = get_gem_targets(gem_path=gem_path) - if len(modules) == 0: - logger.error(f'No gem modules found.') - return 1 - - # if the user has not set a specific gem target remove all of them - - # if gem target not specified, see if there is only 1 module - if not gem_target: - if len(modules) == 1: - gem_target = modules[0] - else: - logger.error(f'Gem target not specified: {modules}') - return 1 - elif gem_target not in modules: - logger.error(f'Gem target not in gem modules: {modules}') - return 1 - - # if the user has not specified either we will assume they meant the most common which is runtime - if not runtime_dependency and not tool_dependency and not server_dependency and not dependencies_file: - logger.warning("Dependency type not specified: Assuming '--runtime-dependency'") - runtime_dependency = True - - # when removing we will try to do as much as possible even with failures so ret_val will be the last error code - ret_val = 0 - - # if the user has specified the dependencies file then ignore the runtime_dependency and tool_dependency flags - if dependencies_file: - dependencies_file = pathlib.Path(dependencies_file).resolve() - # make sure this is a project has a dependencies_file - if not dependencies_file.is_file(): - logger.error(f'Dependencies file {dependencies_file} is not present.') - return 1 - # remove the dependency - error_code = remove_gem_dependency(dependencies_file, gem_target) - if error_code: - ret_val = error_code - else: - if ',' in platforms: - platforms = platforms.split(',') - else: - platforms = [platforms] - for platform in platforms: - if runtime_dependency: - # make sure this is a project has a runtime_dependencies.cmake file - project_runtime_dependencies_file = pathlib.Path( - get_dependencies_cmake_file(project_path=project_path, dependency_type='runtime', platform=platform)).resolve() - if not project_runtime_dependencies_file.is_file(): - logger.error(f'Runtime dependencies file {project_runtime_dependencies_file} is not present.') - else: - # remove the dependency - error_code = remove_gem_dependency(project_runtime_dependencies_file, gem_target) - if error_code: - ret_val = error_code - - if tool_dependency: - # make sure this is a project has a tool_dependencies.cmake file - project_tool_dependencies_file = pathlib.Path( - get_dependencies_cmake_file(project_path=project_path, dependency_type='tool', platform=platform)).resolve() - if not project_tool_dependencies_file.is_file(): - logger.error(f'Tool dependencies file {project_tool_dependencies_file} is not present.') - else: - # remove the dependency - error_code = remove_gem_dependency(project_tool_dependencies_file, gem_target) - if error_code: - ret_val = error_code - - if server_dependency: - # make sure this is a project has a tool_dependencies.cmake file - project_server_dependencies_file = pathlib.Path( - get_dependencies_cmake_file(project_path=project_path, dependency_type='server', platform=platform)).resolve() - if not project_server_dependencies_file.is_file(): - logger.error(f'Server dependencies file {project_server_dependencies_file} is not present.') - else: - # remove the dependency - error_code = remove_gem_dependency(project_server_dependencies_file, gem_target) - if error_code: - ret_val = error_code - - if remove_from_cmake: - error_code = remove_gem_from_cmake(gem_path=gem_path) - if error_code: - ret_val = error_code - - return ret_val - - -def sha256(file_path: str or pathlib.Path, - json_path: str or pathlib.Path = None) -> int: - if not file_path: - logger.error(f'File path cannot be empty.') - return 1 - file_path = pathlib.Path(file_path).resolve() - if not file_path.is_file(): - logger.error(f'File path {file_path} does not exist.') - return 1 - - if json_path: - json_path = pathlib.Path(json_path).resolve() - if not json_path.is_file(): - logger.error(f'Json path {json_path} does not exist.') - return 1 - - sha256 = hashlib.sha256(file_path.open('rb').read()).hexdigest() - - if json_path: - with json_path.open('r') as s: - try: - json_data = json.load(s) - except Exception as e: - logger.error(f'Failed to read Json path {json_path}: {str(e)}') - return 1 - json_data.update({"sha256": sha256}) - backup_file(json_path) - with json_path.open('w') as s: - try: - s.write(json.dumps(json_data, indent=4)) - except Exception as e: - logger.error(f'Failed to write Json path {json_path}: {str(e)}') - return 1 - else: - print(sha256) - return 0 - - -def _run_get_registered(args: argparse) -> str or pathlib.Path: - if args.override_home_folder: - global override_home_folder - override_home_folder = args.override_home_folder - - return get_registered(args.engine_name, - args.project_name, - args.gem_name, - args.template_name, - args.default_folder, - args.repo_name, - args.restricted_name) - - -def _run_register_show(args: argparse) -> int: - if args.override_home_folder: - global override_home_folder - override_home_folder = args.override_home_folder - - if args.this_engine: - print_this_engine(args.verbose) - return 0 - - elif args.engines: - print_engines(args.verbose) - return 0 - elif args.projects: - print_projects(args.verbose) - return 0 - elif args.gems: - print_gems(args.verbose) - return 0 - elif args.templates: - print_templates(args.verbose) - return 0 - elif args.repos: - register_show_repos(args.verbose) - return 0 - elif args.restricted: - print_restricted(args.verbose) - return 0 - - elif args.engine_projects: - print_engine_projects(args.verbose) - return 0 - elif args.engine_gems: - print_engine_gems(args.verbose) - return 0 - elif args.engine_templates: - print_engine_templates(args.verbose) - return 0 - elif args.engine_restricted: - print_engine_restricted(args.verbose) - return 0 - elif args.external_subdirectories: - print_external_subdirectories(args.verbose) - return 0 - - elif args.all_projects: - print_all_projects(args.verbose) - return 0 - elif args.all_gems: - print_all_gems(args.verbose) - return 0 - elif args.all_templates: - print_all_templates(args.verbose) - return 0 - elif args.all_restricted: - print_all_restricted(args.verbose) - return 0 - - elif args.downloadables: - print_downloadables(args.verbose) - return 0 - if args.downloadable_engines: - print_downloadable_engines(args.verbose) - return 0 - elif args.downloadable_projects: - print_downloadable_projects(args.verbose) - return 0 - elif args.downloadable_gems: - print_downloadable_gems(args.verbose) - return 0 - elif args.downloadable_templates: - print_downloadable_templates(args.verbose) - return 0 - else: - register_show(args.verbose) - return 0 - - -def _run_download(args: argparse) -> int: - if args.override_home_folder: - global override_home_folder - override_home_folder = args.override_home_folder - - if args.engine_name: - return download_engine(args.engine_name, - args.dest_path) - elif args.project_name: - return download_project(args.project_name, - args.dest_path) - elif args.gem_nanme: - return download_gem(args.gem_name, - args.dest_path) - elif args.template_name: - return download_template(args.template_name, - args.dest_path) - - -def _run_register(args: argparse) -> int: - if args.override_home_folder: - global override_home_folder - override_home_folder = args.override_home_folder - - if args.update: - remove_invalid_o3de_objects() - return refresh_repos() - elif args.this_engine: - ret_val = register(engine_path=get_this_engine_path()) - error_code = register_shipped_engine_o3de_objects() - if error_code: - ret_val = error_code - return ret_val - elif args.all_engines_path: - return register_all_engines_in_folder(args.all_engines_path, args.remove) - elif args.all_projects_path: - return register_all_projects_in_folder(args.all_projects_path, args.remove) - elif args.all_gems_path: - return register_all_gems_in_folder(args.all_gems_path, args.remove) - elif args.all_templates_path: - return register_all_templates_in_folder(args.all_templates_path, args.remove) - elif args.all_restricted_path: - return register_all_restricted_in_folder(args.all_restricted_path, args.remove) - elif args.all_repo_uri: - return register_all_repos_in_folder(args.all_restricted_path, args.remove) - else: - return register(engine_path=args.engine_path, - project_path=args.project_path, - gem_path=args.gem_path, - template_path=args.template_path, - restricted_path=args.restricted_path, - repo_uri=args.repo_uri, - default_engines_folder=args.default_engines_folder, - default_projects_folder=args.default_projects_folder, - default_gems_folder=args.default_gems_folder, - default_templates_folder=args.default_templates_folder, - default_restricted_folder=args.default_restricted_folder, - remove=args.remove) - - -def _run_add_external_subdirectory(args: argparse) -> int: - if args.override_home_folder: - global override_home_folder - override_home_folder = args.override_home_folder - - return add_external_subdirectory(args.external_subdirectory) - - -def _run_remove_external_subdirectory(args: argparse) -> int: - if args.override_home_folder: - global override_home_folder - override_home_folder = args.override_home_folder - - return remove_external_subdirectory(args.external_subdirectory) - - -def _run_add_gem_to_cmake(args: argparse) -> int: - if args.override_home_folder: - global override_home_folder - override_home_folder = args.override_home_folder - - return add_gem_to_cmake(gem_name=args.gem_name, gem_path=args.gem_path) - - -def _run_remove_gem_from_cmake(args: argparse) -> int: - if args.override_home_folder: - global override_home_folder - override_home_folder = args.override_home_folder - - return remove_gem_from_cmake(args.gem_name, args.gem_path) - - -def _run_add_gem_to_project(args: argparse) -> int: - if args.override_home_folder: - global override_home_folder - override_home_folder = args.override_home_folder - - return add_gem_to_project(args.gem_name, - args.gem_path, - args.gem_target, - args.project_name, - args.project_path, - args.dependencies_file, - args.runtime_dependency, - args.tool_dependency, - args.server_dependency, - args.platforms, - args.add_to_cmake) - - -def _run_remove_gem_from_project(args: argparse) -> int: - if args.override_home_folder: - global override_home_folder - override_home_folder = args.override_home_folder - - return remove_gem_from_project(args.gem_name, - args.gem_path, - args.gem_target, - args.project_path, - args.project_name, - args.dependencies_file, - args.runtime_dependency, - args.tool_dependency, - args.server_dependency, - args.platforms, - args.remove_from_cmake) - - -def _run_sha256(args: argparse) -> int: - return sha256(args.file_path, - args.json_path) - - -def add_args(parser, subparsers) -> None: - """ - add_args is called to add expected parser arguments and subparsers arguments to each command such that it can be - invoked locally or added by a central python file. - Ex. Directly run from this file alone with: python register.py register --gem-path "C:/TestGem" - OR - o3de.py can downloadable commands by importing engine_template, - call add_args and execute: python o3de.py register --gem-path "C:/TestGem" - :param parser: the caller instantiates a parser and passes it in here - :param subparsers: the caller instantiates subparsers and passes it in here - """ - # register - register_subparser = subparsers.add_parser('register') - group = register_subparser.add_mutually_exclusive_group(required=True) - group.add_argument('--this-engine', action='store_true', required=False, - default=False, - help='Registers the engine this script is running from.') - group.add_argument('-ep', '--engine-path', type=str, required=False, - help='Engine path to register/remove.') - group.add_argument('-pp', '--project-path', type=str, required=False, - help='Project path to register/remove.') - group.add_argument('-gp', '--gem-path', type=str, required=False, - help='Gem path to register/remove.') - group.add_argument('-tp', '--template-path', type=str, required=False, - help='Template path to register/remove.') - group.add_argument('-rp', '--restricted-path', type=str, required=False, - help='A restricted folder to register/remove.') - group.add_argument('-ru', '--repo-uri', type=str, required=False, - help='A repo uri to register/remove.') - group.add_argument('-aep', '--all-engines-path', type=str, required=False, - help='All engines under this folder to register/remove.') - group.add_argument('-app', '--all-projects-path', type=str, required=False, - help='All projects under this folder to register/remove.') - group.add_argument('-agp', '--all-gems-path', type=str, required=False, - help='All gems under this folder to register/remove.') - group.add_argument('-atp', '--all-templates-path', type=str, required=False, - help='All templates under this folder to register/remove.') - group.add_argument('-arp', '--all-restricted-path', type=str, required=False, - help='All templates under this folder to register/remove.') - group.add_argument('-aru', '--all-repo-uri', type=str, required=False, - help='All repos under this folder to register/remove.') - group.add_argument('-def', '--default-engines-folder', type=str, required=False, - help='The default engines folder to register/remove.') - group.add_argument('-dpf', '--default-projects-folder', type=str, required=False, - help='The default projects folder to register/remove.') - group.add_argument('-dgf', '--default-gems-folder', type=str, required=False, - help='The default gems folder to register/remove.') - group.add_argument('-dtf', '--default-templates-folder', type=str, required=False, - help='The default templates folder to register/remove.') - group.add_argument('-drf', '--default-restricted-folder', type=str, required=False, - help='The default restricted folder to register/remove.') - group.add_argument('-u', '--update', action='store_true', required=False, - default=False, - help='Refresh the repo cache.') - - register_subparser.add_argument('-ohf', '--override-home-folder', type=str, required=False, - help='By default the home folder is the user folder, override it to this folder.') - - register_subparser.add_argument('-r', '--remove', action='store_true', required=False, - default=False, - help='Remove entry.') - register_subparser.set_defaults(func=_run_register) - - # show - register_show_subparser = subparsers.add_parser('register-show') - group = register_show_subparser.add_mutually_exclusive_group(required=False) - group.add_argument('-te', '--this-engine', action='store_true', required=False, - default=False, - help='Just the local engines.') - - group.add_argument('-e', '--engines', action='store_true', required=False, - default=False, - help='Just the local engines.') - group.add_argument('-p', '--projects', action='store_true', required=False, - default=False, - help='Just the local projects.') - group.add_argument('-g', '--gems', action='store_true', required=False, - default=False, - help='Just the local gems.') - group.add_argument('-t', '--templates', action='store_true', required=False, - default=False, - help='Just the local templates.') - group.add_argument('-r', '--repos', action='store_true', required=False, - default=False, - help='Just the local repos. Ignores repos.') - group.add_argument('-rs', '--restricted', action='store_true', required=False, - default=False, - help='The local restricted folders.') - - group.add_argument('-ep', '--engine-projects', action='store_true', required=False, - default=False, - help='Just the local projects. Ignores repos.') - group.add_argument('-eg', '--engine-gems', action='store_true', required=False, - default=False, - help='Just the local gems. Ignores repos') - group.add_argument('-et', '--engine-templates', action='store_true', required=False, - default=False, - help='Just the local templates. Ignores repos.') - group.add_argument('-ers', '--engine-restricted', action='store_true', required=False, - default=False, - help='The restricted folders.') - group.add_argument('-x', '--external-subdirectories', action='store_true', required=False, - default=False, - help='The external subdirectories.') - - group.add_argument('-ap', '--all-projects', action='store_true', required=False, - default=False, - help='Just the local projects. Ignores repos.') - group.add_argument('-ag', '--all-gems', action='store_true', required=False, - default=False, - help='Just the local gems. Ignores repos') - group.add_argument('-at', '--all-templates', action='store_true', required=False, - default=False, - help='Just the local templates. Ignores repos.') - group.add_argument('-ars', '--all-restricted', action='store_true', required=False, - default=False, - help='The restricted folders.') - - group.add_argument('-d', '--downloadables', action='store_true', required=False, - default=False, - help='Combine all repos into a single list of resources.') - group.add_argument('-de', '--downloadable-engines', action='store_true', required=False, - default=False, - help='Combine all repos engines into a single list of resources.') - group.add_argument('-dp', '--downloadable-projects', action='store_true', required=False, - default=False, - help='Combine all repos projects into a single list of resources.') - group.add_argument('-dg', '--downloadable-gems', action='store_true', required=False, - default=False, - help='Combine all repos gems into a single list of resources.') - group.add_argument('-dt', '--downloadable-templates', action='store_true', required=False, - default=False, - help='Combine all repos templates into a single list of resources.') - - register_show_subparser.add_argument('-v', '--verbose', action='count', required=False, - default=0, - help='How verbose do you want the output to be.') - - register_show_subparser.add_argument('-ohf', '--override-home-folder', type=str, required=False, - help='By default the home folder is the user folder, override it to this folder.') - - register_show_subparser.set_defaults(func=_run_register_show) - - # get-registered - get_registered_subparser = subparsers.add_parser('get-registered') - group = get_registered_subparser.add_mutually_exclusive_group(required=True) - group.add_argument('-en', '--engine-name', type=str, required=False, - help='Engine name.') - group.add_argument('-pn', '--project-name', type=str, required=False, - help='Project name.') - group.add_argument('-gn', '--gem-name', type=str, required=False, - help='Gem name.') - group.add_argument('-tn', '--template-name', type=str, required=False, - help='Template name.') - group.add_argument('-df', '--default-folder', type=str, required=False, - choices=['engines', 'projects', 'gems', 'templates', 'restricted'], - help='The default folders for o3de.') - group.add_argument('-rn', '--repo-name', type=str, required=False, - help='Repo name.') - group.add_argument('-rsn', '--restricted-name', type=str, required=False, - help='Restricted name.') - - get_registered_subparser.add_argument('-ohf', '--override-home-folder', type=str, required=False, - help='By default the home folder is the user folder, override it to this folder.') - - get_registered_subparser.set_defaults(func=_run_get_registered) - - # download - download_subparser = subparsers.add_parser('download') - group = download_subparser.add_mutually_exclusive_group(required=True) - group.add_argument('-e', '--engine-name', type=str, required=False, - help='Downloadable engine name.') - group.add_argument('-p', '--project-name', type=str, required=False, - help='Downloadable project name.') - group.add_argument('-g', '--gem-name', type=str, required=False, - help='Downloadable gem name.') - group.add_argument('-t', '--template-name', type=str, required=False, - help='Downloadable template name.') - download_subparser.add_argument('-dp', '--dest-path', type=str, required=False, - default=None, - help='Optional destination folder to download into.' - ' i.e. download --project-name "StarterGame" --dest-path "C:/projects"' - ' will result in C:/projects/StarterGame' - ' If blank will download to default object type folder') - - download_subparser.add_argument('-ohf', '--override-home-folder', type=str, required=False, - help='By default the home folder is the user folder, override it to this folder.') - - download_subparser.set_defaults(func=_run_download) - - # add external subdirectories - add_external_subdirectory_subparser = subparsers.add_parser('add-external-subdirectory') - add_external_subdirectory_subparser.add_argument('external_subdirectory', metavar='external_subdirectory', type=str, - help='add an external subdirectory to cmake') - - add_external_subdirectory_subparser.add_argument('-ohf', '--override-home-folder', type=str, required=False, - help='By default the home folder is the user folder, override it to this folder.') - - add_external_subdirectory_subparser.set_defaults(func=_run_add_external_subdirectory) - - # remove external subdirectories - remove_external_subdirectory_subparser = subparsers.add_parser('remove-external-subdirectory') - remove_external_subdirectory_subparser.add_argument('external_subdirectory', metavar='external_subdirectory', - type=str, - help='remove external subdirectory from cmake') - - remove_external_subdirectory_subparser.add_argument('-ohf', '--override-home-folder', type=str, required=False, - help='By default the home folder is the user folder, override it to this folder.') - - remove_external_subdirectory_subparser.set_defaults(func=_run_remove_external_subdirectory) - - # add gems to cmake - # convenience functions to disambiguate the gem name -> gem_path and call add-external-subdirectory on gem_path - add_gem_to_cmake_subparser = subparsers.add_parser('add-gem-to-cmake') - group = add_gem_to_cmake_subparser.add_mutually_exclusive_group(required=True) - group.add_argument('-gp', '--gem-path', type=str, required=False, - help='The path to the gem.') - group.add_argument('-gn', '--gem-name', type=str, required=False, - help='The name of the gem.') - - add_gem_to_cmake_subparser.add_argument('-ohf', '--override-home-folder', type=str, required=False, - help='By default the home folder is the user folder, override it to this folder.') - - add_gem_to_cmake_subparser.set_defaults(func=_run_add_gem_to_cmake) - - # remove gems from cmake - # convenience functions to disambiguate the gem name -> gem_path and call remove-external-subdirectory on gem_path - remove_gem_from_cmake_subparser = subparsers.add_parser('remove-gem-from-cmake') - group = remove_gem_from_cmake_subparser.add_mutually_exclusive_group(required=True) - group.add_argument('-gp', '--gem-path', type=str, required=False, - help='The path to the gem.') - group.add_argument('-gn', '--gem-name', type=str, required=False, - help='The name of the gem.') - - remove_gem_from_cmake_subparser.add_argument('-ohf', '--override-home-folder', type=str, required=False, - help='By default the home folder is the user folder, override it to this folder.') - - remove_gem_from_cmake_subparser.set_defaults(func=_run_remove_gem_from_cmake) - - # add a gem to a project - add_gem_subparser = subparsers.add_parser('add-gem-to-project') - group = add_gem_subparser.add_mutually_exclusive_group(required=True) - group.add_argument('-pp', '--project-path', type=str, required=False, - help='The path to the project.') - group.add_argument('-pn', '--project-name', type=str, required=False, - help='The name of the project.') - group = add_gem_subparser.add_mutually_exclusive_group(required=True) - group.add_argument('-gp', '--gem-path', type=str, required=False, - help='The path to the gem.') - group.add_argument('-gn', '--gem-name', type=str, required=False, - help='The name of the gem.') - add_gem_subparser.add_argument('-gt', '--gem-target', type=str, required=False, - help='The cmake target name to add. If not specified it will assume gem_name') - add_gem_subparser.add_argument('-df', '--dependencies-file', type=str, required=False, - help='The cmake dependencies file in which the gem dependencies are specified.' - 'If not specified it will assume ') - add_gem_subparser.add_argument('-rd', '--runtime-dependency', action='store_true', required=False, - default=False, - help='Optional toggle if this gem should be added as a runtime dependency') - add_gem_subparser.add_argument('-td', '--tool-dependency', action='store_true', required=False, - default=False, - help='Optional toggle if this gem should be added as a tool dependency') - add_gem_subparser.add_argument('-sd', '--server-dependency', action='store_true', required=False, - default=False, - help='Optional toggle if this gem should be added as a server dependency') - add_gem_subparser.add_argument('-pl', '--platforms', type=str, required=False, - default='Common', - help='Optional list of platforms this gem should be added to.' - ' Ex. --platforms Mac,Windows,Linux') - add_gem_subparser.add_argument('-a', '--add-to-cmake', type=bool, required=False, - default=True, - help='Automatically call add-gem-to-cmake.') - - add_gem_subparser.add_argument('-ohf', '--override-home-folder', type=str, required=False, - help='By default the home folder is the user folder, override it to this folder.') - - add_gem_subparser.set_defaults(func=_run_add_gem_to_project) - - # remove a gem from a project - remove_gem_subparser = subparsers.add_parser('remove-gem-from-project') - group = remove_gem_subparser.add_mutually_exclusive_group(required=True) - group.add_argument('-pp', '--project-path', type=str, required=False, - help='The path to the project.') - group.add_argument('-pn', '--project-name', type=str, required=False, - help='The name of the project.') - group = remove_gem_subparser.add_mutually_exclusive_group(required=True) - group.add_argument('-gp', '--gem-path', type=str, required=False, - help='The path to the gem.') - group.add_argument('-gn', '--gem-name', type=str, required=False, - help='The name of the gem.') - remove_gem_subparser.add_argument('-gt', '--gem-target', type=str, required=False, - help='The cmake target name to add. If not specified it will assume gem_name') - remove_gem_subparser.add_argument('-df', '--dependencies-file', type=str, required=False, - help='The cmake dependencies file in which the gem dependencies are specified.' - 'If not specified it will assume ') - remove_gem_subparser.add_argument('-rd', '--runtime-dependency', action='store_true', required=False, - default=False, - help='Optional toggle if this gem should be removed as a runtime dependency') - remove_gem_subparser.add_argument('-td', '--tool-dependency', action='store_true', required=False, - default=False, - help='Optional toggle if this gem should be removed as a server dependency') - remove_gem_subparser.add_argument('-sd', '--server-dependency', action='store_true', required=False, - default=False, - help='Optional toggle if this gem should be removed as a server dependency') - remove_gem_subparser.add_argument('-pl', '--platforms', type=str, required=False, - default='Common', - help='Optional list of platforms this gem should be removed from' - ' Ex. --platforms Mac,Windows,Linux') - remove_gem_subparser.add_argument('-r', '--remove-from-cmake', type=bool, required=False, - default=False, - help='Automatically call remove-from-cmake.') - - remove_gem_subparser.add_argument('-ohf', '--override-home-folder', type=str, required=False, - help='By default the home folder is the user folder, override it to this folder.') - - remove_gem_subparser.set_defaults(func=_run_remove_gem_from_project) - - # sha256 - sha256_subparser = subparsers.add_parser('sha256') - sha256_subparser.add_argument('-f', '--file-path', type=str, required=True, - help='The path to the file you want to sha256.') - sha256_subparser.add_argument('-j', '--json-path', type=str, required=False, - help='optional path to an o3de json file to add the "sha256" element to.') - sha256_subparser.set_defaults(func=_run_sha256) - - -if __name__ == "__main__": - # parse the command line args - the_parser = argparse.ArgumentParser() - - # add subparsers - the_subparsers = the_parser.add_subparsers(help='sub-command help') - - # add args to the parser - add_args(the_parser, the_subparsers) - - # parse args - the_args = the_parser.parse_args() - - # run - ret = the_args.func(the_args) - - # return - sys.exit(ret) diff --git a/cmake/Tools/unit_test_add_remove_gem.py b/cmake/Tools/unit_test_add_remove_gem.py deleted file mode 100755 index 81f0fa615e..0000000000 --- a/cmake/Tools/unit_test_add_remove_gem.py +++ /dev/null @@ -1,259 +0,0 @@ -# -# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -# its licensors. -# -# For complete copyright and license terms please see the LICENSE at the root of this -# distribution (the "License"). All use of this software is governed by the License, -# or, if provided, by the license below or the license accompanying this file. Do not -# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# - -import os -import pytest - -from . import add_remove_gem - -TEST_WITHOUT_NO_GEM_CONTENT = """ -# {BEGIN_LICENSE} -# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -# its licensors. -# -# For complete copyright and license terms please see the LICENSE at the root of this -# distribution (the "License"). All use of this software is governed by the License, -# or, if provided, by the license below or the license accompanying this file. Do not -# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# {END_LICENSE} - -set(GEM_DEPENDENCIES -) -""" - -TEST_WITHOUT_ONLY_GEM_CONTENT = """ -# {BEGIN_LICENSE} -# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -# its licensors. -# -# For complete copyright and license terms please see the LICENSE at the root of this -# distribution (the "License"). All use of this software is governed by the License, -# or, if provided, by the license below or the license accompanying this file. Do not -# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# {END_LICENSE} - -set(GEM_DEPENDENCIES - Gem::TestGem -) -""" - -TEST_WITHOUT_ADDED_GEM_CONTENT = """ -# {BEGIN_LICENSE} -# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -# its licensors. -# -# For complete copyright and license terms please see the LICENSE at the root of this -# distribution (the "License"). All use of this software is governed by the License, -# or, if provided, by the license below or the license accompanying this file. Do not -# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# {END_LICENSE} - -set(GEM_DEPENDENCIES - Gem::ExistingGem -) -""" - -TEST_WITH_ADDED_GEM_CONTENT = """ -# {BEGIN_LICENSE} -# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -# its licensors. -# -# For complete copyright and license terms please see the LICENSE at the root of this -# distribution (the "License"). All use of this software is governed by the License, -# or, if provided, by the license below or the license accompanying this file. Do not -# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# {END_LICENSE} - -set(GEM_DEPENDENCIES - Gem::TestGem - Gem::ExistingGem -) -""" - - -@pytest.mark.parametrize( - "contents, gem, expected_result, runtime_present, expect_failure", [ - pytest.param(TEST_WITHOUT_ADDED_GEM_CONTENT, "TestGem", TEST_WITH_ADDED_GEM_CONTENT, True, False), - pytest.param(TEST_WITHOUT_ADDED_GEM_CONTENT, "TestGem", TEST_WITH_ADDED_GEM_CONTENT, False, True), - pytest.param(TEST_WITHOUT_ADDED_GEM_CONTENT, "/TestGem", TEST_WITH_ADDED_GEM_CONTENT, True, True), - pytest.param(TEST_WITHOUT_NO_GEM_CONTENT, "TestGem", TEST_WITHOUT_ONLY_GEM_CONTENT, True, False), - ] -) -def test_add_gem_dependency(tmpdir, contents, gem, expected_result, runtime_present, expect_failure): - dev_root = str(tmpdir.join('dev').realpath()).replace('\\', '/') - os.makedirs(dev_root, exist_ok=True) - - dev_project_gem_code = f'{dev_root}/TestProject/Gem/Code' - os.makedirs(dev_project_gem_code, exist_ok=True) - - runtime_dependencies_cmake_file = f'{dev_project_gem_code}/runtime_dependencies.cmake' - if runtime_present: - if os.path.isfile(runtime_dependencies_cmake_file): - os.unlink(runtime_dependencies_cmake_file) - with open(runtime_dependencies_cmake_file, 'a') as s: - s.write(contents) - - result = add_remove_gem.add_gem_dependency(runtime_dependencies_cmake_file, gem) - - if expect_failure: - assert result != 0 - else: - assert result == 0 - with open(runtime_dependencies_cmake_file, 'r') as s: - s_data = s.read() - assert s_data == expected_result - - -@pytest.mark.parametrize( - "contents, gem, expected_result, runtime_present, expect_failure", [ - pytest.param(TEST_WITH_ADDED_GEM_CONTENT, "TestGem", TEST_WITHOUT_ADDED_GEM_CONTENT, True, False), - pytest.param(TEST_WITH_ADDED_GEM_CONTENT, "TestGem", TEST_WITHOUT_ADDED_GEM_CONTENT, False, True), - pytest.param(TEST_WITHOUT_ADDED_GEM_CONTENT, "TestGem", TEST_WITHOUT_ADDED_GEM_CONTENT, True, True) - ] -) -def test_remove_gem_dependency(tmpdir, contents, gem, expected_result, runtime_present, expect_failure): - dev_root = str(tmpdir.join('dev').realpath()).replace('\\', '/') - os.makedirs(dev_root, exist_ok=True) - - dev_project_gem_code = f'{dev_root}/TestProject/Gem/Code' - os.makedirs(dev_project_gem_code, exist_ok=True) - - runtime_dependencies_cmake_file = f'{dev_project_gem_code}/runtime_dependencies.cmake' - if runtime_present: - if os.path.isfile(runtime_dependencies_cmake_file): - os.unlink(runtime_dependencies_cmake_file) - with open(runtime_dependencies_cmake_file, 'a') as s: - s.write(contents) - - result = add_remove_gem.remove_gem_dependency(runtime_dependencies_cmake_file, gem) - - if expect_failure: - assert result != 0 - else: - assert result == 0 - with open(runtime_dependencies_cmake_file, 'r') as s: - s_data = s.read() - assert s_data == expected_result - - -@pytest.mark.parametrize("add," - " contents, gem, project, expected_result," - " runtime_present, tool_present," - " ask_for_runtime, ask_for_tool," - " expect_failure", [ - pytest.param(True, - TEST_WITHOUT_ADDED_GEM_CONTENT, "TestGem", "TestProject", - TEST_WITH_ADDED_GEM_CONTENT, - True, True, - True, True, - False), - pytest.param(True, - TEST_WITHOUT_ADDED_GEM_CONTENT, "TestGem", "TestProject", - TEST_WITH_ADDED_GEM_CONTENT, - True, False, - True, True, - True), - pytest.param(True, - TEST_WITHOUT_ADDED_GEM_CONTENT, "TestGem", "TestProject", - TEST_WITH_ADDED_GEM_CONTENT, - False, True, - True, True, - True), - pytest.param(True, - TEST_WITHOUT_ADDED_GEM_CONTENT, "TestGem", "TestProject", - TEST_WITH_ADDED_GEM_CONTENT, - False, False, - True, True, - True), - - pytest.param(False, - TEST_WITH_ADDED_GEM_CONTENT, "TestGem", "TestProject", - TEST_WITHOUT_ADDED_GEM_CONTENT, - True, True, - True, True, - False), - pytest.param(False, - TEST_WITH_ADDED_GEM_CONTENT, "TestGem", "TestProject", - TEST_WITHOUT_ADDED_GEM_CONTENT, - True, False, - True, True, - True), - pytest.param(False, - TEST_WITH_ADDED_GEM_CONTENT, "TestGem", "TestProject", - TEST_WITHOUT_ADDED_GEM_CONTENT, - False, True, - True, True, - True), - pytest.param(False, - TEST_WITH_ADDED_GEM_CONTENT, "TestGem", "TestProject", - TEST_WITHOUT_ADDED_GEM_CONTENT, - False, False, - True, True, - True) - ] - ) -def test_add_remove_gem(tmpdir, - add, - contents, gem, project, - expected_result, - runtime_present, tool_present, - ask_for_runtime, ask_for_tool, - expect_failure): - dev_root = str(tmpdir.join('dev').realpath()).replace('\\', '/') - os.makedirs(dev_root, exist_ok=True) - - dev_project_gem_code = f'{dev_root}/TestProject/Gem/Code' - os.makedirs(dev_project_gem_code, exist_ok=True) - - runtime_dependencies_cmake_file = f'{dev_project_gem_code}/runtime_dependencies.cmake' - if runtime_present: - if os.path.isfile(runtime_dependencies_cmake_file): - os.unlink(runtime_dependencies_cmake_file) - with open(runtime_dependencies_cmake_file, 'a') as s: - s.write(contents) - - tool_dependencies_cmake_file = f'{dev_project_gem_code}/tool_dependencies.cmake' - os.makedirs(dev_project_gem_code, exist_ok=True) - - if tool_present: - if os.path.isfile(tool_dependencies_cmake_file): - os.unlink(tool_dependencies_cmake_file) - with open(tool_dependencies_cmake_file, 'w') as s: - s.write(contents) - - project_folder = f'{dev_root}/TestProject' - os.makedirs(project_folder, exist_ok=True) - - gems_folder = f'{dev_root}/Gems' - os.makedirs(gems_folder, exist_ok=True) - - gem_folder = f'{gems_folder}/{gem}' - os.makedirs(gem_folder, exist_ok=True) - - result = add_remove_gem.add_remove_gem(add, dev_root, gem, project, ask_for_runtime, ask_for_tool) - - if expect_failure: - assert result != 0 - else: - assert result == 0 - if runtime_present: - with open(runtime_dependencies_cmake_file, 'r') as s: - s_data = s.read() - assert s_data == expected_result - if tool_present: - with open(tool_dependencies_cmake_file, 'r') as s: - s_data = s.read() - assert s_data == expected_result - diff --git a/cmake/Tools/utils.py b/cmake/Tools/utils.py deleted file mode 100755 index 37c84ea331..0000000000 --- a/cmake/Tools/utils.py +++ /dev/null @@ -1,47 +0,0 @@ -# -# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -# its licensors. -# -# For complete copyright and license terms please see the LICENSE at the root of this -# distribution (the "License"). All use of this software is governed by the License, -# or, if provided, by the license below or the license accompanying this file. Do not -# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# -""" -This file contains utility functions -""" - -import uuid - - -def validate_identifier(identifier: str) -> bool: - """ - Determine if the identifier supplied is valid. - :param identifier: the name which needs to to checked - :return: bool: if the identifier is valid or not - """ - if not identifier: - return False - elif len(identifier) > 64: - return False - elif not identifier[0].isalpha(): - return False - else: - for character in identifier: - if not (character.isalnum() or character == '_' or character == '-'): - return False - return True - - -def validate_uuid4(uuid_string: str) -> bool: - """ - Determine if the uuid supplied is valid. - :param uuid_string: the uuid which needs to to checked - :return: bool: if the uuid is valid or not - """ - try: - val = uuid.UUID(uuid_string, version=4) - except ValueError: - return False - return str(val) == uuid_string diff --git a/cmake/Version.cmake b/cmake/Version.cmake index 08d79d4ce6..1d484fb059 100644 --- a/cmake/Version.cmake +++ b/cmake/Version.cmake @@ -12,4 +12,5 @@ string(TIMESTAMP current_year "%Y") set(LY_VERSION_COPYRIGHT_YEAR ${current_year} CACHE STRING "Open 3D Engine's copyright year") set(LY_VERSION_STRING "0.0.0.0" CACHE STRING "Open 3D Engine's version") -set(LY_VERSION_BUILD_NUMBER 0 CACHE STRING "Open 3D Engine's build number") \ No newline at end of file +set(LY_VERSION_BUILD_NUMBER 0 CACHE STRING "Open 3D Engine's build number") +set(LY_VERSION_ENGINE_NAME "o3de" CACHE STRING "Open 3D Engine's engine name") diff --git a/cmake/cmake_files.cmake b/cmake/cmake_files.cmake index e423eb0dbc..a1fd66a06d 100644 --- a/cmake/cmake_files.cmake +++ b/cmake/cmake_files.cmake @@ -12,20 +12,25 @@ set(FILES 3rdParty.cmake 3rdPartyPackages.cmake + CMakeFiles.cmake CommandExecution.cmake Configurations.cmake Dependencies.cmake Deployment.cmake - EngineFinder.cmake + EngineJson.cmake FileUtil.cmake Findo3de.cmake + Gems.cmake + GeneralSettings.cmake Install.cmake LyAutoGen.cmake + LYPackage_S3Downloader.cmake LySet.cmake LYTestWrappers.cmake LYPython.cmake LYWrappers.cmake Monolithic.cmake + OutputDirectory.cmake Packaging.cmake PAL.cmake PALTools.cmake diff --git a/cmake/install/Copyright.in b/cmake/install/Copyright.in new file mode 100644 index 0000000000..4d5680a30d --- /dev/null +++ b/cmake/install/Copyright.in @@ -0,0 +1,10 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# diff --git a/cmake/Findo3de.cmake.in b/cmake/install/Findo3de.cmake.in similarity index 100% rename from cmake/Findo3de.cmake.in rename to cmake/install/Findo3de.cmake.in diff --git a/cmake/install/InstalledTarget.in b/cmake/install/InstalledTarget.in new file mode 100644 index 0000000000..0503fd5f2b --- /dev/null +++ b/cmake/install/InstalledTarget.in @@ -0,0 +1,23 @@ + +# Generated by O3DE + +ly_add_target( + NAME @NAME_PLACEHOLDER@ IMPORTED @TARGET_TYPE_PLACEHOLDER@ + @NAMESPACE_PLACEHOLDER@ + COMPILE_DEFINITIONS + INTERFACE +@COMPILE_DEFINITIONS_PLACEHOLDER@ + INCLUDE_DIRECTORIES + INTERFACE +@INCLUDE_DIRECTORIES_PLACEHOLDER@ + BUILD_DEPENDENCIES + INTERFACE +@INTERFACE_BUILD_DEPENDENCIES_PLACEHOLDER@ + RUNTIME_DEPENDENCIES +@RUNTIME_DEPENDENCIES_PLACEHOLDER@ +) + +set(configs @CMAKE_CONFIGURATION_TYPES@) +foreach(config ${configs}) + include("@NAME_PLACEHOLDER@_${config}.cmake" OPTIONAL) +endforeach() diff --git a/cmake/install/engine.json.in b/cmake/install/engine.json.in new file mode 100644 index 0000000000..ce2e1be25c --- /dev/null +++ b/cmake/install/engine.json.in @@ -0,0 +1,11 @@ +{ + "engine_name": "@LY_VERSION_ENGINE_NAME@", + "restricted_name": "o3de", + "FileVersion": 1, + "O3DEVersion": "@LY_VERSION_STRING@", + "O3DECopyrightYear": @LY_VERSION_COPYRIGHT_YEAR@, + "O3DEBuildNumber": @LY_VERSION_BUILD_NUMBER@, + "external_subdirectories": [@LY_INSTALL_EXTERNAL_SUBDIRS@], + "projects": [@LY_INSTALL_PROJECTS@], + "templates": [@LY_INSTALL_TEMPLATES@] +} diff --git a/cmake/o3de_manifest.cmake b/cmake/o3de_manifest.cmake deleted file mode 100644 index 1585ec2d2f..0000000000 --- a/cmake/o3de_manifest.cmake +++ /dev/null @@ -1,986 +0,0 @@ -# -# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -# its licensors. -# -# For complete copyright and license terms please see the LICENSE at the root of this -# distribution (the "License"). All use of this software is governed by the License, -# or, if provided, by the license below or the license accompanying this file. Do not -# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# - -# Set the user home directory -set(O3DE_HOME_PATH "" CACHE PATH "Override the user home to this path") -if(O3DE_HOME_PATH) - set(home_directory ${O3DE_HOME_PATH}) -elseif(CMAKE_HOST_WIN32) - file(TO_CMAKE_PATH "$ENV{USERPROFILE}" home_directory) -else() - file(TO_CMAKE_PATH "$ENV{HOME}" home_directory) -endif() -if (NOT home_directory) - message(FATAL_ERROR "Cannot find user home directory, without the user home directory the o3de manifest cannot be found") -endif() - -# Optionally delete the home directory -if(O3DE_DELETE_HOME_PATH) - message(STATUS "O3DE_DELETE_HOME_PATH=${O3DE_DELETE_HOME_PATH}") - if(EXISTS ${home_directory}/.o3de) - message(STATUS "Deleting ${home_directory}/.o3de") - file(REMOVE_RECURSE ${home_directory}/.o3de) - else() - message(STATUS "Home path ${home_directory}/.o3de doesnt exist.") - endif() -endif() - -######################################################################################################################## -# If O3DE_REGISTER_ENGINE_PATH variable is set on the commandline this will allow registration of anything using -# O3DE_REGISTER_ENGINE_PATH o3de script. This is handy for situations like build servers which download the code and -# are expected to build without the need for someone to register o3de objects like this engine by manually typing it in. -# If O3DE_REGISTER_THIS_ENGINE=TRUE is set on the commandline when O3DE_REGISTER_ENGINE_PATH is also set this will call: -# O3DE_REGISTER_ENGINE_PATH/scripts>o3de register --this-engine --override-home-folder -# Note: register --this-engine will automatically register anything it finds in known folders, so if you put your -# o3de objects like projects/gems/templates/restricted/etc... in known folders for those types they will get registered -# automatically. Known folders for types are your .o3de/Projects and .o3de/Gems etc. So if I wanted my project to be -# registered and built by this build server I could simply put them in those known folders on the build server and they -# would get registered automatically by this call. -# OR -# I could put them on the commandline as well. This would be the way if the o3de objects we need to regiater are NOT -# in known folders or you do not intend to call with O3DE_REGISTER_THIS_ENGINE=TRUE Ex. -# -DO3DE_REGISTER_ENGINE_PATH=C:\this\engine -# -DO3DE_REGISTER_PROJECT_PATHS=C:\ThisGame;C:\ThatGame -# -DO3DE_REGISTER_GEM_PATHS=C:\ThisGem;C:\ThatGem;C:\And\Some\Other\Gem -# -DO3DE_REGISTER_RESTRICTED_PATHS=C:\this\engine\Restricted;C:\ThisGame\Restricted;C:\ThisGem\Restricted -######################################################################################################################## -if(O3DE_REGISTER_ENGINE_PATH) - message(STATUS "O3DE_REGISTER_ENGINE_PATH=${O3DE_REGISTER_ENGINE_PATH}") - - if(O3DE_REGISTER_THIS_ENGINE) - message(STATUS "O3DE_REGISTER_THIS_ENGINE=${O3DE_REGISTER_THIS_ENGINE}") - message(STATUS "register --this-engine") - if(CMAKE_HOST_WIN32) - execute_process( - COMMAND cmd /c ${O3DE_REGISTER_ENGINE_PATH}/scripts/o3de.bat register --this-engine --override-home-folder ${home_directory} - RESULT_VARIABLE o3de_register_this_engine_cmd_result - ) - else() - execute_process( - COMMAND sh ${O3DE_REGISTER_ENGINE_PATH}/scripts/o3de.sh register --this-engine --override-home-folder ${home_directory} - RESULT_VARIABLE o3de_register_this_engine_cmd_result - ) - endif() - if(o3de_register_this_engine_cmd_result) - message(FATAL_ERROR "An error occured trying to register --this-engine: ${o3de_register_this_engine_cmd_result}") - else() - message(STATUS "Engine ${O3DE_REGISTER_ENGINE_PATH} registration successfull.") - endif() - endif() - - if(O3DE_REGISTER_RESTRICTED_PATHS) - message(STATUS "O3DE_REGISTER_RESTRICTED_PATHS=${O3DE_REGISTER_RESTRICTED_PATHS}") - foreach(restricted_path ${O3DE_REGISTER_RESTRICTED_PATHS}) - if(CMAKE_HOST_WIN32) - execute_process( - COMMAND cmd /c ${O3DE_REGISTER_ENGINE_PATH}/scripts/o3de.bat register --restricted-path ${restricted_path} --override-home-folder ${home_directory} - RESULT_VARIABLE o3de_register_restricted_cmd_result - ) - else() - execute_process( - COMMAND sh ${O3DE_REGISTER_ENGINE_PATH}/scripts/o3de.sh register --restricted-path ${restricted_path} --override-home-folder ${home_directory} - RESULT_VARIABLE o3de_register_restricted_cmd_result - ) - endif() - if(o3de_register_restricted_cmd_result) - message(FATAL_ERROR "An error occured trying to ${O3DE_REGISTER_ENGINE_PATH}/scripts>o3de register --restricted-path ${restricted_path} --override-home-folder ${home_directory}: ${o3de_register_restricted_cmd_result}") - else() - message(STATUS "Restricted ${restricted_path} registration successfull.") - endif() - endforeach() - endif() - - if(O3DE_REGISTER_PROJECT_PATHS) - message(STATUS "O3DE_REGISTER_PROJECT_PATHS=${O3DE_REGISTER_PROJECT_PATHS}") - foreach(project_path ${O3DE_REGISTER_PROJECT_PATHS}) - if(CMAKE_HOST_WIN32) - execute_process( - COMMAND cmd /c ${O3DE_REGISTER_ENGINE_PATH}/scripts/o3de.bat register --project-path ${project_path} --override-home-folder ${home_directory} - RESULT_VARIABLE o3de_register_project_cmd_result - ) - else() - execute_process( - COMMAND sh ${O3DE_REGISTER_ENGINE_PATH}/scripts/o3de.sh register --project-path ${project_path} --override-home-folder ${home_directory} - RESULT_VARIABLE o3de_register_project_cmd_result - ) - endif() - if(o3de_register_project_cmd_result) - message(FATAL_ERROR "An error occured trying to ${O3DE_REGISTER_ENGINE_PATH}/scripts>o3de register --project-path ${project_path} --override-home-folder ${home_directory}") - else() - message(STATUS "Project ${project_path} registration successfull.") - endif() - endforeach() - endif() - - if(O3DE_REGISTER_GEM_PATHS) - message(STATUS "O3DE_REGISTER_GEM_PATHS=${O3DE_REGISTER_GEM_PATHS}") - foreach(gem_path ${O3DE_REGISTER_GEM_PATHS}) - if(CMAKE_HOST_WIN32) - execute_process( - COMMAND cmd /c ${O3DE_REGISTER_ENGINE_PATH}/scripts/o3de.bat register --gem-path ${gem_path} --override-home-folder ${home_directory} - RESULT_VARIABLE o3de_register_gem_cmd_result - ) - else() - execute_process( - COMMAND sh ${O3DE_REGISTER_ENGINE_PATH}/scripts/o3de.sh register --gem-path ${gem_path} --override-home-folder ${home_directory} - RESULT_VARIABLE o3de_register_gem_cmd_result - ) - endif() - if(o3de_register_gem_cmd_result) - message(FATAL_ERROR "An error occured trying to ${O3DE_REGISTER_ENGINE_PATH}/scripts>o3de register --gem-path ${gem_path} --override-home-folder ${home_directory}") - else() - message(STATUS "Gem ${gem_path} registration successfull.") - endif() - endforeach() - endif() - - if(O3DE_REGISTER_TEMPLATE_PATHS) - message(STATUS "O3DE_REGISTER_TEMPLATE_PATHS=${O3DE_REGISTER_TEMPLATE_PATHS}") - foreach(template_path ${O3DE_REGISTER_TEMPLATE_PATHS}) - if(CMAKE_HOST_WIN32) - execute_process( - COMMAND cmd /c ${O3DE_REGISTER_ENGINE_PATH}/scripts/o3de.bat register --template-path ${template_path} --override-home-folder ${home_directory} - RESULT_VARIABLE o3de_register_template_cmd_result - ) - else() - execute_process( - COMMAND sh ${O3DE_REGISTER_ENGINE_PATH}/scripts/o3de.sh register --template-path ${template_path} --override-home-folder ${home_directory} - RESULT_VARIABLE o3de_register_template_cmd_result - ) - endif() - if(o3de_register_template_cmd_result) - message(FATAL_ERROR "An error occured trying to ${O3DE_REGISTER_ENGINE_PATH}/scripts>o3de register --template-path ${template_path} --override-home-folder ${home_directory}") - else() - message(STATUS "Template ${template_path} registration successfull.") - endif() - endforeach() - endif() - - if(O3DE_REGISTER_REPO_URIS) - message(STATUS "O3DE_REGISTER_REPO_URIS=${O3DE_REGISTER_REPO_URIS}") - foreach(repo_uri ${O3DE_REGISTER_REPO_URIS}) - if(CMAKE_HOST_WIN32) - execute_process( - COMMAND cmd /c ${O3DE_REGISTER_ENGINE_PATH}/scripts/o3de.bat register --repo-uri ${repo_uri} --override-home-folder ${home_directory} - RESULT_VARIABLE o3de_register_repo_cmd_result - ) - else() - execute_process( - COMMAND sh ${O3DE_REGISTER_ENGINE_PATH}/scripts/o3de.sh register --repo-uri ${repo_uri} --override-home-folder ${home_directory} - RESULT_VARIABLE o3de_register_repo_cmd_result - ) - endif() - if(o3de_register_repo_cmd_result) - message(FATAL_ERROR "An error occured trying to ${O3DE_REGISTER_ENGINE_PATH}/scripts>o3de register --repo-uri ${repo_uri} --override-home-folder ${home_directory}") - else() - message(STATUS "Repo ${repo_uri} registration successfull.") - endif() - endforeach() - endif() -endif() - -################################################################################ -# o3de manifest -################################################################################ -# Set manifest json path to the /.o3de/o3de_manifest.json -set(o3de_manifest_json_path ${home_directory}/.o3de/o3de_manifest.json) -if(NOT EXISTS ${o3de_manifest_json_path}) - message(FATAL_ERROR "${o3de_manifest_json_path} not found. You must o3de register --this-engine.") -endif() -file(READ ${o3de_manifest_json_path} manifest_json_data) - -################################################################################ -# o3de manifest name -################################################################################ -string(JSON o3de_manifest_name ERROR_VARIABLE json_error GET ${manifest_json_data} o3de_manifest_name) -message(STATUS "o3de_manifest_name: ${o3de_manifest_name}") -if(json_error) - message(FATAL_ERROR "Unable to read repo_name from '${o3de_manifest_json_path}', error: ${json_error}") -endif() - -################################################################################ -# o3de origin -################################################################################ -string(JSON o3de_origin ERROR_VARIABLE json_error GET ${manifest_json_data} origin) -if(json_error) - message(FATAL_ERROR "Unable to read origin from '${o3de_manifest_json_path}', error: ${json_error}") -endif() - -################################################################################ -# o3de default engines folder -################################################################################ -string(JSON o3de_default_engines_folder ERROR_VARIABLE json_error GET ${manifest_json_data} default_engines_folder) -message(STATUS "default_engines_folder: ${o3de_default_engines_folder}") -if(json_error) - message(FATAL_ERROR "Unable to read default_engines_folder from '${o3de_manifest_json_path}', error: ${json_error}") -endif() - -################################################################################ -# o3de default projects folder -################################################################################ -string(JSON o3de_default_projects_folder ERROR_VARIABLE json_error GET ${manifest_json_data} default_projects_folder) -message(STATUS "default_projects_folder: ${o3de_default_projects_folder}") -if(json_error) - message(FATAL_ERROR "Unable to read default_projects_folder from '${o3de_manifest_json_path}', error: ${json_error}") -endif() - -################################################################################ -# o3de default gems folder -################################################################################ -string(JSON o3de_default_gems_folder ERROR_VARIABLE json_error GET ${manifest_json_data} default_gems_folder) -message(STATUS "default_gems_folder: ${o3de_default_gems_folder}") -if(json_error) - message(FATAL_ERROR "Unable to read default_gems_folder from '${o3de_manifest_json_path}', error: ${json_error}") -endif() - -################################################################################ -# o3de default templates folder -################################################################################ -string(JSON o3de_default_templates_folder ERROR_VARIABLE json_error GET ${manifest_json_data} default_templates_folder) -message(STATUS "default_templates_folder: ${o3de_default_templates_folder}") -if(json_error) - message(FATAL_ERROR "Unable to read default_templates_folder from '${o3de_manifest_json_path}', error: ${json_error}") -endif() - -################################################################################ -# o3de default restricted folder -################################################################################ -string(JSON o3de_default_restricted_folder ERROR_VARIABLE json_error GET ${manifest_json_data} default_restricted_folder) -message(STATUS "default_restricted_folder: ${o3de_default_restricted_folder}") -if(json_error) - message(FATAL_ERROR "Unable to read default_restricted_folder from '${o3de_manifest_json_path}', error: ${json_error}") -endif() - -################################################################################ -# o3de projects -################################################################################ -string(JSON o3de_projects_count ERROR_VARIABLE json_error LENGTH ${manifest_json_data} projects) -if(json_error) - message(FATAL_ERROR "Unable to read key 'projects' from '${o3de_manifest_json_path}', error: ${json_error}") -endif() -if(${o3de_projects_count} GREATER 0) - math(EXPR o3de_projects_count "${o3de_projects_count}-1") - foreach(projects_index RANGE ${o3de_projects_count}) - string(JSON projects_path ERROR_VARIABLE json_error GET ${manifest_json_data} projects ${projects_index}) - if(json_error) - message(FATAL_ERROR "Unable to read projects[${projects_index}] '${o3de_manifest_json_path}', error: ${json_error}") - endif() - list(APPEND o3de_projects ${projects_path}) - list(APPEND o3de_global_projects ${projects_path}) - endforeach() -endif() - -################################################################################ -# o3de gems -################################################################################ -string(JSON o3de_gems_count ERROR_VARIABLE json_error LENGTH ${manifest_json_data} gems) -if(json_error) - message(FATAL_ERROR "Unable to read key 'gems' from '${o3de_manifest_json_path}', error: ${json_error}") -endif() -if(${o3de_gems_count} GREATER 0) - math(EXPR o3de_gems_count "${o3de_gems_count}-1") - foreach(gems_index RANGE ${o3de_gems_count}) - string(JSON gems_path ERROR_VARIABLE json_error GET ${manifest_json_data} gems ${gems_index}) - if(json_error) - message(FATAL_ERROR "Unable to read gems[${gems_index}] '${o3de_manifest_json_path}', error: ${json_error}") - endif() - list(APPEND o3de_gems ${gems_path}) - list(APPEND o3de_global_gems ${gems_path}) - endforeach() -endif() - -################################################################################ -# o3de templates -################################################################################ -string(JSON o3de_templates_count ERROR_VARIABLE json_error LENGTH ${manifest_json_data} templates) -if(json_error) - message(FATAL_ERROR "Unable to read key 'templates' from '${o3de_manifest_json_path}', error: ${json_error}") -endif() -if(${o3de_templates_count} GREATER 0) - math(EXPR o3de_templates_count "${o3de_templates_count}-1") - foreach(templates_index RANGE ${o3de_templates_count}) - string(JSON templates_path ERROR_VARIABLE json_error GET ${manifest_json_data} templates ${templates_index}) - if(json_error) - message(FATAL_ERROR "Unable to read templates[${templates_index}] '${o3de_manifest_json_path}', error: ${json_error}") - endif() - list(APPEND o3de_templates ${templates_path}) - list(APPEND o3de_global_templates ${templates_path}) - endforeach() -endif() - -################################################################################ -# o3de repos -################################################################################ -string(JSON o3de_repos_count ERROR_VARIABLE json_error LENGTH ${manifest_json_data} repos) -if(json_error) - message(FATAL_ERROR "Unable to read key 'repos' from '${o3de_manifest_json_path}', error: ${json_error}") -endif() -if(${o3de_repos_count} GREATER 0) - math(EXPR o3de_repos_count "${o3de_repos_count}-1") - foreach(repos_index RANGE ${o3de_repos_count}) - string(JSON repo_uri ERROR_VARIABLE json_error GET ${manifest_json_data} repos ${repos_index}) - if(json_error) - message(FATAL_ERROR "Unable to read repos[${repos_index}] '${o3de_manifest_json_path}', error: ${json_error}") - endif() - list(APPEND o3de_repos ${repo_uri}) - list(APPEND o3de_global_repos ${repo_uri}) - endforeach() -endif() - -################################################################################ -# o3de restricted -################################################################################ -string(JSON o3de_restricted_count ERROR_VARIABLE json_error LENGTH ${manifest_json_data} restricted) -if(json_error) - message(FATAL_ERROR "Unable to read key 'restricted' from '${o3de_manifest_json_path}', error: ${json_error}") -endif() -if(${o3de_restricted_count} GREATER 0) - math(EXPR o3de_restricted_count "${o3de_restricted_count}-1") - foreach(restricted_index RANGE ${o3de_restricted_count}) - string(JSON restricted_path ERROR_VARIABLE json_error GET ${manifest_json_data} restricted ${restricted_index}) - if(json_error) - message(FATAL_ERROR "Unable to read restricted[${restricted_index}] '${o3de_manifest_json_path}', error: ${json_error}") - endif() - list(APPEND o3de_restricted ${restricted_path}) - list(APPEND o3de_global_restricted ${restricted_path}) - endforeach() -endif() - -################################################################################ -# o3de engines -################################################################################ -string(JSON o3de_engines_count ERROR_VARIABLE json_error LENGTH ${manifest_json_data} engines) -if(json_error) - message(FATAL_ERROR "Unable to read key 'engines' from '${o3de_manifest_json_path}', error: ${json_error}") -endif() - -if(${o3de_engines_count} GREATER 0) - math(EXPR o3de_engines_count "${o3de_engines_count}-1") - # Either the engine_path and engine_json are set in which case the user is configuring from the engine - # or project_path and project_json are set in which case the user is configuring from the project. - # We need to know which engine_path the user is using so if the project_json is set then we need - # to read the project_json and disambiguate the engine_path. - if(NOT o3de_engine_path) - if(NOT o3de_project_json) - message(FATAL_ERROR "Neither o3de_engine_path nor o3de_project_json defined. Cannot determine engine!") - endif() - - # get the name of the engine this project uses - file(READ ${o3de_project_json} project_json_data) - string(JSON project_engine_name ERROR_VARIABLE json_error GET ${project_json_data} engine) - if(json_error) - message(FATAL_ERROR "Unable to read 'engine' from '${o3de_project_json}', error: ${json_error}") - endif() - - # search each engine in order from the manifest to find the matching engine name - foreach(engines_index RANGE ${o3de_engines_count}) - string(JSON engine_data ERROR_VARIABLE json_error GET ${manifest_json_data} engines ${engines_index}) - if(json_error) - message(FATAL_ERROR "Unable to read engines[${engines_index}] '${o3de_manifest_json_path}', error: ${json_error}") - endif() - - # get this engines path - string(JSON this_engine_path ERROR_VARIABLE json_error GET ${engine_data} path) - if(json_error) - message(FATAL_ERROR "Unable to read engine path from '${o3de_manifest_json_path}', error: ${json_error}") - endif() - - # add this engine to the engines list - list(APPEND o3de_engines ${this_engine_path}) - - # use path to get the engine.json - set(this_engine_json ${this_engine_path}/engine.json) - - # read the name of this engine - file(READ ${this_engine_json} this_engine_json_data) - string(JSON this_engine_name ERROR_VARIABLE json_error GET ${this_engine_json_data} engine_name) - if(json_error) - message(FATAL_ERROR "Unable to read engine_name from '${this_engine_json}', error: ${json_error}") - endif() - - # see if this engines name is the same as the one this projects should use - if(${this_engine_name} STREQUAL ${project_engine_name}) - message(STATUS "Found engine: '${project_engine_name}' at ${this_engine_path}") - set(o3de_engine_path ${this_engine_path}) - break() - endif() - endforeach() - endif() -endif() - -#we should have an engine_path at this point -if(NOT o3de_engine_path) - message(FATAL_ERROR "o3de_engine_path not defined. Cannot determine engine!") -endif() - -# now that we have an engine_path read in that engines o3de resources -if(${o3de_engines_count} GREATER -1) - foreach(engines_index RANGE ${o3de_engines_count}) - string(JSON engine_data ERROR_VARIABLE json_error GET ${manifest_json_data} engines ${engines_index}) - if(json_error) - message(FATAL_ERROR "Unable to read engines[${engines_index}] '${o3de_manifest_json_path}', error: ${json_error}") - endif() - - # get this engines path - string(JSON this_engine_path ERROR_VARIABLE json_error GET ${engine_data} path) - if(json_error) - message(FATAL_ERROR "Unable to read engine path from '${o3de_manifest_json_path}', error: ${json_error}") - endif() - - if(${o3de_engine_path} STREQUAL ${this_engine_path}) - ################################################################################ - # o3de engine projects - ################################################################################ - string(JSON o3de_engine_projects_count ERROR_VARIABLE json_error LENGTH ${engine_data} projects) - if(json_error) - message(FATAL_ERROR "Unable to read key 'projects' from '${engine_data}', error: ${json_error}") - endif() - if(${o3de_engine_projects_count} GREATER 0) - math(EXPR o3de_engine_projects_count "${o3de_engine_projects_count}-1") - foreach(engine_projects_index RANGE ${o3de_engine_projects_count}) - string(JSON engine_projects_path ERROR_VARIABLE json_error GET ${engine_data} projects ${engine_projects_index}) - if(json_error) - message(FATAL_ERROR "Unable to read engine projects[${projects_index}] '${o3de_manifest_json_path}', error: ${json_error}") - endif() - list(APPEND o3de_projects ${engine_projects_path}) - list(APPEND o3de_engine_projects ${engine_projects_path}) - endforeach() - endif() - - ################################################################################ - # o3de engine gems - ################################################################################ - string(JSON o3de_engine_gems_count ERROR_VARIABLE json_error LENGTH ${engine_data} gems) - if(json_error) - message(FATAL_ERROR "Unable to read key 'gems' from '${engine_data}', error: ${json_error}") - endif() - if(${o3de_engine_gems_count} GREATER 0) - math(EXPR o3de_engine_gems_count "${o3de_engine_gems_count}-1") - foreach(engine_gems_index RANGE ${o3de_engine_gems_count}) - string(JSON engine_gems_path ERROR_VARIABLE json_error GET ${engine_data} gems ${engine_gems_index}) - if(json_error) - message(FATAL_ERROR "Unable to read engine gems[${gems_index}] '${o3de_manifest_json_path}', error: ${json_error}") - endif() - list(APPEND o3de_gems ${engine_gems_path}) - list(APPEND o3de_engine_gems ${engine_gems_path}) - endforeach() - endif() - - ################################################################################ - # o3de engine templates - ################################################################################ - string(JSON o3de_engine_templates_count ERROR_VARIABLE json_error LENGTH ${engine_data} templates) - if(json_error) - message(FATAL_ERROR "Unable to read key 'templates' from '${o3de_manifest_json_path}', error: ${json_error}") - endif() - if(${o3de_engine_gems_count} GREATER 0) - math(EXPR o3de_engine_templates_count "${o3de_engine_templates_count}-1") - foreach(engine_templates_index RANGE ${o3de_engine_templates_count}) - string(JSON engine_templates_path ERROR_VARIABLE json_error GET ${engine_data} templates ${engine_templates_index}) - if(json_error) - message(FATAL_ERROR "Unable to read engine templates[${templates_index}] '${o3de_manifest_json_path}', error: ${json_error}") - endif() - list(APPEND o3de_templates ${engine_templates_path}) - list(APPEND o3de_engine_templates ${engine_templates_path}) - endforeach() - endif() - - ################################################################################ - # o3de engine restricted - ################################################################################ - string(JSON o3de_engine_restricted_count ERROR_VARIABLE json_error LENGTH ${engine_data} restricted) - if(json_error) - message(FATAL_ERROR "Unable to read key 'restricted' from '${o3de_manifest_json_path}', error: ${json_error}") - endif() - if(${o3de_engine_restricted_count} GREATER 0) - math(EXPR o3de_engine_restricted_count "${o3de_engine_restricted_count}-1") - foreach(engine_restricted_index RANGE ${o3de_engine_restricted_count}) - string(JSON engine_restricted_path ERROR_VARIABLE json_error GET ${engine_data} restricted ${engine_restricted_index}) - if(json_error) - message(FATAL_ERROR "Unable to read engine restricted[${engine_restricted_index}] '${o3de_manifest_json_path}', error: ${json_error}") - endif() - list(APPEND o3de_restricted ${engine_restricted_path}) - list(APPEND o3de_engine_restricted ${engine_restricted_path}) - endforeach() - endif() - - ################################################################################ - # o3de engine external_subdirectories - ################################################################################ - string(JSON o3de_external_subdirectories_count ERROR_VARIABLE json_error LENGTH ${engine_data} external_subdirectories) - if(json_error) - message(FATAL_ERROR "Unable to read key 'external_subdirectories' from '${o3de_manifest_json_path}', error: ${json_error}") - endif() - if(${o3de_external_subdirectories_count} GREATER 0) - math(EXPR o3de_external_subdirectories_count "${o3de_external_subdirectories_count}-1") - foreach(external_subdirectories_index RANGE ${o3de_external_subdirectories_count}) - string(JSON external_subdirectories_path ERROR_VARIABLE json_error GET ${engine_data} external_subdirectories ${external_subdirectories_index}) - if(json_error) - message(FATAL_ERROR "Unable to read engine external_subdirectories[${gems_index}] '${o3de_manifest_json_path}', error: ${json_error}") - endif() - list(APPEND o3de_engine_external_subdirectories ${external_subdirectories_path}) - endforeach() - endif() - - break() - - endif() - endforeach() -endif() - - -################################################################################ -#! o3de_engine_id: -# -# \arg:engine returns the engine association element from an o3de json -# \arg:o3de_json_file name of the o3de json file -################################################################################ -function(o3de_engine_id o3de_json_file engine) - file(READ ${o3de_json_file} json_data) - string(JSON engine_entry ERROR_VARIABLE json_error GET ${json_data} engine) - if(json_error) - message(WARNING "Unable to read engine from '${o3de_json_file}', error: ${json_error}") - message(WARNING "Setting engine to engine default 'o3de'") - set(engine_entry "o3de") - endif() - if(engine_entry) - set(${engine} ${engine_entry} PARENT_SCOPE) - endif() -endfunction() - - -################################################################################ -#! o3de_project_id: -# -# \arg:project returns the project association element from an o3de json -# \arg:o3de_json_file name of the o3de json file -################################################################################ -function(o3de_project_id o3de_json_file project) - file(READ ${o3de_json_file} json_data) - string(JSON project_entry ERROR_VARIABLE json_error GET ${json_data} project) - if(json_error) - message(FATAL_ERROR "Unable to read project from '${o3de_json_file}', error: ${json_error}") - endif() - if(project_entry) - set(${project} ${project_entry} PARENT_SCOPE) - endif() -endfunction() - - -################################################################################ -#! o3de_gem_id: -# -# \arg:gem returns the gem association element from an o3de json -# \arg:o3de_json_file name of the o3de json file -################################################################################ -function(o3de_gem_id o3de_json_file gem) - file(READ ${o3de_json_file} json_data) - string(JSON gem_entry ERROR_VARIABLE json_error GET ${json_data} gem) - if(json_error) - message(FATAL_ERROR "Unable to read gem from '${o3de_json_file}', error: ${json_error}") - endif() - if(gem_entry) - set(${gem} ${gem_entry} PARENT_SCOPE) - endif() -endfunction() - - -################################################################################ -#! o3de_template_id: -# -# \arg:template returns the template association element from an o3de json -# \arg:o3de_json_file name of the o3de json file -################################################################################ -function(o3de_template_id o3de_json_file template) - file(READ ${o3de_json_file} json_data) - string(JSON template_entry ERROR_VARIABLE json_error GET ${json_data} template) - if(json_error) - message(FATAL_ERROR "Unable to read template from '${o3de_json_file}', error: ${json_error}") - endif() - if(template_entry) - set(${template} ${template_entry} PARENT_SCOPE) - endif() -endfunction() - - -################################################################################ -#! o3de_repo_id: -# -# \arg:repo returns the repo association element from an o3de json -# \arg:o3de_json_file name of the o3de json file -################################################################################ -function(o3de_repo_id o3de_json_file repo) - file(READ ${o3de_json_file} json_data) - string(JSON repo_entry ERROR_VARIABLE json_error GET ${json_data} repo) - if(json_error) - message(FATAL_ERROR "Unable to read repo from '${o3de_json_file}', error: ${json_error}") - endif() - if(repo_entry) - set(${repo} ${repo_entry} PARENT_SCOPE) - endif() -endfunction() - - -################################################################################ -#! o3de_restricted_id: -# -# \arg:restricted returns the restricted association element from an o3de json, otherwise engine 'o3de' is assumed -# \arg:o3de_json_file name of the o3de json file -################################################################################ -function(o3de_restricted_id o3de_json_file restricted) - file(READ ${o3de_json_file} json_data) - string(JSON restricted_entry ERROR_VARIABLE json_error GET ${json_data} restricted) - if(json_error) - message(WARNING "Unable to read restricted from '${o3de_json_file}', error: ${json_error}") - message(WARNING "Setting restricted to engine default 'o3de'") - set(restricted_entry "o3de") - endif() - if(restricted_entry) - set(${restricted} ${restricted_entry} PARENT_SCOPE) - endif() -endfunction() - - -################################################################################ -#! o3de_find_engine_folder: -# -# \arg:engine_path returns the path of the o3de engine folder with name engine_name -# \arg:engine_name name of the engine -################################################################################ -function(o3de_find_engine_folder engine_name engine_path) - foreach(engine_entry ${o3de_engines}) - set(engine_json_file ${engine_entry}/engine.json) - file(READ ${engine_json_file} engine_json) - string(JSON this_engine_name ERROR_VARIABLE json_error GET ${engine_json} engine_name) - if(json_error) - message(WARNING "Unable to read engine_name from '${engine_json_file}', error: ${json_error}") - else() - if(this_engine_name STREQUAL engine_name) - set(${engine_path} ${engine_entry} PARENT_SCOPE) - return() - endif() - endif() - endforeach() - message(FATAL_ERROR "Unable to find repo_name: '${engine_name}'") -endfunction() - - -################################################################################ -#! o3de_find_project_folder: -# -# \arg:project_path returns the path of the o3de project folder with name project_name -# \arg:project_name name of the project -################################################################################ -function(o3de_find_project_folder project_name project_path) - foreach(project_entry ${o3de_projects}) - set(project_json_file ${project_entry}/project.json) - file(READ ${project_json_file} project_json) - string(JSON this_project_name ERROR_VARIABLE json_error GET ${project_json} project_name) - if(json_error) - message(WARNING "Unable to read project_name from '${project_json_file}', error: ${json_error}") - else() - if(this_project_name STREQUAL project_name) - set(${project_path} ${project_entry} PARENT_SCOPE) - return() - endif() - endif() - endforeach() - message(FATAL_ERROR "Unable to find project_name: '${project_name}'") -endfunction() - - -################################################################################ -#! o3de_find_gem_folder: -# -# \arg:gem_path returns the path of the o3de gem folder with name gem_name -# \arg:gem_name name of the gem -################################################################################ -function(o3de_find_gem_folder gem_name gem_path) - foreach(gem_entry ${o3de_gems}) - set(gem_json_file ${gem_entry}/gem.json) - file(READ ${gem_json_file} gem_json) - string(JSON this_gem_name ERROR_VARIABLE json_error GET ${gem_json} gem_name) - if(json_error) - message(WARNING "Unable to read gem_name from '${gem_json_file}', error: ${json_error}") - else() - if(this_gem_name STREQUAL gem_name) - set(${gem_path} ${gem_entry} PARENT_SCOPE) - return() - endif() - endif() - endforeach() - message(FATAL_ERROR "Unable to find gem_name: '${gem_name}'") -endfunction() - - -################################################################################ -#! o3de_find_template_folder: -# -# \arg:template_path returns the path of the o3de template folder with name template_name -# \arg:template_name name of the template -################################################################################ -function(o3de_find_template_folder template_name template_path) - foreach(template_entry ${o3de_templates}) - set(template_json_file ${template_entry}/template.json) - file(READ ${template_json_file} template_json) - string(JSON this_template_name ERROR_VARIABLE json_error GET ${template_json} template_name) - if(json_error) - message(WARNING "Unable to read template_name from '${template_json_file}', error: ${json_error}") - else() - if(this_template_name STREQUAL template_name) - set(${template_path} ${template_entry} PARENT_SCOPE) - return() - endif() - endif() - endforeach() - message(FATAL_ERROR "Unable to find template_name: '${template_name}'") -endfunction() - - -################################################################################ -#! o3de_find_repo_folder: -# -# \arg:repo_path returns the path of the o3de repo folder with name repo_name -# \arg:repo_name name of the repo -################################################################################ -function(o3de_find_repo_folder repo_name repo_path) - foreach(repo_entry ${o3de_repos}) - set(repo_json_file ${repo_entry}/repo.json) - file(READ ${repo_json_file} repo_json) - string(JSON this_repo_name ERROR_VARIABLE json_error GET ${repo_json} repo_name) - if(json_error) - message(WARNING "Unable to read repo_name from '${repo_json_file}', error: ${json_error}") - else() - if(this_repo_name STREQUAL repo_name) - set(${repo_path} ${repo_entry} PARENT_SCOPE) - return() - endif() - endif() - endforeach() - message(FATAL_ERROR "Unable to find repo_name: '${repo_name}'") -endfunction() - - -################################################################################ -#! o3de_find_restricted_folder: -# -# \arg:restricted_path returns the path of the o3de restricted folder with name restricted_name -# \arg:restricted_name name of the restricted -################################################################################ -function(o3de_find_restricted_folder restricted_name restricted_path) - foreach(restricted_entry ${o3de_restricted}) - set(restricted_json_file ${restricted_entry}/restricted.json) - file(READ ${restricted_json_file} restricted_json) - string(JSON this_restricted_name ERROR_VARIABLE json_error GET ${restricted_json} restricted_name) - if(json_error) - message(WARNING "Unable to read restricted_name from '${restricted_json_file}', error: ${json_error}") - else() - if(this_restricted_name STREQUAL restricted_name) - set(${restricted_path} ${restricted_entry} PARENT_SCOPE) - return() - endif() - endif() - endforeach() - message(FATAL_ERROR "Unable to find restricted_name: '${restricted_name}'") -endfunction() - - -################################################################################ -#! o3de_engine_name: -# -# \arg:engine returns the engine_name element from an engine.json -# \arg:o3de_engine_json_file name of the o3de json file -################################################################################ -function(o3de_engine_name o3de_engine_json_file engine) - file(READ ${o3de_engine_json_file} json_data) - string(JSON engine_entry ERROR_VARIABLE json_error GET ${json_data} engine_name) - if(json_error) - message(FATAL_ERROR "Unable to read engine_name from '${o3de_json_file}', error: ${json_error}") - endif() - if(engine_entry) - set(${engine} ${engine_entry} PARENT_SCOPE) - endif() -endfunction() - - -################################################################################ -#! o3de_project_name: -# -# \arg:project returns the project_name element from an project.json -# \arg:o3de_project_json_file name of the o3de json file -################################################################################ -function(o3de_project_name o3de_project_json_file project) - file(READ ${o3de_project_json_file} json_data) - string(JSON project_entry ERROR_VARIABLE json_error GET ${json_data} project_name) - if(json_error) - message(FATAL_ERROR "Unable to read project_name from '${o3de_json_file}', error: ${json_error}") - endif() - if(project_entry) - set(${project} ${project_entry} PARENT_SCOPE) - endif() -endfunction() - - -################################################################################ -#! o3de_gem_name: -# -# \arg:gem returns the gem_name element from an gem.json -# \arg:o3de_gem_json_file name of the o3de json file -################################################################################ -function(o3de_gem_name o3de_gem_json_file gem) - file(READ ${o3de_gem_json_file} json_data) - string(JSON gem_entry ERROR_VARIABLE json_error GET ${json_data} gem_name) - if(json_error) - message(FATAL_ERROR "Unable to read gem_name from '${o3de_json_file}', error: ${json_error}") - endif() - if(gem_entry) - set(${gem} ${gem_entry} PARENT_SCOPE) - endif() -endfunction() - - -################################################################################ -#! o3de_template_name: -# -# \arg:template returns the template_name element from an template json -# \arg:o3de_template_json_file name of the o3de json file -################################################################################ -function(o3de_template_name o3de_template_json_file template) - file(READ ${o3de_template_json_file} json_data) - string(JSON template_entry ERROR_VARIABLE json_error GET ${json_data} template_name) - if(json_error) - message(FATAL_ERROR "Unable to read template_name from '${o3de_json_file}', error: ${json_error}") - endif() - if(template_entry) - set(${template} ${template_entry} PARENT_SCOPE) - endif() -endfunction() - - -################################################################################ -#! o3de_repo_name: -# -# \arg:repo returns the repo_name element from an repo.json or o3de_manifest.json -# \arg:o3de_repo_json_file name of the o3de json file -################################################################################ -function(o3de_repo_name o3de_repo_json_file repo) - file(READ ${o3de_repo_json_file} json_data) - string(JSON repo_entry ERROR_VARIABLE json_error GET ${json_data} repo_name) - if(json_error) - message(FATAL_ERROR "Unable to read repo_name from '${o3de_json_file}', error: ${json_error}") - endif() - if(repo_entry) - set(${repo} ${repo_entry} PARENT_SCOPE) - endif() -endfunction() - - -################################################################################ -#! o3de_restricted_name: -# -# \arg:restricted returns the restricted association element from an o3de json -# \arg:o3de_json_file name of the o3de json file -################################################################################ -function(o3de_restricted_name o3de_json_file restricted) - file(READ ${o3de_json_file} json_data) - string(JSON restricted_entry ERROR_VARIABLE json_error GET ${json_data} restricted_name) - if(json_error) - message(WARNING "FATAL_ERROR to read restricted_name from '${o3de_json_file}', error: ${json_error}") - endif() - if(restricted_entry) - set(${restricted} ${restricted_entry} PARENT_SCOPE) - endif() -endfunction() - - -################################################################################ -#! o3de_engine_path: -# -# \arg:engine_path returns the path of the o3de engine folder with name engine_name -# \arg:engine_name name of the engine -################################################################################ -function(o3de_engine_path o3de_json_file engine_path) - o3de_engine_id(${o3de_json_file} engine_name) - if(engine_name) - o3de_find_engine_folder(${engine_name} engine_folder) - if(engine_folder) - set(${engine_path} ${engine_folder} PARENT_SCOPE) - endif() - endif() -endfunction() - - -################################################################################ -#! o3de_project_path: -# -# \arg:project_path returns the path of the o3de project folder with name project_name -# \arg:project_name name of the project -################################################################################ -function(o3de_project_path o3de_json_file project_path) - o3de_project_id(${o3de_json_file} project_name) - if(project_name) - o3de_find_project_folder(${project_name} project_folder) - if(project_folder) - set(${project_path} ${project_folder} PARENT_SCOPE) - endif() - endif() -endfunction() - - -################################################################################ -#! o3de_template_path: -# -# \arg:template_path returns the path of the o3de template folder with name template_name -# \arg:template_name name of the template -################################################################################ -function(o3de_template_path o3de_json_file template_path) - o3de_template_id(${o3de_json_file} template_name) - if(template_name) - o3de_find_template_folder(${template_name} template_folder) - if(template_folder) - set(${template_path} ${template_folder} PARENT_SCOPE) - endif() - endif() -endfunction() - - -################################################################################ -#! o3de_repo_path: -# -# \arg:repo_path returns the path of the o3de repo folder with name repo_name -# \arg:repo_name name of the repo -################################################################################ -function(o3de_repo_path o3de_json_file repo_path) - o3de_repo_id(${o3de_json_file} repo_name) - if(repo_name) - o3de_find_repo_folder(${repo_name} repo_folder) - if(repo_folder) - set(${repo_path} ${repo_folder} PARENT_SCOPE) - endif() - endif() -endfunction() - - -################################################################################ -#! o3de_restricted_path: -# -# \arg:restricted_path returns the path of the o3de restricted folder with name restricted_name -# \arg:restricted_name name of the restricted -################################################################################ -function(o3de_restricted_path o3de_json_file restricted_path) - o3de_restricted_id(${o3de_json_file} restricted_name) - if(restricted_name) - o3de_find_restricted_folder(${restricted_name} restricted_folder) - if(restricted_folder) - set(${restricted_path} ${restricted_folder} PARENT_SCOPE) - endif() - endif() -endfunction() diff --git a/engine.json b/engine.json index 22ac2512f5..09bb3f5ff6 100644 --- a/engine.json +++ b/engine.json @@ -1,7 +1,98 @@ { "engine_name": "o3de", - "restricted": "o3de", + "restricted_name": "o3de", "FileVersion": 1, "O3DEVersion": "0.0.0.0", - "O3DECopyrightYear": 2021 + "O3DECopyrightYear": 2021, + "O3DEBuildNumber": 0, + "external_subdirectories": [ + "Gems/Achievements", + "Gems/AssetMemoryAnalyzer", + "Gems/AssetValidation", + "Gems/Atom", + "Gems/AtomContent", + "Gems/AtomLyIntegration", + "Gems/AtomTressFX", + "Gems/AudioEngineWwise", + "Gems/AudioSystem", + "Gems/AutomatedLauncherTesting", + "Gems/AWSClientAuth", + "Gems/AWSCore", + "Gems/AWSMetrics", + "Gems/Blast", + "Gems/Camera", + "Gems/CameraFramework", + "Gems/CertificateManager", + "Gems/CrashReporting", + "Gems/CustomAssetExample", + "Gems/DebugDraw", + "Gems/DevTextures", + "Gems/EditorPythonBindings", + "Gems/EMotionFX", + "Gems/ExpressionEvaluation", + "Gems/FastNoise", + "Gems/GameState", + "Gems/GameStateSamples", + "Gems/Gestures", + "Gems/GradientSignal", + "Gems/GraphCanvas", + "Gems/GraphModel", + "Gems/HttpRequestor", + "Gems/ImGui", + "Gems/InAppPurchases", + "Gems/LandscapeCanvas", + "Gems/LmbrCentral", + "Gems/LocalUser", + "Gems/LyShine", + "Gems/LyShineExamples", + "Gems/Maestro", + "Gems/MessagePopup", + "Gems/Metastream", + "Gems/Microphone", + "Gems/Multiplayer", + "Gems/MultiplayerCompression", + "Gems/NvCloth", + "Gems/PBSreferenceMaterials", + "Gems/PhysicsEntities", + "Gems/PhysX", + "Gems/PhysXDebug", + "Gems/PhysXSamples", + "Gems/Prefab", + "Gems/Presence", + "Gems/PrimitiveAssets", + "Gems/PythonAssetBuilder", + "Gems/QtForPython", + "Gems/RADTelemetry", + "Gems/SaveData", + "Gems/SceneLoggingExample", + "Gems/SceneProcessing", + "Gems/ScriptCanvas", + "Gems/ScriptCanvasDeveloper", + "Gems/ScriptCanvasPhysics", + "Gems/ScriptCanvasTesting", + "Gems/ScriptedEntityTweener", + "Gems/ScriptEvents", + "Gems/SliceFavorites", + "Gems/StartingPointCamera", + "Gems/StartingPointInput", + "Gems/StartingPointMovement", + "Gems/SurfaceData", + "Gems/TestAssetBuilder", + "Gems/TextureAtlas", + "Gems/TickBusOrderViewer", + "Gems/Twitch", + "Gems/UiBasics", + "Gems/Vegetation", + "Gems/Vegetation_Gem_Assets", + "Gems/VideoPlaybackFramework", + "Gems/VirtualGamepad", + "Gems/WhiteBox" + ], + "projects": [ + "AutomatedTesting" + ], + "templates": [ + "Templates/DefaultGem", + "Templates/DefaultProject" + ] } diff --git a/python/get_python.bat b/python/get_python.bat index e9f18441b5..e11c4ab92f 100644 --- a/python/get_python.bat +++ b/python/get_python.bat @@ -25,7 +25,8 @@ call python.cmd --version > NUL IF !ERRORLEVEL!==0 ( echo get_python.bat: Python is already installed: call python.cmd --version - call "%CMD_DIR%\pip.cmd" install -r "%CMD_DIR%/requirements.txt" --quiet --disable-pip-version-check + call "%CMD_DIR%\pip.cmd" install -r "%CMD_DIR%/requirements.txt" --quiet --disable-pip-version-check --no-warn-script-location + call "%CMD_DIR%\pip.cmd" install -e "%CMD_DIR%/../scripts/o3de" --quiet --disable-pip-version-check --no-warn-script-location --no-deps exit /B 0 ) @@ -65,6 +66,7 @@ if ERRORLEVEL 1 ( ) echo calling PIP to install requirements... -call "%CMD_DIR%\pip.cmd" install -r "%CMD_DIR%/requirements.txt" --disable-pip-version-check +call "%CMD_DIR%\pip.cmd" install -r "%CMD_DIR%/requirements.txt" --disable-pip-version-check --no-warn-script-location +call "%CMD_DIR%\pip.cmd" install -e "%CMD_DIR%/../scripts/o3de" --disable-pip-version-check --no-warn-script-location --no-deps exit /B %ERRORLEVEL% diff --git a/scripts/CMakeLists.txt b/scripts/CMakeLists.txt index d2843a9013..d3c9640665 100644 --- a/scripts/CMakeLists.txt +++ b/scripts/CMakeLists.txt @@ -11,5 +11,6 @@ add_subdirectory(detect_file_changes) add_subdirectory(commit_validation) +add_subdirectory(o3de) add_subdirectory(project_manager) add_subdirectory(ctest) diff --git a/scripts/build/Platform/Android/build_config.json b/scripts/build/Platform/Android/build_config.json index fe5e223612..33248ffe26 100644 --- a/scripts/build/Platform/Android/build_config.json +++ b/scripts/build/Platform/Android/build_config.json @@ -35,7 +35,7 @@ "PARAMETERS": { "CONFIGURATION":"debug", "OUTPUT_DIRECTORY":"build\\android", - "CMAKE_OPTIONS":"-G \"Ninja Multi-Config\" -DCMAKE_TOOLCHAIN_FILE=cmake\\Platform\\Android\\Toolchain_android.cmake -DANDROID_ABI=arm64-v8a -DANDROID_ARM_MODE=arm -DANDROID_ARM_NEON=FALSE -DANDROID_NATIVE_API_LEVEL=21 -DLY_NDK_DIR=\"!LY_3RDPARTY_PATH!\\android-ndk\\r21d\" -DLY_UNITY_BUILD=TRUE -DO3DE_HOME_PATH=\"!WORKSPACE!/home\" -DO3DE_REGISTER_ENGINE_PATH=\"!WORKSPACE!/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", + "CMAKE_OPTIONS":"-G \"Ninja Multi-Config\" -DCMAKE_TOOLCHAIN_FILE=cmake\\Platform\\Android\\Toolchain_android.cmake -DANDROID_ABI=arm64-v8a -DANDROID_ARM_MODE=arm -DANDROID_ARM_NEON=FALSE -DANDROID_NATIVE_API_LEVEL=21 -DLY_NDK_DIR=\"!LY_3RDPARTY_PATH!\\android-ndk\\r21d\" -DLY_UNITY_BUILD=TRUE", "CMAKE_LY_PROJECTS":"AutomatedTesting", "CMAKE_TARGET":"all", "CMAKE_BUILD_ARGS":"-j!NUMBER_OF_PROCESSORS!" @@ -60,7 +60,7 @@ "PARAMETERS": { "CONFIGURATION":"profile", "OUTPUT_DIRECTORY":"build\\android", - "CMAKE_OPTIONS":"-G \"Ninja Multi-Config\" -DCMAKE_TOOLCHAIN_FILE=cmake\\Platform\\Android\\Toolchain_android.cmake -DANDROID_ABI=arm64-v8a -DANDROID_ARM_MODE=arm -DANDROID_ARM_NEON=FALSE -DANDROID_NATIVE_API_LEVEL=21 -DLY_NDK_DIR=\"!LY_3RDPARTY_PATH!\\android-ndk\\r21d\" -DLY_UNITY_BUILD=TRUE -DO3DE_HOME_PATH=\"!WORKSPACE!/home\" -DO3DE_REGISTER_ENGINE_PATH=\"!WORKSPACE!/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", + "CMAKE_OPTIONS":"-G \"Ninja Multi-Config\" -DCMAKE_TOOLCHAIN_FILE=cmake\\Platform\\Android\\Toolchain_android.cmake -DANDROID_ABI=arm64-v8a -DANDROID_ARM_MODE=arm -DANDROID_ARM_NEON=FALSE -DANDROID_NATIVE_API_LEVEL=21 -DLY_NDK_DIR=\"!LY_3RDPARTY_PATH!\\android-ndk\\r21d\" -DLY_UNITY_BUILD=TRUE", "CMAKE_LY_PROJECTS":"AutomatedTesting", "CMAKE_TARGET":"all", "CMAKE_BUILD_ARGS":"-j!NUMBER_OF_PROCESSORS!" @@ -76,7 +76,7 @@ "PARAMETERS": { "CONFIGURATION":"profile", "OUTPUT_DIRECTORY":"build\\android", - "CMAKE_OPTIONS":"-G \"Ninja Multi-Config\" -DCMAKE_TOOLCHAIN_FILE=cmake\\Platform\\Android\\Toolchain_android.cmake -DANDROID_ABI=arm64-v8a -DANDROID_ARM_MODE=arm -DANDROID_ARM_NEON=FALSE -DANDROID_NATIVE_API_LEVEL=21 -DLY_NDK_DIR=\"!LY_3RDPARTY_PATH!\\android-ndk\\r21d\" -DLY_UNITY_BUILD=FALSE -DO3DE_HOME_PATH=\"!WORKSPACE!/home\" -DO3DE_REGISTER_ENGINE_PATH=\"!WORKSPACE!/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", + "CMAKE_OPTIONS":"-G \"Ninja Multi-Config\" -DCMAKE_TOOLCHAIN_FILE=cmake\\Platform\\Android\\Toolchain_android.cmake -DANDROID_ABI=arm64-v8a -DANDROID_ARM_MODE=arm -DANDROID_ARM_NEON=FALSE -DANDROID_NATIVE_API_LEVEL=21 -DLY_NDK_DIR=\"!LY_3RDPARTY_PATH!\\android-ndk\\r21d\" -DLY_UNITY_BUILD=FALSE", "CMAKE_LY_PROJECTS":"AutomatedTesting", "CMAKE_TARGET":"all", "CMAKE_BUILD_ARGS":"-j!NUMBER_OF_PROCESSORS!" @@ -93,7 +93,7 @@ "PARAMETERS": { "CONFIGURATION":"profile", "OUTPUT_DIRECTORY":"build\\windows_vs2019", - "CMAKE_OPTIONS":"-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE -DO3DE_HOME_PATH=\"!WORKSPACE!/home\" -DO3DE_REGISTER_ENGINE_PATH=\"!WORKSPACE!/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", + "CMAKE_OPTIONS":"-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE", "CMAKE_LY_PROJECTS":"AutomatedTesting", "CMAKE_TARGET":"AssetProcessorBatch", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo", @@ -112,7 +112,7 @@ "PARAMETERS": { "CONFIGURATION":"release", "OUTPUT_DIRECTORY":"build\\android", - "CMAKE_OPTIONS":"-G \"Ninja Multi-Config\" -DCMAKE_TOOLCHAIN_FILE=cmake\\Platform\\Android\\Toolchain_android.cmake -DANDROID_ABI=arm64-v8a -DANDROID_ARM_MODE=arm -DANDROID_ARM_NEON=FALSE -DANDROID_NATIVE_API_LEVEL=21 -DLY_NDK_DIR=\"!LY_3RDPARTY_PATH!\\android-ndk\\r21d\" -DLY_UNITY_BUILD=TRUE -DO3DE_HOME_PATH=\"!WORKSPACE!/home\" -DO3DE_REGISTER_ENGINE_PATH=\"!WORKSPACE!/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", + "CMAKE_OPTIONS":"-G \"Ninja Multi-Config\" -DCMAKE_TOOLCHAIN_FILE=cmake\\Platform\\Android\\Toolchain_android.cmake -DANDROID_ABI=arm64-v8a -DANDROID_ARM_MODE=arm -DANDROID_ARM_NEON=FALSE -DANDROID_NATIVE_API_LEVEL=21 -DLY_NDK_DIR=\"!LY_3RDPARTY_PATH!\\android-ndk\\r21d\" -DLY_UNITY_BUILD=TRUE", "CMAKE_LY_PROJECTS":"AutomatedTesting", "CMAKE_TARGET":"all", "CMAKE_BUILD_ARGS":"-j!NUMBER_OF_PROCESSORS!" @@ -128,7 +128,7 @@ "PARAMETERS": { "CONFIGURATION":"release", "OUTPUT_DIRECTORY":"build\\mono_android", - "CMAKE_OPTIONS":"-G \"Ninja Multi-Config\" -DCMAKE_TOOLCHAIN_FILE=cmake\\Platform\\Android\\Toolchain_android.cmake -DANDROID_ABI=arm64-v8a -DANDROID_ARM_MODE=arm -DANDROID_ARM_NEON=FALSE -DANDROID_NATIVE_API_LEVEL=21 -DLY_NDK_DIR=\"!LY_3RDPARTY_PATH!\\android-ndk\\r21d\" -DLY_MONOLITHIC_GAME=TRUE -DLY_UNITY_BUILD=TRUE -DO3DE_HOME_PATH=\"!WORKSPACE!/home\" -DO3DE_REGISTER_ENGINE_PATH=\"!WORKSPACE!/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", + "CMAKE_OPTIONS":"-G \"Ninja Multi-Config\" -DCMAKE_TOOLCHAIN_FILE=cmake\\Platform\\Android\\Toolchain_android.cmake -DANDROID_ABI=arm64-v8a -DANDROID_ARM_MODE=arm -DANDROID_ARM_NEON=FALSE -DANDROID_NATIVE_API_LEVEL=21 -DLY_NDK_DIR=\"!LY_3RDPARTY_PATH!\\android-ndk\\r21d\" -DLY_MONOLITHIC_GAME=TRUE -DLY_UNITY_BUILD=TRUE", "CMAKE_LY_PROJECTS":"AutomatedTesting", "CMAKE_TARGET":"all", "CMAKE_BUILD_ARGS":"-j!NUMBER_OF_PROCESSORS!" @@ -136,15 +136,14 @@ }, "gradle": { "TAGS":[ + "default", "weekly-build-metrics" ], "COMMAND":"gradle_windows.cmd", "PARAMETERS": { "CONFIGURATION":"profile", - "OUTPUT_DIRECTORY":"build\\android_gradle", + "OUTPUT_DIRECTORY":"build\\ad_grd", "GAME_PROJECT": "AutomatedTesting", - "ANDROID_NDK_PLATFORM": "21", - "ANDROID_SDK_PLATFORM": "29", "SIGN_APK": "false", "GRADLE_BUILD_CMD": "build", "ADDITIONAL_GENERATE_ARGS": "" @@ -158,8 +157,6 @@ "CONFIGURATION":"profile", "OUTPUT_DIRECTORY":"build\\android_unittest", "GAME_PROJECT": "AutomatedTesting", - "ANDROID_NDK_PLATFORM": "21", - "ANDROID_SDK_PLATFORM": "29", "SIGN_APK": "true", "GRADLE_BUILD_CMD": "assemble", "ADDITIONAL_GENERATE_ARGS": "--unit-test" diff --git a/scripts/build/Platform/Android/gradle_windows.cmd b/scripts/build/Platform/Android/gradle_windows.cmd index 56423af95a..dd5285bdbf 100644 --- a/scripts/build/Platform/Android/gradle_windows.cmd +++ b/scripts/build/Platform/Android/gradle_windows.cmd @@ -17,20 +17,12 @@ IF NOT EXIST "%LY_3RDPARTY_PATH%" ( GOTO :error ) -IF NOT EXIST "%GRADLE_HOME%" ( +IF NOT EXIST "%GRADLE_BUILD_HOME%" ( REM This is the default for developers - SET GRADLE_HOME=C:\Gradle\gradle-5.6.4 + SET GRADLE_BUILD_HOME=C:\Gradle\gradle-7.0 ) -IF NOT EXIST "%GRADLE_HOME%" ( - ECHO [ci_build] FAIL: GRADLE_HOME=%GRADLE_HOME% - GOTO :error -) - -IF NOT EXIST "%CMAKE_HOME%" ( - SET CMAKE_HOME=%LY_3RDPARTY_PATH%/CMake/3.19.1/Windows/ -) -IF NOT EXIST "%CMAKE_HOME%" ( - ECHO [ci_build] FAIL: CMAKE_HOME=%CMAKE_HOME% +IF NOT EXIST "%GRADLE_BUILD_HOME%" ( + ECHO [ci_build] FAIL: GRADLE_BUILD_HOME=%GRADLE_BUILD_HOME% GOTO :error ) @@ -50,20 +42,9 @@ ECHO Ninja wasnt in the call path, add the value set by LY_NINJA_PATH SET PATH=%PATH%;%LY_NINJA_PATH% :ninja_on_path -IF NOT EXIST "%LY_ANDROID_SDK%" ( - SET LY_ANDROID_SDK=!LY_3RDPARTY_PATH!/android-sdk/platform-29 -) -IF NOT EXIST "%LY_ANDROID_SDK%" ( - ECHO [ci_build] FAIL: LY_ANDROID_SDK=!LY_ANDROID_SDK! - GOTO :error -) -IF NOT EXIST "%LY_ANDROID_NDK%" ( - set LY_ANDROID_NDK=!LY_3RDPARTY_PATH!/android-ndk/r21d -) -IF NOT EXIST "%LY_ANDROID_NDK%" ( - ECHO [ci_build] LY_ANDROID_NDK=!LY_ANDROID_NDK! - GOTO :error +IF NOT "%ANDROID_GRADLE_PLUGIN%" == "" ( + set ANDROID_GRADLE_PLUGIN_OPTION=--gradle-plugin-version=%ANDROID_GRADLE_PLUGIN% ) IF NOT EXIST %OUTPUT_DIRECTORY% ( @@ -154,11 +135,11 @@ IF "%GENERATE_SIGNED_APK%"=="true" ( ECHO Using keystore file at %CI_ANDROID_KEYSTORE_FILE_ABS% ) - ECHO [ci_build] %PYTHON% cmake\Tools\Platform\Android\generate_android_project.py --engine-root=. --build-dir=%OUTPUT_DIRECTORY% -g %GAME_PROJECT% --gradle-install-path=%GRADLE_HOME% --cmake-install-path=%CMAKE_HOME% --ninja-install-path=%LY_NINJA_PATH% --third-party-path=%LY_3RDPARTY_PATH% --android-ndk-path=%LY_ANDROID_NDK% --android-sdk-path=%LY_ANDROID_SDK% --android-ndk-version=%ANDROID_NDK_PLATFORM% --android-sdk-version=%ANDROID_SDK_PLATFORM% --signconfig-store-file %CI_ANDROID_KEYSTORE_FILE_ABS% --signconfig-store-password %CI_ANDROID_KEYSTORE_PASSWORD% --signconfig-key-alias %CI_ANDROID_KEYSTORE_ALIAS% --signconfig-key-password %CI_ANDROID_KEYSTORE_PASSWORD% %OPTIONAL_TEST_FLAG% %ADDITIONAL_GENERATE_ARGS% --overwrite-existing - CALL %PYTHON% cmake\Tools\Platform\Android\generate_android_project.py --engine-root=. --build-dir=%OUTPUT_DIRECTORY% -g %GAME_PROJECT% --gradle-install-path=%GRADLE_HOME% --cmake-install-path=%CMAKE_HOME% --ninja-install-path=%LY_NINJA_PATH% --third-party-path=%LY_3RDPARTY_PATH% --android-ndk-path=%LY_ANDROID_NDK% --android-sdk-path=%LY_ANDROID_SDK% --android-ndk-version=%ANDROID_NDK_PLATFORM% --android-sdk-version=%ANDROID_SDK_PLATFORM% --signconfig-store-file %CI_ANDROID_KEYSTORE_FILE_ABS% --signconfig-store-password %CI_ANDROID_KEYSTORE_PASSWORD% --signconfig-key-alias %CI_ANDROID_KEYSTORE_ALIAS% --signconfig-key-password %CI_ANDROID_KEYSTORE_PASSWORD% %ADDITIONAL_GENERATE_ARGS% --overwrite-existing + ECHO [ci_build] %PYTHON% cmake\Tools\Platform\Android\generate_android_project.py --engine-root=. --build-dir=%OUTPUT_DIRECTORY% -g %GAME_PROJECT% --gradle-install-path=%GRADLE_BUILD_HOME% --ninja-install-path=%LY_NINJA_PATH% --third-party-path=%LY_3RDPARTY_PATH% --android-sdk-path=%ANDROID_HOME% %ANDROID_GRADLE_PLUGIN_OPTION% --signconfig-store-file %CI_ANDROID_KEYSTORE_FILE_ABS% --signconfig-store-password %CI_ANDROID_KEYSTORE_PASSWORD% --signconfig-key-alias %CI_ANDROID_KEYSTORE_ALIAS% --signconfig-key-password %CI_ANDROID_KEYSTORE_PASSWORD% %ADDITIONAL_GENERATE_ARGS% --overwrite-existing + CALL %PYTHON% cmake\Tools\Platform\Android\generate_android_project.py --engine-root=. --build-dir=%OUTPUT_DIRECTORY% -g %GAME_PROJECT% --gradle-install-path=%GRADLE_BUILD_HOME% --ninja-install-path=%LY_NINJA_PATH% --third-party-path=%LY_3RDPARTY_PATH% --android-sdk-path=%ANDROID_HOME% %ANDROID_GRADLE_PLUGIN_OPTION% --signconfig-store-file %CI_ANDROID_KEYSTORE_FILE_ABS% --signconfig-store-password %CI_ANDROID_KEYSTORE_PASSWORD% --signconfig-key-alias %CI_ANDROID_KEYSTORE_ALIAS% --signconfig-key-password %CI_ANDROID_KEYSTORE_PASSWORD% %ADDITIONAL_GENERATE_ARGS% --overwrite-existing ) ELSE ( - ECHO [ci_build] %PYTHON% cmake\Tools\Platform\Android\generate_android_project.py --engine-root=. --build-dir=%OUTPUT_DIRECTORY% -g %GAME_PROJECT% --gradle-install-path=%GRADLE_HOME% --cmake-install-path=%CMAKE_HOME% --ninja-install-path=%LY_NINJA_PATH% --third-party-path=%LY_3RDPARTY_PATH% --android-ndk-path=%LY_ANDROID_NDK% --android-sdk-path=%LY_ANDROID_SDK% --android-ndk-version=%ANDROID_NDK_PLATFORM% --android-sdk-version=%ANDROID_SDK_PLATFORM% %ADDITIONAL_GENERATE_ARGS% --overwrite-existing - CALL %PYTHON% cmake\Tools\Platform\Android\generate_android_project.py --engine-root=. --build-dir=%OUTPUT_DIRECTORY% -g %GAME_PROJECT% --gradle-install-path=%GRADLE_HOME% --cmake-install-path=%CMAKE_HOME% --ninja-install-path=%LY_NINJA_PATH% --third-party-path=%LY_3RDPARTY_PATH% --android-ndk-path=%LY_ANDROID_NDK% --android-sdk-path=%LY_ANDROID_SDK% --android-ndk-version=%ANDROID_NDK_PLATFORM% --android-sdk-version=%ANDROID_SDK_PLATFORM% %ADDITIONAL_GENERATE_ARGS% --overwrite-existing + ECHO [ci_build] %PYTHON% cmake\Tools\Platform\Android\generate_android_project.py --engine-root=. --build-dir=%OUTPUT_DIRECTORY% -g %GAME_PROJECT% %GRADLE_OVERRIDE_OPTION% --ninja-install-path=%LY_NINJA_PATH% --third-party-path=%LY_3RDPARTY_PATH% %ANDROID_GRADLE_PLUGIN_OPTION% --android-sdk-path=%ANDROID_HOME% %ADDITIONAL_GENERATE_ARGS% --overwrite-existing + CALL %PYTHON% cmake\Tools\Platform\Android\generate_android_project.py --engine-root=. --build-dir=%OUTPUT_DIRECTORY% -g %GAME_PROJECT% --gradle-install-path=%GRADLE_BUILD_HOME% --ninja-install-path=%LY_NINJA_PATH% --third-party-path=%LY_3RDPARTY_PATH% %ANDROID_GRADLE_PLUGIN_OPTION% --android-sdk-path=%ANDROID_HOME% %ADDITIONAL_GENERATE_ARGS% --overwrite-existing ) REM Validate the android project generation diff --git a/scripts/build/Platform/Android/pipeline.json b/scripts/build/Platform/Android/pipeline.json index ed10e7022d..551374a027 100644 --- a/scripts/build/Platform/Android/pipeline.json +++ b/scripts/build/Platform/Android/pipeline.json @@ -1,7 +1,7 @@ { "ENV": { - "GRADLE_HOME": "C:/Gradle/gradle-5.6.4", - "NODE_LABEL": "windows-047e5cdf", + "GRADLE_HOME": "C:/Gradle/gradle-7.0", + "NODE_LABEL": "windows-b3c8994f1", "LY_3RDPARTY_PATH": "C:/ly/3rdParty", "TIMEOUT": 30, "WORKSPACE": "D:/workspace", diff --git a/scripts/build/Platform/Linux/build_config.json b/scripts/build/Platform/Linux/build_config.json index 610c6a6514..ee6da77b29 100644 --- a/scripts/build/Platform/Linux/build_config.json +++ b/scripts/build/Platform/Linux/build_config.json @@ -37,7 +37,7 @@ "PARAMETERS": { "CONFIGURATION": "debug", "OUTPUT_DIRECTORY": "build/linux", - "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4 -DO3DE_HOME_PATH=\"${WORKSPACE}/home\" -DO3DE_REGISTER_ENGINE_PATH=\"${WORKSPACE}/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", + "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "all" } @@ -53,7 +53,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/linux", - "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4 -DO3DE_HOME_PATH=\"${WORKSPACE}/home\" -DO3DE_REGISTER_ENGINE_PATH=\"${WORKSPACE}/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", + "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "all" } @@ -66,7 +66,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/linux", - "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=FALSE -DLY_PARALLEL_LINK_JOBS=4 -DO3DE_HOME_PATH=\"${WORKSPACE}/home\" -DO3DE_REGISTER_ENGINE_PATH=\"${WORKSPACE}/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", + "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=FALSE -DLY_PARALLEL_LINK_JOBS=4", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "all" } @@ -80,7 +80,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/linux", - "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4 -DO3DE_HOME_PATH=\"${WORKSPACE}/home\" -DO3DE_REGISTER_ENGINE_PATH=\"${WORKSPACE}/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", + "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "all", "CTEST_OPTIONS": "-E (Gem::EMotionFX.Editor.Tests|Gem::AWSClientAuth.Tests|Gem::AWSCore.Editor.Tests) -LE SUITE_sandbox -L FRAMEWORK_googletest" @@ -92,7 +92,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/linux", - "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=FALSE -DLY_PARALLEL_LINK_JOBS=4 -DO3DE_HOME_PATH=\"${WORKSPACE}/home\" -DO3DE_REGISTER_ENGINE_PATH=\"${WORKSPACE}/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", + "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=FALSE -DLY_PARALLEL_LINK_JOBS=4", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "all", "CTEST_OPTIONS": "-E (Gem::EMotionFX.Editor.Tests|Gem::AWSClientAuth.Tests|Gem::AWSCore.Editor.Tests) -LE SUITE_sandbox -L FRAMEWORK_googletest" @@ -108,7 +108,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/linux", - "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4 -DO3DE_HOME_PATH=\"${WORKSPACE}/home\" -DO3DE_REGISTER_ENGINE_PATH=\"${WORKSPACE}/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", + "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "AssetProcessorBatch", "ASSET_PROCESSOR_BINARY": "bin/profile/AssetProcessorBatch", @@ -122,7 +122,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/linux", - "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=FALSE -DLY_PARALLEL_LINK_JOBS=4 -DO3DE_HOME_PATH=\"${WORKSPACE}/home\" -DO3DE_REGISTER_ENGINE_PATH=\"${WORKSPACE}/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", + "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=FALSE -DLY_PARALLEL_LINK_JOBS=4", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "AssetProcessorBatch", "ASSET_PROCESSOR_BINARY": "bin/profile/AssetProcessorBatch", @@ -140,7 +140,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/linux", - "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4 -DO3DE_HOME_PATH=\"${WORKSPACE}/home\" -DO3DE_REGISTER_ENGINE_PATH=\"${WORKSPACE}/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", + "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "TEST_SUITE_periodic", "CTEST_OPTIONS": "-L (SUITE_periodic)" @@ -175,7 +175,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/linux", - "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4 -DO3DE_HOME_PATH=\"${WORKSPACE}/home\" -DO3DE_REGISTER_ENGINE_PATH=\"${WORKSPACE}/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", + "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "TEST_SUITE_benchmark", "CTEST_OPTIONS": "-L (SUITE_benchmark)" @@ -191,7 +191,7 @@ "PARAMETERS": { "CONFIGURATION": "release", "OUTPUT_DIRECTORY": "build/linux", - "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4 -DO3DE_HOME_PATH=\"${WORKSPACE}/home\" -DO3DE_REGISTER_ENGINE_PATH=\"${WORKSPACE}/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", + "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "all" } @@ -206,7 +206,7 @@ "PARAMETERS": { "CONFIGURATION": "release", "OUTPUT_DIRECTORY": "build/mono_linux", - "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_MONOLITHIC_GAME=TRUE -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4 -DO3DE_HOME_PATH=\"${WORKSPACE}/home\" -DO3DE_REGISTER_ENGINE_PATH=\"${WORKSPACE}/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", + "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_MONOLITHIC_GAME=TRUE -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "all" } diff --git a/scripts/build/Platform/Mac/build_config.json b/scripts/build/Platform/Mac/build_config.json index bcaffce880..971e5e47a1 100644 --- a/scripts/build/Platform/Mac/build_config.json +++ b/scripts/build/Platform/Mac/build_config.json @@ -37,7 +37,7 @@ "PARAMETERS": { "CONFIGURATION": "debug", "OUTPUT_DIRECTORY": "build/mac", - "CMAKE_OPTIONS": "-G Xcode -DLY_UNITY_BUILD=TRUE -DO3DE_HOME_PATH=\"${WORKSPACE}/home\" -DO3DE_REGISTER_ENGINE_PATH=\"${WORKSPACE}/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", + "CMAKE_OPTIONS": "-G Xcode -DLY_UNITY_BUILD=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "ALL_BUILD" } @@ -51,7 +51,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/mac", - "CMAKE_OPTIONS": "-G Xcode -DLY_UNITY_BUILD=TRUE -DO3DE_HOME_PATH=\"${WORKSPACE}/home\" -DO3DE_REGISTER_ENGINE_PATH=\"${WORKSPACE}/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", + "CMAKE_OPTIONS": "-G Xcode -DLY_UNITY_BUILD=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "ALL_BUILD" } @@ -66,7 +66,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/mac", - "CMAKE_OPTIONS": "-G Xcode -DLY_UNITY_BUILD=FALSE -DO3DE_HOME_PATH=\"${WORKSPACE}/home\" -DO3DE_REGISTER_ENGINE_PATH=\"${WORKSPACE}/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", + "CMAKE_OPTIONS": "-G Xcode -DLY_UNITY_BUILD=FALSE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "ALL_BUILD" } @@ -81,7 +81,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/mac", - "CMAKE_OPTIONS": "-G Xcode -DLY_UNITY_BUILD=TRUE -DO3DE_HOME_PATH=\"${WORKSPACE}/home\" -DO3DE_REGISTER_ENGINE_PATH=\"${WORKSPACE}/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", + "CMAKE_OPTIONS": "-G Xcode -DLY_UNITY_BUILD=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "AssetProcessorBatch", "ASSET_PROCESSOR_BINARY": "bin/profile/AssetProcessorBatch", @@ -99,7 +99,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/mac", - "CMAKE_OPTIONS": "-G Xcode -DLY_UNITY_BUILD=TRUE -DO3DE_HOME_PATH=\"${WORKSPACE}/home\" -DO3DE_REGISTER_ENGINE_PATH=\"${WORKSPACE}/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", + "CMAKE_OPTIONS": "-G Xcode -DLY_UNITY_BUILD=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "TEST_SUITE_periodic", "CTEST_OPTIONS": "-L \"(SUITE_periodic)\"" @@ -115,7 +115,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/mac", - "CMAKE_OPTIONS": "-G Xcode -DLY_UNITY_BUILD=TRUE -DO3DE_HOME_PATH=\"${WORKSPACE}/home\" -DO3DE_REGISTER_ENGINE_PATH=\"${WORKSPACE}/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", + "CMAKE_OPTIONS": "-G Xcode -DLY_UNITY_BUILD=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "TEST_SUITE_benchmark", "CTEST_OPTIONS": "-L \"(SUITE_benchmark)\"" @@ -131,7 +131,7 @@ "PARAMETERS": { "CONFIGURATION": "release", "OUTPUT_DIRECTORY": "build/mac", - "CMAKE_OPTIONS": "-G Xcode -DLY_UNITY_BUILD=TRUE -DO3DE_HOME_PATH=\"${WORKSPACE}/home\" -DO3DE_REGISTER_ENGINE_PATH=\"${WORKSPACE}/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", + "CMAKE_OPTIONS": "-G Xcode -DLY_UNITY_BUILD=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "ALL_BUILD" } @@ -146,7 +146,7 @@ "PARAMETERS": { "CONFIGURATION": "release", "OUTPUT_DIRECTORY": "build/mono_mac", - "CMAKE_OPTIONS": "-G Xcode -DLY_MONOLITHIC_GAME=TRUE -DLY_UNITY_BUILD=TRUE -DO3DE_HOME_PATH=\"${WORKSPACE}/home\" -DO3DE_REGISTER_ENGINE_PATH=\"${WORKSPACE}/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", + "CMAKE_OPTIONS": "-G Xcode -DLY_MONOLITHIC_GAME=TRUE -DLY_UNITY_BUILD=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "ALL_BUILD" } diff --git a/scripts/build/Platform/Mac/pipeline.json b/scripts/build/Platform/Mac/pipeline.json index 58f62b421d..81c57002b6 100644 --- a/scripts/build/Platform/Mac/pipeline.json +++ b/scripts/build/Platform/Mac/pipeline.json @@ -1,6 +1,6 @@ { "ENV": { - "NODE_LABEL": "mac", + "NODE_LABEL": "mac-catalina-7ad2e45b", "LY_3RDPARTY_PATH": "/Users/lybuilder/3rdParty", "TIMEOUT": 30, "WORKSPACE": "/Users/lybuilder/workspace", diff --git a/scripts/build/Platform/Windows/build_config.json b/scripts/build/Platform/Windows/build_config.json index ee34bae3e3..38cd7d6ad8 100644 --- a/scripts/build/Platform/Windows/build_config.json +++ b/scripts/build/Platform/Windows/build_config.json @@ -87,7 +87,7 @@ "PARAMETERS": { "CONFIGURATION": "debug", "OUTPUT_DIRECTORY": "build\\windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE -DLY_BUILD_WITH_INCREMENTAL_LINKING_DEBUG=FALSE -DO3DE_HOME_PATH=\"!WORKSPACE!/home\" -DO3DE_REGISTER_ENGINE_PATH=\"!WORKSPACE!/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE -DLY_BUILD_WITH_INCREMENTAL_LINKING_DEBUG=FALSE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "ALL_BUILD", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo" @@ -101,7 +101,7 @@ "PARAMETERS": { "CONFIGURATION": "debug", "OUTPUT_DIRECTORY": "build\\windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE -DLY_BUILD_WITH_INCREMENTAL_LINKING_DEBUG=FALSE -DO3DE_HOME_PATH=\"!WORKSPACE!/home\" -DO3DE_REGISTER_ENGINE_PATH=\"!WORKSPACE!/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE -DLY_BUILD_WITH_INCREMENTAL_LINKING_DEBUG=FALSE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "TEST_SUITE_smoke TEST_SUITE_main", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo", @@ -119,7 +119,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build\\windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE -DO3DE_HOME_PATH=\"!WORKSPACE!/home\" -DO3DE_REGISTER_ENGINE_PATH=\"!WORKSPACE!/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "ALL_BUILD", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo" @@ -135,7 +135,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build\\windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=FALSE -DO3DE_HOME_PATH=\"!WORKSPACE!/home\" -DO3DE_REGISTER_ENGINE_PATH=\"!WORKSPACE!/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=FALSE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "ALL_BUILD", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo" @@ -150,7 +150,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build\\windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE -DO3DE_HOME_PATH=\"!WORKSPACE!/home\" -DO3DE_REGISTER_ENGINE_PATH=\"!WORKSPACE!/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "TEST_SUITE_smoke TEST_SUITE_main", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo", @@ -171,7 +171,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build\\windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE -DO3DE_HOME_PATH=\"!WORKSPACE!/home\" -DO3DE_REGISTER_ENGINE_PATH=\"!WORKSPACE!/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "TEST_SUITE_smoke TEST_SUITE_main", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo", @@ -190,7 +190,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build\\windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE -DO3DE_HOME_PATH=\"!WORKSPACE!/home\" -DO3DE_REGISTER_ENGINE_PATH=\"!WORKSPACE!/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "AssetProcessorBatch", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo", @@ -209,7 +209,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build\\windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE -DO3DE_HOME_PATH=\"!WORKSPACE!/home\" -DO3DE_REGISTER_ENGINE_PATH=\"!WORKSPACE!/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "TEST_SUITE_periodic", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo", @@ -231,7 +231,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build\\windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE -DO3DE_HOME_PATH=\"!WORKSPACE!/home\" -DO3DE_REGISTER_ENGINE_PATH=\"!WORKSPACE!/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "TEST_SUITE_sandbox", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo", @@ -250,7 +250,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build\\windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE -DO3DE_HOME_PATH=\"!WORKSPACE!/home\" -DO3DE_REGISTER_ENGINE_PATH=\"!WORKSPACE!/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "TEST_SUITE_benchmark", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo", @@ -269,7 +269,7 @@ "PARAMETERS": { "CONFIGURATION": "release", "OUTPUT_DIRECTORY": "build\\windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE -DO3DE_HOME_PATH=\"!WORKSPACE!/home\" -DO3DE_REGISTER_ENGINE_PATH=\"!WORKSPACE!/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "ALL_BUILD", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo" @@ -285,7 +285,7 @@ "PARAMETERS": { "CONFIGURATION": "release", "OUTPUT_DIRECTORY": "build\\mono_windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_MONOLITHIC_GAME=TRUE -DLY_UNITY_BUILD=TRUE -DO3DE_HOME_PATH=\"!WORKSPACE!/home\" -DO3DE_REGISTER_ENGINE_PATH=\"!WORKSPACE!/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_MONOLITHIC_GAME=TRUE -DLY_UNITY_BUILD=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "ALL_BUILD", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo" @@ -300,7 +300,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build\\windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE -DLY_DISABLE_TEST_MODULES=TRUE -DCMAKE_INSTALL_PREFIX=install -DO3DE_HOME_PATH=\"!WORKSPACE!/home\" -DO3DE_REGISTER_ENGINE_PATH=\"!WORKSPACE!/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE -DLY_DISABLE_TEST_MODULES=TRUE", "CMAKE_LY_PROJECTS": "", "CMAKE_TARGET": "INSTALL", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo" @@ -332,7 +332,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build\\windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE -DCMAKE_MODULE_PATH=!WORKSPACE!/o3de/build/windows_vs2019/install/cmake", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE -DCMAKE_MODULE_PATH=!WORKSPACE!/o3de/install/cmake", "CMAKE_LY_PROJECTS": "", "CMAKE_TARGET": "ALL_BUILD", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo" diff --git a/scripts/build/Platform/Windows/package_build_config.json b/scripts/build/Platform/Windows/package_build_config.json index 40f479a098..a5ca861377 100644 --- a/scripts/build/Platform/Windows/package_build_config.json +++ b/scripts/build/Platform/Windows/package_build_config.json @@ -4,7 +4,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "windows_vs2017", - "CMAKE_OPTIONS": "-G \"Visual Studio 15 2017\" -A x64 -T host=x64 -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE -DO3DE_HOME_PATH=\"!WORKSPACE!/home\" -DO3DE_REGISTER_ENGINE_PATH=\"!WORKSPACE!/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", + "CMAKE_OPTIONS": "-G \"Visual Studio 15 2017\" -A x64 -T host=x64 -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE", "CMAKE_LY_PROJECTS": "AtomTest;AtomSampleViewer", "CMAKE_TARGET": "ALL_BUILD", "CMAKE_NATIVE_BUILD_ARGS": "/m:4 /p:CL_MPCount=!HALF_PROCESSORS! /nologo" @@ -15,7 +15,7 @@ "PARAMETERS": { "CONFIGURATION":"profile", "OUTPUT_DIRECTORY":"windows_vs2019", - "CMAKE_OPTIONS":"-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE -DO3DE_HOME_PATH=\"!WORKSPACE!/home\" -DO3DE_REGISTER_ENGINE_PATH=\"!WORKSPACE!/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", + "CMAKE_OPTIONS":"-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE", "CMAKE_LY_PROJECTS":"AtomTest;AtomSampleViewer", "CMAKE_TARGET":"ALL_BUILD", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo" diff --git a/scripts/build/Platform/iOS/build_config.json b/scripts/build/Platform/iOS/build_config.json index d689c600a1..2d9c57f6ee 100644 --- a/scripts/build/Platform/iOS/build_config.json +++ b/scripts/build/Platform/iOS/build_config.json @@ -27,7 +27,7 @@ "PARAMETERS": { "CONFIGURATION": "debug", "OUTPUT_DIRECTORY": "build/ios", - "CMAKE_OPTIONS": "-G Xcode -DCMAKE_TOOLCHAIN_FILE=cmake/Platform/iOS/Toolchain_ios.cmake -DLY_MONOLITHIC_GAME=TRUE -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED=FALSE -DLY_IOS_CODE_SIGNING_IDENTITY=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED=FALSE -DLY_UNITY_BUILD=TRUE -DO3DE_HOME_PATH=\"${WORKSPACE}/home\" -DO3DE_REGISTER_ENGINE_PATH=\"${WORKSPACE}/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", + "CMAKE_OPTIONS": "-G Xcode -DCMAKE_TOOLCHAIN_FILE=cmake/Platform/iOS/Toolchain_ios.cmake -DLY_MONOLITHIC_GAME=TRUE -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED=FALSE -DLY_IOS_CODE_SIGNING_IDENTITY=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED=FALSE -DLY_UNITY_BUILD=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "ALL_BUILD", "CMAKE_NATIVE_BUILD_ARGS": "-destination generic/platform=iOS" @@ -44,7 +44,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/ios", - "CMAKE_OPTIONS": "-G Xcode -DCMAKE_TOOLCHAIN_FILE=cmake/Platform/iOS/Toolchain_ios.cmake -DLY_MONOLITHIC_GAME=TRUE -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED=FALSE -DLY_IOS_CODE_SIGNING_IDENTITY=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED=FALSE -DLY_UNITY_BUILD=TRUE -DO3DE_HOME_PATH=\"${WORKSPACE}/home\" -DO3DE_REGISTER_ENGINE_PATH=\"${WORKSPACE}/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", + "CMAKE_OPTIONS": "-G Xcode -DCMAKE_TOOLCHAIN_FILE=cmake/Platform/iOS/Toolchain_ios.cmake -DLY_MONOLITHIC_GAME=TRUE -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED=FALSE -DLY_IOS_CODE_SIGNING_IDENTITY=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED=FALSE -DLY_UNITY_BUILD=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "ALL_BUILD", "CMAKE_NATIVE_BUILD_ARGS": "-destination generic/platform=iOS" @@ -60,7 +60,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/ios", - "CMAKE_OPTIONS": "-G Xcode -DCMAKE_TOOLCHAIN_FILE=cmake/Platform/iOS/Toolchain_ios.cmake -DLY_MONOLITHIC_GAME=TRUE -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED=FALSE -DLY_IOS_CODE_SIGNING_IDENTITY=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED=FALSE -DLY_UNITY_BUILD=FALSE -DO3DE_HOME_PATH=\"${WORKSPACE}/home\" -DO3DE_REGISTER_ENGINE_PATH=\"${WORKSPACE}/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", + "CMAKE_OPTIONS": "-G Xcode -DCMAKE_TOOLCHAIN_FILE=cmake/Platform/iOS/Toolchain_ios.cmake -DLY_MONOLITHIC_GAME=TRUE -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED=FALSE -DLY_IOS_CODE_SIGNING_IDENTITY=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED=FALSE -DLY_UNITY_BUILD=FALSE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "ALL_BUILD", "CMAKE_NATIVE_BUILD_ARGS": "-destination generic/platform=iOS" @@ -94,7 +94,7 @@ "PARAMETERS": { "CONFIGURATION": "release", "OUTPUT_DIRECTORY": "build/ios", - "CMAKE_OPTIONS": "-G Xcode -DCMAKE_TOOLCHAIN_FILE=cmake/Platform/iOS/Toolchain_ios.cmake -DLY_MONOLITHIC_GAME=TRUE -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED=FALSE -DLY_IOS_CODE_SIGNING_IDENTITY=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED=FALSE -DLY_UNITY_BUILD=TRUE -DO3DE_HOME_PATH=\"${WORKSPACE}/home\" -DO3DE_REGISTER_ENGINE_PATH=\"${WORKSPACE}/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", + "CMAKE_OPTIONS": "-G Xcode -DCMAKE_TOOLCHAIN_FILE=cmake/Platform/iOS/Toolchain_ios.cmake -DLY_MONOLITHIC_GAME=TRUE -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED=FALSE -DLY_IOS_CODE_SIGNING_IDENTITY=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED=FALSE -DLY_UNITY_BUILD=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "ALL_BUILD", "CMAKE_NATIVE_BUILD_ARGS": "-destination generic/platform=iOS" diff --git a/scripts/build/Platform/iOS/pipeline.json b/scripts/build/Platform/iOS/pipeline.json index 58f62b421d..81c57002b6 100644 --- a/scripts/build/Platform/iOS/pipeline.json +++ b/scripts/build/Platform/iOS/pipeline.json @@ -1,6 +1,6 @@ { "ENV": { - "NODE_LABEL": "mac", + "NODE_LABEL": "mac-catalina-7ad2e45b", "LY_3RDPARTY_PATH": "/Users/lybuilder/3rdParty", "TIMEOUT": 30, "WORKSPACE": "/Users/lybuilder/workspace", diff --git a/scripts/o3de.bat b/scripts/o3de.bat index 0a65b722d7..9031933e61 100644 --- a/scripts/o3de.bat +++ b/scripts/o3de.bat @@ -12,15 +12,15 @@ REM pushd %~dp0% CD %~dp0.. -SET BASE_PATH=%CD% +SET "BASE_PATH=%CD%" CD %~dp0 -SET PYTHON_DIRECTORY=%BASE_PATH%\python +SET "PYTHON_DIRECTORY=%BASE_PATH%\python" IF EXIST "%PYTHON_DIRECTORY%" GOTO pythonPathAvailable GOTO pythonDirNotFound :pythonPathAvailable SET PYTHON_EXECUTABLE=%PYTHON_DIRECTORY%\python.cmd IF NOT EXIST "%PYTHON_EXECUTABLE%" GOTO pythonExeNotFound -CALL "%PYTHON_EXECUTABLE%" %BASE_PATH%\scripts\o3de.py %* +CALL "%PYTHON_EXECUTABLE%" "%BASE_PATH%\scripts\o3de.py" %* GOTO end :pythonDirNotFound ECHO Python directory not found: %PYTHON_DIRECTORY% diff --git a/scripts/o3de.py b/scripts/o3de.py index dbb9c53e4b..8d7532878c 100755 --- a/scripts/o3de.py +++ b/scripts/o3de.py @@ -10,23 +10,54 @@ # import argparse +import pathlib import sys -import os - -# Resolve the common python module -ROOT_DEV_PATH = os.path.realpath(os.path.join(os.path.dirname(__file__), '..')) -if ROOT_DEV_PATH not in sys.path: - sys.path.append(ROOT_DEV_PATH) - -from cmake.Tools import engine_template -from cmake.Tools import global_project -from cmake.Tools import registration def add_args(parser, subparsers) -> None: - global_project.add_args(parser, subparsers) - engine_template.add_args(parser, subparsers) - registration.add_args(parser, subparsers) + """ + add_args is called to add expected parser arguments and subparsers arguments to each command such that it can be + invoked by o3de.py + Ex o3de.py can invoke the register downloadable commands by importing register, + call add_args and execute: python o3de.py register --gem-path "C:/TestGem" + :param parser: the caller instantiates a parser and passes it in here + :param subparsers: the caller instantiates subparsers and passes it in here + """ + + # As o3de.py shares the same name as the o3de package attempting to use a regular + # from o3de import line tries to import from the current o3de.py script and not the package + # So the {current script directory} / 'o3de' is added to the front of the sys.path + script_dir = pathlib.Path(__file__).parent + o3de_package_dir = (script_dir / 'o3de').resolve() + # add the scripts/o3de directory to the front of the sys.path + sys.path.insert(0, str(o3de_package_dir)) + from o3de import engine_template, global_project, register, print_registration, get_registration, \ + enable_gem, disable_gem, sha256 + # Remove the temporarily added path + sys.path = sys.path[1:] + + # global_project + global_project.add_args(subparsers) + # engine templaate + engine_template.add_args(subparsers) + + # register + register.add_args(subparsers) + + # show + print_registration.add_args(subparsers) + + # get-registered + get_registration.add_args(subparsers) + + # add a gem to a project + enable_gem.add_args(subparsers) + + # remove a gem from a project + disable_gem.add_args(subparsers) + + # sha256 + sha256.add_args(subparsers) if __name__ == "__main__": diff --git a/AutomatedTesting/Gem/Code/Platform/Windows/tool_dependencies.cmake b/scripts/o3de/CMakeLists.txt similarity index 91% rename from AutomatedTesting/Gem/Code/Platform/Windows/tool_dependencies.cmake rename to scripts/o3de/CMakeLists.txt index 933dd7927b..9819c1cd6e 100644 --- a/AutomatedTesting/Gem/Code/Platform/Windows/tool_dependencies.cmake +++ b/scripts/o3de/CMakeLists.txt @@ -9,6 +9,4 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # -set(GEM_DEPENDENCIES - Gem::QtForPython.Editor -) \ No newline at end of file +add_subdirectory(tests) diff --git a/scripts/o3de/README.txt b/scripts/o3de/README.txt new file mode 100644 index 0000000000..51bbf78cbd --- /dev/null +++ b/scripts/o3de/README.txt @@ -0,0 +1,41 @@ +All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +its licensors. + +For complete copyright and license terms please see the LICENSE at the root of this +distribution (the "License"). All use of this software is governed by the License, +or, if provided, by the license below or the license accompanying this file. Do not +remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + + +INTRODUCTION +------------ + +o3de is a package of scripts containing functionality to register engine, projects, gems, +templates and download repositories with the o3de manifests +It also contains functionality for creating new projects, gems and templates as well +as querying existing gems and templates + + +REQUIREMENTS +------------ + + * Python 3.7.10 (64-bit) + +INSTALL +----------- +It is recommended to set up these these tools with O3DE's CMake build commands. +Assuming CMake is already setup on your operating system, below are some sample build commands: + cd /path/to/od3e/ + cmake -B windows_vs2019 -S . -G"Visual Studio 16" -DLY_3RDPARTY_PATH="%LY_3RDPARTY_PATH%" + +To manually install the project in development mode using your own installed Python interpreter: + cd /path/to/od3e/o3de + /path/to/your/python -m pip install -e . + + +UNINSTALLATION +-------------- + +The preferred way to uninstall the project is: + /path/to/your/python -m pip uninstall o3de diff --git a/scripts/o3de/o3de/__init__.py b/scripts/o3de/o3de/__init__.py new file mode 100644 index 0000000000..4d5680a30d --- /dev/null +++ b/scripts/o3de/o3de/__init__.py @@ -0,0 +1,10 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# diff --git a/scripts/o3de/o3de/cmake.py b/scripts/o3de/o3de/cmake.py new file mode 100644 index 0000000000..f8e8d6ce0c --- /dev/null +++ b/scripts/o3de/o3de/cmake.py @@ -0,0 +1,193 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# +""" +Contains methods for query CMake gem target information +""" + +import logging +import os +import pathlib + +from o3de import manifest + +logger = logging.getLogger() +logging.basicConfig() + +enable_gem_start_marker = 'set(ENABLED_GEMS' +enable_gem_end_marker = ')' + + +def add_gem_dependency(cmake_file: pathlib.Path, + gem_name: str) -> int: + """ + adds a gem dependency to a cmake file + :param cmake_file: path to the cmake file + :param gem_name: name of the gem + :return: 0 for success or non 0 failure code + """ + if not cmake_file.is_file(): + logger.error(f'Failed to locate cmake file {str(cmake_file)}') + return 1 + + # on a line by basis, see if there already is {gem_name} + # find the first occurrence of a gem, copy its formatting and replace + # the gem name with the new one and append it + # if the gem is already present fail + t_data = [] + added = False + line_index_to_append = None + with open(cmake_file, 'r') as s: + line_index = 0 + for line in s: + if line.strip().startswith(enable_gem_start_marker): + line_index_to_append = line_index + if f'{gem_name}' == line.strip(): + logger.warning(f'{gem_name} is already enabled in file {str(cmake_file)}.') + return 0 + t_data.append(line) + line_index += 1 + + + indent = 4 + if line_index_to_append: + # Insert the gem after the 'set(ENABLED_GEMS)...` line + t_data.insert(line_index_to_append + 1, f'{" " * indent}{gem_name}\n') + added = True + + # if we didn't add, then create a new set(ENABLED_GEMS) variable + # add a new gem, if empty the correct format is 1 tab=4spaces + if not added: + t_data.append('\n') + t_data.append(f'{enable_gem_start_marker}\n') + t_data.append(f'{" " * indent}{gem_name}\n') + t_data.append(f'{enable_gem_end_marker}\n') + + # write the cmake + with open(cmake_file, 'w') as s: + s.writelines(t_data) + + return 0 + +def remove_gem_dependency(cmake_file: pathlib.Path, + gem_name: str) -> int: + """ + removes a gem dependency from a cmake file + :param cmake_file: path to the cmake file + :param gem_name: name of the gem + :return: 0 for success or non 0 failure code + """ + if not cmake_file.is_file(): + logger.error(f'Failed to locate cmake file {cmake_file}') + return 1 + + # on a line by basis, remove any line with {gem_name} + t_data = [] + # Remove the gem from the enabled_gem file by skipping the gem name entry + removed = False + with open(cmake_file, 'r') as s: + for line in s: + if gem_name == line.strip(): + removed = True + else: + t_data.append(line) + + if not removed: + logger.error(f'Failed to remove {gem_name} from cmake file {cmake_file}') + return 1 + + # write the cmake + with open(cmake_file, 'w') as s: + s.writelines(t_data) + + return 0 + + +def get_project_gems(project_path: pathlib.Path, + platform: str = 'Common') -> set: + return get_gems_from_cmake_file(get_enabled_gem_cmake_file(project_path=project_path, platform=platform)) + + +def get_enabled_gems(cmake_file: pathlib.Path) -> set: + """ + Gets a list of enabled gems from the cmake file + :param cmake_file: path to the cmake file + :return: set of gem targets found + """ + cmake_file = pathlib.Path(cmake_file).resolve() + + if not cmake_file.is_file(): + logger.error(f'Failed to locate cmake file {cmake_file}') + return set() + + gem_target_set = set() + with cmake_file.open('r') as s: + in_gem_list = False + for line in s: + line = line.strip() + if line.startswith(enable_gem_start_marker): + # Set the flag to indicate that we are in the ENABLED_GEMS variable + in_gem_list = True + # Skip pass the 'set(ENABLED_GEMS' marker just in case their are gems declared on the same line + line = line[len(enable_gem_start_marker):] + if in_gem_list: + # Since we are inside the ENABLED_GEMS variable determine if the line has the end_marker of ')' + if line.endswith(enable_gem_end_marker): + # Strip away the line end marker + line = line[:-len(enable_gem_end_marker)] + # Set the flag to indicate that we are no longer in the ENABLED_GEMS variable after this line + in_gem_list = False + # Split the rest of the line on whitespace just in case there are multiple gems in a line + gem_name_list = line.split() + gem_target_set.update(gem_name_list) + + return gem_target_set + + +def get_project_gem_paths(project_path: pathlib.Path, + platform: str = 'Common') -> set: + gem_names = get_project_gems(project_path, platform) + gem_paths = set() + for gem_name in gem_names: + gem_paths.add(manifest.get_registered(gem_name=gem_name)) + return gem_paths + + +def get_enabled_gem_cmake_file(project_name: str = None, + project_path: str or pathlib.Path = None, + platform: str = 'Common') -> pathlib.Path or None: + """ + get the standard cmake file name for a particular type of dependency + :param gem_name: name of the gem, resolves gem_path + :param gem_path: path of the gem + :return: list of gem targets + """ + if not project_name and not project_path: + logger.error(f'Must supply either a Project Name or Project Path.') + return None + + if project_name and not project_path: + project_path = manifest.get_registered(project_name=project_name) + + project_path = pathlib.Path(project_path).resolve() + enable_gem_filename = "enabled_gems.cmake" + + if platform == 'Common': + project_code_dir = project_path / 'Gem/Code' + if project_code_dir.is_dir(): + dependencies_file_path = project_code_dir / enable_gem_filename + return dependencies_file_path.resolve() + return (project_path / 'Code' / enable_gem_filename).resolve() + else: + project_code_dir = project_path / 'Gem/Code/Platform' / platform + if project_code_dir.is_dir(): + dependencies_file_path = project_code_dir / enable_gem_filename + return dependencies_file_path.resolve() + return (project_path / 'Code/Platform' / platform / enable_gem_filename).resolve() diff --git a/scripts/o3de/o3de/disable_gem.py b/scripts/o3de/o3de/disable_gem.py new file mode 100644 index 0000000000..82fe9c8ac6 --- /dev/null +++ b/scripts/o3de/o3de/disable_gem.py @@ -0,0 +1,177 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# +""" +Contains methods for removing a gem from a project +""" + +import argparse +import logging +import os +import pathlib +import sys + +from o3de import cmake, manifest + +logger = logging.getLogger() +logging.basicConfig() + + +def disable_gem_in_project(gem_name: str = None, + gem_path: pathlib.Path = None, + project_name: str = None, + project_path: pathlib.Path = None, + enabled_gem_file: pathlib.Path = None) -> int: + """ + disable a gem in a projects enabled_gems.cmake file + :param gem_name: name of the gem to add + :param gem_path: path to the gem to add + :param project_name: name of the project to add the gem to + :param project_path: path to the project to add the gem to + :param enabled_gem_file: File to remove enabled gem from + :return: 0 for success or non 0 failure code + """ + + # we need either a project name or path + if not project_name and not project_path: + logger.error(f'Must either specify a Project path or Project Name.') + return 1 + + # if project name resolve it into a path + if project_name and not project_path: + project_path = manifest.get_registered(project_name=project_name) + if not project_path: + logger.error(f'Unable to locate project path from the registered manifest.json files:' + f' {str(pathlib.Path("~/.o3de/o3de_manifest.json").expanduser())}, engine.json') + return 1 + + project_path = pathlib.Path(project_path).resolve() + if not project_path.is_dir(): + logger.error(f'Project path {project_path} is not a folder.') + return 1 + + # We need either a gem name or path + if not gem_name and not gem_path: + logger.error(f'Must either specify a Gem path or Gem Name.') + return 1 + + # if gem name resolve it into a path + if gem_name and not gem_path: + gem_path = manifest.get_registered(gem_name=gem_name) + if not gem_path: + logger.error(f'Unable to locate gem path from the registered manifest.json files:' + f' {str(pathlib.Path.home() / ".o3de/manifest.json")},' + f' {project_path / "project.json"}, engine.json') + return 1 + gem_path = pathlib.Path(gem_path).resolve() + # make sure this gem already exists if we're adding. We can always remove a gem. + if not gem_path.is_dir(): + logger.error(f'Gem Path {gem_path} does not exist.') + return 1 + + + # Read gem.json from the gem path + gem_json_data = manifest.get_gem_json_data(gem_path=gem_path) + if not gem_json_data: + logger.error(f'Could not read gem.json content under {gem_path}.') + return 1 + + # when removing we will try to do as much as possible even with failures so ret_val will be the last error code + ret_val = 0 + + if not enabled_gem_file: + enabled_gem_file = cmake.get_enabled_gem_cmake_file(project_path=project_path) + + # make sure this is a project has an enabled gems file + if not enabled_gem_file.is_file(): + logger.error(f'Enabled gem file {enabled_gem_file} is not present.') + return 1 + # remove the gem + error_code = cmake.remove_gem_dependency(enabled_gem_file, gem_json_data['gem_name']) + if error_code: + ret_val = error_code + + return ret_val + + +def _run_disable_gem_in_project(args: argparse) -> int: + if args.override_home_folder: + manifest.override_home_folder = args.override_home_folder + + return disable_gem_in_project(args.gem_name, + args.gem_path, + args.project_name, + args.project_path, + args.enabled_gem_file) + + +def add_parser_args(parser): + """ + add_parser_args is called to add arguments to each command such that it can be + invoked locally or added by a central python file. + Ex. Directly run from this file alone with: python disable_gem.py --project-path D:/Test --gem-name Atom + :param parser: the caller passes an argparse parser like instance to this method + """ + group = parser.add_mutually_exclusive_group(required=True) + group.add_argument('-pp', '--project-path', type=pathlib.Path, required=False, + help='The path to the project.') + group.add_argument('-pn', '--project-name', type=str, required=False, + help='The name of the project.') + group = parser.add_mutually_exclusive_group(required=True) + group.add_argument('-gp', '--gem-path', type=pathlib.Path, required=False, + help='The path to the gem.') + group.add_argument('-gn', '--gem-name', type=str, required=False, + help='The name of the gem.') + parser.add_argument('-egf', '--enabled-gem-file', type=pathlib.Path, required=False, + help='The cmake enabled gem file in which gem names are to be removed from.' + 'If not specified it will assume ') + + parser.add_argument('-ohf', '--override-home-folder', type=pathlib.Path, required=False, + help='By default the home folder is the user folder, override it to this folder.') + + parser.set_defaults(func=_run_disable_gem_in_project) + + +def add_args(subparsers) -> None: + """ + add_args is called to add subparsers arguments to each command such that it can be + a central python file such as o3de.py. + It can be run from the o3de.py script as follows + call add_args and execute: python o3de.py disable-gem-from-cmake --project-path D:/Test --gem-name Atom + :param subparsers: the caller instantiates subparsers and passes it in here + """ + disable_gem_project_subparser = subparsers.add_parser('disable-gem') + add_parser_args(disable_gem_project_subparser) + + +def main(): + """ + Runs disable_gem_project.py script as standalone script + """ + # parse the command line args + the_parser = argparse.ArgumentParser() + + # add subparsers + + # add args to the parser + add_parser_args(the_parser) + + # parse args + the_args = the_parser.parse_args() + + # run + ret = the_args.func(the_args) if hasattr(the_args, 'func') else 1 + + # return + sys.exit(ret) + + +if __name__ == "__main__": + main() diff --git a/scripts/o3de/o3de/download.py b/scripts/o3de/o3de/download.py new file mode 100644 index 0000000000..6f1b82e754 --- /dev/null +++ b/scripts/o3de/o3de/download.py @@ -0,0 +1,240 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# +""" +Implements functionality for downloading o3de objecs either locally or from a URI +""" + +import argparse +import hashlib +import json +import logging +import pathlib +import shutil +import sys +import urllib.parse +import urllib.request + +from o3de import manifest, repo, utils, validation + +logger = logging.getLogger() +logging.basicConfig() + +def unzip_manifest_json_data(download_zip_path: pathlib.Path, zip_file_name: str) -> dict: + json_data = {} + with zipfile.ZipFile(download_zip_path, 'r') as zip_data: + with zip_data.open(zip_file_name) as manifest_json_file: + try: + json_data = json.load(manifest_json_file) + except json.JSONDecodeError as e: + logger.error(f'UnZip exception:{str(e)}') + + return json_data + +def validate_downloaded_zip_sha256(download_uri_json_data: dict, download_zip_path: pathlib.Path, + manifest_json_name) -> int: + # if the engine.json has a sha256 check it against a sha256 of the zip + try: + sha256A = download_uri_json_data['sha256'] + except KeyError as e: + logger.warn(f'SECURITY WARNING: The advertised o3de object you downloaded has no "sha256"!!! Be VERY careful!!!' + f' We cannot verify this is the actually the advertised object!!!') + else: + sha256B = hashlib.sha256(download_zip_path.open('rb').read()).hexdigest() + if sha256A != sha256B: + logger.error(f'SECURITY VIOLATION: Downloaded zip sha256 {sha256B} does not match' + f' the advertised "sha256":{sha256A} in the f{manifest_json_name}. Deleting unzipped files!!!') + shutil.rmtree(dest_path) + return 1 + + manifest_json_data = unzip_manifest_json_data(download_zip_path, manifest_json_name) + + # remove the sha256 if present in the advertised downloadable manifest json + # then compare it to the json in the zip, they should now be identical + try: + del download_uri_json_data['sha256'] + except KeyError as e: + pass + + sha256A = hashlib.sha256(json.dumps(download_uri_json_data, indent=4).encode('utf8')).hexdigest() + with unzipped_manifest_json.open('r') as s: + try: + unzipped_manifest_json_data = json.load(s) + except json.JSONDecodeError as e: + logger.error(f'Failed to read manifest json {unzipped_manifest_json}. Unable to confirm this' + f' is the same template that was advertised.') + return 1 + sha256B = hashlib.sha256(json.dumps(unzipped_manifest_json_data, indent=4).encode('utf8')).hexdigest() + if sha256A != sha256B: + logger.error(f'SECURITY VIOLATION: Downloaded manifest json does not match' + f' the advertised manifest json. Deleting unzipped files!!!') + shutil.rmtree(dest_path) + return 1 + + return 0 + + +def get_downloadable(engine_name: str = None, + project_name: str = None, + gem_name: str = None, + template_name: str = None, + restricted_name: str = None) -> dict or None: + json_data = manifest.load_o3de_manifest() + try: + o3de_object_uris = json_data['repos'] + except KeyError as key_err: + logger.error(f'Unable to load repos from o3de manifest: {str(key_err)}') + return None + + manifest_json = 'repo.json' + search_func = lambda: repo.search_repo(manifest_json, engine_name, project_name, gem_name, template_name) + return repo.search_o3de_object(manifest_json, o3de_object_uris, search_func) + + +def download_o3de_object(object_name: str, default_folder_name: str, dest_path: str or pathlib.Path, + object_type: str, downloadable_kwarg_key) -> int: + if not dest_path: + dest_path = manifest.get_registered(default_folder=default_folder_name) + if not dest_path: + logger.error(f'Destination path not cannot be empty.') + return 1 + + dest_path = pathlib.Path(dest_path).resolve() + dest_path.mkdir(exist_ok=True) + + download_path = manifest.get_o3de_download_folder() / default_folder_name / object_name + download_path.mkdir(exist_ok=True) + download_zip_path = download_path / f'{object_type}.zip' + + downloadable_object_data = get_downloadable(**{downloadable_kwarg_key : object_name}) + if not downloadable_object_data: + logger.error(f'Downloadable o3de object {object_name} not found.') + return 1 + + origin = downloadable_json_data['origin'] + url = f'{origin}/object_type.zip' + parsed_uri = urllib.parse.urlparse(url) + + download_zip_result = utils.download_zip_file(parsed_uri, download_zip_path) + if download_zip_result != 0: + return download_zip_result + + return validate_downloaded_zip_sha256(downloadable_object_data, download_zip_path) + + +def download_engine(engine_name: str, + dest_path: str or pathlib.Path) -> int: + return download_o3de_object(engine_name, 'engines', dest_path, 'engine', 'engine_name') + + +def download_project(project_name: str, + dest_path: str or pathlib.Path) -> int: + return download_o3de_object(project_name, 'projects', dest_path, 'project', 'project_name') + + +def download_gem(gem_name: str, + dest_path: str or pathlib.Path) -> int: + return download_o3de_object(gem_name, 'gems', dest_path, 'gem', 'gem_name') + + +def download_template(template_name: str, + dest_path: str or pathlib.Path) -> int: + return download_o3de_object(template_name, 'templates', dest_path, 'template', 'template_name') + + + +def download_restricted(restricted_name: str, + dest_path: str or pathlib.Path) -> int: + return download_o3de_object(restricted_name, 'restricted', dest_path, 'restricted', 'restricted_name') + + +def _run_download(args: argparse) -> int: + if args.override_home_folder: + manifest.override_home_folder = args.override_home_folder + + if args.engine_name: + return download_engine(args.engine_name, + args.dest_path) + elif args.project_name: + return download_project(args.project_name, + args.dest_path) + elif args.gem_nanme: + return download_gem(args.gem_name, + args.dest_path) + elif args.template_name: + return download_template(args.template_name, + args.dest_path) + + return 1 + +def add_parser_args(parser): + """ + add_parser_args is called to add arguments to each command such that it can be + invoked locally or added by a central python file. + Ex. Directly run from this file alone with: python download.py --engine-name "o3de" + :param parser: the caller passes an argparse parser like instance to this method + """ + group = parser.add_mutually_exclusive_group(required=True) + group.add_argument('-e', '--engine-name', type=str, required=False, + help='Downloadable engine name.') + group.add_argument('-p', '--project-name', type=str, required=False, + help='Downloadable project name.') + group.add_argument('-g', '--gem-name', type=str, required=False, + help='Downloadable gem name.') + group.add_argument('-t', '--template-name', type=str, required=False, + help='Downloadable template name.') + parser.add_argument('-dp', '--dest-path', type=str, required=False, + default=None, + help='Optional destination folder to download into.' + ' i.e. download --project-name "AstomSamplerViewer" --dest-path "C:/projects"' + ' will result in C:/projects/AtomSampleViewer' + ' If blank will download to default object type folder') + + parser.add_argument('-ohf', '--override-home-folder', type=str, required=False, + help='By default the home folder is the user folder, override it to this folder.') + + parser.set_defaults(func=_run_download) + + +def add_args(subparsers) -> None: + """ + add_args is called to add subparsers arguments to each command such that it can be + a central python file such as o3de.py. + It can be run from the o3de.py script as follows + call add_args and execute: python o3de.py download --engine-name "o3de" + :param subparsers: the caller instantiates subparsers and passes it in here + """ + download_subparser = subparsers.add_parser('download') + add_parser_args(download_subparser) + + +def main(): + """ + Runs download.py script as standalone script + """ + # parse the command line args + the_parser = argparse.ArgumentParser() + + # add subparsers + + # add args to the parser + add_parser_args(the_parser) + + # parse args + the_args = the_parser.parse_args() + + # run + ret = the_args.func(the_args) if hasattr(the_args, 'func') else 1 + + # return + sys.exit(ret) + + +# Do not allow running the download.py script as a standalone script until it is reviewed by app-sec diff --git a/scripts/o3de/o3de/enable_gem.py b/scripts/o3de/o3de/enable_gem.py new file mode 100644 index 0000000000..0e007f6370 --- /dev/null +++ b/scripts/o3de/o3de/enable_gem.py @@ -0,0 +1,181 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# +""" +Contains command to add a gem to a project's enabled_gem.cmake file +""" + +import argparse +import json +import logging +import os +import pathlib +import sys + +from o3de import cmake, manifest, validation + +logger = logging.getLogger() +logging.basicConfig() + + +def enable_gem_in_project(gem_name: str = None, + gem_path: pathlib.Path = None, + project_name: str = None, + project_path: pathlib.Path = None, + enabled_gem_file: pathlib.Path = None) -> int: + """ + enable a gem in a projects enabled_gems.cmake file + :param gem_name: name of the gem to add + :param gem_path: path to the gem to add + :param project_name: name of to the project to add the gem to + :param project_path: path to the project to add the gem to + :param enabled_gem_file_file: if this dependency goes/is in a specific file + :return: 0 for success or non 0 failure code + """ + # we need either a project name or path + if not project_name and not project_path: + logger.error(f'Must either specify a Project path or Project Name.') + return 1 + + # if project name resolve it into a path + if project_name and not project_path: + project_path = manifest.get_registered(project_name=project_name) + if not project_path: + logger.error(f'Unable to locate project path from the registered manifest.json files:' + f' {str(pathlib.Path.home() / ".o3de/manifest.json")}, engine.json') + return 1 + + project_path = pathlib.Path(project_path).resolve() + if not project_path.is_dir(): + logger.error(f'Project path {project_path} is not a folder.') + return 1 + + # we need either a gem name or path + if not gem_name and not gem_path: + logger.error(f'Must either specify a Gem path or Gem Name.') + return 1 + + # if gem name resolve it into a path + if gem_name and not gem_path: + gem_path = manifest.get_registered(gem_name=gem_name) + if not gem_path: + logger.error(f'Unable to locate gem path from the registered manifest.json files:' + f' {str(pathlib.Path( "~/.o3de/o3de_manifest.json").expanduser())},' + f' {project_path / "project.json"}, engine.json') + return 1 + + gem_path = pathlib.Path(gem_path).resolve() + # make sure this gem already exists if we're adding. We can always remove a gem. + if not gem_path.is_dir(): + logger.error(f'Gem Path {gem_path} does not exist.') + return 1 + + # Read gem.json from the gem path + gem_json_data = manifest.get_gem_json_data(gem_path=gem_path) + if not gem_json_data: + logger.error(f'Could not read gem.json content under {gem_path}.') + return 1 + + + ret_val = 0 + if enabled_gem_file: + # make sure this is a project has an enabled gems file + if not enabled_gem_file.is_file(): + logger.error(f'Enabled gem file {enabled_gem_file} is not present.') + return 1 + # add the gem + ret_val = cmake.add_gem_dependency(enabled_gem_file, gem_json_data['gem_name']) + + else: + # Find the path to enabled gem file. + # It will be created if it doesn't exist + project_enabled_gem_file = cmake.get_enabled_gem_cmake_file(project_path=project_path) + if not project_enabled_gem_file.is_file(): + project_enabled_gem_file.touch() + # add the gem + ret_val = cmake.add_gem_dependency(project_enabled_gem_file, gem_json_data['gem_name']) + + return ret_val + + +def _run_enable_gem_in_project(args: argparse) -> int: + if args.override_home_folder: + manifest.override_home_folder = args.override_home_folder + + return enable_gem_in_project(args.gem_name, + args.gem_path, + args.project_name, + args.project_path, + args.enabled_gem_file) + + +def add_parser_args(parser): + """ + add_parser_args is called to add arguments to each command such that it can be + invoked locally or added by a central python file. + Ex. Directly run from this file alone with: python enable_gem.py --project-path "D:/TestProject" --gem-path "D:/TestGem" + :param parser: the caller passes an argparse parser like instance to this method + """ + group = parser.add_mutually_exclusive_group(required=True) + group.add_argument('-pp', '--project-path', type=pathlib.Path, required=False, + help='The path to the project.') + group.add_argument('-pn', '--project-name', type=str, required=False, + help='The name of the project.') + group = parser.add_mutually_exclusive_group(required=True) + group.add_argument('-gp', '--gem-path', type=pathlib.Path, required=False, + help='The path to the gem.') + group.add_argument('-gn', '--gem-name', type=str, required=False, + help='The name of the gem.') + parser.add_argument('-egf', '--enabled-gem-file', type=pathlib.Path, required=False, + help='The cmake enabled_gem file in which the gem names are specified.' + 'If not specified it will assume enabled_gems.cmake') + + parser.add_argument('-ohf', '--override-home-folder', type=pathlib.Path, required=False, + help='By default the home folder is the user folder, override it to this folder.') + + parser.set_defaults(func=_run_enable_gem_in_project) + + +def add_args(subparsers) -> None: + """ + add_args is called to add subparsers arguments to each command such that it can be + a central python file such as o3de.py. + It can be run from the o3de.py script as follows + call add_args and execute: python o3de.py add-gem-to-project --project-path "D:/TestProject" --gem-path "D:/TestGem" + :param subparsers: the caller instantiates subparsers and passes it in here + """ + enable_gem_project_subparser = subparsers.add_parser('enable-gem') + add_parser_args(enable_gem_project_subparser) + + +def main(): + """ + Runs enable_gem.py script as standalone script + """ + # parse the command line args + the_parser = argparse.ArgumentParser() + + # add subparsers + + # add args to the parser + add_parser_args(the_parser) + + # parse args + the_args = the_parser.parse_args() + + # run + ret = the_args.func(the_args) if hasattr(the_args, 'func') else 1 + + # return + sys.exit(ret) + + +if __name__ == "__main__": + main() diff --git a/cmake/Tools/engine_template.py b/scripts/o3de/o3de/engine_template.py similarity index 92% rename from cmake/Tools/engine_template.py rename to scripts/o3de/o3de/engine_template.py index 78de209ab4..9bb62eff35 100755 --- a/cmake/Tools/engine_template.py +++ b/scripts/o3de/o3de/engine_template.py @@ -20,8 +20,8 @@ import json import uuid import re -from cmake.Tools import utils -import cmake.Tools.registration as registration + +from o3de import manifest, validation, utils logger = logging.getLogger() logging.basicConfig() @@ -79,7 +79,7 @@ restricted_platforms = { } template_file_name = 'template.json' - +this_script_parent = os.path.dirname(os.path.realpath(__file__)) def _transform(s_data: str, replacements: list, @@ -321,7 +321,7 @@ def _instantiate_template(template_json_data: dict, platform_json = f'{template_restricted_platform_path_rel}/{template_file_name}'.replace('//', '/') if os.path.isfile(platform_json): - if not registration.valid_o3de_template_json(platform_json): + if not validation.valid_o3de_template_json(platform_json): logger.error(f'Template json {platform_json} is invalid.') return 1 @@ -329,7 +329,7 @@ def _instantiate_template(template_json_data: dict, with open(platform_json, 'r') as s: try: json_data = json.load(s) - except Exception as e: + except json.JSONDecodeError as e: logger.error(f'Failed to load {platform_json}: ' + str(e)) return 1 else: @@ -403,11 +403,11 @@ def create_template(source_path: str, template_path = source_name template_path = template_path.replace('\\', '/') if not os.path.isabs(template_path): - default_templates_folder = registration.get_registered(default_folder='templates') + default_templates_folder = manifest.get_registered(default_folder='templates') template_path = f'{default_templates_folder}/{template_path}' logger.info(f'Template path not a full path. Using default templates folder {template_path}') if os.path.isdir(template_path): - logger.error(f'Template path {template_path} is already exists.') + logger.error(f'Template path {template_path} already exists.') return 1 # template name is now the last component of the template_path @@ -419,28 +419,28 @@ def create_template(source_path: str, return 1 if source_restricted_name and not source_restricted_path: - source_restricted_path = registration.get_registered(restricted_name=source_restricted_name) + source_restricted_path = manifest.get_registered(restricted_name=source_restricted_name) # source_restricted_path if source_restricted_path: source_restricted_path = source_restricted_path.replace('\\', '/') if not os.path.isabs(source_restricted_path): - engine_json = f'{registration.get_this_engine_path()}/engine.json' - if not registration.valid_o3de_engine_json(engine_json): + engine_json = f'{manifest.get_this_engine_path()}/engine.json' + if not validation.valid_o3de_engine_json(engine_json): logger.error(f"Engine json {engine_json} is not valid.") return 1 with open(engine_json) as s: try: engine_json_data = json.load(s) - except Exception as e: + except json.JSONDecodeError as e: logger.error(f"Failed to read engine json {engine_json}: {str(e)}") return 1 try: - engine_restricted = engine_json_data['restricted'] - except Exception as e: + engine_restricted = engine_json_data['restricted_name'] + except KeyError as e: logger.error(f"Engine json {engine_json} restricted not found.") return 1 - engine_restricted_folder = registration.get_registered(restricted_name=engine_restricted) + engine_restricted_folder = manifest.get_registered(restricted_name=engine_restricted) new_source_restricted_path = f'{engine_restricted_folder}/{source_restricted_path}' logger.info(f'Source restricted path {source_restricted_path} not a full path. We must assume this engines' f' restricted folder {new_source_restricted_path}') @@ -449,7 +449,7 @@ def create_template(source_path: str, return 1 if template_restricted_name and not template_restricted_path: - template_restricted_path = registration.get_registered(restricted_name=template_restricted_name) + template_restricted_path = manifest.get_registered(restricted_name=template_restricted_name) if not template_restricted_name: template_restricted_name = template_name @@ -458,7 +458,7 @@ def create_template(source_path: str, if template_restricted_path: template_restricted_path = template_restricted_path.replace('\\', '/') if not os.path.isabs(template_restricted_path): - default_templates_restricted_folder = registration.get_registered(restricted_name='templates') + default_templates_restricted_folder = manifest.get_registered(restricted_name='templates') new_template_restricted_path = f'{default_templates_restricted_folder}/{template_restricted_path}' logger.info(f'Template restricted path {template_restricted_path} not a full path. We must assume the' f' default templates restricted folder {new_template_restricted_path}') @@ -466,21 +466,21 @@ def create_template(source_path: str, if os.path.isdir(template_restricted_path): # see if this is already a restricted path, if it is get the "restricted_name" from the restricted json - # so we can set "restricted" to it for this template + # so we can set "restricted_name" to it for this template restricted_json = f'{template_restricted_path}/restricted.json' if os.path.isfile(restricted_json): - if not registration.valid_o3de_restricted_json(restricted_json): + if not validation.valid_o3de_restricted_json(restricted_json): logger.error(f'{restricted_json} is not valid.') return 1 with open(restricted_json, 'r') as s: try: restricted_json_data = json.load(s) - except Exception as e: + except json.JSONDecodeError as e: logger.error(f'Failed to load {restricted_json}: ' + str(e)) return 1 try: template_restricted_name = restricted_json_data['restricted_name'] - except Exception as e: + except KeyError as e: logger.error(f'Failed to read restricted_name from {restricted_json}') return 1 else: @@ -928,7 +928,7 @@ def create_template(source_path: str, json_data.update({'user_tags': [f"{template_name}"]}) json_data.update({'icon_path': "preview.png"}) if template_restricted_path: - json_data.update({'restricted': template_restricted_name}) + json_data.update({'restricted_name': template_restricted_name}) if template_restricted_platform_relative_path != '': json_data.update({'template_restricted_platform_relative_path': template_restricted_platform_relative_path}) json_data.update({'copyFiles': copy_files}) @@ -943,8 +943,7 @@ def create_template(source_path: str, s.write(json.dumps(json_data, indent=4)) # copy the default preview.png - this_script_parent = os.path.dirname(os.path.realpath(__file__)) - preview_png_src = f'{this_script_parent}/preview.png' + preview_png_src = f'{this_script_parent}/resources/preview.png' preview_png_dst = f'{template_path}/Template/preview.png' if not os.path.isfile(preview_png_dst): shutil.copy(preview_png_src, preview_png_dst) @@ -1048,7 +1047,7 @@ def create_from_template(destination_path: str, return 1 if template_name: - template_path = registration.get_registered(template_name=template_name) + template_path = manifest.get_registered(template_name=template_name) if not os.path.isdir(template_path): logger.error(f'Could not find the template {template_name}=>{template_path}') @@ -1059,7 +1058,7 @@ def create_from_template(destination_path: str, # the template.json should be in the template_path, make sure it's there a nd valid template_json = f'{template_path}/template.json' - if not registration.valid_o3de_template_json(template_json): + if not validation.valid_o3de_template_json(template_json): logger.error(f'Template json {template_path} is invalid.') return 1 @@ -1067,14 +1066,14 @@ def create_from_template(destination_path: str, with open(template_json) as s: try: template_json_data = json.load(s) - except Exception as e: + except KeyError as e: logger.error(f'Could read template json {template_json}: {str(e)}.') return 1 # read template name from the json try: template_name = template_json_data['template_name'] - except Exception as e: + except KeyError as e: logger.error(f'Could not read "template_name" from template json {template_json}: {str(e)}.') return 1 @@ -1082,57 +1081,57 @@ def create_from_template(destination_path: str, # see if the template itself specifies a restricted name if not template_restricted_name and not template_restricted_path: try: - template_json_restricted_name = template_json_data['restricted'] - except Exception as e: - # the template json doesn't have a 'restricted' element warn and use it - logger.info(f'The template does not specify a "restricted".') + template_json_restricted_name = template_json_data['restricted_name'] + except KeyError as e: + # the template json doesn't have a 'restricted_name' element warn and use it + logger.info(f'The template does not specify a "restricted_name".') else: template_restricted_name = template_json_restricted_name # if no restricted name or path we continue on as if there is no template restricted files. if template_restricted_name or template_restricted_path: # If the user specified a --template-restricted-name we need to check that against the templates - # 'restricted' if it has one and see if they match. If they match then we don't have a problem. + # 'restricted_name' if it has one and see if they match. If they match then we don't have a problem. # If they don't then we error out. If supplied but not present in the template we warn and use it. # If not supplied we set what's in the template. If not supplied and not in the template we continue # on as if there is no template restricted files. if template_restricted_name: # The user specified a --template-restricted-name try: - template_json_restricted_name = template_json_data['restricted'] - except Exception as e: - # the template json doesn't have a 'restricted' element warn and use it - logger.info(f'The template does not specify a "restricted".' + template_json_restricted_name = template_json_data['restricted_name'] + except KeyError as e: + # the template json doesn't have a 'restricted_name' element warn and use it + logger.info(f'The template does not specify a "restricted_name".' f' Using supplied {template_restricted_name}') else: if template_json_restricted_name != template_restricted_name: logger.error( f'The supplied --template-restricted-name {template_restricted_name} does not match the' - f' templates "restricted". Either the the --template-restricted-name is incorrect or the' - f' templates "restricted" is wrong. Note that since this template specifies "restricted" as' + f' templates "restricted_name". Either the the --template-restricted-name is incorrect or the' + f' templates "restricted_name" is wrong. Note that since this template specifies "restricted_name" as' f' {template_json_restricted_name}, --template-restricted-name need not be supplied.') - template_restricted_path = registration.get_registered(restricted_name=template_restricted_name) + template_restricted_path = manifest.get_registered(restricted_name=template_restricted_name) else: # The user has supplied the --template-restricted-path, see if that matches the template specifies. # If it does then we do not have a problem. If it doesn't match then error out. If not specified # in the template then warn and use the --template-restricted-path template_restricted_path = template_restricted_path.replace('\\', '/') try: - template_json_restricted_name = template_json_data['restricted'] - except Exception as e: - # the template json doesn't have a 'restricted' element warn and use it - logger.info(f'The template does not specify a "restricted".' + template_json_restricted_name = template_json_data['restricted_name'] + except KeyError as e: + # the template json doesn't have a 'restricted_name' element warn and use it + logger.info(f'The template does not specify a "restricted_name".' f' Using supplied {template_restricted_path}') else: - template_json_restricted_path = registration.get_registered( + template_json_restricted_path = manifest.get_registered( restricted_name=template_json_restricted_name) if template_json_restricted_path != template_restricted_path: logger.error( f'The supplied --template-restricted-path {template_restricted_path} does not match the' - f' templates "restricted" {template_restricted_name} => {template_json_restricted_path}.' + f' templates "restricted_name" {template_restricted_name} => {template_json_restricted_path}.' f' Either the the supplied --template-restricted-path is incorrect or the templates' - f' "restricted" is wrong. Note that since this template specifies "restricted" as' + f' "restricted_name" is wrong. Note that since this template specifies "restricted_name" as' f' {template_json_restricted_name} --template-restricted-path need not be supplied' f' and {template_json_restricted_path} will be used.') return 1 @@ -1154,7 +1153,7 @@ def create_from_template(destination_path: str, try: template_json_restricted_platform_relative_path = template_json_data[ 'restricted_platform_relative_path'] - except Exception as e: + except KeyError as e: # the template json doesn't have a 'restricted_platform_relative_path' element warn and use it logger.info(f'The template does not specify a "restricted_platform_relative_path".' f' Using {template_restricted_platform_relative_path}') @@ -1175,7 +1174,7 @@ def create_from_template(destination_path: str, try: template_restricted_platform_relative_path = template_json_data[ 'restricted_platform_relative_path'] - except Exception as e: + except KeyError as e: # The template json doesn't have a 'restricted_platform_relative_path' element, set empty string. template_restricted_platform_relative_path = '' @@ -1203,19 +1202,19 @@ def create_from_template(destination_path: str, # destination restricted name if destination_restricted_name: - destination_restricted_path = registration.get_registered(restricted_name=destination_restricted_name) + destination_restricted_path = manifest.get_registered(restricted_name=destination_restricted_name) # destination restricted path elif destination_restricted_path: destination_restricted_path = destination_restricted_path.replace('\\', '/') if os.path.isabs(destination_restricted_path): - restricted_default_path = registration.get_registered(default='restricted') + restricted_default_path = manifest.get_registered(default='restricted') new_destination_restricted_path = f'{restricted_default_path}/{destination_restricted_path}' logger.info(f'{destination_restricted_path} is not a full path, making it relative' f' to default restricted path = {new_destination_restricted_path}') destination_restricted_path = new_destination_restricted_path elif template_restricted_path: - restricted_default_path = registration.get_registered(default='restricted') + restricted_default_path = manifest.get_registered(default='restricted') logger.info(f'--destination-restricted-path is not specified, using default restricted path / destination name' f' = {restricted_default_path}') destination_restricted_path = restricted_default_path @@ -1280,6 +1279,7 @@ def create_from_template(destination_path: str, def create_project(project_path: str, + project_name: str = None, template_path: str = None, template_name: str = None, project_restricted_path: str = None, @@ -1298,6 +1298,7 @@ def create_project(project_path: str, Template instantiation specialization that makes all default assumptions for a Project template instantiation, reducing the effort needed in instancing a project :param project_path: the project path, can be absolute or relative to default projects path + :param project_name: the project name, defaults to project_path basename if not provided :param template_path: the path to the template you want to instance, can be absolute or relative to default templates path :param template_name: the name the registered template you want to instance, defaults to DefaultProject, resolves template_path :param project_restricted_path: path to the projects restricted folder, can be absolute or relative to the restricted='projects' @@ -1337,8 +1338,12 @@ def create_project(project_path: str, template_name = 'DefaultProject' if template_name and not template_path: - template_path = registration.get_registered(template_name=template_name) + template_path = manifest.get_registered(template_name=template_name) + if not template_path: + logger.error(f'Could not find the template path using name {template_name}.\n' + f'Has the engine been registered yet. It can be registered via the "o3de.py register --this-engine" command') + return 1 if not os.path.isdir(template_path): logger.error(f'Could not find the template {template_name}=>{template_path}') return 1 @@ -1348,7 +1353,7 @@ def create_project(project_path: str, # the template.json should be in the template_path, make sure it's there and valid template_json = f'{template_path}/template.json' - if not registration.valid_o3de_template_json(template_json): + if not validation.valid_o3de_template_json(template_json): logger.error(f'Template json {template_path} is not valid.') return 1 @@ -1356,14 +1361,14 @@ def create_project(project_path: str, with open(template_json) as s: try: template_json_data = json.load(s) - except Exception as e: + except json.JSONDecodeError as e: logger.error(f'Could read template json {template_json}: {str(e)}.') return 1 # read template name from the json try: template_name = template_json_data['template_name'] - except Exception as e: + except KeyError as e: logger.error(f'Could not read "template_name" from template json {template_json}: {str(e)}.') return 1 @@ -1371,57 +1376,57 @@ def create_project(project_path: str, # see if the template itself specifies a restricted name if not template_restricted_name and not template_restricted_path: try: - template_json_restricted_name = template_json_data['restricted'] - except Exception as e: - # the template json doesn't have a 'restricted' element warn and use it - logger.info(f'The template does not specify a "restricted".') + template_json_restricted_name = template_json_data['restricted_name'] + except KeyError as e: + # the template json doesn't have a 'restricted_name' element warn and use it + logger.info(f'The template does not specify a "restricted_name".') else: template_restricted_name = template_json_restricted_name # if no restricted name or path we continue on as if there is no template restricted files. if template_restricted_name or template_restricted_path: # If the user specified a --template-restricted-name we need to check that against the templates - # 'restricted' if it has one and see if they match. If they match then we don't have a problem. + # 'restricted_name' if it has one and see if they match. If they match then we don't have a problem. # If they don't then we error out. If supplied but not present in the template we warn and use it. # If not supplied we set what's in the template. If not supplied and not in the template we continue # on as if there is no template restricted files. if template_restricted_name and not template_restricted_path: # The user specified a --template-restricted-name try: - template_json_restricted_name = template_json_data['restricted'] - except Exception as e: - # the template json doesn't have a 'restricted' element warn and use it - logger.info(f'The template does not specify a "restricted".' + template_json_restricted_name = template_json_data['restricted_name'] + except KeyError as e: + # the template json doesn't have a 'restricted_name' element warn and use it + logger.info(f'The template does not specify a "restricted_name".' f' Using supplied {template_restricted_name}') else: if template_json_restricted_name != template_restricted_name: logger.error( f'The supplied --template-restricted-name {template_restricted_name} does not match the' - f' templates "restricted". Either the the --template-restricted-name is incorrect or the' - f' templates "restricted" is wrong. Note that since this template specifies "restricted" as' + f' templates "restricted_name". Either the the --template-restricted-name is incorrect or the' + f' templates "restricted_name" is wrong. Note that since this template specifies "restricted_name" as' f' {template_json_restricted_name}, --template-restricted-name need not be supplied.') - template_restricted_path = registration.get_registered(restricted_name=template_restricted_name) + template_restricted_path = manifest.get_registered(restricted_name=template_restricted_name) else: # The user has supplied the --template-restricted-path, see if that matches the template specifies. # If it does then we do not have a problem. If it doesn't match then error out. If not specified # in the template then warn and use the --template-restricted-path template_restricted_path = template_restricted_path.replace('\\', '/') try: - template_json_restricted_name = template_json_data['restricted'] - except Exception as e: - # the template json doesn't have a 'restricted' element warn and use it - logger.info(f'The template does not specify a "restricted".' + template_json_restricted_name = template_json_data['restricted_name'] + except KeyError as e: + # the template json doesn't have a 'restricted_name' element warn and use it + logger.info(f'The template does not specify a "restricted_name".' f' Using supplied {template_restricted_path}') else: - template_json_restricted_path = registration.get_registered( + template_json_restricted_path = manifest.get_registered( restricted_name=template_json_restricted_name) if template_json_restricted_path != template_restricted_path: logger.error( f'The supplied --template-restricted-path {template_restricted_path} does not match the' - f' templates "restricted" {template_restricted_name} => {template_json_restricted_path}.' + f' templates "restricted_name" {template_restricted_name} => {template_json_restricted_path}.' f' Either the the supplied --template-restricted-path is incorrect or the templates' - f' "restricted" is wrong. Note that since this template specifies "restricted" as' + f' "restricted_name" is wrong. Note that since this template specifies "restricted_name" as' f' {template_json_restricted_name} --template-restricted-path need not be supplied' f' and {template_json_restricted_path} will be used.') return 1 @@ -1442,7 +1447,7 @@ def create_project(project_path: str, try: template_json_restricted_platform_relative_path = template_json_data[ 'restricted_platform_relative_path'] - except Exception as e: + except KeyError as e: # the template json doesn't have a 'restricted_platform_relative_path' element warn and use it logger.info(f'The template does not specify a "restricted_platform_relative_path".' f' Using {template_restricted_platform_relative_path}') @@ -1463,7 +1468,7 @@ def create_project(project_path: str, try: template_restricted_platform_relative_path = template_json_data[ 'restricted_platform_relative_path'] - except Exception as e: + except KeyError as e: # The template json doesn't have a 'restricted_platform_relative_path' element, set empty string. template_restricted_platform_relative_path = '' if not template_restricted_platform_relative_path: @@ -1475,7 +1480,7 @@ def create_project(project_path: str, return 1 project_path = project_path.replace('\\', '/') if not os.path.isabs(project_path): - default_projects_folder = registration.get_registered(default_folder='projects') + default_projects_folder = manifest.get_registered(default_folder='projects') new_project_path = f'{default_projects_folder}/{project_path}' logger.info(f'Project Path {project_path} is not a full path, we must assume its relative' f' to default projects path = {new_project_path}') @@ -1486,29 +1491,34 @@ def create_project(project_path: str, elif not os.path.isdir(project_path): os.makedirs(project_path) - # project name is now the last component of the project_path - project_name = os.path.basename(project_path) + if not project_name: + # project name is now the last component of the project_path + project_name = os.path.basename(project_path) + + if not utils.validate_identifier(project_name): + logger.error(f'Project name must be fewer than 64 characters, contain only alphanumeric, "_" or "-" characters, and start with a letter. {project_name}') + return 1 # project name cannot be the same as a restricted platform name if project_name in restricted_platforms: - logger.error(f'Project path cannot be a restricted name. {project_name}') + logger.error(f'Project name cannot be a restricted name. {project_name}') return 1 # project restricted name if project_restricted_name and not project_restricted_path: - project_restricted_path = registration.get_registered(restricted_name=project_restricted_name) + project_restricted_path = manifest.get_registered(restricted_name=project_restricted_name) # project restricted path elif project_restricted_path: project_restricted_path = project_restricted_path.replace('\\', '/') if not os.path.isabs(project_restricted_path): - default_projects_restricted_folder = registration.get_registered(restricted_name='projects') + default_projects_restricted_folder = manifest.get_registered(restricted_name='projects') new_project_restricted_path = f'{default_projects_restricted_folder}/{project_restricted_path}' logger.info(f'Project restricted path {project_restricted_path} is not a full path, we must assume its' f' relative to default projects restricted path = {new_project_restricted_path}') project_restricted_path = new_project_restricted_path elif template_restricted_path: - project_restricted_default_path = registration.get_registered(restricted_name='projects') + project_restricted_default_path = manifest.get_registered(restricted_name='projects') logger.info(f'--project-restricted-path is not specified, using default project restricted path / project name' f' = {project_restricted_default_path}') project_restricted_path = project_restricted_default_path @@ -1585,7 +1595,7 @@ def create_project(project_path: str, # read the restricted_name from the projects restricted.json restricted_json = f"{project_restricted_path}/restricted.json".replace('//', '/') if os.path.isfile(restricted_json): - if not registration.valid_o3de_restricted_json(restricted_json): + if not validation.valid_o3de_restricted_json(restricted_json): logger.error(f'Restricted json {restricted_json} is not valid.') return 1 else: @@ -1597,35 +1607,35 @@ def create_project(project_path: str, with open(restricted_json, 'r') as s: try: restricted_json_data = json.load(s) - except Exception as e: + except json.JSONDecodeError as e: logger.error(f'Failed to load restricted json {restricted_json}.') return 1 try: restricted_name = restricted_json_data["restricted_name"] - except Exception as e: + except KeyError as e: logger.error(f'Failed to read "restricted_name" from restricted json {restricted_json}.') return 1 - # set the "restricted": "restricted_name" element of the project.json + # set the "restricted_name": "restricted_name" element of the project.json project_json = f"{project_path}/project.json".replace('//', '/') - if not registration.valid_o3de_project_json(project_json): + if not validation.valid_o3de_project_json(project_json): logger.error(f'Project json {project_json} is not valid.') return 1 with open(project_json, 'r') as s: try: project_json_data = json.load(s) - except Exception as e: + except json.JSONDecodeError as e: logger.error(f'Failed to load project json {project_json}.') return 1 - project_json_data.update({"restricted": restricted_name}) + project_json_data.update({"restricted_name": restricted_name}) os.unlink(project_json) with open(project_json, 'w') as s: try: s.write(json.dumps(project_json_data, indent=4)) - except Exception as e: + except OSError as e: logger.error(f'Failed to write project json {project_json}.') return 1 @@ -1652,10 +1662,22 @@ def create_project(project_path: str, d.write('# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n') d.write('# {END_LICENSE}\n') - # copy the o3de_manifest.cmake into the project root - engine_path = registration.get_this_engine_path() - o3de_manifest_cmake = f'{engine_path}/cmake/o3de_manifest.cmake' - shutil.copy(o3de_manifest_cmake, project_path) + # set the "engine" element of the project.json + engine_json_data = manifest.get_engine_json_data(engine_path=manifest.get_this_engine_path()) + try: + engine_name = engine_json_data['engine_name'] + except KeyError as e: + logger.error(f"engine_name for this engine not found in engine.json.") + return 1 + + project_json_data = manifest.get_project_json_data(project_path=project_path) + project_json_data.update({"engine": engine_name}) + with open(project_json, 'w') as s: + try: + s.write(json.dumps(project_json_data, indent=4)) + except OSError as e: + logger.error(f'Failed to write project json at {project_path}.') + return 1 return 0 @@ -1718,7 +1740,7 @@ def create_gem(gem_path: str, template_name = 'DefaultGem' if template_name and not template_path: - template_path = registration.get_registered(template_name=template_name) + template_path = manifest.get_registered(template_name=template_name) if not os.path.isdir(template_path): logger.error(f'Could not find the template {template_name}=>{template_path}') @@ -1729,7 +1751,7 @@ def create_gem(gem_path: str, # the template.json should be in the template_path, make sure it's there and valid template_json = f'{template_path}/template.json' - if not registration.valid_o3de_template_json(template_json): + if not validation.valid_o3de_template_json(template_json): logger.error(f'Template json {template_path} is not valid.') return 1 @@ -1737,14 +1759,14 @@ def create_gem(gem_path: str, with open(template_json) as s: try: template_json_data = json.load(s) - except Exception as e: + except json.JSONDecodeError as e: logger.error(f'Could read template json {template_json}: {str(e)}.') return 1 # read template name from the json try: template_name = template_json_data['template_name'] - except Exception as e: + except KeyError as e: logger.error(f'Could not read "template_name" from template json {template_json}: {str(e)}.') return 1 @@ -1752,56 +1774,56 @@ def create_gem(gem_path: str, # see if the template itself specifies a restricted name if not template_restricted_name and not template_restricted_path: try: - template_json_restricted_name = template_json_data['restricted'] - except Exception as e: - # the template json doesn't have a 'restricted' element warn and use it - logger.info(f'The template does not specify a "restricted".') + template_json_restricted_name = template_json_data['restricted_name'] + except KeyError as e: + # the template json doesn't have a 'restricted_name' element warn and use it + logger.info(f'The template does not specify a "restricted_name".') else: template_restricted_name = template_json_restricted_name # if no restricted name or path we continue on as if there is no template restricted files. if template_restricted_name or template_restricted_path: - # if the user specified a --template-restricted-name we need to check that against the templates 'restricted' + # if the user specified a --template-restricted-name we need to check that against the templates 'restricted_name' # if it has one and see if they match. If they match then we don't have a problem. If they don't then we error # out. If supplied but not present in the template we warn and use it. If not supplied we set what's in the # template. If not supplied and not in the template we continue on as if there is no template restricted files. if template_restricted_name and not template_restricted_path: # The user specified a --template-restricted-name try: - template_json_restricted_name = template_json_data['restricted'] - except Exception as e: - # the template json doesn't have a 'restricted' element warn and use it - logger.info(f'The template does not specify a "restricted".' + template_json_restricted_name = template_json_data['restricted_name'] + except KeyError as e: + # the template json doesn't have a 'restricted_name' element warn and use it + logger.info(f'The template does not specify a "restricted_name".' f' Using supplied {template_restricted_name}') else: if template_json_restricted_name != template_restricted_name: logger.error( f'The supplied --template-restricted-name {template_restricted_name} does not match the' - f' templates "restricted". Either the the --template-restricted-name is incorrect or the' - f' templates "restricted" is wrong. Note that since this template specifies "restricted" as' + f' templates "restricted_name". Either the the --template-restricted-name is incorrect or the' + f' templates "restricted_name" is wrong. Note that since this template specifies "restricted_name" as' f' {template_json_restricted_name}, --template-restricted-name need not be supplied.') - template_restricted_path = registration.get_registered(restricted_name=template_restricted_name) + template_restricted_path = manifest.get_registered(restricted_name=template_restricted_name) else: # The user has supplied the --template-restricted-path, see if that matches the template specifies. # If it does then we do not have a problem. If it doesn't match then error out. If not specified # in the template then warn and use the --template-restricted-path template_restricted_path = template_restricted_path.replace('\\', '/') try: - template_json_restricted_name = template_json_data['restricted'] - except Exception as e: - # the template json doesn't have a 'restricted' element warn and use it - logger.info(f'The template does not specify a "restricted".' + template_json_restricted_name = template_json_data['restricted_name'] + except KeyError as e: + # the template json doesn't have a 'restricted_name' element warn and use it + logger.info(f'The template does not specify a "restricted_name".' f' Using supplied {template_restricted_path}') else: - template_json_restricted_path = registration.get_registered( + template_json_restricted_path = manifest.get_registered( restricted_name=template_json_restricted_name) if template_json_restricted_path != template_restricted_path: logger.error( f'The supplied --template-restricted-path {template_restricted_path} does not match the' - f' templates "restricted" {template_restricted_name} => {template_json_restricted_path}.' + f' templates "restricted_name" {template_restricted_name} => {template_json_restricted_path}.' f' Either the the supplied --template-restricted-path is incorrect or the templates' - f' "restricted" is wrong. Note that since this template specifies "restricted" as' + f' "restricted_name" is wrong. Note that since this template specifies "restricted_name" as' f' {template_json_restricted_name} --template-restricted-path need not be supplied' f' and {template_json_restricted_path} will be used.') return 1 @@ -1821,7 +1843,7 @@ def create_gem(gem_path: str, try: template_json_restricted_platform_relative_path = template_json_data[ 'restricted_platform_relative_path'] - except Exception as e: + except KeyError as e: # the template json doesn't have a 'restricted_platform_relative_path' element warn and use it logger.info(f'The template does not specify a "restricted_platform_relative_path".' f' Using {template_restricted_platform_relative_path}') @@ -1842,7 +1864,7 @@ def create_gem(gem_path: str, try: template_restricted_platform_relative_path = template_json_data[ 'restricted_platform_relative_path'] - except Exception as e: + except KeyError as e: # The template json doesn't have a 'restricted_platform_relative_path' element, set empty string. template_restricted_platform_relative_path = '' if not template_restricted_platform_relative_path: @@ -1854,7 +1876,7 @@ def create_gem(gem_path: str, return 1 gem_path = gem_path.replace('\\', '/') if not os.path.isabs(gem_path): - default_gems_folder = registration.get_registered(default_folder='gems') + default_gems_folder = manifest.get_registered(default_folder='gems') new_gem_path = f'{default_gems_folder}/{gem_path}' logger.info(f'Gem Path {gem_path} is not a full path, we must assume its relative' f' to default gems path = {new_gem_path}') @@ -1875,19 +1897,19 @@ def create_gem(gem_path: str, # gem restricted name if gem_restricted_name and not gem_restricted_path: - gem_restricted_path = registration.get_registered(restricted_name=gem_restricted_name) + gem_restricted_path = manifest.get_registered(restricted_name=gem_restricted_name) # gem restricted path elif gem_restricted_path: gem_restricted_path = gem_restricted_path.replace('\\', '/') if not os.path.isabs(gem_restricted_path): - default_gems_restricted_folder = registration.get_registered(restricted_name='gems') + default_gems_restricted_folder = manifest.get_registered(restricted_name='gems') new_gem_restricted_path = f'{default_gems_restricted_folder}/{gem_restricted_path}' logger.info(f'Gem restricted path {gem_restricted_path} is not a full path, we must assume its' f' relative to default gems restricted path = {new_gem_restricted_path}') gem_restricted_path = new_gem_restricted_path elif template_restricted_path: - gem_restricted_default_path = registration.get_registered(restricted_name='gems') + gem_restricted_default_path = manifest.get_registered(restricted_name='gems') logger.info(f'--gem-restricted-path is not specified, using default gem restricted path / gem name' f' = {gem_restricted_default_path}') gem_restricted_path = gem_restricted_default_path @@ -1964,7 +1986,7 @@ def create_gem(gem_path: str, # read the restricted_name from the gems restricted.json restricted_json = f"{gem_restricted_path}/restricted.json".replace('//', '/') if os.path.isfile(restricted_json): - if not registration.valid_o3de_restricted_json(restricted_json): + if not validation.valid_o3de_restricted_json(restricted_json): logger.error(f'Restricted json {restricted_json} is not valid.') return 1 else: @@ -1976,35 +1998,35 @@ def create_gem(gem_path: str, with open(restricted_json, 'r') as s: try: restricted_json_data = json.load(s) - except Exception as e: + except json.JSONDecodeError as e: logger.error(f'Failed to load restricted json {restricted_json}.') return 1 try: restricted_name = restricted_json_data["restricted_name"] - except Exception as e: + except KeyError as e: logger.error(f'Failed to read "restricted_name" from restricted json {restricted_json}.') return 1 - # set the "restricted": "restricted_name" element of the gem.json + # set the "restricted_name": "restricted_name" element of the gem.json gem_json = f"{gem_path}/gem.json".replace('//', '/') - if not registration.valid_o3de_gem_json(gem_json): + if not validation.valid_o3de_gem_json(gem_json): logger.error(f'Gem json {gem_json} is not valid.') return 1 with open(gem_json, 'r') as s: try: gem_json_data = json.load(s) - except Exception as e: + except json.JSONDecodeError as e: logger.error(f'Failed to load gem json {gem_json}.') return 1 - gem_json_data.update({"restricted": restricted_name}) + gem_json_data.update({"restricted_name": restricted_name}) os.unlink(gem_json) with open(gem_json, 'w') as s: try: s.write(json.dumps(gem_json_data, indent=4)) - except Exception as e: + except OSError as e: logger.error(f'Failed to write project json {gem_json}.') return 1 @@ -2064,6 +2086,7 @@ def _run_create_from_template(args: argparse) -> int: def _run_create_project(args: argparse) -> int: return create_project(args.project_path, + args.project_name, args.template_path, args.template_name, args.project_restricted_path, @@ -2098,15 +2121,14 @@ def _run_create_gem(args: argparse) -> int: args.module_id) -def add_args(parser, subparsers) -> None: +def add_args(subparsers) -> None: """ add_args is called to add expected parser arguments and subparsers arguments to each command such that it can be invoked locally or aggregated by a central python file. - Ex. Directly run from this file alone with: python engine_template.py create_gem --gem-path TestGem + Ex. Directly run from this file alone with: python engine_template.py create-gem --gem-path TestGem OR o3de.py can aggregate commands by importing engine_template, - call add_args and execute: python o3de.py create_gem --gem-path TestGem - :param parser: the caller instantiates a parser and passes it in here + call add_args and execute: python o3de.py create-gem --gem-path TestGem :param subparsers: the caller instantiates subparsers and passes it in here """ # turn a directory into a template @@ -2116,7 +2138,7 @@ def add_args(parser, subparsers) -> None: create_template_subparser.add_argument('-tp', '--template-path', type=str, required=False, help='The path to the template to create, can be absolute or relative' ' to default templates path') - group = create_template_subparser.add_mutually_exclusive_group(required=True) + group = create_template_subparser.add_mutually_exclusive_group(required=False) group.add_argument('-srp', '--source-restricted-path', type=str, required=False, default=None, help='The path to the source restricted folder.') @@ -2125,7 +2147,7 @@ def add_args(parser, subparsers) -> None: help='The name of the source restricted folder. If supplied this will resolve' ' the --source-restricted-path.') - group = create_template_subparser.add_mutually_exclusive_group(required=True) + group = create_template_subparser.add_mutually_exclusive_group(required=False) group.add_argument('-trp', '--template-restricted-path', type=str, required=False, default=None, help='The path to the templates restricted folder.') @@ -2248,10 +2270,15 @@ def add_args(parser, subparsers) -> None: # creation of a project from a template (like create from template but makes project assumptions) create_project_subparser = subparsers.add_parser('create-project') create_project_subparser.add_argument('-pp', '--project-path', type=str, required=True, - help='The name of the project you wish to create from the template,' + help='The location of the project you wish to create from the template,' ' can be an absolute path or dev root relative.' ' Ex. C:/o3de/TestProject' - ' TestProject = ') + ' TestProject = if --project-name not provided') + create_project_subparser.add_argument('-pn', '--project-name', type=str, required=False, + help='The name of the project you wish to use, must be alphanumeric, ' + ' and can contain _ and - characters.' + ' If no name is provided, will use last component of project path.' + ' Ex. New_Project-123') group = create_project_subparser.add_mutually_exclusive_group(required=False) group.add_argument('-tp', '--template-path', type=str, required=False, @@ -2423,16 +2450,16 @@ if __name__ == "__main__": the_parser = argparse.ArgumentParser() # add subparsers - the_subparsers = the_parser.add_subparsers(help='sub-command help') + the_subparsers = the_parser.add_subparsers(help='sub-command help', dest='command', required=True) # add args to the parser - add_args(the_parser, the_subparsers) + add_args(the_subparsers) # parse args the_args = the_parser.parse_args() # run - ret = the_args.func(the_args) + ret = the_args.func(the_args) if hasattr(the_args, 'func') else 1 # return sys.exit(ret) diff --git a/scripts/o3de/o3de/get_registration.py b/scripts/o3de/o3de/get_registration.py new file mode 100644 index 0000000000..d51600826c --- /dev/null +++ b/scripts/o3de/o3de/get_registration.py @@ -0,0 +1,96 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +import argparse +import pathlib +import sys + +from o3de import manifest + +def _run_get_registered(args: argparse) -> str or pathlib.Path: + if args.override_home_folder: + manifest.override_home_folder = args.override_home_folder + + return manifest.get_registered(args.engine_name, + args.project_name, + args.gem_name, + args.template_name, + args.default_folder, + args.repo_name, + args.restricted_name) + + +def add_parser_args(parser): + """ + add_parser_args is called to add arguments to each command such that it can be + invoked locally or added by a central python file. + Ex. Directly run from this file alone with: python get_registration.py --engine-name "o3de" + :param parser: the caller passes an argparse parser like instance to this method + """ + group = parser.add_mutually_exclusive_group(required=True) + group.add_argument('-en', '--engine-name', type=str, required=False, + help='Engine name.') + group.add_argument('-pn', '--project-name', type=str, required=False, + help='Project name.') + group.add_argument('-gn', '--gem-name', type=str, required=False, + help='Gem name.') + group.add_argument('-tn', '--template-name', type=str, required=False, + help='Template name.') + group.add_argument('-df', '--default-folder', type=str, required=False, + choices=['engines', 'projects', 'gems', 'templates', 'restricted'], + help='The default folders for o3de.') + group.add_argument('-rn', '--repo-name', type=str, required=False, + help='Repo name.') + group.add_argument('-rsn', '--restricted-name', type=str, required=False, + help='Restricted name.') + + parser.add_argument('-ohf', '--override-home-folder', type=str, required=False, + help='By default the home folder is the user folder, override it to this folder.') + + parser.set_defaults(func=_run_get_registered) + + +def add_args(subparsers) -> None: + """ + add_args is called to add subparsers arguments to each command such that it can be + a central python file such as o3de.py. + It can be run from the o3de.py script as follows + call add_args and execute: python o3de.py get-registered --engine-name "o3de" + :param subparsers: the caller instantiates subparsers and passes it in here + """ + get_registered_subparser = subparsers.add_parser('get-registered') + add_parser_args(get_registered_subparser) + + +def main(): + """ + Runs get_registration.py script as standalone script + """ + # parse the command line args + the_parser = argparse.ArgumentParser() + + # add subparsers + + # add args to the parser + add_parser_args(the_parser) + + # parse args + the_args = the_parser.parse_args() + + # run + ret = the_args.func(the_args) if hasattr(the_args, 'func') else 1 + + # return + sys.exit(ret) + + +if __name__ == "__main__": + main() diff --git a/scripts/o3de/o3de/global_project.py b/scripts/o3de/o3de/global_project.py new file mode 100644 index 0000000000..d165510250 --- /dev/null +++ b/scripts/o3de/o3de/global_project.py @@ -0,0 +1,214 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +import argparse +import logging +import os +import sys +import re +import pathlib +import json + +from o3de import manifest, validation + +logger = logging.getLogger() +logging.basicConfig() + +DEFAULT_BOOTSTRAP_SETREG = pathlib.Path('~/.o3de/Registry/bootstrap.setreg').expanduser() +PROJECT_PATH_KEY = ('Amazon', 'AzCore', 'Bootstrap', 'project_path') + +def get_json_data(input_path: pathlib.Path): + setreg_json_data = {} + # If the output_path exist validate that it is a valid json file + if input_path.is_file(): + with input_path.open('r') as f: + try: + setreg_json_data = json.load(f) + except json.JSONDecodeError as e: + logger.error(f'The file: {input_path} is not a valid json file: {str(e)}') + + return setreg_json_data + +def set_global_project(output_path: pathlib.Path, + project_name: str = None, + project_path: pathlib.Path = None, + force: bool = False) -> int: + """ + Adds a project path the a settings registry file in the users ~/.o3de/Registry directory + :param output_path: path to .setreg file to store project_path value into + :param project_name: name of the project to lookup path for + :param project_path: path to the project to add to .setreg file + :param force: if set, the project path will be set within the .setreg file regardless of if the path doesn't exist + :return: 0 for success or non 0 failure code + """ + # we need either a project name or path + if not project_name and not project_path: + logger.error(f'Must either specify a Project path or Project Name.') + return 1 + + # if project name resolve it into a path + if project_name and not project_path: + project_path = manifest.get_registered(project_name=project_name) + + if not project_path: + logger.error( + f'The project name has been supplied. Unable to locate project path from the registered manifest.json files:' + f' {str(pathlib.Path("~/.o3de/o3de_manifest.json").expanduser())}, engine.json\n' + 'A The --project-path parameter can be used directly to skip checking the manifest') + return 1 + + # Only perform project path validations when force=False + if not force: + if not project_path.is_dir(): + logger.error(f'Project path {project_path} is not a folder.') + return 1 + + # Validate that the supplied path points contains a valid project.json + if not validation.valid_o3de_project_json(project_path / 'project.json'): + logger.error(f'The supplied project path does not contain a valid project.json.\n' + f'The Path will not be set') + return 1 + + # If the output_path exist validate that it is a valid json file and read it's json data + setreg_json_data = get_json_data(output_path) + if output_path.is_file(): + with output_path.open('r') as f: + try: + setreg_json_data = json.load(f) + except (json.JSONDecodeError) as e: + logger.error(f'The output file: {output_path} is not a valid json file: {str(e)}') + return 1 + + # Add a json dictionary that will be merged with any existing json data from the .setreg file + merge_json_data = {} + json_object_iter = merge_json_data + for json_key in PROJECT_PATH_KEY[:-1]: + # Add the parent json object for the key to update + json_object_iter = json_object_iter.setdefault(json_key, {}) + + # Set the project path value here + json_object_iter[PROJECT_PATH_KEY[-1]] = project_path.as_posix() + setreg_json_data.update(merge_json_data) + + # Create the parent directories + if output_path.parent: + output_path.parent.mkdir(parents=True, exist_ok=True) + try: + with output_path.open('w') as s: + s.write(json.dumps(setreg_json_data, indent=4) + '\n') + except OSError as e: + logger.error(f'Failed to write project path {project_path} to file {output_path}: {str(e)}') + return 1 + + return 0 + + +def get_global_project(input_path: pathlib.Path) -> pathlib.Path or None: + """ + Retrieves the /Amazon/AzCore/Bootstrap/project_path key from the supplied file path + :return: project_path or None on failure + """ + setreg_json_data = get_json_data(input_path) + + try: + # Iterate over each element of the tuple and read the json key from each successive json object + json_object_iter = setreg_json_data + for json_key in PROJECT_PATH_KEY: + json_object_iter = json_object_iter[json_key] + except KeyError as e: + logger.error(f'Cannot read key /{"/".join(PROJECT_PATH_KEY)} from file {input_path.as_posix()}: {str(e)}') + else: + project_path = json_object_iter + return pathlib.Path(project_path).resolve() + return None + +def _run_get_global_project(args: argparse) -> int: + project_path = get_global_project(args.input_path) + if project_path: + print(project_path.as_posix()) + return 0 + return 1 + + +def _run_set_global_project(args: argparse) -> int: + return set_global_project(args.output_path, + args.project_name, + args.project_path, + args.force) + + +def add_parser_args(get_project_parser, set_project_parser): + """ + add_parser_args is called to add arguments to each command such that it can be + invoked locally or added by a central python file. + Ex. Directly run from this file alone with: python global_project.py --project-path "D:/TestProject" + :param parser: the caller passes an argparse parser like instance to this method + """ + + # get-current-project + get_project_parser.add_argument('-i', '--input-path', type=pathlib.Path, required=False, default=DEFAULT_BOOTSTRAP_SETREG, + help=f'Optional path to file to read /{"/".join(PROJECT_PATH_KEY)} key from.' + f' If not supplied, then {DEFAULT_BOOTSTRAP_SETREG} is used instead') + get_project_parser.set_defaults(func=_run_get_global_project) + + # set-current-project + group = set_project_parser.add_mutually_exclusive_group(required=True) + group.add_argument('-pp', '--project-path', type=pathlib.Path, required=False, + help='The path to the project.') + group.add_argument('-pn', '--project-name', type=str, required=False, + help='The name of the project.') + set_project_parser.add_argument('-o', '--output-path', type=pathlib.Path, required=False, + default=DEFAULT_BOOTSTRAP_SETREG, + help=f'Optional path to output file to write project_path key to. ' + f'If not supplied, then {DEFAULT_BOOTSTRAP_SETREG} is used instead') + set_project_parser.add_argument('-f', '--force', action='store_true', default=False, + help=f'Force the setting of the project path in the supplied setreg file') + set_project_parser.set_defaults(func=_run_set_global_project) + +def add_args(subparsers) -> None: + """ + add_args is called to add subparsers arguments to each command such that it can be + a central python file such as o3de.py. + It can be run from the o3de.py script as follows + call add_args and execute: python o3de.py set-global-project --project-path "D:/TestProject" + :param subparsers: the caller instantiates subparsers and passes it in here + """ + get_project_subparser = subparsers.add_parser('get-global-project') + set_project_subparser = subparsers.add_parser('set-global-project') + add_parser_args(get_project_subparser, set_project_subparser) + + +def main(): + """ + Runs this script as standalone script + """ + # parse the command line args + the_parser = argparse.ArgumentParser() + + # add subparsers + project_subparsers = the_parser.add_subparsers(help="Commands for modifying the project path in the user's home" + " setreg files") + + # add args to the parser + add_args(project_subparsers) + + # parse args + the_args = the_parser.parse_args() + + # run + ret = the_args.func(the_args) if hasattr(the_args, 'func') else 1 + + # return + sys.exit(ret) + + +if __name__ == "__main__": + main() diff --git a/scripts/o3de/o3de/manifest.py b/scripts/o3de/o3de/manifest.py new file mode 100644 index 0000000000..2a7e5bba11 --- /dev/null +++ b/scripts/o3de/o3de/manifest.py @@ -0,0 +1,675 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# +""" +Contains functions for data from json files such as the o3de_manifests.json, engine.json, project.json, etc... +""" + +import json +import logging +import os +import pathlib + +from o3de import validation + +logger = logging.getLogger() +logging.basicConfig() + +# Directory methods +override_home_folder = None + + +def get_this_engine_path() -> pathlib.Path: + return pathlib.Path(os.path.realpath(__file__)).parents[3].resolve() + + +def get_home_folder() -> pathlib.Path: + if override_home_folder: + return pathlib.Path(override_home_folder).resolve() + else: + return pathlib.Path(os.path.expanduser("~")).resolve() + + +def get_o3de_folder() -> pathlib.Path: + o3de_folder = get_home_folder() / '.o3de' + o3de_folder.mkdir(parents=True, exist_ok=True) + return o3de_folder + + +def get_o3de_registry_folder() -> pathlib.Path: + registry_folder = get_o3de_folder() / 'Registry' + registry_folder.mkdir(parents=True, exist_ok=True) + return registry_folder + + +def get_o3de_cache_folder() -> pathlib.Path: + cache_folder = get_o3de_folder() / 'Cache' + cache_folder.mkdir(parents=True, exist_ok=True) + return cache_folder + + +def get_o3de_download_folder() -> pathlib.Path: + download_folder = get_o3de_folder() / 'Download' + download_folder.mkdir(parents=True, exist_ok=True) + return download_folder + + +def get_o3de_engines_folder() -> pathlib.Path: + engines_folder = get_o3de_folder() / 'Engines' + engines_folder.mkdir(parents=True, exist_ok=True) + return engines_folder + + +def get_o3de_projects_folder() -> pathlib.Path: + projects_folder = get_o3de_folder() / 'Projects' + projects_folder.mkdir(parents=True, exist_ok=True) + return projects_folder + + +def get_o3de_gems_folder() -> pathlib.Path: + gems_folder = get_o3de_folder() / 'Gems' + gems_folder.mkdir(parents=True, exist_ok=True) + return gems_folder + + +def get_o3de_templates_folder() -> pathlib.Path: + templates_folder = get_o3de_folder() / 'Templates' + templates_folder.mkdir(parents=True, exist_ok=True) + return templates_folder + + +def get_o3de_restricted_folder() -> pathlib.Path: + restricted_folder = get_o3de_folder() / 'Restricted' + restricted_folder.mkdir(parents=True, exist_ok=True) + return restricted_folder + + +def get_o3de_logs_folder() -> pathlib.Path: + logs_folder = get_o3de_folder() / 'Logs' + logs_folder.mkdir(parents=True, exist_ok=True) + return logs_folder + + +# o3de manifest file methods +def get_o3de_manifest() -> pathlib.Path: + manifest_path = get_o3de_folder() / 'o3de_manifest.json' + if not manifest_path.is_file(): + username = os.path.split(get_home_folder())[-1] + + o3de_folder = get_o3de_folder() + default_registry_folder = get_o3de_registry_folder() + default_cache_folder = get_o3de_cache_folder() + default_downloads_folder = get_o3de_download_folder() + default_logs_folder = get_o3de_logs_folder() + default_engines_folder = get_o3de_engines_folder() + default_projects_folder = get_o3de_projects_folder() + default_gems_folder = get_o3de_gems_folder() + default_templates_folder = get_o3de_templates_folder() + default_restricted_folder = get_o3de_restricted_folder() + + default_projects_restricted_folder = default_projects_folder / 'Restricted' + default_projects_restricted_folder.mkdir(parents=True, exist_ok=True) + default_gems_restricted_folder = default_gems_folder / 'Restricted' + default_gems_restricted_folder.mkdir(parents=True, exist_ok=True) + default_templates_restricted_folder = default_templates_folder / 'Restricted' + default_templates_restricted_folder.mkdir(parents=True, exist_ok=True) + + json_data = {} + json_data.update({'o3de_manifest_name': f'{username}'}) + json_data.update({'origin': o3de_folder.as_posix()}) + json_data.update({'default_engines_folder': default_engines_folder.as_posix()}) + json_data.update({'default_projects_folder': default_projects_folder.as_posix()}) + json_data.update({'default_gems_folder': default_gems_folder.as_posix()}) + json_data.update({'default_templates_folder': default_templates_folder.as_posix()}) + json_data.update({'default_restricted_folder': default_restricted_folder.as_posix()}) + + json_data.update({'projects': []}) + json_data.update({'external_subdirectories': []}) + json_data.update({'templates': []}) + json_data.update({'restricted': []}) + json_data.update({'repos': []}) + json_data.update({'engines': []}) + + default_restricted_folder_json = default_restricted_folder / 'restricted.json' + if not default_restricted_folder_json.is_file(): + with default_restricted_folder_json.open('w') as s: + restricted_json_data = {} + restricted_json_data.update({'restricted_name': 'o3de'}) + s.write(json.dumps(restricted_json_data, indent=4) + '\n') + json_data.update({'default_restricted_folder': default_restricted_folder.as_posix()}) + + default_projects_restricted_folder_json = default_projects_restricted_folder / 'restricted.json' + if not default_projects_restricted_folder_json.is_file(): + with default_projects_restricted_folder_json.open('w') as s: + restricted_json_data = {} + restricted_json_data.update({'restricted_name': 'projects'}) + s.write(json.dumps(restricted_json_data, indent=4) + '\n') + + default_gems_restricted_folder_json = default_gems_restricted_folder / 'restricted.json' + if not default_gems_restricted_folder_json.is_file(): + with default_gems_restricted_folder_json.open('w') as s: + restricted_json_data = {} + restricted_json_data.update({'restricted_name': 'gems'}) + s.write(json.dumps(restricted_json_data, indent=4) + '\n') + + default_templates_restricted_folder_json = default_templates_restricted_folder / 'restricted.json' + if not default_templates_restricted_folder_json.is_file(): + with default_templates_restricted_folder_json.open('w') as s: + restricted_json_data = {} + restricted_json_data.update({'restricted_name': 'templates'}) + s.write(json.dumps(restricted_json_data, indent=4) + '\n') + + with manifest_path.open('w') as s: + s.write(json.dumps(json_data, indent=4) + '\n') + + return manifest_path + + +def load_o3de_manifest(manifest_path: pathlib.Path = None) -> dict: + """ + Loads supplied manifest file or ~/.o3de/o3de_manifest.json if None + + :param manifest_path: optional path to manifest file to load + """ + if not manifest_path: + manifest_path = get_o3de_manifest() + with manifest_path.open('r') as f: + try: + json_data = json.load(f) + except json.JSONDecodeError as e: + logger.error(f'Manifest json failed to load: {str(e)}') + return {} + else: + return json_data + + +def save_o3de_manifest(json_data: dict, manifest_path: pathlib.Path = None) -> None: + """ + Save the json dictionary to the supplied manifest file or ~/.o3de/o3de_manifest.json if None + + :param json_data: dictionary to save in json format at the file path + :param manifest_path: optional path to manifest file to save + """ + if not manifest_path: + manifest_path = get_o3de_manifest() + with manifest_path.open('w') as s: + try: + s.write(json.dumps(json_data, indent=4) + '\n') + except OSError as e: + logger.error(f'Manifest json failed to save: {str(e)}') + + +# Data query methods +def get_this_engine() -> dict: + json_data = load_o3de_manifest() + engine_data = find_engine_data(json_data) + return engine_data + + +def get_engines() -> list: + json_data = load_o3de_manifest() + return json_data['engines'] if 'engines' in json_data else [] + + +def get_projects() -> list: + json_data = load_o3de_manifest() + return json_data['projects'] if 'projects' in json_data else [] + + +def get_gems() -> list: + def is_gem_subdirectory(subdir): + return (pathlib.Path(subdir) / 'gem.json').exists() + + external_subdirs = get_external_subdirectories() + return list(filter(is_gem_subdirectory, external_subdirs)) if external_subdirs else [] + + +def get_external_subdirectories() -> list: + json_data = load_o3de_manifest() + return json_data['external_subdirectories'] if 'external_subdirectories' in json_data else [] + + +def get_templates() -> list: + json_data = load_o3de_manifest() + return json_data['templates'] if 'templates' in json_data else [] + + +def get_restricted() -> list: + json_data = load_o3de_manifest() + return json_data['restricted'] if 'restricted' in json_data else [] + + +def get_repos() -> list: + json_data = load_o3de_manifest() + return json_data['repos'] if 'repos' in json_data else [] + +# engine.json queries +def get_engine_projects() -> list: + engine_path = get_this_engine_path() + engine_object = get_engine_json_data(engine_path=engine_path) + return list(map(lambda rel_path: (pathlib.Path(engine_path) / rel_path).as_posix(), + engine_object['projects'])) if 'projects' in engine_object else [] + + +def get_engine_gems() -> list: + def is_gem_subdirectory(subdir): + return (pathlib.Path(subdir) / 'gem.json').exists() + + external_subdirs = get_engine_external_subdirectories() + return list(filter(is_gem_subdirectory, external_subdirs)) if external_subdirs else [] + + +def get_engine_external_subdirectories() -> list: + engine_path = get_this_engine_path() + engine_object = get_engine_json_data(engine_path=engine_path) + return list(map(lambda rel_path: (pathlib.Path(engine_path) / rel_path).as_posix(), + engine_object['external_subdirectories'])) if 'external_subdirectories' in engine_object else [] + + +def get_engine_templates() -> list: + engine_path = get_this_engine_path() + engine_object = get_engine_json_data(engine_path=engine_path) + return list(map(lambda rel_path: (pathlib.Path(engine_path) / rel_path).as_posix(), + engine_object['templates'])) + + +def get_engine_restricted() -> list: + engine_path = get_this_engine_path() + engine_object = get_engine_json_data(engine_path=engine_path) + return list(map(lambda rel_path: (pathlib.Path(engine_path) / rel_path).as_posix(), + engine_object['restricted'])) if 'restricted' in engine_object else [] + + +# project.json queries +def get_project_gems(project_path: pathlib.Path) -> list: + def is_gem_subdirectory(subdir): + return (pathlib.Path(subdir) / 'gem.json').exists() + + external_subdirs = get_project_external_subdirectories(project_path) + return list(filter(is_gem_subdirectory, external_subdirs)) if external_subdirs else [] + + +def get_project_external_subdirectories(project_path: pathlib.Path) -> list: + project_object = get_project_json_data(project_path=project_path) + return list(map(lambda rel_path: (pathlib.Path(project_path) / rel_path).as_posix(), + project_object['external_subdirectories'])) if 'external_subdirectories' in project_object else [] + + +# Combined manifest queries +def get_all_projects() -> list: + projects_data = set(get_projects()) + projects_data.update(get_engine_projects()) + return list(projects_data) + + +def get_all_gems(project_path: pathlib.Path = None) -> list: + gems_data = set(get_gems()) + gems_data.update(get_engine_gems()) + if project_path: + gems_data.update(get_project_gems(project_path)) + return list(gems_data) + + +def get_all_external_subdirectories(project_path: pathlib.Path = None) -> list: + external_subdirectories_data = set(get_external_subdirectories()) + external_subdirectories_data.update(get_engine_external_subdirectories()) + if project_path: + external_subdirectories_data.update(get_project_external_subdirectories(project_path)) + return list(templates_data) + + +def get_all_templates() -> list: + templates_data = set(get_templates()) + templates_data.update(get_engine_templates()) + return list(templates_data) + + +def get_all_restricted() -> list: + restricted_data = set(get_restricted()) + restricted_data.update(get_engine_restricted()) + return list(gems_data) + + +# Template functions +def get_project_templates(): # temporary until we have a better way to do this... maybe template_type element + project_templates = [] + for template in get_all_templates(): + if 'Project' in template: + project_templates.append(template) + return project_templates + + +def get_gem_templates(): # temporary until we have a better way to do this... maybe template_type element + gem_templates = [] + for template in get_all_templates(): + if 'Gem' in template: + gem_templates.append(template) + return gem_templates + + +def get_generic_templates(): # temporary until we have a better way to do this... maybe template_type element + generic_templates = [] + for template in get_all_templates(): + if 'Project' not in template and 'Gem' not in template: + generic_templates.append(template) + return generic_templates + + +def get_all_restricted() -> list: + engine_restricted = get_engine_restricted() + restricted_data = get_restricted() + restricted_data.extend(engine_restricted) + return restricted_data + + +def find_engine_data(json_data: dict, + engine_path: str or pathlib.Path = None) -> dict or None: + if not engine_path: + engine_path = get_this_engine_path() + engine_path = pathlib.Path(engine_path).resolve() + + for engine_object in json_data['engines']: + engine_object_path = pathlib.Path(engine_object['path']).resolve() + if engine_path == engine_object_path: + return engine_object + + return None + + +def get_engine_json_data(engine_name: str = None, + engine_path: str or pathlib.Path = None) -> dict or None: + if not engine_name and not engine_path: + logger.error('Must specify either a Engine name or Engine Path.') + return None + + if engine_name and not engine_path: + engine_path = get_registered(engine_name=engine_name) + + if not engine_path: + logger.error(f'Engine Path {engine_path} has not been registered.') + return None + + engine_path = pathlib.Path(engine_path).resolve() + engine_json = engine_path / 'engine.json' + if not engine_json.is_file(): + logger.error(f'Engine json {engine_json} is not present.') + return None + if not validation.valid_o3de_engine_json(engine_json): + logger.error(f'Engine json {engine_json} is not valid.') + return None + + with engine_json.open('r') as f: + try: + engine_json_data = json.load(f) + except json.JSONDecodeError as e: + logger.warn(f'{engine_json} failed to load: {str(e)}') + else: + return engine_json_data + + return None + + +def get_project_json_data(project_name: str = None, + project_path: str or pathlib.Path = None) -> dict or None: + if not project_name and not project_path: + logger.error('Must specify either a Project name or Project Path.') + return None + + if project_name and not project_path: + project_path = get_registered(project_name=project_name) + + if not project_path: + logger.error(f'Project Path {project_path} has not been registered.') + return None + + project_path = pathlib.Path(project_path).resolve() + project_json = project_path / 'project.json' + if not project_json.is_file(): + logger.error(f'Project json {project_json} is not present.') + return None + if not validation.valid_o3de_project_json(project_json): + logger.error(f'Project json {project_json} is not valid.') + return None + + with project_json.open('r') as f: + try: + project_json_data = json.load(f) + except json.JSONDecodeError as e: + logger.warn(f'{project_json} failed to load: {str(e)}') + else: + return project_json_data + + return None + + +def get_gem_json_data(gem_name: str = None, + gem_path: str or pathlib.Path = None) -> dict or None: + if not gem_name and not gem_path: + logger.error('Must specify either a Gem name or Gem Path.') + return None + + if gem_name and not gem_path: + gem_path = get_registered(gem_name=gem_name) + + if not gem_path: + logger.error(f'Gem Path {gem_path} has not been registered.') + return None + + gem_path = pathlib.Path(gem_path).resolve() + gem_json = gem_path / 'gem.json' + if not gem_json.is_file(): + logger.error(f'Gem json {gem_json} is not present.') + return None + if not validation.valid_o3de_gem_json(gem_json): + logger.error(f'Gem json {gem_json} is not valid.') + return None + + with gem_json.open('r') as f: + try: + gem_json_data = json.load(f) + except json.JSONDecodeError as e: + logger.warn(f'{gem_json} failed to load: {str(e)}') + else: + return gem_json_data + + return None + + +def get_template_json_data(template_name: str = None, + template_path: str or pathlib.Path = None) -> dict or None: + if not template_name and not template_path: + logger.error('Must specify either a Template name or Template Path.') + return None + + if template_name and not template_path: + template_path = get_registered(template_name=template_name) + + if not template_path: + logger.error(f'Template Path {template_path} has not been registered.') + return None + + template_path = pathlib.Path(template_path).resolve() + template_json = template_path / 'template.json' + if not template_json.is_file(): + logger.error(f'Template json {template_json} is not present.') + return None + if not validation.valid_o3de_template_json(template_json): + logger.error(f'Template json {template_json} is not valid.') + return None + + with template_json.open('r') as f: + try: + template_json_data = json.load(f) + except json.JSONDecodeError as e: + logger.warn(f'{template_json} failed to load: {str(e)}') + else: + return template_json_data + + return None + + +def get_restricted_data(restricted_name: str = None, + restricted_path: str or pathlib.Path = None) -> dict or None: + if not restricted_name and not restricted_path: + logger.error('Must specify either a Restricted name or Restricted Path.') + return None + + if restricted_name and not restricted_path: + restricted_path = get_registered(restricted_name=restricted_name) + + if not restricted_path: + logger.error(f'Restricted Path {restricted_path} has not been registered.') + return None + + restricted_path = pathlib.Path(restricted_path).resolve() + restricted_json = restricted_path / 'restricted.json' + if not restricted_json.is_file(): + logger.error(f'Restricted json {restricted_json} is not present.') + return None + if not validation.valid_o3de_restricted_json(restricted_json): + logger.error(f'Restricted json {restricted_json} is not valid.') + return None + + with restricted_json.open('r') as f: + try: + restricted_json_data = json.load(f) + except json.JSONDecodeError as e: + logger.warn(f'{restricted_json} failed to load: {str(e)}') + else: + return restricted_json_data + + return None + + +def get_registered(engine_name: str = None, + project_name: str = None, + gem_name: str = None, + template_name: str = None, + default_folder: str = None, + repo_name: str = None, + restricted_name: str = None) -> pathlib.Path or None: + json_data = load_o3de_manifest() + + # check global first then this engine + if isinstance(engine_name, str): + for engine in json_data['engines']: + engine_path = pathlib.Path(engine['path']).resolve() + engine_json = engine_path / 'engine.json' + with engine_json.open('r') as f: + try: + engine_json_data = json.load(f) + except json.JSONDecodeError as e: + logger.warn(f'{engine_json} failed to load: {str(e)}') + else: + this_engines_name = engine_json_data['engine_name'] + if this_engines_name == engine_name: + return engine_path + + elif isinstance(project_name, str): + enging_projects = get_engine_projects() + projects = json_data['projects'].copy() + projects.extend(engine_object['projects']) + for project_path in projects: + project_path = pathlib.Path(project_path).resolve() + project_json = project_path / 'project.json' + with project_json.open('r') as f: + try: + project_json_data = json.load(f) + except json.JSONDecodeError as e: + logger.warn(f'{project_json} failed to load: {str(e)}') + else: + this_projects_name = project_json_data['project_name'] + if this_projects_name == project_name: + return project_path + + elif isinstance(gem_name, str): + gems = get_all_gems() + for gem_path in gems: + gem_path = pathlib.Path(gem_path).resolve() + gem_json = gem_path / 'gem.json' + with gem_json.open('r') as f: + try: + gem_json_data = json.load(f) + except json.JSONDecodeError as e: + logger.warn(f'{gem_json} failed to load: {str(e)}') + else: + this_gems_name = gem_json_data['gem_name'] + if this_gems_name == gem_name: + return gem_path + + elif isinstance(template_name, str): + engine_templates = get_engine_templates() + templates = json_data['templates'].copy() + templates.extend(engine_templates) + for template_path in templates: + template_path = pathlib.Path(template_path).resolve() + template_json = template_path / 'template.json' + with template_json.open('r') as f: + try: + template_json_data = json.load(f) + except json.JSONDecodeError as e: + logger.warn(f'{template_path} failed to load: {str(e)}') + else: + this_templates_name = template_json_data['template_name'] + if this_templates_name == template_name: + return template_path + + elif isinstance(restricted_name, str): + engine_restricted = get_engine_restricted() + restricted = json_data['restricted'].copy() + restricted.extend(engine_restricted) + for restricted_path in restricted: + restricted_path = pathlib.Path(restricted_path).resolve() + restricted_json = restricted_path / 'restricted.json' + with restricted_json.open('r') as f: + try: + restricted_json_data = json.load(f) + except json.JSONDecodeError as e: + logger.warn(f'{restricted_json} failed to load: {str(e)}') + else: + this_restricted_name = restricted_json_data['restricted_name'] + if this_restricted_name == restricted_name: + return restricted_path + + elif isinstance(default_folder, str): + if default_folder == 'engines': + default_engines_folder = pathlib.Path(json_data['default_engines_folder']).resolve() + return default_engines_folder + elif default_folder == 'projects': + default_projects_folder = pathlib.Path(json_data['default_projects_folder']).resolve() + return default_projects_folder + elif default_folder == 'gems': + default_gems_folder = pathlib.Path(json_data['default_gems_folder']).resolve() + return default_gems_folder + elif default_folder == 'templates': + default_templates_folder = pathlib.Path(json_data['default_templates_folder']).resolve() + return default_templates_folder + elif default_folder == 'restricted': + default_restricted_folder = pathlib.Path(json_data['default_restricted_folder']).resolve() + return default_restricted_folder + + elif isinstance(repo_name, str): + cache_folder = get_o3de_cache_folder() + for repo_uri in json_data['repos']: + repo_uri = pathlib.Path(repo_uri).resolve() + repo_sha256 = hashlib.sha256(repo_uri.encode()) + cache_file = cache_folder / str(repo_sha256.hexdigest() + '.json') + if cache_file.is_file(): + repo = pathlib.Path(cache_file).resolve() + with repo.open('r') as f: + try: + repo_json_data = json.load(f) + except json.JSONDecodeError as e: + logger.warn(f'{cache_file} failed to load: {str(e)}') + else: + this_repos_name = repo_json_data['repo_name'] + if this_repos_name == repo_name: + return repo_uri + return None diff --git a/scripts/o3de/o3de/print_registration.py b/scripts/o3de/o3de/print_registration.py new file mode 100644 index 0000000000..292f2224bc --- /dev/null +++ b/scripts/o3de/o3de/print_registration.py @@ -0,0 +1,490 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +import argparse +import json +import hashlib +import logging +import sys +import urllib.parse + +from o3de import manifest, validation + +logger = logging.getLogger() +logging.basicConfig() + +def print_this_engine(verbose: int) -> None: + engine_data = manifest.get_this_engine() + print(json.dumps(engine_data, indent=4)) + if verbose > 0: + print_engines_data(engine_data) + + +def print_engines(verbose: int) -> None: + engines_data = manifest.get_engines() + print(json.dumps(engines_data, indent=4)) + if verbose > 0: + print_engines_data(engines_data) + + +def print_projects(verbose: int) -> None: + projects_data = manifest.get_projects() + print(json.dumps(projects_data, indent=4)) + if verbose > 0: + print_projects_data(projects_data) + + +def print_gems(verbose: int) -> None: + gems_data = manifest.get_gems() + print(json.dumps(gems_data, indent=4)) + if verbose > 0: + print_gems_data(gems_data) + + +def print_templates(verbose: int) -> None: + templates_data = manifest.get_templates() + print(json.dumps(templates_data, indent=4)) + if verbose > 0: + print_templates_data(templates_data) + + +def print_restricted(verbose: int) -> None: + restricted_data = manifest.get_restricted() + print(json.dumps(restricted_data, indent=4)) + if verbose > 0: + print_restricted_data(restricted_data) + +def print_engine_projects(verbose: int) -> None: + engine_projects_data = manifest.get_engine_projects() + print(json.dumps(engine_projects_data, indent=4)) + if verbose > 0: + print_projects_data(engine_projects_data) + + +def print_engine_gems(verbose: int) -> None: + engine_gems_data = manifest.get_engine_gems() + print(json.dumps(engine_gems_data, indent=4)) + if verbose > 0: + print_gems_data(engine_gems_data) + + +def print_engine_templates(verbose: int) -> None: + engine_templates_data = manifest.get_engine_templates() + print(json.dumps(engine_templates_data, indent=4)) + if verbose > 0: + print_templates_data(engine_templates_data) + + +def print_engine_restricted(verbose: int) -> None: + engine_restricted_data = manifest.get_engine_restricted() + print(json.dumps(engine_restricted_data, indent=4)) + if verbose > 0: + print_restricted_data(engine_restricted_data) + + +def print_engine_external_subdirectories(verbose: int) -> None: + external_subdirs_data = manifest.get_engine_external_subdirectories() + print(json.dumps(external_subdirs_data, indent=4)) + + +def print_all_projects(verbose: int) -> None: + all_projects_data = manifest.get_all_projects() + print(json.dumps(all_projects_data, indent=4)) + if verbose > 0: + print_projects_data(all_projects_data) + + +def print_all_gems(verbose: int) -> None: + all_gems_data = manifest.get_all_gems() + print(json.dumps(all_gems_data, indent=4)) + if verbose > 0: + print_gems_data(all_gems_data) + + +def print_all_templates(verbose: int) -> None: + all_templates_data = manifest.get_all_templates() + print(json.dumps(all_templates_data, indent=4)) + if verbose > 0: + print_templates_data(all_templates_data) + + +def print_all_restricted(verbose: int) -> None: + all_restricted_data = manifest.get_all_restricted() + print(json.dumps(all_restricted_data, indent=4)) + if verbose > 0: + print_restricted_data(all_restricted_data) + + +def print_engines_data(engines_data: dict) -> None: + print('\n') + print("Engines================================================") + for engine_object in engines_data: + # if it's not local it should be in the cache + engine_uri = engine_object['path'] + parsed_uri = urllib.parse.urlparse(engine_uri) + if parsed_uri.scheme == 'http' or \ + parsed_uri.scheme == 'https' or \ + parsed_uri.scheme == 'ftp' or \ + parsed_uri.scheme == 'ftps': + repo_sha256 = hashlib.sha256(engine_uri.encode()) + cache_folder = manifest.get_o3de_cache_folder() + engine = cache_folder / str(repo_sha256.hexdigest() + '.json') + print(f'{engine_uri}/engine.json cached as:') + else: + engine_json = pathlib.Path(engine_uri).resolve() / 'engine.json' + + with engine_json.open('r') as f: + try: + engine_json_data = json.load(f) + except json.JSONDecodeError as e: + logger.warn(f'{engine_json} failed to load: {str(e)}') + else: + print(engine_json) + print(json.dumps(engine_json_data, indent=4)) + print('\n') + + +def print_projects_data(projects_data: dict) -> None: + print('\n') + print("Projects================================================") + for project_uri in projects_data: + # if it's not local it should be in the cache + parsed_uri = urllib.parse.urlparse(project_uri) + if parsed_uri.scheme == 'http' or \ + parsed_uri.scheme == 'https' or \ + parsed_uri.scheme == 'ftp' or \ + parsed_uri.scheme == 'ftps': + repo_sha256 = hashlib.sha256(project_uri.encode()) + cache_folder = manifest.get_o3de_cache_folder() + project_json = cache_folder / str(repo_sha256.hexdigest() + '.json') + else: + project_json = pathlib.Path(project_uri).resolve() / 'project.json' + + with project_json.open('r') as f: + try: + project_json_data = json.load(f) + except json.JSONDecodeError as e: + logger.warn(f'{project_json} failed to load: {str(e)}') + else: + print(project_json) + print(json.dumps(project_json_data, indent=4)) + print('\n') + + +def print_gems_data(gems_data: dict) -> None: + print('\n') + print("Gems================================================") + for gem_uri in gems_data: + # if it's not local it should be in the cache + parsed_uri = urllib.parse.urlparse(gem_uri) + if parsed_uri.scheme == 'http' or \ + parsed_uri.scheme == 'https' or \ + parsed_uri.scheme == 'ftp' or \ + parsed_uri.scheme == 'ftps': + repo_sha256 = hashlib.sha256(gem_uri.encode()) + cache_folder = manifest.get_o3de_cache_folder() + gem_json = cache_folder / str(repo_sha256.hexdigest() + '.json') + else: + gem_json = pathlib.Path(gem_uri).resolve() / 'gem.json' + + with gem_json.open('r') as f: + try: + gem_json_data = json.load(f) + except json.JSONDecodeError as e: + logger.warn(f'{gem_json} failed to load: {str(e)}') + else: + print(gem_json) + print(json.dumps(gem_json_data, indent=4)) + print('\n') + + +def print_templates_data(templates_data: dict) -> None: + print('\n') + print("Templates================================================") + for template_uri in templates_data: + # if it's not local it should be in the cache + parsed_uri = urllib.parse.urlparse(template_uri) + if parsed_uri.scheme == 'http' or \ + parsed_uri.scheme == 'https' or \ + parsed_uri.scheme == 'ftp' or \ + parsed_uri.scheme == 'ftps': + repo_sha256 = hashlib.sha256(template_uri.encode()) + cache_folder = manifest.get_o3de_cache_folder() + template_json = cache_folder / str(repo_sha256.hexdigest() + '.json') + else: + template_json = pathlib.Path(template_uri).resolve() / 'template.json' + + with template_json.open('r') as f: + try: + template_json_data = json.load(f) + except json.JSONDecodeError as e: + logger.warn(f'{template_json} failed to load: {str(e)}') + else: + print(template_json) + print(json.dumps(template_json_data, indent=4)) + print('\n') + + +def print_repos_data(repos_data: dict) -> None: + print('\n') + print("Repos================================================") + cache_folder = manifest.get_o3de_cache_folder() + for repo_uri in repos_data: + repo_sha256 = hashlib.sha256(repo_uri.encode()) + cache_file = cache_folder / str(repo_sha256.hexdigest() + '.json') + if validation.valid_o3de_repo_json(cache_file): + with cache_file.open('r') as s: + try: + repo_json_data = json.load(s) + except json.JSONDecodeError as e: + logger.warn(f'{cache_file} failed to load: {str(e)}') + else: + print(f'{repo_uri}/repo.json cached as:') + print(cache_file) + print(json.dumps(repo_json_data, indent=4)) + print('\n') + + +def print_restricted_data(restricted_data: dict) -> None: + print('\n') + print("Restricted================================================") + for restricted_path in restricted_data: + restricted_json = pathlib.Path(restricted_path).resolve() / 'restricted.json' + with restricted_json.open('r') as f: + try: + restricted_json_data = json.load(f) + except json.JSONDecodeError as e: + logger.warn(f'{restricted_json} failed to load: {str(e)}') + else: + print(restricted_json) + print(json.dumps(restricted_json_data, indent=4)) + print('\n') + + +def register_show_repos(verbose: int) -> None: + repos_data = get_repos() + print(json.dumps(repos_data, indent=4)) + if verbose > 0: + print_repos_data(repos_data) + + +def register_show(verbose: int) -> None: + json_data = manifest.load_o3de_manifest() + print(f"{manifest.get_o3de_manifest()}:") + print(json.dumps(json_data, indent=4)) + + if verbose > 0: + print_engines_data(manifest.get_engines()) + print_projects_data(manifest.get_all_projects()) + print_gems_data(manifest.get_gems()) + print_templates_data(manifest.get_all_templates()) + print_restricted_data(manifest.get_all_restricted()) + print_repos_data(manifest.get_repos()) + + +def _run_register_show(args: argparse) -> int: + if args.override_home_folder: + manifest.override_home_folder = args.override_home_folder + + if args.this_engine: + print_this_engine(args.verbose) + return 0 + + elif args.engines: + print_engines(args.verbose) + return 0 + elif args.projects: + print_projects(args.verbose) + return 0 + elif args.gems: + print_gems(args.verbose) + return 0 + elif args.templates: + print_templates(args.verbose) + return 0 + elif args.repos: + register_show_repos(args.verbose) + return 0 + elif args.restricted: + print_restricted(args.verbose) + return 0 + + elif args.engine_projects: + print_engine_projects(args.verbose) + return 0 + elif args.engine_gems: + print_engine_gems(args.verbose) + return 0 + elif args.engine_templates: + print_engine_templates(args.verbose) + return 0 + elif args.engine_restricted: + print_engine_restricted(args.verbose) + return 0 + elif args.engine_external_subdirectories: + print_engine_external_subdirectories(args.verbose) + return 0 + + elif args.all_projects: + print_all_projects(args.verbose) + return 0 + elif args.all_gems: + print_all_gems(args.verbose) + return 0 + elif args.all_templates: + print_all_templates(args.verbose) + return 0 + elif args.all_restricted: + print_all_restricted(args.verbose) + return 0 + + elif args.downloadables: + print_downloadables(args.verbose) + return 0 + if args.downloadable_engines: + print_downloadable_engines(args.verbose) + return 0 + elif args.downloadable_projects: + print_downloadable_projects(args.verbose) + return 0 + elif args.downloadable_gems: + print_downloadable_gems(args.verbose) + return 0 + elif args.downloadable_templates: + print_downloadable_templates(args.verbose) + return 0 + else: + register_show(args.verbose) + return 0 + + +def add_parser_args(parser): + """ + add_parser_args is called to add arguments to each command such that it can be + invoked locally or added by a central python file. + Ex. Directly run from this file alone with: python print_registration.py --engine-projects + :param parser: the caller passes an argparse parser like instance to this method + """ + group = parser.add_mutually_exclusive_group(required=False) + group.add_argument('-te', '--this-engine', action='store_true', required=False, + default=False, + help='Just the local engines.') + + group.add_argument('-e', '--engines', action='store_true', required=False, + default=False, + help='Just the local engines.') + group.add_argument('-p', '--projects', action='store_true', required=False, + default=False, + help='Just the local projects.') + group.add_argument('-g', '--gems', action='store_true', required=False, + default=False, + help='Just the local gems.') + group.add_argument('-t', '--templates', action='store_true', required=False, + default=False, + help='Just the local templates.') + group.add_argument('-r', '--repos', action='store_true', required=False, + default=False, + help='Just the local repos. Ignores repos.') + group.add_argument('-rs', '--restricted', action='store_true', required=False, + default=False, + help='The local restricted folders.') + + group.add_argument('-ep', '--engine-projects', action='store_true', required=False, + default=False, + help='Just the local projects. Ignores repos.') + group.add_argument('-eg', '--engine-gems', action='store_true', required=False, + default=False, + help='Just the local gems. Ignores repos') + group.add_argument('-et', '--engine-templates', action='store_true', required=False, + default=False, + help='Just the local templates. Ignores repos.') + group.add_argument('-ers', '--engine-restricted', action='store_true', required=False, + default=False, + help='The restricted folders.') + group.add_argument('-x', '--engine-external-subdirectories', action='store_true', required=False, + default=False, + help='The external subdirectories.') + + group.add_argument('-ap', '--all-projects', action='store_true', required=False, + default=False, + help='Just the local projects. Ignores repos.') + group.add_argument('-ag', '--all-gems', action='store_true', required=False, + default=False, + help='Just the local gems. Ignores repos') + group.add_argument('-at', '--all-templates', action='store_true', required=False, + default=False, + help='Just the local templates. Ignores repos.') + group.add_argument('-ars', '--all-restricted', action='store_true', required=False, + default=False, + help='The restricted folders.') + + group.add_argument('-d', '--downloadables', action='store_true', required=False, + default=False, + help='Combine all repos into a single list of resources.') + group.add_argument('-de', '--downloadable-engines', action='store_true', required=False, + default=False, + help='Combine all repos engines into a single list of resources.') + group.add_argument('-dp', '--downloadable-projects', action='store_true', required=False, + default=False, + help='Combine all repos projects into a single list of resources.') + group.add_argument('-dg', '--downloadable-gems', action='store_true', required=False, + default=False, + help='Combine all repos gems into a single list of resources.') + group.add_argument('-dt', '--downloadable-templates', action='store_true', required=False, + default=False, + help='Combine all repos templates into a single list of resources.') + + parser.add_argument('-v', '--verbose', action='count', required=False, + default=0, + help='How verbose do you want the output to be.') + + parser.add_argument('-ohf', '--override-home-folder', type=str, required=False, + help='By default the home folder is the user folder, override it to this folder.') + + parser.set_defaults(func=_run_register_show) + + +def add_args(subparsers) -> None: + """ + add_args is called to add subparsers arguments to each command such that it can be + a central python file such as o3de.py. + It can be run from the o3de.py script as follows + call add_args and execute: python o3de.py register-show --engine-projects + :param subparsers: the caller instantiates subparsers and passes it in here + """ + register_show_subparser = subparsers.add_parser('register-show') + add_parser_args(register_show_subparser) + + +def main(): + """ + Runs print_registration.py script as standalone script + """ + # parse the command line args + the_parser = argparse.ArgumentParser() + + # add subparsers + + # add args to the parser + add_parser_args(the_parser) + + # parse args + the_args = the_parser.parse_args() + + # run + ret = the_args.func(the_args) if hasattr(the_args, 'func') else 1 + + # return + sys.exit(ret) + + +if __name__ == "__main__": + main() diff --git a/scripts/o3de/o3de/register.py b/scripts/o3de/o3de/register.py new file mode 100644 index 0000000000..68575488dc --- /dev/null +++ b/scripts/o3de/o3de/register.py @@ -0,0 +1,864 @@ + +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# +""" +This file contains all the code that has to do with registering engines, projects, gems and templates +""" + +import argparse +import hashlib +import logging +import json +import os +import pathlib +import shutil +import sys +import urllib.parse +import urllib.request + +from o3de import get_registration, manifest, repo, utils, validation + +logger = logging.getLogger() +logging.basicConfig() + + +def register_shipped_engine_o3de_objects(force: bool = False) -> int: + engine_path = manifest.get_this_engine_path() + + ret_val = 0 + + # register anything in the users default folders globally + error_code = register_all_engines_in_folder(manifest.get_registered(default_folder='engines'), force=force) + if error_code: + ret_val = error_code + error_code = register_all_projects_in_folder(manifest.get_registered(default_folder='projects')) + if error_code: + ret_val = error_code + error_code = register_all_gems_in_folder(manifest.get_registered(default_folder='gems')) + if error_code: + ret_val = error_code + error_code = register_all_templates_in_folder(manifest.get_registered(default_folder='templates')) + if error_code: + ret_val = error_code + error_code = register_all_restricted_in_folder(manifest.get_registered(default_folder='restricted')) + if error_code: + ret_val = error_code + error_code = register_all_restricted_in_folder(manifest.get_registered(default_folder='projects')) + if error_code: + ret_val = error_code + error_code = register_all_restricted_in_folder(manifest.get_registered(default_folder='gems')) + if error_code: + ret_val = error_code + error_code = register_all_restricted_in_folder(manifest.get_registered(default_folder='templates')) + if error_code: + ret_val = error_code + + return ret_val + + +def register_all_in_folder(folder_path: str or pathlib.Path, + remove: bool = False, + engine_path: str or pathlib.Path = None, + exclude: list = None) -> int: + if not folder_path: + logger.error(f'Folder path cannot be empty.') + return 1 + + folder_path = pathlib.Path(folder_path).resolve() + if not folder_path.is_dir(): + logger.error(f'Folder path is not dir.') + return 1 + + engines_set = set() + projects_set = set() + gems_set = set() + templates_set = set() + restricted_set = set() + repo_set = set() + + ret_val = 0 + for root, dirs, files in os.walk(folder_path): + if root in exclude: + continue + + for name in files: + if name == 'engine.json': + engines_set.add(root) + elif name == 'project.json': + projects_set.add(root) + elif name == 'gem.json': + gems_set.add(root) + elif name == 'template.json': + templates_set.add(root) + elif name == 'restricted.json': + restricted_set.add(root) + elif name == 'repo.json': + repo_set.add(root) + + for engine in sorted(engines_set, reverse=True): + error_code = register(engine_path=engine, remove=remove) + if error_code: + ret_val = error_code + + for project in sorted(projects_set, reverse=True): + error_code = register(engine_path=engine_path, project_path=project, remove=remove) + if error_code: + ret_val = error_code + + for gem in sorted(gems_set, reverse=True): + error_code = register(engine_path=engine_path, gem_path=gem, remove=remove) + if error_code: + ret_val = error_code + + for template in sorted(templates_set, reverse=True): + error_code = register(engine_path=engine_path, template_path=template, remove=remove) + if error_code: + ret_val = error_code + + for restricted in sorted(restricted_set, reverse=True): + error_code = register(engine_path=engine_path, restricted_path=restricted, remove=remove) + if error_code: + ret_val = error_code + + for repo in sorted(repo_set, reverse=True): + error_code = register(engine_path=engine_path, repo_uri=repo, remove=remove) + if error_code: + ret_val = error_code + + return ret_val + + +def register_all_o3de_objects_of_type_in_folder(o3de_object_path: str or pathlib.Path, + o3de_object_type: str, + remove: bool, + force: bool, + **register_kwargs) -> int: + if not o3de_object_path: + logger.error(f'Engines path cannot be empty.') + return 1 + + o3de_object_path = pathlib.Path(o3de_object_path).resolve() + if not o3de_object_path.is_dir(): + logger.error(f'Engines path is not dir.') + return 1 + + o3de_object_type_set = set() + register_path_kwarg = f'{o3de_object_type}_path' if o3de_object_type != 'repo' else f'{o3de_object_type}_uri' + + ret_val = 0 + for root, dirs, files in os.walk(o3de_object_path): + if f'{o3de_object_type}.json' in files: + o3de_object_type_set.add(root) + # Stop iteration of any subdirectories + # Nested o3de objects of the same type aren't supported(i.e an engine cannot be inside of a engine). + dirs[:] = [] + + for o3de_object_type_root in sorted(o3de_object_type_set, reverse=True): + error_code = register(**{register_path_kwarg: o3de_object_type_root}, + remove=remove, force=force, **register_kwargs) + if error_code: + ret_val = error_code + + return ret_val + + +def register_all_engines_in_folder(engines_path: str or pathlib.Path, + remove: bool = False, + force: bool = False) -> int: + return register_all_o3de_objects_of_type_in_folder(engines_path, 'engine', remove, force) + + +def register_all_projects_in_folder(projects_path: str or pathlib.Path, + remove: bool = False, + engine_path: str or pathlib.Path = None) -> int: + return register_all_o3de_objects_of_type_in_folder(projects_path, 'project', remove, False, engine_path=engine_path) + + +def register_all_gems_in_folder(gems_path: str or pathlib.Path, + remove: bool = False, + engine_path: pathlib.Path = None, + project_path: pathlib.Path = None) -> int: + return register_all_o3de_objects_of_type_in_folder(gems_path, 'gem', remove, False, engine_path=engine_path) + + +def register_all_templates_in_folder(templates_path: str or pathlib.Path, + remove: bool = False, + engine_path: str or pathlib.Path = None) -> int: + return register_all_o3de_objects_of_type_in_folder(templates_path, 'template', remove, False, engine_path=engine_path) + + +def register_all_restricted_in_folder(restricted_path: str or pathlib.Path, + remove: bool = False, + engine_path: str or pathlib.Path = None) -> int: + return register_all_o3de_objects_of_type_in_folder(restricted_path, 'restricted', remove, False, engine_path=engine_path) + + +def register_all_repos_in_folder(repos_path: str or pathlib.Path, + remove: bool = False, + engine_path: str or pathlib.Path = None) -> int: + return register_all_o3de_objects_of_type_in_folder(repos_path, 'repo', remove, force, engine_path=engine_path) + + +def remove_engine_name_to_path(json_data: dict, + engine_path: pathlib.Path) -> int: + """ + Remove the engine at the specified path if it exist in the o3de manifest + :param json_data in-memory json view of the o3de_manifest.json data + :param engine_path path to engine to remove from the manifest data + + returns 0 to indicate no issues has occurred with removal + """ + if engine_path.is_dir() and validation.valid_o3de_engine_json(engine_path): + engine_json_data = manifest.get_engine_json_data(engine_path=engine_path) + if 'engine_name' in engine_json_data and 'engines_path' in json_data: + engine_name = engine_json_data['engine_name'] + try: + del json_data['engines_path'][engine_name] + except KeyError: + # Attempting to remove a non-existent engine_name is fine + pass + return 0 + + +def add_engine_name_to_path(json_data: dict, engine_path: pathlib.Path, force: bool): + # Add an engine path JSON object which maps the "engine_name" -> "engine_path" + engine_json_data = manifest.get_engine_json_data(engine_path=engine_path) + if not engine_json_data: + logger.error(f'Unable to retrieve json data from engine.json at path {engine_path.as_posix()}') + return 1 + engines_path_json = json_data.setdefault('engines_path', {}) + if 'engine_name' not in engine_json_data: + logger.error(f'engine.json at path {engine_path.as_posix()} is missing "engine_name" key') + return 1 + + engine_name = engine_json_data['engine_name'] + if not force and engine_name in engines_path_json and \ + pathlib.PurePath(engines_path_json[engine_name]) != engine_path: + logger.error( + f'Attempting to register existing engine "{engine_name}" with a new path of {engine_path.as_posix()}.' + f' The current path is {pathlib.Path(engines_path_json[engine_name]).as_posix()}.' + f' To force registration of a new engine path, specify the -f/--force option.') + return 1 + engines_path_json[engine_name] = engine_path.as_posix() + return 0 + + +def register_engine_path(json_data: dict, + engine_path: str or pathlib.Path, + remove: bool = False, + force: bool = False) -> int: + if not engine_path: + logger.error(f'Engine path cannot be empty.') + return 1 + engine_path = pathlib.Path(engine_path).resolve() + + for engine_object in json_data.get('engines', {}): + engine_object_path = pathlib.Path(engine_object['path']).resolve() + if engine_object_path == engine_path: + json_data['engines'].remove(engine_object) + + if remove: + return remove_engine_name_to_path(json_data, engine_path) + + if not engine_path.is_dir(): + logger.error(f'Engine path {engine_path} does not exist.') + return 1 + + engine_json = engine_path / 'engine.json' + if not validation.valid_o3de_engine_json(engine_json): + logger.error(f'Engine json {engine_json} is not valid.') + return 1 + + engine_object = {} + engine_object.update({'path': engine_path.as_posix()}) + + json_data.setdefault('engines', []).insert(0, engine_object) + + return add_engine_name_to_path(json_data, engine_path, force) + + +def register_o3de_object_path(json_data: dict, + o3de_object_path: str or pathlib.Path, + o3de_object_key: str, + o3de_json_filename: str, + validation_func: callable, + remove: bool = False, + engine_path: pathlib.Path = None, + project_path: pathlib.Path = None) -> int: + # save_path variable is used to save the changes to the store the path to the file to save + # if the registration is for the project or engine + save_path = None + + if not o3de_object_path: + logger.error(f'o3de object path cannot be empty.') + return 1 + + o3de_object_path = pathlib.Path(o3de_object_path).resolve() + + if engine_path and project_path: + logger.error(f'Both a project path: {project_path} and engine path: {engine_path} has been supplied.' + 'A subdirectory can only be registered to either the engine path or project in one command') + + manifest_data = None + if engine_path: + manifest_data = manifest.get_engine_json_data(json_data, engine_path) + if not manifest_data: + logger.error(f'Cannot load engine.json data at path {engine_path}') + return 1 + + save_path = engine_path / 'engine.json' + elif project_path: + manifest_data = manifest.get_project_json_data(json_data, project_path) + if not manifest_data: + logger.error(f'Cannot load project.json data at path {project_path}') + return 1 + + save_path = project_path / 'project.json' + else: + manifest_data = json_data + + paths_to_remove = [o3de_object_path] + if save_path: + try: + paths_to_remove.append(o3de_object_path.relative_to(save_path.parent)) + except ValueError: + pass # It is OK relative path cannot be formed + manifest_data[o3de_object_key] = list(filter(lambda p: pathlib.Path(p) not in paths_to_remove, + manifest_data.setdefault(o3de_object_key, []))) + + if remove: + if save_path: + manifest.save_o3de_manifest(manifest_data, save_path) + return 0 + + if not o3de_object_path.is_dir(): + logger.error(f'o3de object path {o3de_object_path} does not exist.') + return 1 + + manifest_json_path = o3de_object_path / o3de_json_filename + if validation_func and not validation_func(manifest_json_path): + logger.error(f'o3de json {manifest_json_path} is not valid.') + return 1 + + # if there is a save path make it relative the directory containing o3de object json file + if save_path: + try: + o3de_object_path = o3de_object_path.relative_to(save_path.parent) + except ValueError: + pass # It is OK relative path cannot be formed + manifest_data[o3de_object_key].insert(0, o3de_object_path.as_posix()) + if save_path: + manifest.save_o3de_manifest(manifest_data, save_path) + + return 0 + + +def register_external_subdirectory(json_data: dict, + external_subdir_path: str or pathlib.Path, + remove: bool = False, + engine_path: pathlib.Path = None, + project_path: pathlib.Path = None) -> int: + """ + :return An integer return code indicating whether registration or removal of the external subdirectory + completed successfully + """ + return register_o3de_object_path(json_data, external_subdir_path, 'external_subdirectories', '', None, remove, + engine_path, project_path) + + +def register_gem_path(json_data: dict, + gem_path: str or pathlib.Path, + remove: bool = False, + engine_path: pathlib.Path = None, + project_path: pathlib.Path = None) -> int: + return register_o3de_object_path(json_data, gem_path, 'external_subdirectories', 'gem.json', + validation.valid_o3de_gem_json, remove, engine_path, project_path) + + +def register_project_path(json_data: dict, + project_path: str or pathlib.Path, + remove: bool = False, + engine_path: str or pathlib.Path = None) -> int: + result = register_o3de_object_path(json_data, project_path, 'projects', 'project.json', + validation.valid_o3de_project_json, remove, engine_path, None) + + if result != 0: + return result + + # registering a project has the additional step of setting the project.json 'engine' field + this_engine_json = manifest.get_engine_json_data(engine_path=manifest.get_this_engine_path()) + if not this_engine_json: + return 1 + project_json_data = manifest.get_project_json_data(project_path=project_path) + if not project_json_data: + return 1 + + update_project_json = False + try: + update_project_json = project_json_data['engine'] != this_engine_json['engine_name'] + except KeyError as e: + update_project_json = True + + if update_project_json: + project_json_data['engine'] = this_engine_json['engine_name'] + utils.backup_file(project_json) + if not manifest.save_o3de_manifest(project_json_data, project_path): + return 1 + + + return 0 + + +def register_template_path(json_data: dict, + template_path: str or pathlib.Path, + remove: bool = False, + engine_path: str or pathlib.Path = None) -> int: + return register_o3de_object_path(json_data, template_path, 'templates', 'template.json', + validation.valid_o3de_template_json, remove, engine_path, None) + + +def register_restricted_path(json_data: dict, + restricted_path: str or pathlib.Path, + remove: bool = False, + engine_path: str or pathlib.Path = None) -> int: + return register_o3de_object_path(json_data, restricted_path, 'restricted', 'restricted.json', + validation.valid_o3de_restricted_json, remove, engine_path, None) + + +def register_repo(json_data: dict, + repo_uri: str or pathlib.Path, + remove: bool = False) -> int: + if not repo_uri: + logger.error(f'Repo URI cannot be empty.') + return 1 + + url = f'{repo_uri}/repo.json' + parsed_uri = urllib.parse.urlparse(url) + + if parsed_uri.scheme in ['http', 'https', 'ftp', 'ftps']: + while repo_uri in json_data['repos']: + json_data['repos'].remove(repo_uri) + else: + repo_uri = pathlib.Path(repo_uri).resolve() + while repo_uri.as_posix() in json_data['repos']: + json_data['repos'].remove(repo_uri.as_posix()) + + if remove: + logger.warn(f'Removing repo uri {repo_uri}.') + return 0 + + repo_sha256 = hashlib.sha256(url.encode()) + cache_file = manifest.get_o3de_cache_folder() / str(repo_sha256.hexdigest() + '.json') + + result = utils.download_file(url, cache_file) + if result == 0: + json_data['repos'].insert(0, repo_uri.as_posix()) + + result = repo.process_add_o3de_repo(cache_file, repo_set) + + return result + + +def register_default_o3de_object_folder(json_data: dict, + default_o3de_object_folder: str or pathlib.Path, + o3de_object_key: str) -> int: + # make sure the path exists + default_o3de_object_folder = pathlib.Path(default_o3de_object_folder).resolve() + if not default_o3de_object_folder.is_dir(): + logger.error(f'Default o3de object folder {default_o3de_object_folder} does not exist.') + return 1 + + json_data[o3de_object_key] = default_o3de_object_folder.as_posix() + + return 0 + + +def register_default_engines_folder(json_data: dict, + default_engines_folder: str or pathlib.Path, + remove: bool = False) -> int: + return register_default_o3de_object_folder(json_data, + manifest.get_o3de_engines_folder() if remove else default_engines_folder, + 'default_engines_folder', remove) + + +def register_default_projects_folder(json_data: dict, + default_projects_folder: str or pathlib.Path, + remove: bool = False) -> int: + return register_default_o3de_object_folder(json_data, + manifest.get_o3de_projects_folder() if remove else default_projects_folder, + 'default_projects_folder', remove) + + +def register_default_gems_folder(json_data: dict, + default_gems_folder: str or pathlib.Path, + remove: bool = False) -> int: + return register_default_o3de_object_folder(json_data, + manifest.get_o3de_gems_folder() if remove else default_gems_folder, + 'default_gems_folder', remove) + + +def register_default_templates_folder(json_data: dict, + default_templates_folder: str or pathlib.Path, + remove: bool = False) -> int: + return register_default_o3de_object_folder(json_data, + manifest.get_o3de_templates_folder() if remove else default_templates_folder, + 'default_templates_folder', remove) + + +def register_default_restricted_folder(json_data: dict, + default_restricted_folder: str or pathlib.Path, + reset_to_default: bool = False) -> int: + return register_default_o3de_object_folder(json_data, + manifest.get_o3de_restricted_folder() if remove else default_restricted_folder, + 'default_restricted_folder', remove) + + +def register(engine_path: str or pathlib.Path = None, + project_path: str or pathlib.Path = None, + gem_path: str or pathlib.Path = None, + external_subdir_path: str or pathlib.Path = None, + template_path: str or pathlib.Path = None, + restricted_path: str or pathlib.Path = None, + repo_uri: str or pathlib.Path = None, + default_engines_folder: str or pathlib.Path = None, + default_projects_folder: str or pathlib.Path = None, + default_gems_folder: str or pathlib.Path = None, + default_templates_folder: str or pathlib.Path = None, + default_restricted_folder: str or pathlib.Path = None, + external_subdir_engine_path: pathlib.Path = None, + external_subdir_project_path: pathlib.Path = None, + remove: bool = False, + force: bool = False + ) -> int: + """ + Adds/Updates entries to the ~/.o3de/o3de_manifest.json + + :param engine_path: if engine folder is supplied the path will be added to the engine if it can, if not global + :param project_path: project folder + :param gem_path: gem folder + :param external_subdir_path: external subdirectory + :param template_path: template folder + :param restricted_path: restricted folder + :param repo_uri: repo uri + :param default_engines_folder: default engines folder + :param default_projects_folder: default projects folder + :param default_gems_folder: default gems folder + :param default_templates_folder: default templates folder + :param default_restricted_folder: default restricted code folder + :param external_subdir_engine_path: Path to the engine to use when registering an external subdirectory. + The registration occurs in the engine.json file in this case + :param external_subdir_engine_path: Path to the project to use when registering an external subdirectory. + The registrations occurs in the project.json in this case + :param remove: add/remove the entries + :param force: force update of the engine_path for specified "engine_name" from the engine.json file + + :return: 0 for success or non 0 failure code + """ + + json_data = manifest.load_o3de_manifest() + + result = 0 + + # do anything that could require a engine context first + if isinstance(project_path, str) or isinstance(project_path, pathlib.PurePath): + if not project_path: + logger.error(f'Project path cannot be empty.') + return 1 + result = register_project_path(json_data, project_path, remove, engine_path) + + elif isinstance(gem_path, str) or isinstance(gem_path, pathlib.PurePath): + if not gem_path: + logger.error(f'Gem path cannot be empty.') + return 1 + result = register_gem_path(json_data, gem_path, remove, + external_subdir_engine_path, external_subdir_project_path) + elif isinstance(external_subdir_path, str) or isinstance(external_subdir_path, pathlib.PurePath): + if not external_subdir_path: + logger.error(f'External Subdirectory path is None.') + return 1 + result = register_external_subdirectory(json_data, external_subdir_path, remove, + external_subdir_engine_path, external_subdir_project_path) + + elif isinstance(template_path, str) or isinstance(template_path, pathlib.PurePath): + if not template_path: + logger.error(f'Template path cannot be empty.') + return 1 + result = register_template_path(json_data, template_path, remove, engine_path) + + elif isinstance(restricted_path, str) or isinstance(restricted_path, pathlib.PurePath): + if not restricted_path: + logger.error(f'Restricted path cannot be empty.') + return 1 + result = register_restricted_path(json_data, restricted_path, remove, engine_path) + + elif isinstance(repo_uri, str) or isinstance(repo_uri, pathlib.PurePath): + if not repo_uri: + logger.error(f'Repo URI cannot be empty.') + return 1 + result = register_repo(json_data, repo_uri, remove) + + elif isinstance(default_engines_folder, str) or isinstance(default_engines_folder, pathlib.PurePath): + result = register_default_engines_folder(json_data, default_engines_folder, remove) + + elif isinstance(default_projects_folder, str) or isinstance(default_projects_folder, pathlib.PurePath): + result = register_default_projects_folder(json_data, default_projects_folder, remove) + + elif isinstance(default_gems_folder, str) or isinstance(default_gems_folder, pathlib.PurePath): + result = register_default_gems_folder(json_data, default_gems_folder, remove) + + elif isinstance(default_templates_folder, str) or isinstance(default_templates_folder, pathlib.PurePath): + result = register_default_templates_folder(json_data, default_templates_folder, remove) + + elif isinstance(default_restricted_folder, str) or isinstance(default_restricted_folder, pathlib.PurePath): + result = register_default_restricted_folder(json_data, default_restricted_folder, remove) + + # engine is done LAST + # Now that everything that could have an engine context is done, if the engine is supplied that means this is + # registering the engine itself + elif isinstance(engine_path, str) or isinstance(engine_path, pathlib.PurePath): + if not engine_path: + logger.error(f'Engine path cannot be empty.') + return 1 + result = register_engine_path(json_data, engine_path, remove, force) + + if not result: + manifest.save_o3de_manifest(json_data) + + return result + + +def remove_invalid_o3de_objects() -> None: + json_data = manifest.load_o3de_manifest() + + for engine_object in json_data['engines']: + engine_path = engine_object['path'] + if not validation.valid_o3de_engine_json(pathlib.Path(engine_path).resolve() / 'engine.json'): + logger.warn(f"Engine path {engine_path} is invalid.") + register(engine_path=engine_path, remove=True) + + for project in json_data['projects']: + if not validation.valid_o3de_project_json(pathlib.Path(project).resolve() / 'project.json'): + logger.warn(f"Project path {project} is invalid.") + register(project_path=project, remove=True) + + for gem in json_data['gems']: + if not validation.valid_o3de_gem_json(pathlib.Path(gem).resolve() / 'gem.json'): + logger.warn(f"Gem path {gem} is invalid.") + register(gem_path=gem, remove=True) + + for external in json_data['external_subdirectories']: + external = pathlib.Path(external).resolve() + if not external.is_dir(): + logger.warn(f"External subdirectory {external} is invalid.") + register(engine_path=engine_path, external_subdir_path=external, remove=True) + + for template in json_data['templates']: + if not validation.valid_o3de_template_json(pathlib.Path(template).resolve() / 'template.json'): + logger.warn(f"Template path {template} is invalid.") + register(template_path=template, remove=True) + + for restricted in json_data['restricted']: + if not validation.valid_o3de_restricted_json(pathlib.Path(restricted).resolve() / 'restricted.json'): + logger.warn(f"Restricted path {restricted} is invalid.") + register(restricted_path=restricted, remove=True) + + default_engines_folder = pathlib.Path(json_data['default_engines_folder']).resolve() + if not default_engines_folder.is_dir(): + new_default_engines_folder = manifest.get_o3de_folder() / 'Engines' + new_default_engines_folder.mkdir(parents=True, exist_ok=True) + logger.warn( + f"Default engines folder {default_engines_folder} is invalid. Set default {new_default_engines_folder}") + register(default_engines_folder=new_default_engines_folder.as_posix()) + + default_projects_folder = pathlib.Path(json_data['default_projects_folder']).resolve() + if not default_projects_folder.is_dir(): + new_default_projects_folder = manifest.get_o3de_folder() / 'Projects' + new_default_projects_folder.mkdir(parents=True, exist_ok=True) + logger.warn( + f"Default projects folder {default_projects_folder} is invalid. Set default {new_default_projects_folder}") + register(default_projects_folder=new_default_projects_folder.as_posix()) + + default_gems_folder = pathlib.Path(json_data['default_gems_folder']).resolve() + if not default_gems_folder.is_dir(): + new_default_gems_folder = manifest.get_o3de_folder() / 'Gems' + new_default_gems_folder.mkdir(parents=True, exist_ok=True) + logger.warn(f"Default gems folder {default_gems_folder} is invalid." + f" Set default {new_default_gems_folder}") + register(default_gems_folder=new_default_gems_folder.as_posix()) + + default_templates_folder = pathlib.Path(json_data['default_templates_folder']).resolve() + if not default_templates_folder.is_dir(): + new_default_templates_folder = manifest.get_o3de_folder() / 'Templates' + new_default_templates_folder.mkdir(parents=True, exist_ok=True) + logger.warn( + f"Default templates folder {default_templates_folder} is invalid." + f" Set default {new_default_templates_folder}") + register(default_templates_folder=new_default_templates_folder.as_posix()) + + default_restricted_folder = pathlib.Path(json_data['default_restricted_folder']).resolve() + if not default_restricted_folder.is_dir(): + default_restricted_folder = manifest.get_o3de_folder() / 'Restricted' + default_restricted_folder.mkdir(parents=True, exist_ok=True) + logger.warn( + f"Default restricted folder {default_restricted_folder} is invalid." + f" Set default {default_restricted_folder}") + register(default_restricted_folder=default_restricted_folder.as_posix()) + + +def _run_register(args: argparse) -> int: + if args.override_home_folder: + manifest.override_home_folder = args.override_home_folder + + if args.update: + remove_invalid_o3de_objects() + return repo.refresh_repos() + elif args.this_engine: + ret_val = register(engine_path=manifest.get_this_engine_path(), force=args.force) + error_code = register_shipped_engine_o3de_objects(force=args.force) + if error_code: + ret_val = error_code + return ret_val + elif args.all_engines_path: + return register_all_engines_in_folder(args.all_engines_path, args.remove, args.force) + elif args.all_projects_path: + return register_all_projects_in_folder(args.all_projects_path, args.remove) + elif args.all_gems_path: + return register_all_gems_in_folder(args.all_gems_path, args.remove) + elif args.all_templates_path: + return register_all_templates_in_folder(args.all_templates_path, args.remove) + elif args.all_restricted_path: + return register_all_restricted_in_folder(args.all_restricted_path, args.remove) + elif args.all_repo_uri: + return register_all_repos_in_folder(args.all_restricted_path, args.remove) + else: + return register(engine_path=args.engine_path, + project_path=args.project_path, + gem_path=args.gem_path, + external_subdir_path=args.external_subdirectory, + template_path=args.template_path, + restricted_path=args.restricted_path, + repo_uri=args.repo_uri, + default_engines_folder=args.default_engines_folder, + default_projects_folder=args.default_projects_folder, + default_gems_folder=args.default_gems_folder, + default_templates_folder=args.default_templates_folder, + default_restricted_folder=args.default_restricted_folder, + external_subdir_engine_path=args.external_subdirectory_engine_path, + external_subdir_project_path=args.external_subdirectory_project_path, + remove=args.remove, + force=args.force) + + +def add_parser_args(parser): + """ + add_parser_args is called to add arguments to each command such that it can be + invoked locally or added by a central python file. + Ex. Directly run from this file alone with: python register.py --engine-path "C:/o3de" + :param parser: the caller passes an argparse parser like instance to this method + """ + group = parser.add_mutually_exclusive_group(required=True) + group.add_argument('--this-engine', action='store_true', required=False, + default=False, + help='Registers the engine this script is running from.') + group.add_argument('-ep', '--engine-path', type=str, required=False, + help='Engine path to register/remove.') + group.add_argument('-pp', '--project-path', type=str, required=False, + help='Project path to register/remove.') + group.add_argument('-gp', '--gem-path', type=str, required=False, + help='Gem path to register/remove.') + group.add_argument('-es', '--external-subdirectory', type=str, required=False, + help='External subdirectory path to register/remove.') + group.add_argument('-tp', '--template-path', type=str, required=False, + help='Template path to register/remove.') + group.add_argument('-rp', '--restricted-path', type=str, required=False, + help='A restricted folder to register/remove.') + group.add_argument('-ru', '--repo-uri', type=str, required=False, + help='A repo uri to register/remove.') + group.add_argument('-aep', '--all-engines-path', type=str, required=False, + help='All engines under this folder to register/remove.') + group.add_argument('-app', '--all-projects-path', type=str, required=False, + help='All projects under this folder to register/remove.') + group.add_argument('-agp', '--all-gems-path', type=str, required=False, + help='All gems under this folder to register/remove.') + group.add_argument('-atp', '--all-templates-path', type=str, required=False, + help='All templates under this folder to register/remove.') + group.add_argument('-arp', '--all-restricted-path', type=str, required=False, + help='All templates under this folder to register/remove.') + group.add_argument('-aru', '--all-repo-uri', type=str, required=False, + help='All repos under this folder to register/remove.') + group.add_argument('-def', '--default-engines-folder', type=str, required=False, + help='The default engines folder to register/remove.') + group.add_argument('-dpf', '--default-projects-folder', type=str, required=False, + help='The default projects folder to register/remove.') + group.add_argument('-dgf', '--default-gems-folder', type=str, required=False, + help='The default gems folder to register/remove.') + group.add_argument('-dtf', '--default-templates-folder', type=str, required=False, + help='The default templates folder to register/remove.') + group.add_argument('-drf', '--default-restricted-folder', type=str, required=False, + help='The default restricted folder to register/remove.') + group.add_argument('-u', '--update', action='store_true', required=False, + default=False, + help='Refresh the repo cache.') + + parser.add_argument('-ohf', '--override-home-folder', type=str, required=False, + help='By default the home folder is the user folder, override it to this folder.') + parser.add_argument('-r', '--remove', action='store_true', required=False, + default=False, + help='Remove entry.') + parser.add_argument('-f', '--force', action='store_true', default=False, + help='For the update of the registration field being modified.') + + external_subdir_group = parser.add_argument_group(title='external-subdirectory', + description='path arguments to use with the --external-subdirectory option') + external_subdir_path_group = external_subdir_group.add_mutually_exclusive_group() + external_subdir_path_group.add_argument('-esep', '--external-subdirectory-engine-path', type=pathlib.Path, + help='If supplied, registers the external subdirectory with the engine.json at' \ + ' the engine-path location') + external_subdir_path_group.add_argument('-espp', '--external-subdirectory-project-path', type=pathlib.Path) + parser.set_defaults(func=_run_register) + + +def add_args(subparsers) -> None: + """ + add_args is called to add subparsers arguments to each command such that it can be + a central python file such as o3de.py. + It can be run from the o3de.py script as follows + call add_args and execute: python o3de.py register --engine-path "C:/o3de" + :param subparsers: the caller instantiates subparsers and passes it in here + """ + register_subparser = subparsers.add_parser('register') + add_parser_args(register_subparser) + + +def main(): + """ + Runs register.py script as standalone script + """ + # parse the command line args + the_parser = argparse.ArgumentParser() + + # add subparsers + + # add args to the parser + add_parser_args(the_parser) + + # parse args + the_args = the_parser.parse_args() + + # run + ret = the_args.func(the_args) if hasattr(the_args, 'func') else 1 + + # return + sys.exit(ret) + + +if __name__ == "__main__": + main() diff --git a/scripts/o3de/o3de/repo.py b/scripts/o3de/o3de/repo.py new file mode 100644 index 0000000000..c6b4874b6a --- /dev/null +++ b/scripts/o3de/o3de/repo.py @@ -0,0 +1,160 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +import json +import logging +import pathlib +import shutil +import urllib.parse +import urllib.request + +from o3de import manifest, utils, validation + +logger = logging.getLogger() +logging.basicConfig() + + +def process_add_o3de_repo(file_name: str or pathlib.Path, + repo_set: set) -> int: + file_name = pathlib.Path(file_name).resolve() + if not validation.valid_o3de_repo_json(file_name): + return 1 + + cache_folder = manifest.get_o3de_cache_folder() + + with file_name.open('r') as f: + try: + repo_data = json.load(f) + except json.JSONDecodeError as e: + logger.error(f'{file_name} failed to load: {str(e)}') + return 1 + + for o3de_object_uris, manifest_json in [(repo_data['engines'], 'engine.json'), + (repo_data['projects'], 'project.json'), + (repo_data['gems'], 'gem.json'), + (repo_data['template'], 'template.json'), + (repo_data['restricted'], 'restricted.json')]: + for o3de_object_uri in o3de_object_uris: + manifest_json_uri = f'{o3de_object_uri}/{manifest_json}' + manifest_json_sha256 = hashlib.sha256(manifest_json_uri.encode()) + 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 + + repo_set |= repo_data['repos'] + return 0 + + +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 + repo_set = set() + + for repo_uri in json_data['repos']: + if repo_uri not in repo_set: + repo_set.add(repo_uri) + + repo_uri = f'{repo_uri}/repo.json' + repo_sha256 = hashlib.sha256(repo_uri.encode()) + cache_file = cache_folder / str(repo_sha256.hexdigest() + '.json') + if not cache_file.is_file(): + parsed_uri = urllib.parse.urlparse(repo_uri) + download_file_result = utils.download_file(parsed_uri, cache_file) + if download_file_result != 0: + return download_file_result + + if not validation.valid_o3de_repo_json(cache_file): + logger.error(f'Repo json {repo_uri} is not valid.') + cache_file.unlink() + return 1 + + last_failure = process_add_o3de_repo(cache_file, repo_set) + if last_failure: + result = last_failure + + return result + + +def search_repo(repo_json_data: dict, + engine_name: str = None, + project_name: str = None, + gem_name: str = None, + template_name: str = None, + restricted_name: str = None) -> dict or None: + + if isinstance(engine_name, str) or isinstance(engine_name, pathlib.PurePath): + o3de_object_uris = repo_json_data['engines'] + manifest_json = 'engine.json' + json_key = 'engine_name' + search_func = lambda: None if manifest_json_data.get(json_key, '') == engine_name else manifest_json_data + elif isinstance(project_name, str) or isinstance(project_name, pathlib.PurePath): + o3de_object_uris = repo_json_data['projects'] + manifest_json = 'project.json' + json_key = 'project_name' + search_func = lambda: None if manifest_json_data.get(json_key, '') == project_name else manifest_json_data + elif isinstance(gem_name, str) or isinstance(gem_name, pathlib.PurePath): + o3de_object_uris = repo_json_data['gems'] + manifest_json = 'gem.json' + json_key = 'gem_name' + search_func = lambda: None if manifest_json_data.get(json_key, '') == gem_name else manifest_json_data + elif isinstance(template_name, str) or isinstance(template_name, pathlib.PurePath): + o3de_object_uris = repo_json_data['template'] + manifest_json = 'template.json' + json_key = 'template_name' + search_func = lambda: None if manifest_json_data.get(json_key, '') == template_name_name else manifest_json_data + elif isinstance(restricted_name, str) or isinstance(restricted_name, pathlib.PurePath): + o3de_object_uris = repo_json_data['restricted'] + manifest_json = 'restricted.json' + json_key = 'restricted_name' + search_func = lambda: None if manifest_json_data.get(json_key, '') == restricted_name else manifest_json_data + else: + return None + + o3de_object = search_o3de_object(manifest_json, o3de_object_uris, search_func) + if o3de_object: + return o3de_object + + # recurse into the repos object to search for the o3de object + o3de_object_uris = repo_json_data['repos'] + manifest_json = 'repo.json' + search_func = lambda: search_repo(manifest_json, engine_name, project_name, gem_name, template_name) + return search_o3de_object(manifest_json, o3de_object_uris, search_func) + + +def search_o3de_object(manifest_json, o3de_object_uris, search_func): + # Search for the o3de object based on the supplied object name in the current repo + cache_folder = manifest.get_o3de_cache_folder() + for o3de_object_uri in o3de_object_uris: + manifest_json_uri = f'{o3de_object_uri}/{manifest_json}' + manifest_json_sha256 = hashlib.sha256(manifest_json_uri.encode()) + cache_file = cache_folder / str(manifest_json_sha256.hexdigest() + '.json') + if cache_file.is_file(): + with cache_file.open('r') as f: + try: + manifest_json_data = json.load(f) + except json.JSONDecodeError as e: + logger.warn(f'{cache_file} failed to load: {str(e)}') + else: + result_json_data = search_func() + if result_json_data: + return result_json_data + return None diff --git a/cmake/Tools/preview.png b/scripts/o3de/o3de/resources/preview.png similarity index 100% rename from cmake/Tools/preview.png rename to scripts/o3de/o3de/resources/preview.png diff --git a/scripts/o3de/o3de/sha256.py b/scripts/o3de/o3de/sha256.py new file mode 100644 index 0000000000..db0a1fe834 --- /dev/null +++ b/scripts/o3de/o3de/sha256.py @@ -0,0 +1,117 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +import argparse +import json +import logging +import hashlib +import pathlib +import sys + +from o3de import utils + +logger = logging.getLogger() +logging.basicConfig() + + +def sha256(file_path: str or pathlib.Path, + json_path: str or pathlib.Path = None) -> int: + if not file_path: + logger.error(f'File path cannot be empty.') + return 1 + file_path = pathlib.Path(file_path).resolve() + if not file_path.is_file(): + logger.error(f'File path {file_path} does not exist.') + return 1 + + if json_path: + json_path = pathlib.Path(json_path).resolve() + if not json_path.is_file(): + logger.error(f'Json path {json_path} does not exist.') + return 1 + + sha256 = hashlib.sha256(file_path.open('rb').read()).hexdigest() + + if json_path: + with json_path.open('r') as s: + try: + json_data = json.load(s) + except json.JSONDecodeError as e: + logger.error(f'Failed to read Json path {json_path}: {str(e)}') + return 1 + json_data.update({"sha256": sha256}) + utils.backup_file(json_path) + with json_path.open('w') as s: + try: + s.write(json.dumps(json_data, indent=4) + '\n') + except OSError as e: + logger.error(f'Failed to write Json path {json_path}: {str(e)}') + return 1 + else: + print(sha256) + return 0 + + +def _run_sha256(args: argparse) -> int: + return sha256(args.file_path, + args.json_path) + + +def add_parser_args(parser): + """ + add_parser_args is called to add arguments to each command such that it can be + invoked locally or added by a central python file. + Ex. Directly run from this file alone with: python sha256.py --file-path "C:/TestGem" + :param parser: the caller passes an argparse parser like instance to this method + """ + parser.add_argument('-f', '--file-path', type=str, required=True, + help='The path to the file you want to sha256.') + parser.add_argument('-j', '--json-path', type=str, required=False, + help='optional path to an o3de json file to add the "sha256" element to.') + parser.set_defaults(func=_run_sha256) + + +def add_args(subparsers) -> None: + """ + add_args is called to add subparsers arguments to each command such that it can be + a central python file such as o3de.py. + It can be run from the o3de.py script as follows + call add_args and execute: python o3de.py sha256 --file-path "C:/TestGem" + :param subparsers: the caller instantiates subparsers and passes it in here + """ + sha256_subparser = subparsers.add_parser('sha256') + add_parser_args(sha256_subparser) + + +def main(): + """ + Runs sha256.py script as standalone script + """ + # parse the command line args + the_parser = argparse.ArgumentParser() + + # add subparsers + + # add args to the parser + add_parser_args(the_parser) + + # parse args + the_args = the_parser.parse_args() + + # run + ret = the_args.func(the_args) if hasattr(the_args, 'func') else 1 + + # return + sys.exit(ret) + + +if __name__ == "__main__": + main() diff --git a/scripts/o3de/o3de/utils.py b/scripts/o3de/o3de/utils.py new file mode 100755 index 0000000000..4330de25b8 --- /dev/null +++ b/scripts/o3de/o3de/utils.py @@ -0,0 +1,112 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# +""" +This file contains utility functions +""" + +import uuid +import pathlib +import shutil +import urllib.request + +def validate_identifier(identifier: str) -> bool: + """ + Determine if the identifier supplied is valid. + :param identifier: the name which needs to to checked + :return: bool: if the identifier is valid or not + """ + if not identifier: + return False + elif len(identifier) > 64: + return False + elif not identifier[0].isalpha(): + return False + else: + for character in identifier: + if not (character.isalnum() or character == '_' or character == '-'): + return False + return True + + +def validate_uuid4(uuid_string: str) -> bool: + """ + Determine if the uuid supplied is valid. + :param uuid_string: the uuid which needs to to checked + :return: bool: if the uuid is valid or not + """ + try: + val = uuid.UUID(uuid_string, version=4) + except ValueError: + return False + return str(val) == uuid_string + + +def backup_file(file_name: str or pathlib.Path) -> None: + index = 0 + renamed = False + while not renamed: + backup_file_name = pathlib.Path(str(file_name) + '.bak' + str(index)).resolve() + index += 1 + if not backup_file_name.is_file(): + file_name = pathlib.Path(file_name).resolve() + file_name.rename(backup_file_name) + if backup_file_name.is_file(): + renamed = True + + +def backup_folder(folder: str or pathlib.Path) -> None: + index = 0 + renamed = False + while not renamed: + backup_folder_name = pathlib.Path(str(folder) + '.bak' + str(index)).resolve() + index += 1 + if not backup_folder_name.is_dir(): + folder = pathlib.Path(folder).resolve() + folder.rename(backup_folder_name) + if backup_folder_name.is_dir(): + renamed = True + + +def download_file(parsed_uri, download_path: pathlib.Path) -> int: + """ + :param parsed_uri: uniform resource identifier to zip file to download + :param download_path: location path on disk to download file + """ + if download_path.is_file(): + logger.warn(f'File already downloaded to {download_path}.') + elif parsed_uri.scheme in ['http', 'https', 'ftp', 'ftps']: + with urllib.request.urlopen(url) as s: + with download_path.open('wb') as f: + shutil.copyfileobj(s, f) + else: + origin_file = pathlib.Path(url).resolve() + if not origin_file.is_file(): + return 1 + shutil.copy(origin_file, download_path) + + return 0 + + +def download_zip_file(parsed_uri, download_zip_path: pathlib.Path) -> 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) + if download_file_result != 0: + return download_file_result + + if not zipfile.is_zipfile(download_zip_path): + logger.error(f"File zip {download_zip_path} is invalid.") + download_zip_path.unlink() + return 1 + + return 0 \ No newline at end of file diff --git a/scripts/o3de/o3de/validation.py b/scripts/o3de/o3de/validation.py new file mode 100644 index 0000000000..721b7eae09 --- /dev/null +++ b/scripts/o3de/o3de/validation.py @@ -0,0 +1,102 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# +""" +This file validating o3de object json files +""" +import json +import pathlib + +def valid_o3de_json_dict(json_data: dict, key: str) -> bool: + return key in json_data + + +def valid_o3de_repo_json(file_name: str or pathlib.Path) -> bool: + file_name = pathlib.Path(file_name).resolve() + if not file_name.is_file(): + return False + + with file_name.open('r') as f: + try: + json_data = json.load(f) + test = json_data['repo_name'] + test = json_data['origin'] + except (json.JSONDecodeError, KeyError) as e: + return False + + return True + + +def valid_o3de_engine_json(file_name: str or pathlib.Path) -> bool: + file_name = pathlib.Path(file_name).resolve() + if not file_name.is_file(): + return False + + with file_name.open('r') as f: + try: + json_data = json.load(f) + test = json_data['engine_name'] + except (json.JSONDecodeError, KeyError) as e: + return False + return True + + +def valid_o3de_project_json(file_name: str or pathlib.Path) -> bool: + file_name = pathlib.Path(file_name).resolve() + if not file_name.is_file(): + return False + + with file_name.open('r') as f: + try: + json_data = json.load(f) + test = json_data['project_name'] + except (json.JSONDecodeError, KeyError) as e: + return False + return True + + +def valid_o3de_gem_json(file_name: str or pathlib.Path) -> bool: + file_name = pathlib.Path(file_name).resolve() + if not file_name.is_file(): + return False + + with file_name.open('r') as f: + try: + json_data = json.load(f) + test = json_data['gem_name'] + except (json.JSONDecodeError, KeyError) as e: + return False + return True + + +def valid_o3de_template_json(file_name: str or pathlib.Path) -> bool: + file_name = pathlib.Path(file_name).resolve() + if not file_name.is_file(): + return False + with file_name.open('r') as f: + try: + json_data = json.load(f) + test = json_data['template_name'] + except (json.JSONDecodeError, KeyError) as e: + return False + return True + + +def valid_o3de_restricted_json(file_name: str or pathlib.Path) -> bool: + file_name = pathlib.Path(file_name).resolve() + if not file_name.is_file(): + return False + with file_name.open('r') as f: + try: + json_data = json.load(f) + test = json_data['restricted_name'] + except (json.JSONDecodeError, KeyError) as e: + return False + return True diff --git a/scripts/o3de/setup.py b/scripts/o3de/setup.py new file mode 100644 index 0000000000..595f477c45 --- /dev/null +++ b/scripts/o3de/setup.py @@ -0,0 +1,42 @@ +""" +All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +its licensors. + +For complete copyright and license terms please see the LICENSE at the root of this +distribution (the "License"). All use of this software is governed by the License, +or, if provided, by the license below or the license accompanying this file. Do not +remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +""" +import os +import platform + +from setuptools import setup, find_packages +from setuptools.command.develop import develop +from setuptools.command.build_py import build_py + +PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) + +PYTHON_64 = platform.architecture()[0] == '64bit' + + +if __name__ == '__main__': + if not PYTHON_64: + raise RuntimeError("32-bit Python is not a supported platform.") + + with open(os.path.join(PACKAGE_ROOT, 'README.txt')) as f: + long_description = f.read() + + setup( + name="o3de", + version="1.0.0", + description='O3DE editor Python bindings test tools', + long_description=long_description, + packages=find_packages(where='o3de', exclude=['tests']), + install_requires=[ + ], + tests_require=[ + ], + entry_points={ + }, + ) diff --git a/scripts/o3de/tests/CMakeLists.txt b/scripts/o3de/tests/CMakeLists.txt new file mode 100644 index 0000000000..0526c7740d --- /dev/null +++ b/scripts/o3de/tests/CMakeLists.txt @@ -0,0 +1,36 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +if(NOT PAL_TRAIT_BUILD_TESTS_SUPPORTED) + return() +endif() + +# Add a test to test out the o3de package `o3de.py register` command +ly_add_pytest( + NAME o3de_register + PATH ${CMAKE_CURRENT_LIST_DIR}/unit_test_register.py + TEST_SUITE smoke + EXCLUDE_TEST_RUN_TARGET_FROM_IDE +) + +ly_add_pytest( + NAME o3de_cmake + PATH ${CMAKE_CURRENT_LIST_DIR}/unit_test_cmake.py + TEST_SUITE smoke + EXCLUDE_TEST_RUN_TARGET_FROM_IDE +) + +ly_add_pytest( + NAME o3de_global_project + PATH ${CMAKE_CURRENT_LIST_DIR}/unit_test_global_project.py + TEST_SUITE smoke + EXCLUDE_TEST_RUN_TARGET_FROM_IDE +) diff --git a/scripts/o3de/tests/__init__.py b/scripts/o3de/tests/__init__.py new file mode 100644 index 0000000000..4d5680a30d --- /dev/null +++ b/scripts/o3de/tests/__init__.py @@ -0,0 +1,10 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# diff --git a/scripts/o3de/tests/unit_test_cmake.py b/scripts/o3de/tests/unit_test_cmake.py new file mode 100644 index 0000000000..e5ce17dc03 --- /dev/null +++ b/scripts/o3de/tests/unit_test_cmake.py @@ -0,0 +1,69 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +import io +import json +import logging +import pytest +import pathlib +from unittest.mock import patch + +from o3de import cmake + + +class TestGetEnabledGems: + @pytest.mark.parametrize( + "enable_gems_cmake_data, expected_set", [ + pytest.param(""" + # Comment + set(ENABLED_GEMS foo bar baz) + """, set(['foo', 'bar', 'baz'])), + pytest.param(""" + # Comment + set(ENABLED_GEMS + foo + bar + baz + ) + """, set(['foo', 'bar', 'baz'])), + pytest.param(""" + # Comment + set(ENABLED_GEMS + foo + bar + baz) + """, set(['foo', 'bar', 'baz'])), + pytest.param(""" + # Comment + set(ENABLED_GEMS + foo bar + baz) + """, set(['foo', 'bar', 'baz'])), + pytest.param(""" + # Comment + set(RANDOM_VARIABLE TestGame, TestProject Test Engine) + set(ENABLED_GEMS HelloWorld IceCream + foo + baz bar + baz baz baz baz baz morebaz lessbaz + ) + Random Text + """, set(['HelloWorld', 'IceCream', 'foo', 'bar', 'baz', 'morebaz', 'lessbaz'])), + ] + ) + def test_get_enabled_gems(self, enable_gems_cmake_data, expected_set): + enabled_gems_set = set() + with patch('pathlib.Path.resolve', return_value=pathlib.Path('enabled_gems.cmake')) as pathlib_is_resolve_mock,\ + patch('pathlib.Path.is_file', return_value=True) as pathlib_is_file_mock,\ + patch('pathlib.Path.open', return_value=io.StringIO(enable_gems_cmake_data)) as pathlib_open_mock: + enabled_gems_set = cmake.get_enabled_gems(pathlib.Path('enabled_gems.cmake')) + + assert enabled_gems_set == expected_set diff --git a/cmake/Tools/unit_test_engine_template.py b/scripts/o3de/tests/unit_test_engine_template.py similarity index 100% rename from cmake/Tools/unit_test_engine_template.py rename to scripts/o3de/tests/unit_test_engine_template.py diff --git a/scripts/o3de/tests/unit_test_global_project.py b/scripts/o3de/tests/unit_test_global_project.py new file mode 100644 index 0000000000..1d3a4dd4f4 --- /dev/null +++ b/scripts/o3de/tests/unit_test_global_project.py @@ -0,0 +1,40 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +import io +import json +import logging +import pytest +import pathlib +from unittest.mock import patch + +from o3de import global_project + + +logger = logging.getLogger() +logging.basicConfig() + +DEFAULT_BOOTSTRAP_SETREG = pathlib.Path('~/.o3de/Registry/bootstrap.setreg').expanduser() +PROJECT_PATH_KEY = ('Amazon', 'AzCore', 'Bootstrap', 'project_path') + +class TestSetGlobalProject: + @pytest.mark.parametrize( + "output_path, project_path, force, expected_result", [ + pytest.param(pathlib.Path('~/.o3de/Registry/bootstrap.setreg'), pathlib.Path('A:/'), False, False), + pytest.param(pathlib.Path('~/.o3de/Registry/bootstrap.setreg'), pathlib.Path('A:/'), True, True) + ] + ) + def test_set_global_project_non_existent_project_path(self, output_path, project_path, force, expected_result): + with patch('pathlib.Path.open', return_value=io.StringIO()) as pathlib_open_mock: + result = global_project.set_global_project(output_path, project_path=project_path, force=force) == 0 + + + assert result == expected_result diff --git a/scripts/o3de/tests/unit_test_register.py b/scripts/o3de/tests/unit_test_register.py new file mode 100644 index 0000000000..eb866e76d4 --- /dev/null +++ b/scripts/o3de/tests/unit_test_register.py @@ -0,0 +1,117 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +import argparse +import json +import logging +import pytest +import pathlib +from unittest.mock import patch + +from o3de import register + +string_manifest_data = '{}' + +@pytest.mark.parametrize( + "engine_path, engine_name, force, expected_result", [ + pytest.param(pathlib.PurePath('D:/o3de/o3de'), "o3de", False, 0), + # Same engine_name and path should result in valid registration + pytest.param(pathlib.PurePath('D:/o3de/o3de'), "o3de", False, 0), + # Same engine_name and but different path should fail + pytest.param(pathlib.PurePath('D:/o3de/engine-path'), "o3de", False, 1), + # New engine_name should result in valid registration + pytest.param(pathlib.PurePath('D:/o3de/engine-path'), "o3de-other", False, 0), + # Same engine_name and but different path with --force should result in valid registration + pytest.param(pathlib.PurePath('F:/Open3DEngine'), "o3de", True, 0), + ] +) +def test_register_engine_path(engine_path, engine_name, force, expected_result): + parser = argparse.ArgumentParser() + + # Register the registration script subparsers with the current argument parser + register.add_parser_args(parser) + arg_list = ['--engine-path', str(engine_path)] + if force: + arg_list += ['--force'] + args = parser.parse_args(arg_list) + + def load_manifest_from_string() -> dict: + try: + manifest_json = json.loads(string_manifest_data) + except json.JSONDecodeError as err: + logging.error("Error decoding Json from Manifest file") + else: + return manifest_json + def save_manifest_to_string(manifest_json: dict) -> None: + global string_manifest_data + string_manifest_data = json.dumps(manifest_json) + + engine_json_data = {'engine_name': engine_name} + with patch('o3de.manifest.load_o3de_manifest', side_effect=load_manifest_from_string) as load_manifest_mock, \ + patch('o3de.manifest.save_o3de_manifest', side_effect=save_manifest_to_string) as save_manifest_mock, \ + patch('o3de.manifest.get_engine_json_data', return_value=engine_json_data) as engine_paths_mock, \ + patch('o3de.validation.valid_o3de_engine_json', return_value=True) as valid_engine_mock, \ + patch('pathlib.Path.is_dir', return_value=True) as pathlib_is_dir_mock: + result = register._run_register(args) + assert result == expected_result + + +@pytest.fixture(scope='class') +def init_manifest_data(request): + class ManifestData: + def __init__(self): + self.json_string = json.dumps({'default_engines_folder': '', + 'default_projects_folder': '', 'default_gems_folder': '', + 'default_templates_folder': '', 'default_restricted_folder': ''}) + + request.cls.manifest_data = ManifestData() + + +@pytest.mark.usefixtures('init_manifest_data') +class TestRegisterThisEngine: + @pytest.mark.parametrize( + "engine_path, engine_name, force, expected_result", [ + pytest.param(pathlib.PurePath('D:/o3de/o3de'), "o3de", False, 0), + pytest.param(pathlib.PurePath('F:/Open3DEngine'), "o3de", False, 1), + pytest.param(pathlib.PurePath('F:/Open3DEngine'), "o3de", True, 0) + ] + ) + def test_register_this_engine(self, engine_path, engine_name, force, expected_result): + parser = argparse.ArgumentParser() + + # Register the registration script subparsers with the current argument parser + register.add_parser_args(parser) + arg_list = ['--this-engine'] + if force: + arg_list += ['--force'] + args = parser.parse_args(arg_list) + + def load_manifest_from_string() -> dict: + try: + manifest_json = json.loads(self.manifest_data.json_string) + except json.JSONDecodeError as err: + logging.error("Error decoding Json from Manifest file") + else: + return manifest_json + def save_manifest_to_string(manifest_json: dict) -> None: + self.manifest_data.json_string = json.dumps(manifest_json) + + engine_json_data = {'engine_name': engine_name} + + with patch('o3de.manifest.load_o3de_manifest', side_effect=load_manifest_from_string) as load_manifest_mock, \ + patch('o3de.manifest.save_o3de_manifest', side_effect=save_manifest_to_string) as save_manifest_mock, \ + patch('o3de.manifest.get_engine_json_data', return_value=engine_json_data) as engine_paths_mock, \ + patch('o3de.manifest.get_this_engine_path', return_value=engine_path) as engine_paths_mock, \ + patch('o3de.validation.valid_o3de_engine_json', return_value=True) as valid_engine_mock, \ + patch('pathlib.Path.is_dir', return_value=True) as pathlib_is_dir_mock: + result = register._run_register(args) + assert result == expected_result + diff --git a/cmake/Tools/unit_test_utils.py b/scripts/o3de/tests/unit_test_utils.py similarity index 100% rename from cmake/Tools/unit_test_utils.py rename to scripts/o3de/tests/unit_test_utils.py diff --git a/scripts/project_manager/projects.py b/scripts/project_manager/projects.py index 51ffc2be91..f9f40aa4bf 100755 --- a/scripts/project_manager/projects.py +++ b/scripts/project_manager/projects.py @@ -29,11 +29,10 @@ executable_path = '' logger = logging.getLogger() logger.setLevel(logging.INFO) -from cmake.Tools import engine_template -from cmake.Tools import registration +from o3de import disable_gem, enable_gem, cmake, engine_template, manifest, register -o3de_folder = registration.get_o3de_folder() -o3de_logs_folder = registration.get_o3de_logs_folder() +o3de_folder = manifest.get_o3de_folder() +o3de_logs_folder = manifest.get_o3de_logs_folder() project_manager_log_file_path = o3de_logs_folder / "project_manager.log" log_file_handler = RotatingFileHandler(filename=project_manager_log_file_path, maxBytes=1024 * 1024, backupCount=1) formatter = logging.Formatter('%(asctime)s | %(levelname)s : %(message)s') @@ -124,7 +123,7 @@ class ProjectManagerDialog(QObject): super(ProjectManagerDialog, self).__init__(parent) self.ui_path = (pathlib.Path(__file__).parent / 'ui').resolve() - self.home_folder = registration.get_home_folder() + self.home_folder = manifest.get_home_folder() self.log_display = None self.dialog_logger = DialogLogger(self) @@ -187,12 +186,8 @@ class ProjectManagerDialog(QObject): self.remove_restricted_button = self.dialog.findChild(QPushButton, 'removeRestrictedButton') self.remove_restricted_button.clicked.connect(self.remove_restricted_handler) - self.manage_runtime_project_gem_targets_button = self.dialog.findChild(QPushButton, 'manageRuntimeGemTargetsButton') - self.manage_runtime_project_gem_targets_button.clicked.connect(self.manage_runtime_project_gem_targets_handler) - self.manage_tool_project_gem_targets_button = self.dialog.findChild(QPushButton, 'manageToolGemTargetsButton') - self.manage_tool_project_gem_targets_button.clicked.connect(self.manage_tool_project_gem_targets_handler) - self.manage_server_project_gem_targets_button = self.dialog.findChild(QPushButton, 'manageServerGemTargetsButton') - self.manage_server_project_gem_targets_button.clicked.connect(self.manage_server_project_gem_targets_handler) + self.manage_project_gem_targets_button = self.dialog.findChild(QPushButton, 'manageRuntimeGemTargetsButton') + self.manage_project_gem_targets_button.clicked.connect(self.manage_project_gem_targets_handler) self.log_display = self.dialog.findChild(QLabel, 'logDisplay') @@ -202,7 +197,7 @@ class ProjectManagerDialog(QObject): self.dialog.show() def refresh_project_list(self) -> None: - projects = registration.get_all_projects() + projects = manifest.get_all_projects() self.project_list_box.clear() for this_slot in range(len(projects)): display_name = f'{os.path.basename(os.path.normpath(projects[this_slot]))} ({projects[this_slot]})' @@ -256,7 +251,7 @@ class ProjectManagerDialog(QObject): return self.project_list_box.itemData(self.project_list_box.currentIndex(), Qt.ToolTipRole) def get_selected_project_name(self) -> str: - project_data = registration.get_project_data(project_path=self.get_selected_project_path()) + project_data = manifest.get_project_json_data(project_path=self.get_selected_project_path()) return project_data['project_name'] def create_project_handler(self): @@ -298,7 +293,7 @@ class ProjectManagerDialog(QObject): return folder_dialog = QFileDialog(self.dialog, "Select a Folder and Enter a New Project Name", - registration.get_o3de_projects_folder().as_posix()) + manifest.get_o3de_projects_folder().as_posix()) folder_dialog.setFileMode(QFileDialog.AnyFile) folder_dialog.setOptions(QFileDialog.ShowDirsOnly) project_count = 0 @@ -314,7 +309,7 @@ class ProjectManagerDialog(QObject): if engine_template.create_project(project_path=project_folder[0], template_path=project_template_path) == 0: # Success - registration.register(project_path=project_folder[0]) + register.register(project_path=project_folder[0]) self.refresh_project_list() msg_box = QMessageBox(parent=self.dialog) msg_box.setWindowTitle("O3DE") @@ -360,7 +355,7 @@ class ProjectManagerDialog(QObject): return folder_dialog = QFileDialog(self.dialog, "Select a Folder and Enter a New Gem Name", - registration.get_o3de_gems_folder().as_posix()) + manifest.get_o3de_gems_folder().as_posix()) folder_dialog.setFileMode(QFileDialog.AnyFile) folder_dialog.setOptions(QFileDialog.ShowDirsOnly) gem_count = 0 @@ -376,7 +371,7 @@ class ProjectManagerDialog(QObject): if engine_template.create_gem(gem_path=gem_folder[0], template_path=gem_template_path) == 0: # Success - registration.register(gem_path=gem_folder[0]) + register.register(gem_path=gem_folder[0]) msg_box = QMessageBox(parent=self.dialog) msg_box.setWindowTitle("O3DE") msg_box.setText(f"Gem {gem_folder[0]} created.") @@ -392,13 +387,13 @@ class ProjectManagerDialog(QObject): source_folder = QFileDialog.getExistingDirectory(self.dialog, "Select a Folder to make a template out of.", - registration.get_o3de_folder().as_posix()) + manifest.get_o3de_folder().as_posix()) if not source_folder: return destination_template_folder_dialog = QFileDialog(self.dialog, "Select where the template is to be created and named.", - registration.get_o3de_templates_folder().as_posix()) + manifest.get_o3de_templates_folder().as_posix()) destination_template_folder_dialog.setFileMode(QFileDialog.AnyFile) destination_template_folder_dialog.setOptions(QFileDialog.ShowDirsOnly) destination_folder = None @@ -410,7 +405,7 @@ class ProjectManagerDialog(QObject): if engine_template.create_template(source_path=source_folder, template_path=destination_folder[0]) == 0: # Success - registration.register(template_path=destination_folder[0]) + register.register(template_path=destination_folder[0]) msg_box = QMessageBox(parent=self.dialog) msg_box.setWindowTitle("O3DE") msg_box.setText(f"Template {destination_folder[0]} created.") @@ -454,7 +449,7 @@ class ProjectManagerDialog(QObject): return folder_dialog = QFileDialog(self.dialog, "Select a Folder and Enter a New Gem Name", - registration.get_o3de_gems_folder().as_posix()) + manifest.get_o3de_gems_folder().as_posix()) folder_dialog.setFileMode(QFileDialog.AnyFile) folder_dialog.setOptions(QFileDialog.ShowDirsOnly) gem_count = 0 @@ -483,9 +478,9 @@ class ProjectManagerDialog(QObject): :return: None """ project_folder = QFileDialog.getExistingDirectory(self.dialog, "Select Project Folder", - registration.get_o3de_projects_folder().as_posix()) + manifest.get_o3de_projects_folder().as_posix()) if project_folder: - if registration.register(project_path=project_folder) == 0: + if register.register(project_path=project_folder) == 0: # Success self.refresh_project_list() @@ -502,9 +497,9 @@ class ProjectManagerDialog(QObject): :return: None """ gem_folder = QFileDialog.getExistingDirectory(self.dialog, "Select Gem Folder", - registration.get_o3de_gems_folder().as_posix()) + manifest.get_o3de_gems_folder().as_posix()) if gem_folder: - if registration.register(gem_path=gem_folder) == 0: + if register.register(gem_path=gem_folder) == 0: # Success msg_box = QMessageBox(parent=self.dialog) msg_box.setWindowTitle("O3DE") @@ -519,9 +514,9 @@ class ProjectManagerDialog(QObject): :return: None """ template_folder = QFileDialog.getExistingDirectory(self.dialog, "Select Template Folder", - registration.get_o3de_templates_folder().as_posix()) + manifest.get_o3de_templates_folder().as_posix()) if template_folder: - if registration.register(template_path=template_folder) == 0: + if register.register(template_path=template_folder) == 0: # Success msg_box = QMessageBox(parent=self.dialog) msg_box.setWindowTitle("O3DE") @@ -536,9 +531,9 @@ class ProjectManagerDialog(QObject): :return: None """ restricted_folder = QFileDialog.getExistingDirectory(self.dialog, "Select Restricted Folder", - registration.get_o3de_restricted_folder().as_posix()) + manifest.get_o3de_restricted_folder().as_posix()) if restricted_folder: - if registration.register(restricted_path=restricted_folder) == 0: + if register.register(restricted_path=restricted_folder) == 0: # Success msg_box = QMessageBox(parent=self.dialog) msg_box.setWindowTitle("O3DE") @@ -553,9 +548,9 @@ class ProjectManagerDialog(QObject): :return: None """ project_folder = QFileDialog.getExistingDirectory(self.dialog, "Select Project Folder", - registration.get_o3de_projects_folder().as_posix()) + manifest.get_o3de_projects_folder().as_posix()) if project_folder: - if registration.register(project_path=project_folder, remove=True) == 0: + if register.register(project_path=project_folder, remove=True) == 0: # Success self.refresh_project_list() @@ -572,9 +567,9 @@ class ProjectManagerDialog(QObject): :return: None """ gem_folder = QFileDialog.getExistingDirectory(self.dialog, "Select Gem Folder", - registration.get_o3de_gems_folder().as_posix()) + manifest.get_o3de_gems_folder().as_posix()) if gem_folder: - if registration.register(gem_path=gem_folder, remove=True) == 0: + if register.register(gem_path=gem_folder, remove=True) == 0: # Success msg_box = QMessageBox(parent=self.dialog) msg_box.setWindowTitle("O3DE") @@ -589,9 +584,9 @@ class ProjectManagerDialog(QObject): :return: None """ template_folder = QFileDialog.getExistingDirectory(self.dialog, "Select Template Folder", - registration.get_o3de_templates_folder().as_posix()) + manifest.get_o3de_templates_folder().as_posix()) if template_folder: - if registration.register(template_path=template_folder, remove=True) == 0: + if register.register(template_path=template_folder, remove=True) == 0: # Success msg_box = QMessageBox(parent=self.dialog) msg_box.setWindowTitle("O3DE") @@ -606,9 +601,9 @@ class ProjectManagerDialog(QObject): :return: None """ restricted_folder = QFileDialog.getExistingDirectory(self.dialog, "Select Restricted Folder", - registration.get_o3de_restricted_folder().as_posix()) + manifest.get_o3de_restricted_folder().as_posix()) if restricted_folder: - if registration.register(restricted_path=restricted_folder, remove=True) == 0: + if register.register(restricted_path=restricted_folder, remove=True) == 0: # Success msg_box = QMessageBox(parent=self.dialog) msg_box.setWindowTitle("O3DE") @@ -616,7 +611,7 @@ class ProjectManagerDialog(QObject): msg_box.exec() return - def manage_runtime_project_gem_targets_handler(self): + def manage_project_gem_targets_handler(self): """ Opens the Gem management pane. Waits for the load thread to complete if still running and displays all active gems for the current project as well as all available gems which aren't currently active. @@ -643,121 +638,26 @@ class ProjectManagerDialog(QObject): logger.error(f'Failed to load gems dialog file at {self.manage_project_gem_targets_ui_file_path.as_posix()}') return - self.manage_project_gem_targets_dialog.setWindowTitle(f"Manage Runtime Gem Targets for Project:" + self.manage_project_gem_targets_dialog.setWindowTitle(f"Manage Gems for Project:" f" {self.get_selected_project_name()}") self.add_gem_button = self.manage_project_gem_targets_dialog.findChild(QPushButton, 'addGemTargetsButton') - self.add_gem_button.clicked.connect(self.add_runtime_project_gem_targets_handler) + self.add_gem_button.clicked.connect(self.add_project_gem_targets_handler) self.available_gem_targets_list = self.manage_project_gem_targets_dialog.findChild(QListView, 'availableGemTargetsList') - self.refresh_runtime_project_gem_targets_available_list() + self.refresh_project_gem_targets_available_list() self.remove_project_gem_targets_button = self.manage_project_gem_targets_dialog.findChild(QPushButton, 'removeGemTargetsButton') - self.remove_project_gem_targets_button.clicked.connect(self.remove_runtime_project_gem_targets_handler) + self.remove_project_gem_targets_button.clicked.connect(self.remove_project_gem_targets_handler) self.enabled_gem_targets_list = self.manage_project_gem_targets_dialog.findChild(QListView, 'enabledGemTargetsList') - self.refresh_runtime_project_gem_targets_enabled_list() + self.refresh_project_gem_targets_enabled_list() self.manage_project_gem_targets_dialog.exec() - def manage_tool_project_gem_targets_handler(self): - """ - Opens the Gem management pane. Waits for the load thread to complete if still running and displays all - active gems for the current project as well as all available gems which aren't currently active. - :return: None - """ - - if not self.get_selected_project_path(): - msg_box = QMessageBox(parent=self.dialog) - msg_box.setWindowTitle("O3DE") - msg_box.setText("Please select a project") - msg_box.exec() - return - - loader = QUiLoader() - self.manage_project_gem_targets_file = QFile(self.manage_project_gem_targets_ui_file_path.as_posix()) - - if not self.manage_project_gem_targets_file: - logger.error(f'Failed to load manage gem targets UI file at {self.manage_project_gem_targets_ui_file_path}') - return - - self.manage_project_gem_targets_dialog = loader.load(self.manage_project_gem_targets_file) - - if not self.manage_project_gem_targets_dialog: - logger.error( - f'Failed to load gems dialog file at {self.manage_project_gem_targets_ui_file_path.as_posix()}') - return - - self.manage_project_gem_targets_dialog.setWindowTitle(f"Manage Tool Gem Targets for Project:" - f" {self.get_selected_project_name()}") - - self.add_gem_button = self.manage_project_gem_targets_dialog.findChild(QPushButton, 'addGemTargetsButton') - self.add_gem_button.clicked.connect(self.add_tool_project_gem_targets_handler) - - self.available_gem_targets_list = self.manage_project_gem_targets_dialog.findChild(QListView, - 'availableGemTargetsList') - self.refresh_tool_project_gem_targets_available_list() - - self.remove_project_gem_targets_button = self.manage_project_gem_targets_dialog.findChild(QPushButton, - 'removeGemTargetsButton') - self.remove_project_gem_targets_button.clicked.connect(self.remove_tool_project_gem_targets_handler) - - self.enabled_gem_targets_list = self.manage_project_gem_targets_dialog.findChild(QListView, - 'enabledGemTargetsList') - self.refresh_tool_project_gem_targets_enabled_list() - - self.manage_project_gem_targets_dialog.exec() - - def manage_server_project_gem_targets_handler(self): - """ - Opens the Gem management pane. Waits for the load thread to complete if still running and displays all - active gems for the current project as well as all available gems which aren't currently active. - :return: None - """ - - if not self.get_selected_project_path(): - msg_box = QMessageBox(parent=self.dialog) - msg_box.setWindowTitle("O3DE") - msg_box.setText("Please select a project") - msg_box.exec() - return - - loader = QUiLoader() - self.manage_project_gem_targets_file = QFile(self.manage_project_gem_targets_ui_file_path.as_posix()) - - if not self.manage_project_gem_targets_file: - logger.error(f'Failed to load manage gem targets UI file at {self.manage_project_gem_targets_ui_file_path}') - return - - self.manage_project_gem_targets_dialog = loader.load(self.manage_project_gem_targets_file) - - if not self.manage_project_gem_targets_dialog: - logger.error( - f'Failed to load gems dialog file at {self.manage_project_gem_targets_ui_file_path.as_posix()}') - return - - self.manage_project_gem_targets_dialog.setWindowTitle(f"Manage Server Gem Targets for Project:" - f" {self.get_selected_project_name()}") - - self.add_gem_button = self.manage_project_gem_targets_dialog.findChild(QPushButton, 'addGemTargetsButton') - self.add_gem_button.clicked.connect(self.add_server_project_gem_targets_handler) - - self.available_gem_targets_list = self.manage_project_gem_targets_dialog.findChild(QListView, - 'availableGemTargetsList') - self.refresh_server_project_gem_targets_available_list() - - self.remove_project_gem_targets_button = self.manage_project_gem_targets_dialog.findChild(QPushButton, - 'removeGemTargetsButton') - self.remove_project_gem_targets_button.clicked.connect(self.remove_server_project_gem_targets_handler) - - self.enabled_gem_targets_list = self.manage_project_gem_targets_dialog.findChild(QListView, - 'enabledGemTargetsList') - self.refresh_server_project_gem_targets_enabled_list() - - self.manage_project_gem_targets_dialog.exec() def manage_project_gem_targets_get_selected_available_gems(self) -> list: selected_items = self.available_gem_targets_list.selectionModel().selectedRows() @@ -767,185 +667,67 @@ class ProjectManagerDialog(QObject): selected_items = self.enabled_gem_targets_list.selectionModel().selectedRows() return [(self.enabled_gem_targets_list.model().data(item)) for item in selected_items] - def add_runtime_project_gem_targets_handler(self) -> None: - gem_paths = registration.get_all_gems() + def add_project_gem_targets_handler(self) -> None: + gem_paths = manifest.get_all_gems() for gem_target in self.manage_project_gem_targets_get_selected_available_gems(): for gem_path in gem_paths: - this_gems_targets = registration.get_gem_targets(gem_path=gem_path) - for this_gem_target in this_gems_targets: - if gem_target == this_gem_target: - registration.add_gem_to_project(gem_path=gem_path, - gem_target=gem_target, - project_path=self.get_selected_project_path(), - runtime_dependency=True) - self.refresh_runtime_project_gem_targets_available_list() - self.refresh_runtime_project_gem_targets_enabled_list() - return - self.refresh_runtime_project_gem_targets_available_list() - self.refresh_runtime_project_gem_targets_enabled_list() + enable_gem.enable_gem_in_project(gem_path=gem_path, + project_path=self.get_selected_project_path()) + self.refresh_project_gem_targets_available_list() + self.refresh_project_gem_targets_enabled_list() + return + self.refresh_project_gem_targets_available_list() + self.refresh_project_gem_targets_enabled_list() - def remove_runtime_project_gem_targets_handler(self): - gem_paths = registration.get_all_gems() + def remove_project_gem_targets_handler(self): + gem_paths = manifest.get_all_gems() for gem_target in self.manage_project_gem_targets_get_selected_enabled_gems(): for gem_path in gem_paths: - this_gems_targets = registration.get_gem_targets(gem_path=gem_path) - for this_gem_target in this_gems_targets: - if gem_target == this_gem_target: - registration.remove_gem_from_project(gem_path=gem_path, - gem_target=gem_target, - project_path=self.get_selected_project_path(), - runtime_dependency=True) - self.refresh_runtime_project_gem_targets_available_list() - self.refresh_runtime_project_gem_targets_enabled_list() - return - self.refresh_runtime_project_gem_targets_available_list() - self.refresh_runtime_project_gem_targets_enabled_list() + disable_gem.disable_gem_in_project(gem_path=gem_path, + project_path=self.get_selected_project_path()) + self.refresh_project_gem_targets_available_list() + self.refresh_project_gem_targets_enabled_list() + return + self.refresh_project_gem_targets_available_list() + self.refresh_project_gem_targets_enabled_list() - def add_tool_project_gem_targets_handler(self) -> None: - gem_paths = registration.get_all_gems() - for gem_target in self.manage_project_gem_targets_get_selected_available_gems(): - for gem_path in gem_paths: - this_gems_targets = registration.get_gem_targets(gem_path=gem_path) - for this_gem_target in this_gems_targets: - if gem_target == this_gem_target: - registration.add_gem_to_project(gem_path=gem_path, - gem_target=gem_target, - project_path=self.get_selected_project_path(), - tool_dependency=True) - self.refresh_tool_project_gem_targets_available_list() - self.refresh_tool_project_gem_targets_enabled_list() - return - self.refresh_tool_project_gem_targets_available_list() - self.refresh_tool_project_gem_targets_enabled_list() - - def remove_tool_project_gem_targets_handler(self): - gem_paths = registration.get_all_gems() - for gem_target in self.manage_project_gem_targets_get_selected_enabled_gems(): - for gem_path in gem_paths: - this_gems_targets = registration.get_gem_targets(gem_path=gem_path) - for this_gem_target in this_gems_targets: - if gem_target == this_gem_target: - registration.remove_gem_from_project(gem_path=gem_path, - gem_target=gem_target, - project_path=self.get_selected_project_path(), - tool_dependency=True) - self.refresh_tool_project_gem_targets_available_list() - self.refresh_tool_project_gem_targets_enabled_list() - return - self.refresh_tool_project_gem_targets_available_list() - self.refresh_tool_project_gem_targets_enabled_list() - - def add_server_project_gem_targets_handler(self) -> None: - gem_paths = registration.get_all_gems() - for gem_target in self.manage_project_gem_targets_get_selected_available_gems(): - for gem_path in gem_paths: - this_gems_targets = registration.get_gem_targets(gem_path=gem_path) - for this_gem_target in this_gems_targets: - if gem_target == this_gem_target: - registration.add_gem_to_project(gem_path=gem_path, - gem_target=gem_target, - project_path=self.get_selected_project_path(), - server_dependency=True) - self.refresh_server_project_gem_targets_available_list() - self.refresh_server_project_gem_targets_enabled_list() - return - self.refresh_server_project_gem_targets_available_list() - self.refresh_server_project_gem_targets_enabled_list() - - def remove_server_project_gem_targets_handler(self): - gem_paths = registration.get_all_gems() - for gem_target in self.manage_project_gem_targets_get_selected_enabled_gems(): - for gem_path in gem_paths: - this_gems_targets = registration.get_gem_targets(gem_path=gem_path) - for this_gem_target in this_gems_targets: - if gem_target == this_gem_target: - registration.remove_gem_from_project(gem_path=gem_path, - gem_target=gem_target, - project_path=self.get_selected_project_path(), - server_dependency=True) - self.refresh_server_project_gem_targets_available_list() - self.refresh_server_project_gem_targets_enabled_list() - return - self.refresh_server_project_gem_targets_available_list() - self.refresh_server_project_gem_targets_enabled_list() - - def refresh_runtime_project_gem_targets_enabled_list(self) -> None: + def refresh_project_gem_targets_enabled_list(self) -> None: enabled_project_gem_targets_model = QStandardItemModel() - enabled_project_gem_targets = registration.get_project_runtime_gem_targets( - project_path=self.get_selected_project_path()) - for gem_target in sorted(enabled_project_gem_targets): + enabled_project_gems = cmake.get_project_gems(project_path=self.get_selected_project_path()) + for gem_target in sorted(enabled_project_gems): model_item = QStandardItem(gem_target) enabled_project_gem_targets_model.appendRow(model_item) self.enabled_gem_targets_list.setModel(enabled_project_gem_targets_model) - def refresh_runtime_project_gem_targets_available_list(self) -> None: + + def refresh_project_gem_targets_available_list(self) -> None: available_project_gem_targets_model = QStandardItemModel() - enabled_project_gem_targets = registration.get_project_runtime_gem_targets( - project_path=self.get_selected_project_path()) - all_gem_targets = registration.get_all_gem_targets() + enabled_project_gem_targets = cmake.get_project_gems(project_path=self.get_selected_project_path()) + all_gem_targets = manifest.get_all_gems() for gem_target in sorted(all_gem_targets): if gem_target not in enabled_project_gem_targets: model_item = QStandardItem(gem_target) available_project_gem_targets_model.appendRow(model_item) self.available_gem_targets_list.setModel(available_project_gem_targets_model) - - def refresh_tool_project_gem_targets_enabled_list(self) -> None: - enabled_project_gem_targets_model = QStandardItemModel() - enabled_project_gem_targets = registration.get_project_tool_gem_targets( - project_path=self.get_selected_project_path()) - for gem_target in sorted(enabled_project_gem_targets): - model_item = QStandardItem(gem_target) - enabled_project_gem_targets_model.appendRow(model_item) - self.enabled_gem_targets_list.setModel(enabled_project_gem_targets_model) - def refresh_tool_project_gem_targets_available_list(self) -> None: - available_project_gem_targets_model = QStandardItemModel() - enabled_project_gem_targets = registration.get_project_tool_gem_targets( - project_path=self.get_selected_project_path()) - all_gem_targets = registration.get_all_gem_targets() - for gem_target in sorted(all_gem_targets): - if gem_target not in enabled_project_gem_targets: - model_item = QStandardItem(gem_target) - available_project_gem_targets_model.appendRow(model_item) - self.available_gem_targets_list.setModel(available_project_gem_targets_model) - - def refresh_server_project_gem_targets_enabled_list(self) -> None: - enabled_project_gem_targets_model = QStandardItemModel() - enabled_project_gem_targets = registration.get_project_server_gem_targets( - project_path=self.get_selected_project_path()) - for gem_target in sorted(enabled_project_gem_targets): - model_item = QStandardItem(gem_target) - enabled_project_gem_targets_model.appendRow(model_item) - self.enabled_gem_targets_list.setModel(enabled_project_gem_targets_model) - - def refresh_server_project_gem_targets_available_list(self) -> None: - available_project_gem_targets_model = QStandardItemModel() - enabled_project_gem_targets = registration.get_project_server_gem_targets( - project_path=self.get_selected_project_path()) - all_gem_targets = registration.get_all_gem_targets() - for gem_target in sorted(all_gem_targets): - if gem_target not in enabled_project_gem_targets: - model_item = QStandardItem(gem_target) - available_project_gem_targets_model.appendRow(model_item) - self.available_gem_targets_list.setModel(available_project_gem_targets_model) def refresh_create_project_template_list(self) -> None: self.create_project_template_model = QStandardItemModel() - for project_template_path in registration.get_project_templates(): + for project_template_path in manifest.get_project_templates(): model_item = QStandardItem(project_template_path) self.create_project_template_model.appendRow(model_item) self.create_project_template_list.setModel(self.create_project_template_model) def refresh_create_gem_template_list(self) -> None: self.create_gem_template_model = QStandardItemModel() - for gem_template_path in registration.get_gem_templates(): + for gem_template_path in manifest.get_gem_templates(): model_item = QStandardItem(gem_template_path) self.create_gem_template_model.appendRow(model_item) self.create_gem_template_list.setModel(self.create_gem_template_model) def refresh_create_from_template_list(self) -> None: self.create_from_template_model = QStandardItemModel() - for generic_template_path in registration.get_generic_templates(): + for generic_template_path in manifest.get_generic_templates(): model_item = QStandardItem(generic_template_path) self.create_from_template_model.appendRow(model_item) self.create_from_template_list.setModel(self.create_from_template_model)