diff --git a/.clang-format b/.clang-format
index 565f28130e..04e0284f97 100644
--- a/.clang-format
+++ b/.clang-format
@@ -46,7 +46,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/Assets/Objects/Foliage/Textures/grass_atlas_diff.tif.exportsettings b/AutomatedTesting/Assets/Objects/Foliage/Textures/grass_atlas_diff.tif.exportsettings
index 5c4c862583..b65133fbb0 100644
--- a/AutomatedTesting/Assets/Objects/Foliage/Textures/grass_atlas_diff.tif.exportsettings
+++ b/AutomatedTesting/Assets/Objects/Foliage/Textures/grass_atlas_diff.tif.exportsettings
@@ -1 +1 @@
-/autooptimizefile=0 /preset=AlbedoWithGenericAlpha /reduce="es3:2,ios:2,osx_gl:0,pc:0,provo:0"
\ No newline at end of file
+/autooptimizefile=0 /preset=AlbedoWithGenericAlpha /reduce="android:2,ios:2,mac:0,pc:0,provo:0"
\ No newline at end of file
diff --git a/AutomatedTesting/Assets/Objects/Foliage/Textures/grass_atlas_sss.tif.exportsettings b/AutomatedTesting/Assets/Objects/Foliage/Textures/grass_atlas_sss.tif.exportsettings
index 441a11bc68..e8da408b36 100644
--- a/AutomatedTesting/Assets/Objects/Foliage/Textures/grass_atlas_sss.tif.exportsettings
+++ b/AutomatedTesting/Assets/Objects/Foliage/Textures/grass_atlas_sss.tif.exportsettings
@@ -1 +1 @@
-/autooptimizefile=0 /preset=Albedo /reduce="es3:3,ios:3,osx_gl:0,pc:0,provo:0"
\ No newline at end of file
+/autooptimizefile=0 /preset=Albedo /reduce="android:3,ios:3,mac:0,pc:0,provo:0"
\ No newline at end of file
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()
+
+ 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_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}")
+ 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 ea40001c31..9254c3d4eb 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 []
@@ -126,6 +141,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(
@@ -134,6 +181,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
@@ -143,6 +191,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.
"""
@@ -150,9 +200,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/PythonAssetBuilder/AssetBuilder_test.py b/AutomatedTesting/Gem/PythonTests/PythonAssetBuilder/AssetBuilder_test.py
index 818dc23079..ecf08cfcbd 100644
--- a/AutomatedTesting/Gem/PythonTests/PythonAssetBuilder/AssetBuilder_test.py
+++ b/AutomatedTesting/Gem/PythonTests/PythonAssetBuilder/AssetBuilder_test.py
@@ -31,13 +31,13 @@ class TestPythonAssetProcessing(object):
unexpected_lines = []
expected_lines = [
'Mock asset exists',
- 'Expected subId for asset (gem/pythontests/pythonassetbuilder/geom_group_fbx_cube_100cm_z_positive_1.azmodel) found',
- 'Expected subId for asset (gem/pythontests/pythonassetbuilder/geom_group_fbx_cube_100cm_z_negative_1.azmodel) found',
- 'Expected subId for asset (gem/pythontests/pythonassetbuilder/geom_group_fbx_cube_100cm_y_positive_1.azmodel) found',
- 'Expected subId for asset (gem/pythontests/pythonassetbuilder/geom_group_fbx_cube_100cm_y_negative_1.azmodel) found',
- 'Expected subId for asset (gem/pythontests/pythonassetbuilder/geom_group_fbx_cube_100cm_x_positive_1.azmodel) found',
- 'Expected subId for asset (gem/pythontests/pythonassetbuilder/geom_group_fbx_cube_100cm_x_negative_1.azmodel) found',
- 'Expected subId for asset (gem/pythontests/pythonassetbuilder/geom_group_fbx_cube_100cm_center_1.azmodel) found'
+ 'Expected subId for asset (gem/pythontests/pythonassetbuilder/geom_group_fbx_cube_100cm_z_positive.azmodel) found',
+ 'Expected subId for asset (gem/pythontests/pythonassetbuilder/geom_group_fbx_cube_100cm_z_negative.azmodel) found',
+ 'Expected subId for asset (gem/pythontests/pythonassetbuilder/geom_group_fbx_cube_100cm_y_positive.azmodel) found',
+ 'Expected subId for asset (gem/pythontests/pythonassetbuilder/geom_group_fbx_cube_100cm_y_negative.azmodel) found',
+ 'Expected subId for asset (gem/pythontests/pythonassetbuilder/geom_group_fbx_cube_100cm_x_positive.azmodel) found',
+ 'Expected subId for asset (gem/pythontests/pythonassetbuilder/geom_group_fbx_cube_100cm_x_negative.azmodel) found',
+ 'Expected subId for asset (gem/pythontests/pythonassetbuilder/geom_group_fbx_cube_100cm_center.azmodel) found'
]
timeout = 180
halt_on_unexpected = False
diff --git a/AutomatedTesting/Gem/PythonTests/PythonAssetBuilder/AssetBuilder_test_case.py b/AutomatedTesting/Gem/PythonTests/PythonAssetBuilder/AssetBuilder_test_case.py
index cd9adfdbcf..a7907778b2 100644
--- a/AutomatedTesting/Gem/PythonTests/PythonAssetBuilder/AssetBuilder_test_case.py
+++ b/AutomatedTesting/Gem/PythonTests/PythonAssetBuilder/AssetBuilder_test_case.py
@@ -38,16 +38,16 @@ def test_azmodel_product(generatedModelAssetPath, expectedSubId):
assetId = azlmbr.asset.AssetCatalogRequestBus(azlmbr.bus.Broadcast, 'GetAssetIdByPath', generatedModelAssetPath, azModelAssetType, False)
assetIdString = assetId.to_string()
if (assetIdString.endswith(':' + expectedSubId) is False):
- raise_and_stop(f'Asset has unexpected asset ID ({assetIdString}) for ({generatedModelAssetPath})!')
+ raise_and_stop(f'Asset at path {generatedModelAssetPath} has unexpected asset ID ({assetIdString}) for ({generatedModelAssetPath}), expected {expectedSubId}!')
else:
print(f'Expected subId for asset ({generatedModelAssetPath}) found')
-test_azmodel_product('gem/pythontests/pythonassetbuilder/geom_group_fbx_cube_100cm_z_positive_1.azmodel', '10315ae0')
-test_azmodel_product('gem/pythontests/pythonassetbuilder/geom_group_fbx_cube_100cm_z_negative_1.azmodel', '10661093')
-test_azmodel_product('gem/pythontests/pythonassetbuilder/geom_group_fbx_cube_100cm_y_positive_1.azmodel', '10af8810')
-test_azmodel_product('gem/pythontests/pythonassetbuilder/geom_group_fbx_cube_100cm_y_negative_1.azmodel', '10f8c263')
-test_azmodel_product('gem/pythontests/pythonassetbuilder/geom_group_fbx_cube_100cm_x_positive_1.azmodel', '100ac47f')
-test_azmodel_product('gem/pythontests/pythonassetbuilder/geom_group_fbx_cube_100cm_x_negative_1.azmodel', '105d8e0c')
-test_azmodel_product('gem/pythontests/pythonassetbuilder/geom_group_fbx_cube_100cm_center_1.azmodel', '1002d464')
+test_azmodel_product('gem/pythontests/pythonassetbuilder/geom_group_fbx_cube_100cm_z_positive.azmodel', '1024be55')
+test_azmodel_product('gem/pythontests/pythonassetbuilder/geom_group_fbx_cube_100cm_z_negative.azmodel', '1052c94e')
+test_azmodel_product('gem/pythontests/pythonassetbuilder/geom_group_fbx_cube_100cm_y_positive.azmodel', '10130556')
+test_azmodel_product('gem/pythontests/pythonassetbuilder/geom_group_fbx_cube_100cm_y_negative.azmodel', '1065724d')
+test_azmodel_product('gem/pythontests/pythonassetbuilder/geom_group_fbx_cube_100cm_x_positive.azmodel', '10d16e68')
+test_azmodel_product('gem/pythontests/pythonassetbuilder/geom_group_fbx_cube_100cm_x_negative.azmodel', '10a71973')
+test_azmodel_product('gem/pythontests/pythonassetbuilder/geom_group_fbx_cube_100cm_center.azmodel', '10412075')
azlmbr.editor.EditorToolsApplicationRequestBus(azlmbr.bus.Broadcast, 'ExitNoPrompt')
diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/ap_fixtures/ap_all_platforms_setup_fixture.py b/AutomatedTesting/Gem/PythonTests/assetpipeline/ap_fixtures/ap_all_platforms_setup_fixture.py
index e729ee9882..9a5b93ca80 100755
--- a/AutomatedTesting/Gem/PythonTests/assetpipeline/ap_fixtures/ap_all_platforms_setup_fixture.py
+++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/ap_fixtures/ap_all_platforms_setup_fixture.py
@@ -34,10 +34,10 @@ def ap_all_platforms_setup_fixture(request, workspace, ap_setup_fixture) -> Dict
# Specific platform cache locations
resources["pc_cache_location"] = os.path.join(cache_dir, "pc")
- resources["es3_cache_location"] = os.path.join(cache_dir, "es3")
+ resources["android_cache_location"] = os.path.join(cache_dir, "android")
resources["ios_cache_location"] = os.path.join(cache_dir, "ios")
- resources["osx_gl_cache_location"] = os.path.join(cache_dir, "osx_gl")
+ resources["mac_cache_location"] = os.path.join(cache_dir, "mac")
resources["provo_cache_location"] = os.path.join(cache_dir, "provo")
- resources["all_platforms"] = ["pc", "es3", "ios", "osx_gl", "provo"]
+ resources["all_platforms"] = ["pc", "android", "ios", "mac", "provo"]
return resources
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 580816e7b5..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
@@ -54,7 +54,7 @@ def bundler_batch_setup_fixture(request, workspace, asset_processor, timeout) ->
platforms = [platform.strip() for platform in platforms.split(",")]
else:
# No commandline argument provided, default to mac and pc
- platforms = ["pc", "osx_gl"]
+ platforms = ["pc", "mac"]
class BundlerBatchFixture:
"""
@@ -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
# ******
@@ -241,11 +241,11 @@ def bundler_batch_setup_fixture(request, workspace, asset_processor, timeout) ->
def get_platform_flag(self, platform_name: str) -> int:
if (platform_name == "pc"):
return 1
- elif (platform_name == "es3"):
+ elif (platform_name == "android"):
return 2
elif (platform_name == "ios"):
return 4
- elif (platform_name == "osx_gl"):
+ elif (platform_name == "mac"):
return 8
elif (platform_name == "server"):
return 128
@@ -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 d236e87aa2..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,15 +482,25 @@ 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
- assert "pc" in helper["platforms"] and "osx_gl" in helper["platforms"], \
+ assert "pc" in helper["platforms"] and "mac" in helper["platforms"], \
"This test requires both PC and MAC platforms to be enabled. " \
- "Please rerun with commandline option: '--bundle_platforms=pc,osx_gl'"
+ "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,21 +537,21 @@ class TestsAssetBundlerBatch_WindowsAndMac(object):
for bundle_file in bundle_files.values():
assert os.path.isfile(bundle_file)
- # This asset is created on osx_gl 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
- # Extract the delta catalog file from osx_gl archive. {file_to_check} SHOULD be present for MAC
- file_contents = helper.extract_file_content(bundle_files["osx_gl"], "DeltaCatalog.xml")
+ # Extract the delta catalog file from mac archive. {file_to_check} SHOULD be present for MAC
+ file_contents = helper.extract_file_content(bundle_files["mac"], "DeltaCatalog.xml")
# fmt:off
assert file_to_check in file_contents, \
- f"{file_to_check} was not found in DeltaCatalog.xml in darwin bundle file {bundle_files['osx_gl']}"
+ f"{file_to_check} was not found in DeltaCatalog.xml in darwin bundle file {bundle_files['mac']}"
# fmt:on
# Gather checksums for first set of bundles
@@ -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
@@ -613,20 +666,20 @@ class TestsAssetBundlerBatch_WindowsAndMac(object):
helper.call_seeds(
seedListFile=helper["seed_list_file"],
addSeed=test_asset,
- platform="pc,osx_gl",
+ platform="pc,mac",
)
# 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
helper.call_seeds(
seedListFile=helper["seed_list_file"],
removePlatformFromSeeds="",
- platform="osx_gl",
+ platform="mac",
)
# Validate only pc platform for seed. Save file contents to variable
all_lines = check_seed_platform(helper["seed_list_file"], test_asset, helper["platform_values"]["pc"])
@@ -646,12 +699,12 @@ class TestsAssetBundlerBatch_WindowsAndMac(object):
helper.call_seeds(
seedListFile=helper["seed_list_file"],
addPlatformToSeeds="",
- platform="osx_gl",
+ platform="mac",
)
# 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)
@@ -670,7 +723,7 @@ class TestsAssetBundlerBatch_WindowsAndMac(object):
helper.call_seeds(
seedListFile=helper["seed_list_file"],
removeSeed=test_asset,
- platform="pc,osx_gl",
+ platform="pc,mac",
)
# Validate seed was removed from file
@@ -692,14 +745,20 @@ 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
# fmt:off
- assert "pc" in helper["platforms"] and "osx_gl" in helper["platforms"], \
+ assert "pc" in helper["platforms"] and "mac" in helper["platforms"], \
"This test requires both PC and MAC platforms to be enabled. " \
- "Please rerun with commandline option: '--bundle_platforms=pc,osx_gl'"
+ "Please rerun with commandline option: '--bundle_platforms=pc,mac'"
# fmt:on
# Test assets arranged in common lists: six (0-5) .txt files and .dat files
@@ -717,16 +776,16 @@ class TestsAssetBundlerBatch_WindowsAndMac(object):
file_platforms = {
"txtfile_0.txt": "pc",
"txtfile_1.txt": "pc",
- "txtfile_2.txt": "pc,osx_gl",
- "txtfile_3.txt": "pc,osx_gl",
- "txtfile_4.txt": "osx_gl",
- "txtfile_5.txt": "osx_gl",
+ "txtfile_2.txt": "pc,mac",
+ "txtfile_3.txt": "pc,mac",
+ "txtfile_4.txt": "mac",
+ "txtfile_5.txt": "mac",
"datfile_0.dat": "pc",
"datfile_1.dat": "pc",
- "datfile_2.dat": "pc,osx_gl",
- "datfile_3.dat": "pc,osx_gl",
- "datfile_4.dat": "osx_gl",
- "datfile_5.dat": "osx_gl",
+ "datfile_2.dat": "pc,mac",
+ "datfile_3.dat": "pc,mac",
+ "datfile_4.dat": "mac",
+ "datfile_5.dat": "mac",
}
# Comparison rules files and their associated 'comparisonType' flags
@@ -741,7 +800,7 @@ class TestsAssetBundlerBatch_WindowsAndMac(object):
# Get our test assets ready and processed
utils.prepare_test_assets(env["tests_dir"], "C16877178", env["project_test_assets_dir"])
- asset_processor.batch_process(timeout=timeout, fastscan=False, platforms="pc,osx_gl")
+ asset_processor.batch_process(timeout=timeout, fastscan=False, platforms="pc,mac")
# *** Some helper functions *** #
@@ -759,7 +818,7 @@ class TestsAssetBundlerBatch_WindowsAndMac(object):
helper.call_assetLists(
assetListFile=os.path.join(helper["test_dir"], asset_list_file_name),
seedListFile=os.path.join(helper["test_dir"], seed_file_name),
- platform="pc,osx_gl",
+ platform="pc,mac",
)
def get_platform_assets(asset_name_list: List[str]) -> Dict[str, List[str]]:
@@ -769,7 +828,7 @@ class TestsAssetBundlerBatch_WindowsAndMac(object):
for asset_name in asset_name_list:
if "pc" in file_platforms[asset_name]:
win_assets.append(asset_name)
- if "osx_gl" in file_platforms[asset_name]:
+ if "mac" in file_platforms[asset_name]:
mac_assets.append(asset_name)
return {"win": win_assets, "mac": mac_assets}
@@ -798,7 +857,7 @@ class TestsAssetBundlerBatch_WindowsAndMac(object):
# Get platform result file names
win_asset_list_file = helper.platform_file_name(request_file, platforms["pc"])
- mac_asset_list_file = helper.platform_file_name(request_file, platforms["osx_gl"])
+ mac_asset_list_file = helper.platform_file_name(request_file, platforms["mac"])
# Get expected platforms for each asset in asset_names
platform_files = get_platform_assets(asset_names)
@@ -879,14 +938,14 @@ class TestsAssetBundlerBatch_WindowsAndMac(object):
# fmt:on
# End verify_asset_list_contents()
- def run_compare_command_and_verify(platform_arg: str, expect_pc_output: bool, expect_osx_gl_output: bool) -> None:
+ def run_compare_command_and_verify(platform_arg: str, expect_pc_output: bool, expect_mac_output: bool) -> None:
# Expected asset list to equal result of comparison
expected_pc_asset_list = None
- expected_osx_gl_asset_list = None
+ expected_mac_asset_list = None
# Last output file. Use this for comparison to 'expected'
output_pc_asset_list = None
- output_osx_gl_asset_list = None
+ output_mac_asset_list = None
# Add the platform to the file name to match what the Bundler will create
last_output_arg = output_arg.split(",")[-1]
@@ -895,10 +954,10 @@ class TestsAssetBundlerBatch_WindowsAndMac(object):
expected_pc_asset_list = os.path.join(helper["test_dir"], helper.platform_file_name(expected_asset_list, platform))
output_pc_asset_list = helper.platform_file_name(last_output_arg, platform)
- if expect_osx_gl_output:
- platform = platforms["osx_gl"]
- expected_osx_gl_asset_list = os.path.join(helper["test_dir"], helper.platform_file_name(expected_asset_list, platform))
- output_osx_gl_asset_list = helper.platform_file_name(last_output_arg, platform)
+ if expect_mac_output:
+ platform = platforms["mac"]
+ expected_mac_asset_list = os.path.join(helper["test_dir"], helper.platform_file_name(expected_asset_list, platform))
+ output_mac_asset_list = helper.platform_file_name(last_output_arg, platform)
# Build execution command
cmd = generate_compare_command(platform_arg)
@@ -911,15 +970,15 @@ class TestsAssetBundlerBatch_WindowsAndMac(object):
verify_asset_list_contents(expected_pc_asset_list, output_pc_asset_list)
fs.delete([output_pc_asset_list], True, True)
- if expect_osx_gl_output:
- verify_asset_list_contents(expected_osx_gl_asset_list, output_osx_gl_asset_list)
- fs.delete([output_osx_gl_asset_list], True, True)
+ if expect_mac_output:
+ verify_asset_list_contents(expected_mac_asset_list, output_mac_asset_list)
+ fs.delete([output_mac_asset_list], True, True)
# End run_compare_command_and_verify()
# Generate command, run and validate for each platform
run_compare_command_and_verify("pc", True, False)
- run_compare_command_and_verify("osx_gl", False, True)
- run_compare_command_and_verify("pc,osx_gl", True, True)
+ run_compare_command_and_verify("mac", False, True)
+ run_compare_command_and_verify("pc,mac", True, True)
#run_compare_command_and_verify(None, True, True)
# End compare_and_check()
@@ -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 50b3af1438..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,9 +100,17 @@ 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("osx_gl")
+ asset_processor.enable_asset_processor_platform("mac")
result, _ = asset_processor.batch_process()
assert result, "AP Batch failed"
@@ -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/editor/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/editor/CMakeLists.txt
index e8f3349df4..834254134e 100644
--- a/AutomatedTesting/Gem/PythonTests/editor/CMakeLists.txt
+++ b/AutomatedTesting/Gem/PythonTests/editor/CMakeLists.txt
@@ -39,4 +39,19 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_
COMPONENT
Editor
)
+
+ ly_add_pytest(
+ NAME AutomatedTesting::EditorTests_Sandbox
+ TEST_SUITE sandbox
+ TEST_SERIAL
+ PATH ${CMAKE_CURRENT_LIST_DIR}
+ PYTEST_MARKS "SUITE_sandbox"
+ TIMEOUT 1500
+ RUNTIME_DEPENDENCIES
+ Legacy::Editor
+ AZ::AssetProcessor
+ AutomatedTesting.Assets
+ COMPONENT
+ Editor
+ )
endif()
diff --git a/AutomatedTesting/Gem/PythonTests/editor/test_Docking.py b/AutomatedTesting/Gem/PythonTests/editor/test_Docking.py
index c2d515e250..f887560a19 100644
--- a/AutomatedTesting/Gem/PythonTests/editor/test_Docking.py
+++ b/AutomatedTesting/Gem/PythonTests/editor/test_Docking.py
@@ -39,7 +39,7 @@ class TestDocking(object):
file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
@pytest.mark.test_case_id("C6376081")
- @pytest.mark.SUITE_periodic
+ @pytest.mark.SUITE_sandbox
def test_Docking_BasicDockedTools(self, request, editor, level, launcher_platform):
expected_lines = [
"The tools are all docked together in a tabbed widget",
diff --git a/AutomatedTesting/Gem/PythonTests/editor/test_Menus.py b/AutomatedTesting/Gem/PythonTests/editor/test_Menus.py
index 70a22f9e2a..c2da1343de 100644
--- a/AutomatedTesting/Gem/PythonTests/editor/test_Menus.py
+++ b/AutomatedTesting/Gem/PythonTests/editor/test_Menus.py
@@ -39,7 +39,7 @@ class TestMenus(object):
file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True)
@pytest.mark.test_case_id("C16780783", "C2174438")
- @pytest.mark.SUITE_periodic
+ @pytest.mark.SUITE_sandbox
def test_Menus_EditMenuOptions_Work(self, request, editor, level, launcher_platform):
expected_lines = [
"Undo Action triggered",
@@ -113,7 +113,7 @@ class TestMenus(object):
)
@pytest.mark.test_case_id("C16780778")
- @pytest.mark.SUITE_periodic
+ @pytest.mark.SUITE_sandbox
def test_Menus_FileMenuOptions_Work(self, request, editor, level, launcher_platform):
expected_lines = [
"New Level Action triggered",
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/SurfaceMaskFilter_BasicSurfaceTagCreation.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/SurfaceMaskFilter_BasicSurfaceTagCreation.py
index 4f58a23a19..730a557a9e 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/SurfaceMaskFilter_BasicSurfaceTagCreation.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/SurfaceMaskFilter_BasicSurfaceTagCreation.py
@@ -23,6 +23,25 @@ class TestSurfaceMaskFilter_BasicSurfaceTagCreation(EditorTestHelper):
EditorTestHelper.__init__(self, log_prefix="TestSurfaceMaskFilter_BasicSurfaceTagCreation", args=["level"])
def run_test(self):
+ """
+ Summary:
+ Verifies basic surface tag value equality
+
+ Expected Behavior:
+ Surface tags of the same name are equal, and different names aren't.
+
+ Test Steps:
+ 1) Open level
+ 2) Create 2 new surface tags of identical names and verify they resolve as equal.
+ 3) Create another new tag of a different name and verify they resolve as different.
+
+ 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
+ """
self.log("SurfaceTag test started")
# Create a level
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/VegetationInstances_DespawnWhenOutOfRange.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/VegetationInstances_DespawnWhenOutOfRange.py
index c25761d655..46c5483988 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/VegetationInstances_DespawnWhenOutOfRange.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/VegetationInstances_DespawnWhenOutOfRange.py
@@ -33,6 +33,25 @@ class TestVegetationInstances_DespawnWhenOutOfRange(EditorTestHelper):
EditorTestHelper.__init__(self, log_prefix='VegetationInstances_DespawnWhenOutOfRange', args=['level'])
def run_test(self):
+ """
+ Summary:
+ Verifies that vegetation instances properly spawn/despawn based on camera range.
+
+ Expected Behavior:
+ Vegetation instances despawn when out of camera range.
+
+ Test Steps:
+ 1) Create a new level
+ 2) Create a simple vegetation area, and set the view position near the spawner. Verify instances plant.
+ 3) Move the view position away from the spawner. Verify instances despawn.
+
+ 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
+ """
# Create a new level
self.test_success = self.create_level(
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/gradient_signal/EditorScripts/GradientGenerators_Incompatibilities.py b/AutomatedTesting/Gem/PythonTests/largeworlds/gradient_signal/EditorScripts/GradientGenerators_Incompatibilities.py
index cc9a15bba0..c37bc9780f 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/gradient_signal/EditorScripts/GradientGenerators_Incompatibilities.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/gradient_signal/EditorScripts/GradientGenerators_Incompatibilities.py
@@ -28,8 +28,21 @@ class TestGradientGeneratorIncompatibilities(EditorTestHelper):
def run_test(self):
"""
Summary:
- Verify that Entities are not active when a Gradient Generator and incompatible component are both present
- on the same Entity.
+ This test verifies that components are disabled when conflicting components are present on the same entity.
+
+ Expected Behavior:
+ Gradient Generator components are incompatible with Vegetation area components.
+
+ Test Steps:
+ 1) Create a new level
+ 2) Create a new entity in the level
+ 3) Add each Gradient Generator component to an entity, and add a Vegetation Area component to the same entity
+ 4) Verify that components are only enabled when entity is free of a conflicting component
+
+ 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
"""
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/gradient_signal/EditorScripts/GradientModifiers_Incompatibilities.py b/AutomatedTesting/Gem/PythonTests/largeworlds/gradient_signal/EditorScripts/GradientModifiers_Incompatibilities.py
index b7d12d074a..f2edc2924e 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/gradient_signal/EditorScripts/GradientModifiers_Incompatibilities.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/gradient_signal/EditorScripts/GradientModifiers_Incompatibilities.py
@@ -28,8 +28,21 @@ class TestGradientModifiersIncompatibilities(EditorTestHelper):
def run_test(self):
"""
Summary:
- Verify that Entities are not active when a Gradient Modifier and incompatible component are both present
- on the same Entity.
+ This test verifies that components are disabled when conflicting components are present on the same entity.
+
+ Expected Behavior:
+ Gradient Modifier components are incompatible with Vegetation area components.
+
+ Test Steps:
+ 1) Create a new level
+ 2) Create a new entity in the level
+ 3) Add each Gradient Modifier component to an entity, and add a Vegetation Area component to the same entity
+ 4) Verify that components are only enabled when entity is free of a conflicting component
+
+ 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
"""
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/gradient_signal/EditorScripts/GradientPreviewSettings_ClearingPinnedEntitySetsPreviewToOrigin.py b/AutomatedTesting/Gem/PythonTests/largeworlds/gradient_signal/EditorScripts/GradientPreviewSettings_ClearingPinnedEntitySetsPreviewToOrigin.py
index c37ee36265..45da74d6cd 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/gradient_signal/EditorScripts/GradientPreviewSettings_ClearingPinnedEntitySetsPreviewToOrigin.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/gradient_signal/EditorScripts/GradientPreviewSettings_ClearingPinnedEntitySetsPreviewToOrigin.py
@@ -9,19 +9,6 @@ remove or modify any license notices. This file is distributed on an "AS IS" BAS
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
"""
-"""
-The below cases are combined in this script
-C2676829
-C3961326
-C3980659
-C3980664
-C3980669
-C3416548
-C2676823
-C3961321
-C2676826
-"""
-
import os
import sys
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/gradient_signal/EditorScripts/GradientPreviewSettings_DefaultPinnedEntityIsSelf.py b/AutomatedTesting/Gem/PythonTests/largeworlds/gradient_signal/EditorScripts/GradientPreviewSettings_DefaultPinnedEntityIsSelf.py
index 5a758b9d89..b8f4114d30 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/gradient_signal/EditorScripts/GradientPreviewSettings_DefaultPinnedEntityIsSelf.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/gradient_signal/EditorScripts/GradientPreviewSettings_DefaultPinnedEntityIsSelf.py
@@ -44,7 +44,21 @@ class TestGradientPreviewSettings(EditorTestHelper):
def run_test(self):
"""
Summary:
- Verify if the current entity is set to the pin preview to shape entity by default for several components.
+ This test verifies default values for the pinned entity for Gradient Preview settings.
+
+ Expected Behavior:
+ Pinned entity is self for all gradient generator/modifiers.
+
+ Test Steps:
+ 1) Create a new level
+ 2) Create a new entity in the level
+ 3) Add each Gradient Generator component to an entity, and verify the Pin Preview to Shape property is set to
+ self
+
+ 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
"""
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/gradient_signal/EditorScripts/GradientSurfaceTagEmitter_ComponentDependencies.py b/AutomatedTesting/Gem/PythonTests/largeworlds/gradient_signal/EditorScripts/GradientSurfaceTagEmitter_ComponentDependencies.py
index a16e37e0fc..8e2d0611af 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/gradient_signal/EditorScripts/GradientSurfaceTagEmitter_ComponentDependencies.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/gradient_signal/EditorScripts/GradientSurfaceTagEmitter_ComponentDependencies.py
@@ -31,11 +31,21 @@ class TestGradientSurfaceTagEmitterDependencies(EditorTestHelper):
def run_test(self):
"""
Summary:
- Component has a dependency on a Gradient component
+ This test verifies that the Gradient Surface Tag Emitter component is dependent on a gradient component.
Expected Result:
- Component is disabled until a Gradient Generator, Modifier or Gradient Reference component
- (and any sub-dependencies) is added to the entity.
+ Gradient Surface Tag Emitter component is disabled until a Gradient Generator, Modifier or Gradient Reference
+ component (and any sub-dependencies) is added to the entity.
+
+ Test Steps:
+ 1) Open level
+ 2) Create a new entity with a Gradient Surface Tag Emitter component
+ 3) Verify the component is disabled until a dependent component is also added to the entity
+
+ 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
"""
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/gradient_signal/EditorScripts/GradientTransform_RequiresShape.py b/AutomatedTesting/Gem/PythonTests/largeworlds/gradient_signal/EditorScripts/GradientTransform_RequiresShape.py
index e1e901f2f7..2311363db9 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/gradient_signal/EditorScripts/GradientTransform_RequiresShape.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/gradient_signal/EditorScripts/GradientTransform_RequiresShape.py
@@ -28,8 +28,20 @@ class TestGradientTransformRequiresShape(EditorTestHelper):
def run_test(self):
"""
Summary:
- Verify that Gradient Transform Modifier component requires a
- Shape component before the Entity can become active.
+ This test verifies that the Gradient Transform Modifier component is dependent on a shape component.
+
+ Expected Result:
+ Gradient Transform Modifier component is disabled until a shape component is added to the entity.
+
+ Test Steps:
+ 1) Open level
+ 2) Create a new entity with a Gradient Transform Modifier component
+ 3) Verify the component is disabled until a shape component is also added to the entity
+
+ 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
"""
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/gradient_signal/EditorScripts/ImageGradient_RequiresShape.py b/AutomatedTesting/Gem/PythonTests/largeworlds/gradient_signal/EditorScripts/ImageGradient_RequiresShape.py
index dab8e6928a..a5d9632fd6 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/gradient_signal/EditorScripts/ImageGradient_RequiresShape.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/gradient_signal/EditorScripts/ImageGradient_RequiresShape.py
@@ -28,8 +28,20 @@ class TestImageGradientRequiresShape(EditorTestHelper):
def run_test(self):
"""
Summary:
- Verify that Image Gradient component requires a
- Shape component before the Entity can become active.
+ This test verifies that the Image Gradient component is dependent on a shape component.
+
+ Expected Result:
+ Gradient Transform Modifier component is disabled until a shape component is added to the entity.
+
+ Test Steps:
+ 1) Open level
+ 2) Create a new entity with a Image Gradient component
+ 3) Verify the component is disabled until a shape component is also added to the entity
+
+ 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
"""
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/AreaNodes_DependentComponentsAdded.py b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/AreaNodes_DependentComponentsAdded.py
index d1e0b68ef4..c41d153cfa 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/AreaNodes_DependentComponentsAdded.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/AreaNodes_DependentComponentsAdded.py
@@ -33,6 +33,26 @@ class TestAreaNodeComponentDependency(EditorTestHelper):
EditorTestHelper.__init__(self, log_prefix="AreaNodeComponentDependency", args=["level"])
def run_test(self):
+ """
+ Summary:
+ This test verifies that the Landscape Canvas nodes can be added to a graph, and correctly create entities with
+ proper dependent components.
+
+ Expected Behavior:
+ All expected component dependencies are met when adding an area node to a graph.
+
+ Test Steps:
+ 1) Create a new level
+ 2) Open Landscape Canvas and create a new graph
+ 3) Drag each of the area nodes to the graph area, and ensure the proper dependent components are added
+
+ 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
+ """
def onEntityCreated(parameters):
global newEntityId
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/AreaNodes_EntityCreatedOnNodeAdd.py b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/AreaNodes_EntityCreatedOnNodeAdd.py
index 4e429a192b..fb977b4987 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/AreaNodes_EntityCreatedOnNodeAdd.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/AreaNodes_EntityCreatedOnNodeAdd.py
@@ -33,7 +33,25 @@ class TestGradientNodeEntityCreate(EditorTestHelper):
EditorTestHelper.__init__(self, log_prefix="AreaNodeEntityCreate", args=["level"])
def run_test(self):
+ """
+ Summary:
+ This test verifies that the Landscape Canvas nodes can be added to a graph, and correctly create entities.
+ Expected Behavior:
+ New entities are created when dragging area nodes to graph area.
+
+ Test Steps:
+ 1) Create a new level
+ 2) Open Landscape Canvas and create a new graph
+ 3) Drag each of the area nodes to the graph area, and ensure a new entity is created
+
+ 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
+ """
def onEntityCreated(parameters):
global newEntityId
newEntityId = parameters[0]
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/AreaNodes_EntityRemovedOnNodeDelete.py b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/AreaNodes_EntityRemovedOnNodeDelete.py
index 38f8641b4c..57ba8fc006 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/AreaNodes_EntityRemovedOnNodeDelete.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/AreaNodes_EntityRemovedOnNodeDelete.py
@@ -34,7 +34,26 @@ class TestAreaNodeEntityDelete(EditorTestHelper):
EditorTestHelper.__init__(self, log_prefix="AreaNodeEntityDelete", args=["level"])
def run_test(self):
-
+ """
+ Summary:
+ This test verifies that the Landscape Canvas node deletion properly cleans up entities in the Editor.
+
+ Expected Behavior:
+ Entities are removed when area nodes are deleted from a graph.
+
+ Test Steps:
+ 1) Create a new level
+ 2) Open Landscape Canvas and create a new graph
+ 3) Drag each of the area nodes to the graph area, and ensure a new entity is created
+ 4) Delete the nodes, and ensure the newly created entities are removed
+
+ 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
+ """
def onEntityCreated(parameters):
global createdEntityId
createdEntityId = parameters[0]
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/ComponentUpdates_UpdateGraph.py b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/ComponentUpdates_UpdateGraph.py
index 26062c01f8..60527b64d2 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/ComponentUpdates_UpdateGraph.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/ComponentUpdates_UpdateGraph.py
@@ -9,24 +9,6 @@ remove or modify any license notices. This file is distributed on an "AS IS" BAS
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
"""
-
-"""
-C22602072 - Graph is updated when underlying components are added/removed
-
-1. Open Level.
-2. Find LandscapeCanvas named entity.
-3. Ensure Vegetation Distribution Component is present on the BushSpawner entity.
-4. Open graph and ensure Distribution Filter wrapped node is present.
-5. Delete the Vegetation Distribution Filter component from the BushSpawner entity via Entity Inspector.
-6. Ensure the Vegetation Distribution Filter component was deleted from the BushSpawner entity and node is no longer
-present in the graph.
-7. Add Vegetation Altitude Filter to the BushSpawner entity through Entity Inspector.
-8. Ensure Altitude Filter was added to the BushSpawner node in the open graph.
-9. Add a new entity with unique name as a child of the Landscape Canvas entity.
-10. Add a Box Shape component to the new child entity.
-11. Ensure Box Shape node is present on the open graph.
-"""
-
import os
import sys
@@ -50,6 +32,36 @@ class TestComponentUpdatesUpdateGraph(EditorTestHelper):
EditorTestHelper.__init__(self, log_prefix="ComponentUpdatesUpdateGraph", args=["level"])
def run_test(self):
+ """
+ Summary:
+ This test verifies that the Landscape Canvas graphs update properly when components are added/removed outside of
+ Landscape Canvas.
+
+ Expected Behavior:
+ Graphs properly reflect component changes made to entities outside of Landscape Canvas.
+
+ Test Steps:
+ 1. Open Level
+ 2. Find LandscapeCanvas named entity
+ 3. Ensure Vegetation Distribution Component is present on the BushSpawner entity
+ 4. Open graph and ensure Distribution Filter wrapped node is present
+ 5. Delete the Vegetation Distribution Filter component from the BushSpawner entity via Entity Inspector
+ 6. Ensure the Vegetation Distribution Filter component was deleted from the BushSpawner entity and node is
+ no longer present in the graph
+ 7. Add Vegetation Altitude Filter to the BushSpawner entity through Entity Inspector
+ 8. Ensure Altitude Filter was added to the BushSpawner node in the open graph
+ 9. Add a new entity with unique name as a child of the Landscape Canvas entity
+ 10. Add a Box Shape component to the new child entity
+ 11. Ensure Box Shape node is present on the open graph
+
+ 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
+ """
+
# Create a new empty level and instantiate LC_BushFlowerBlender.slice
self.test_success = self.create_level(
self.args["level"],
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/CreateNewGraph.py b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/CreateNewGraph.py
index 5fed13985d..4b5e03abbc 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/CreateNewGraph.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/CreateNewGraph.py
@@ -37,6 +37,25 @@ class TestCreateNewGraph(EditorTestHelper):
print("New root entity created")
def run_test(self):
+ """
+ Summary:
+ This test verifies that new graphs can be created in Landscape Canvas.
+
+ Expected Behavior:
+ New graphs can be created, and proper entity is created to hold graph data with a Landscape Canvas component.
+
+ Test Steps:
+ 1) Create a new level
+ 2) Open Landscape Canvas and create a new graph
+ 3) Ensures the root entity created contains a Landscape Canvas component
+
+ 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
+ """
self.test_success = self.create_level(
self.args["level"],
heightmap_resolution=128,
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/Edit_DisabledNodeDuplication.py b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/Edit_DisabledNodeDuplication.py
index 7fd3f075e0..81e24b20e1 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/Edit_DisabledNodeDuplication.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/Edit_DisabledNodeDuplication.py
@@ -33,7 +33,25 @@ class TestDisabledNodeDuplication(EditorTestHelper):
EditorTestHelper.__init__(self, log_prefix="DisabledNodeDuplication", args=["level"])
def run_test(self):
+ """
+ Summary:
+ This test verifies Editor stability after duplicating disabled Landscape Canvas nodes.
+ Expected Behavior:
+ Editor remains stable and free of crashes.
+
+ Test Steps:
+ 1) Create a new level
+ 2) Open Landscape Canvas and create a new graph
+ 3) Create several new nodes, disable the nodes via disabling/deleting components, and duplicate the nodes
+
+ 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
+ """
def onEntityCreated(parameters):
global newEntityId
newEntityId = parameters[0]
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/Edit_UndoNodeDelete_SliceEntity.py b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/Edit_UndoNodeDelete_SliceEntity.py
index 27ab6fded3..61c4cf9ac2 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/Edit_UndoNodeDelete_SliceEntity.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/Edit_UndoNodeDelete_SliceEntity.py
@@ -9,17 +9,6 @@ remove or modify any license notices. This file is distributed on an "AS IS" BAS
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
"""
-
-"""
-C30813586 - Editor remains stable after Undoing deletion of a node on a slice entity
-
-1. Open level with instantiated slice.
-2. Open the graph.
-3. Find the BushSpawner's Vegetation Layer Spawner node.
-4. Delete the node.
-5. Undo to restore the node.
-"""
-
import os
import sys
@@ -44,7 +33,26 @@ class TestUndoNodeDeleteSlice(EditorTestHelper):
EditorTestHelper.__init__(self, log_prefix="UndoNodeDeleteSlice", args=["level"])
def run_test(self):
-
+ """
+ Summary:
+ This test verifies Editor stability after undoing the deletion of nodes on a slice entity.
+
+ Expected Behavior:
+ Editor remains stable and free of crashes.
+
+ Test Steps:
+ 1) Create a new level
+ 2) Instantiate a slice with a Landscape Canvas setup
+ 3) Find a specific node on the graph, and delete it
+ 4) Restore the node with Undo
+
+ 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
+ """
# Create a new empty level and instantiate LC_BushFlowerBlender.slice
self.test_success = self.create_level(
self.args["level"],
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/GradientMixer_NodeConstruction.py b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/GradientMixer_NodeConstruction.py
index ca3bc04f47..124baf9d2e 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/GradientMixer_NodeConstruction.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/GradientMixer_NodeConstruction.py
@@ -34,6 +34,27 @@ class TestGradientMixerNodeConstruction(EditorTestHelper):
EditorTestHelper.__init__(self, log_prefix="GradientMixerNodeConstruction", args=["level"])
def run_test(self):
+ """
+ Summary:
+ This test verifies a Gradient Mixer vegetation setup can be constructed through Landscape Canvas.
+
+ Expected Behavior:
+ Entities contain all required components and component references after creating nodes and setting connections
+ on a Landscape Canvas graph.
+
+ Test Steps:
+ 1) Create a new level
+ 2) Open Landscape Canvas and create a new graph
+ 3) Add all necessary nodes to the graph and set connections to form a Gradient Mixer setup
+ 4) Verify all components and component references were properly set during graph construction
+
+ 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
+ """
def onEntityCreated(parameters):
global newEntityId
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/GradientModifierNodes_EntityCreatedOnNodeAdd.py b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/GradientModifierNodes_EntityCreatedOnNodeAdd.py
index d40b19e7db..aa98eb3dc3 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/GradientModifierNodes_EntityCreatedOnNodeAdd.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/GradientModifierNodes_EntityCreatedOnNodeAdd.py
@@ -33,6 +33,25 @@ class TestGradientModifierNodeEntityCreate(EditorTestHelper):
EditorTestHelper.__init__(self, log_prefix="GradientModifierNodeEntityCreate", args=["level"])
def run_test(self):
+ """
+ Summary:
+ This test verifies that the Landscape Canvas nodes can be added to a graph, and correctly create entities.
+
+ Expected Behavior:
+ New entities are created when dragging Gradient Modifier nodes to graph area.
+
+ Test Steps:
+ 1) Create a new level
+ 2) Open Landscape Canvas and create a new graph
+ 3) Drag each of the Gradient Modifier nodes to the graph area, and ensure a new entity is created
+
+ 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
+ """
def onEntityCreated(parameters):
global newEntityId
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/GradientModifierNodes_EntityRemovedOnNodeDelete.py b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/GradientModifierNodes_EntityRemovedOnNodeDelete.py
index dc263924d1..6a82b05039 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/GradientModifierNodes_EntityRemovedOnNodeDelete.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/GradientModifierNodes_EntityRemovedOnNodeDelete.py
@@ -34,7 +34,26 @@ class TestGradientModifierNodeEntityDelete(EditorTestHelper):
EditorTestHelper.__init__(self, log_prefix="GradientModifierNodeEntityDelete", args=["level"])
def run_test(self):
-
+ """
+ Summary:
+ This test verifies that the Landscape Canvas node deletion properly cleans up entities in the Editor.
+
+ Expected Behavior:
+ Entities are removed when Gradient Modifier nodes are deleted from a graph.
+
+ Test Steps:
+ 1) Create a new level
+ 2) Open Landscape Canvas and create a new graph
+ 3) Drag each of the Gradient Modifier nodes to the graph area, and ensure a new entity is created
+ 4) Delete the nodes, and ensure the newly created entities are removed
+
+ 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
+ """
def onEntityCreated(parameters):
global createdEntityId
createdEntityId = parameters[0]
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/GradientNodes_DependentComponentsAdded.py b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/GradientNodes_DependentComponentsAdded.py
index 5e203e1892..f9360fe356 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/GradientNodes_DependentComponentsAdded.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/GradientNodes_DependentComponentsAdded.py
@@ -33,6 +33,27 @@ class TestGradientNodeComponentDependency(EditorTestHelper):
EditorTestHelper.__init__(self, log_prefix="GradientNodeComponentDependency", args=["level"])
def run_test(self):
+ """
+ Summary:
+ This test verifies that the Landscape Canvas nodes can be added to a graph, and correctly create entities with
+ proper dependent components.
+
+ Expected Behavior:
+ All expected component dependencies are met when adding a Gradient Modifier node to a graph.
+
+ Test Steps:
+ 1) Create a new level
+ 2) Open Landscape Canvas and create a new graph
+ 3) Drag each of the Gradient Modifier nodes to the graph area, and ensure the proper dependent components are
+ added
+
+ 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
+ """
def onEntityCreated(parameters):
global newEntityId
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/GradientNodes_EntityCreatedOnNodeAdd.py b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/GradientNodes_EntityCreatedOnNodeAdd.py
index 6d4a2f58a7..8aaad9b81d 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/GradientNodes_EntityCreatedOnNodeAdd.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/GradientNodes_EntityCreatedOnNodeAdd.py
@@ -32,6 +32,25 @@ class TestGradientNodeEntityCreate(EditorTestHelper):
EditorTestHelper.__init__(self, log_prefix="GradientNodeEntityCreate", args=["level"])
def run_test(self):
+ """
+ Summary:
+ This test verifies that the Landscape Canvas nodes can be added to a graph, and correctly create entities.
+
+ Expected Behavior:
+ New entities are created when dragging Gradient nodes to graph area.
+
+ Test Steps:
+ 1) Create a new level
+ 2) Open Landscape Canvas and create a new graph
+ 3) Drag each of the Gradient nodes to the graph area, and ensure a new entity is created
+
+ 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
+ """
def onEntityCreated(parameters):
global newEntityId
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/GradientNodes_EntityRemovedOnNodeDelete.py b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/GradientNodes_EntityRemovedOnNodeDelete.py
index 2b49e3a911..d74b86d0bf 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/GradientNodes_EntityRemovedOnNodeDelete.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/GradientNodes_EntityRemovedOnNodeDelete.py
@@ -34,6 +34,26 @@ class TestGradientNodeEntityDelete(EditorTestHelper):
EditorTestHelper.__init__(self, log_prefix="GradientNodeEntityDelete", args=["level"])
def run_test(self):
+ """
+ Summary:
+ This test verifies that the Landscape Canvas node deletion properly cleans up entities in the Editor.
+
+ Expected Behavior:
+ Entities are removed when Gradient nodes are deleted from a graph.
+
+ Test Steps:
+ 1) Create a new level
+ 2) Open Landscape Canvas and create a new graph
+ 3) Drag each of the Gradient nodes to the graph area, and ensure a new entity is created
+ 4) Delete the nodes, and ensure the newly created entities are removed
+
+ 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
+ """
def onEntityCreated(parameters):
global createdEntityId
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/GraphClosed_OnEntityDelete.py b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/GraphClosed_OnEntityDelete.py
index d3ad5c1c1e..6aa539b554 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/GraphClosed_OnEntityDelete.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/GraphClosed_OnEntityDelete.py
@@ -31,6 +31,26 @@ class TestGraphClosedOnEntityDelete(EditorTestHelper):
EditorTestHelper.__init__(self, log_prefix="GraphClosedOnEntityDelete", args=["level"])
def run_test(self):
+ """
+ Summary:
+ This test verifies that Landscape Canvas graphs are auto-closed when the corresponding entity is deleted.
+
+ Expected Behavior:
+ When a Landscape Canvas root entity is deleted, the corresponding graph automatically closes.
+
+ Test Steps:
+ 1) Create a new level
+ 2) Open Landscape Canvas and create a new graph
+ 3) Delete the automatically created entity
+ 4) Verify the open graph is closed
+
+ 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
+ """
def onEntityCreated(parameters):
global newRootEntityId
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/GraphClosed_OnLevelChange.py b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/GraphClosed_OnLevelChange.py
index b7b0008eb2..ebc75ab621 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/GraphClosed_OnLevelChange.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/GraphClosed_OnLevelChange.py
@@ -29,7 +29,26 @@ class TestGraphClosedOnLevelChange(EditorTestHelper):
EditorTestHelper.__init__(self, log_prefix="GraphClosedOnLevelChange", args=["level"])
def run_test(self):
-
+ """
+ Summary:
+ This test verifies that Landscape Canvas graphs are auto-closed when the currently open level changes.
+
+ Expected Behavior:
+ When a new level is loaded in the Editor, open Landscape Canvas graphs are automatically closed.
+
+ Test Steps:
+ 1) Create a new level
+ 2) Open Landscape Canvas and create a new graph
+ 3) Open a different level
+ 4) Verify the open graph is closed
+
+ 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
+ """
# Create a new empty level
self.test_success = self.create_level(
self.args["level"],
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/GraphClosed_TabbedGraph.py b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/GraphClosed_TabbedGraph.py
index efd1cc5a55..4b018aeb45 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/GraphClosed_TabbedGraph.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/GraphClosed_TabbedGraph.py
@@ -29,6 +29,26 @@ class TestGraphClosedTabbedGraph(EditorTestHelper):
EditorTestHelper.__init__(self, log_prefix="GraphClosedTabbedGraph", args=["level"])
def run_test(self):
+ """
+ Summary:
+ This test verifies that Landscape Canvas tabbed graphs can be independently closed.
+
+ Expected Behavior:
+ Closing a tabbed graph only closes the appropriate graph.
+
+ Test Steps:
+ 1) Create a new level
+ 2) Open Landscape Canvas and create several new graphs
+ 3) Close one of the open graphs
+ 4) Ensure the graph properly closed, and other open graphs remain open
+
+ 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
+ """
# Create a new empty level
self.test_success = self.create_level(
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/GraphUpdates_UpdateComponents.py b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/GraphUpdates_UpdateComponents.py
index f350d37178..f94a6c2e3a 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/GraphUpdates_UpdateComponents.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/GraphUpdates_UpdateComponents.py
@@ -9,21 +9,6 @@ remove or modify any license notices. This file is distributed on an "AS IS" BAS
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
"""
-
-"""
-C22715182 - Components are updated when nodes are added/removed/updated
-
-1. Open Level.
-2. Open the graph on LC_BushFlowerBlender.slice
-3. Find the Rotation Modifier node on the BushSpawner entity
-4. Delete the Rotation Modifier node
-5. Ensure the Vegetation Rotation Modifier component is removed from the BushSpawner entity
-6. Delete the Vegetation Layer Spawner node from the graph
-7. Ensure BushSpawner entity is deleted
-8. Change connection from second Rotation Modifier node to a different Gradient
-9. Ensure Gradient reference on component is updated
-"""
-
import os
import sys
@@ -50,6 +35,31 @@ class TestGraphUpdatesUpdateComponents(EditorTestHelper):
EditorTestHelper.__init__(self, log_prefix="GraphUpdatesUpdateComponents", args=["level"])
def run_test(self):
+ """
+ Summary:
+ This test verifies that components are properly updated as nodes are added/removed/updated.
+
+ Expected Behavior:
+ Landscape Canvas node CRUD properly updates component entities.
+
+ Test Steps:
+ 1. Open Level.
+ 2. Open the graph on LC_BushFlowerBlender.slice
+ 3. Find the Rotation Modifier node on the BushSpawner entity
+ 4. Delete the Rotation Modifier node
+ 5. Ensure the Vegetation Rotation Modifier component is removed from the BushSpawner entity
+ 6. Delete the Vegetation Layer Spawner node from the graph
+ 7. Ensure BushSpawner entity is deleted
+ 8. Change connection from second Rotation Modifier node to a different Gradient
+ 9. Ensure Gradient reference on component is updated
+
+ 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
+ """
# Create a new empty level and instantiate LC_BushFlowerBlender.slice
self.test_success = self.create_level(
self.args["level"],
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/LandscapeCanvasComponent_AddedRemoved.py b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/LandscapeCanvasComponent_AddedRemoved.py
index 176429885f..c3857e1393 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/LandscapeCanvasComponent_AddedRemoved.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/LandscapeCanvasComponent_AddedRemoved.py
@@ -30,6 +30,26 @@ class TestLandscapeCanvasComponentAddedRemoved(EditorTestHelper):
EditorTestHelper.__init__(self, log_prefix="LandscapeCanvasComponentAddedRemoved", args=["level"])
def run_test(self):
+ """
+ Summary:
+ This test verifies that the Landscape Canvas component can be added to/removed from an entity.
+
+ Expected Behavior:
+ Closing a tabbed graph only closes the appropriate graph.
+
+ Test Steps:
+ 1) Create a new level
+ 2) Create a new entity
+ 3) Add a Landscape Canvas component to the entity
+ 4) Remove the Landscape Canvas component from the entity
+
+ 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
+ """
# Create a new empty level
self.test_success = self.create_level(
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/LandscapeCanvas_SliceCreateInstantiate.py b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/LandscapeCanvas_SliceCreateInstantiate.py
index e0f13adaa9..f174a52610 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/LandscapeCanvas_SliceCreateInstantiate.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/LandscapeCanvas_SliceCreateInstantiate.py
@@ -30,12 +30,21 @@ class TestLandscapeCanvasSliceCreateInstantiate(EditorTestHelper):
def run_test(self):
"""
Summary:
- C22602016 A slice containing the LandscapeCanvas component can be created/instantiated.
+ A slice containing the LandscapeCanvas component can be created/instantiated.
Expected Result:
- Slice is created and processed successfully and free of errors/warnings.
- Another copy of the slice is instantiated.
-
+ Slice is created/processed/instantiated successfully and free of errors/warnings.
+
+ Test Steps:
+ 1) Create a new level
+ 2) Create a new entity with a Landscape Canvas component
+ 3) Create a slice of the new entity
+ 4) Instantiate a new copy of the slice
+
+ 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
"""
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/LayerBlender_NodeConstruction.py b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/LayerBlender_NodeConstruction.py
index ecc529b9b4..82a2abf5ea 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/LayerBlender_NodeConstruction.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/LayerBlender_NodeConstruction.py
@@ -34,6 +34,27 @@ class TestLayerBlenderNodeConstruction(EditorTestHelper):
EditorTestHelper.__init__(self, log_prefix="LayerBlenderNodeConstruction", args=["level"])
def run_test(self):
+ """
+ Summary:
+ This test verifies a Layer Blender vegetation setup can be constructed through Landscape Canvas.
+
+ Expected Behavior:
+ Entities contain all required components and component references after creating nodes and setting connections
+ on a Landscape Canvas graph.
+
+ Test Steps:
+ 1) Create a new level
+ 2) Open Landscape Canvas and create a new graph
+ 3) Add all necessary nodes to the graph and set connections to form a Layer Blender setup
+ 4) Verify all components and component references were properly set during graph construction
+
+ 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
+ """
def onEntityCreated(parameters):
global newEntityId
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/LayerExtenderNodes_ComponentEntitySync.py b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/LayerExtenderNodes_ComponentEntitySync.py
index 00fcb5170c..df3c549fff 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/LayerExtenderNodes_ComponentEntitySync.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/LayerExtenderNodes_ComponentEntitySync.py
@@ -34,6 +34,25 @@ class TestLayerExtenderNodeComponentEntitySync(EditorTestHelper):
EditorTestHelper.__init__(self, log_prefix="LayerExtenderNodeComponentEntitySync", args=["level"])
def run_test(self):
+ """
+ Summary:
+ This test verifies that all wrapped nodes can be successfully added to/removed from parent nodes.
+
+ Expected Behavior:
+ All wrapped extender nodes can be added to/removed from appropriate parent nodes.
+
+ Test Steps:
+ 1) Create a new level
+ 2) Open Landscape Canvas and create a new graph
+ 3) Add Area Blender and Layer Spawner nodes to the graph, and add/remove each extender node to/from each
+
+ 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
+ """
def onEntityCreated(parameters):
global newEntityId
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/ShapeNodes_EntityCreatedOnNodeAdd.py b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/ShapeNodes_EntityCreatedOnNodeAdd.py
index bd10e5f4c6..cd4915ea24 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/ShapeNodes_EntityCreatedOnNodeAdd.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/ShapeNodes_EntityCreatedOnNodeAdd.py
@@ -33,6 +33,25 @@ class TestShapeNodeEntityCreate(EditorTestHelper):
EditorTestHelper.__init__(self, log_prefix="ShapeNodeEntityCreate", args=["level"])
def run_test(self):
+ """
+ Summary:
+ This test verifies that the Landscape Canvas nodes can be added to a graph, and correctly create entities.
+
+ Expected Behavior:
+ New entities are created when dragging shape nodes to graph area.
+
+ Test Steps:
+ 1) Create a new level
+ 2) Open Landscape Canvas and create a new graph
+ 3) Drag each of the shape nodes to the graph area, and ensure a new entity is created
+
+ 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
+ """
def onEntityCreated(parameters):
global newEntityId
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/ShapeNodes_EntityRemovedOnNodeDelete.py b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/ShapeNodes_EntityRemovedOnNodeDelete.py
index f71f5ae906..fcfbe03576 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/ShapeNodes_EntityRemovedOnNodeDelete.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/ShapeNodes_EntityRemovedOnNodeDelete.py
@@ -34,7 +34,27 @@ class TestShapeNodeEntityDelete(EditorTestHelper):
EditorTestHelper.__init__(self, log_prefix="ShapeNodeEntityDelete", args=["level"])
def run_test(self):
-
+ """
+ Summary:
+ This test verifies that the Landscape Canvas node deletion properly cleans up entities in the Editor.
+
+ Expected Behavior:
+ Entities are removed when shape nodes are deleted from a graph.
+
+ Test Steps:
+ 1) Create a new level
+ 2) Open Landscape Canvas and create a new graph
+ 3) Drag each of the shape nodes to the graph area, and ensure a new entity is created
+ 4) Delete the nodes, and ensure the newly created entities are removed
+
+ 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
+ """
+
def onEntityCreated(parameters):
global createdEntityId
createdEntityId = parameters[0]
diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/SlotConnections_UpdateComponentReferences.py b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/SlotConnections_UpdateComponentReferences.py
index 968f39c64d..183c3f7ccb 100755
--- a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/SlotConnections_UpdateComponentReferences.py
+++ b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/SlotConnections_UpdateComponentReferences.py
@@ -33,6 +33,27 @@ class TestSlotConnectionsUpdateComponents(EditorTestHelper):
EditorTestHelper.__init__(self, log_prefix="SlotConnectionsUpdateComponents", args=["level"])
def run_test(self):
+ """
+ Summary:
+ This test verifies that the Landscape Canvas slot connections properly update component references.
+
+ Expected Behavior:
+ A reference created through slot connections in Landscape Canvas is reflected in the Entity Inspector.
+
+ Test Steps:
+ 1) Create a new level
+ 2) Open Landscape Canvas and create a new graph
+ 3) Several nodes are added to a graph, and connections are set between the nodes
+ 4) Component references are verified via Entity Inspector
+
+ 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
+ """
+
# Retrieve the proper component TypeIds per component name
componentNames = [
'Random Noise Gradient',
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/Levels/WaterSample/WaterSample.ly b/AutomatedTesting/Levels/WaterSample/WaterSample.ly
deleted file mode 100644
index b1899f3710..0000000000
--- a/AutomatedTesting/Levels/WaterSample/WaterSample.ly
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:d49aceca5ad4e0b9f46c8127afb5c53b68aa30272950b1abd66fba310977ff0c
-size 15032
diff --git a/AutomatedTesting/Levels/WaterSample/filelist.xml b/AutomatedTesting/Levels/WaterSample/filelist.xml
deleted file mode 100644
index d14b2fdaf2..0000000000
--- a/AutomatedTesting/Levels/WaterSample/filelist.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/AutomatedTesting/Levels/WaterSample/halfsphere.cgf b/AutomatedTesting/Levels/WaterSample/halfsphere.cgf
deleted file mode 100644
index 4426d8a232..0000000000
--- a/AutomatedTesting/Levels/WaterSample/halfsphere.cgf
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:5f221acd847ec8a15e1333a5163d6d0fd886b8eda46fa7b133f76ddbf1d11216
-size 41472
diff --git a/AutomatedTesting/Levels/WaterSample/halfsphere2.cgf b/AutomatedTesting/Levels/WaterSample/halfsphere2.cgf
deleted file mode 100644
index c776ff68b8..0000000000
--- a/AutomatedTesting/Levels/WaterSample/halfsphere2.cgf
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:c8e5dcfbe65fd2fd8ea29a38a96e703683c544fd42b9424857b1df3718c7775a
-size 41472
diff --git a/AutomatedTesting/Levels/WaterSample/level.pak b/AutomatedTesting/Levels/WaterSample/level.pak
deleted file mode 100644
index 1753ef4b93..0000000000
--- a/AutomatedTesting/Levels/WaterSample/level.pak
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:0378911c27933302042550d5a031a5f9104296162edc2b21e44893f1b8cff969
-size 44124
diff --git a/AutomatedTesting/Levels/WaterSample/leveldata/Environment.xml b/AutomatedTesting/Levels/WaterSample/leveldata/Environment.xml
deleted file mode 100644
index 6a95c631bb..0000000000
--- a/AutomatedTesting/Levels/WaterSample/leveldata/Environment.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/AutomatedTesting/Levels/WaterSample/leveldata/TerrainTexture.xml b/AutomatedTesting/Levels/WaterSample/leveldata/TerrainTexture.xml
deleted file mode 100644
index 21741afe52..0000000000
--- a/AutomatedTesting/Levels/WaterSample/leveldata/TerrainTexture.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
diff --git a/AutomatedTesting/Levels/WaterSample/leveldata/TimeOfDay.xml b/AutomatedTesting/Levels/WaterSample/leveldata/TimeOfDay.xml
deleted file mode 100644
index 60ad405904..0000000000
--- a/AutomatedTesting/Levels/WaterSample/leveldata/TimeOfDay.xml
+++ /dev/null
@@ -1,356 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/AutomatedTesting/Levels/WaterSample/leveldata/VegetationMap.dat b/AutomatedTesting/Levels/WaterSample/leveldata/VegetationMap.dat
deleted file mode 100644
index dce5631cd0..0000000000
--- a/AutomatedTesting/Levels/WaterSample/leveldata/VegetationMap.dat
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:0e6a5435c928079b27796f6b202bbc2623e7e454244ddc099a3cadf33b7cb9e9
-size 63
diff --git a/AutomatedTesting/Levels/WaterSample/pool.cgf b/AutomatedTesting/Levels/WaterSample/pool.cgf
deleted file mode 100644
index 04bec52a62..0000000000
--- a/AutomatedTesting/Levels/WaterSample/pool.cgf
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:12ca8f1942331abde4d58724aea22609c8d7951cc415afa6e5f1c550a14e67b0
-size 363624
diff --git a/AutomatedTesting/Levels/WaterSample/pool2.cgf b/AutomatedTesting/Levels/WaterSample/pool2.cgf
deleted file mode 100644
index 204306f8a8..0000000000
--- a/AutomatedTesting/Levels/WaterSample/pool2.cgf
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:f5b525a410730d84c0b3e97396d392e1e72f4b894742ddef3de4ede5542b0f8e
-size 86148
diff --git a/AutomatedTesting/Levels/WaterSample/tags.txt b/AutomatedTesting/Levels/WaterSample/tags.txt
deleted file mode 100644
index 0d6c1880e7..0000000000
--- a/AutomatedTesting/Levels/WaterSample/tags.txt
+++ /dev/null
@@ -1,12 +0,0 @@
-0,0,0,0,0,0
-0,0,0,0,0,0
-0,0,0,0,0,0
-0,0,0,0,0,0
-0,0,0,0,0,0
-0,0,0,0,0,0
-0,0,0,0,0,0
-0,0,0,0,0,0
-0,0,0,0,0,0
-0,0,0,0,0,0
-0,0,0,0,0,0
-0,0,0,0,0,0
diff --git a/AutomatedTesting/Levels/WaterSample/terraintexture.pak b/AutomatedTesting/Levels/WaterSample/terraintexture.pak
deleted file mode 100644
index fe3604a050..0000000000
--- a/AutomatedTesting/Levels/WaterSample/terraintexture.pak
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:8739c76e681f900923b900c9df0ef75cf421d39cabb54650c4b9ad19b6a76d85
-size 22
diff --git a/AutomatedTesting/Levels/WaterSample/woodland_canyon_river.mtl b/AutomatedTesting/Levels/WaterSample/woodland_canyon_river.mtl
deleted file mode 100644
index 4548bca421..0000000000
--- a/AutomatedTesting/Levels/WaterSample/woodland_canyon_river.mtl
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
diff --git a/AutomatedTesting/Objects/LumberTank/ProxyGray_ddna.tif.exportsettings b/AutomatedTesting/Objects/LumberTank/ProxyGray_ddna.tif.exportsettings
index 013c774e9e..a4e1a9a3c5 100644
--- a/AutomatedTesting/Objects/LumberTank/ProxyGray_ddna.tif.exportsettings
+++ b/AutomatedTesting/Objects/LumberTank/ProxyGray_ddna.tif.exportsettings
@@ -1 +1 @@
-/autooptimizefile=0 /M=50,50,0,50,50,50 /preset=NormalsWithSmoothness /reduce="es3:1,ios:1,osx_gl:0,pc:0,provo:0"
\ No newline at end of file
+/autooptimizefile=0 /M=50,50,0,50,50,50 /preset=NormalsWithSmoothness /reduce="android:1,ios:1,mac:0,pc:0,provo:0"
\ No newline at end of file
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 2191a0ebc2..c6928d31fc 100644
--- a/AutomatedTesting/preview.png
+++ b/AutomatedTesting/preview.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:a18fae4040a22d2bb359a8ca642b97bb8f6468eeb52e2826b3b029bd8f1350b6
-size 5466
+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 63177e9d60..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,88 +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)
-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/)
+ # 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()
################################################################################
# Post-processing
################################################################################
+# 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()
-# 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()
+# 1. Add any dependencies registered via ly_enable_gems
+ly_enable_gems_delayed()
-# The following steps have to be done after all targets are registered:
-# 1. generate a settings registry .setreg file for all ly_add_project_dependencies() and ly_add_target_dependencies() calls
+# 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
+
+# 5. 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_delayed_generate_runtime_dependencies()
+
+# 6. 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
+
+# 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()
-
- # IMPORTANT: must be included last
+ # 9. CPack information (to be included after install)
include(cmake/Packaging.cmake)
endif()
diff --git a/Code/CryEngine/CryCommon/ISystem.h b/Code/CryEngine/CryCommon/ISystem.h
index f863804f3d..653776f55b 100644
--- a/Code/CryEngine/CryCommon/ISystem.h
+++ b/Code/CryEngine/CryCommon/ISystem.h
@@ -125,7 +125,7 @@ enum ESystemConfigPlatform
{
CONFIG_INVALID_PLATFORM = 0,
CONFIG_PC = 1,
- CONFIG_OSX_GL = 2,
+ CONFIG_MAC = 2,
CONFIG_OSX_METAL = 3,
CONFIG_ANDROID = 4,
CONFIG_IOS = 5,
diff --git a/Code/CryEngine/CryCommon/LyShine/IDraw2d.h b/Code/CryEngine/CryCommon/LyShine/IDraw2d.h
index 16fdfceca3..76a71c9e24 100644
--- a/Code/CryEngine/CryCommon/LyShine/IDraw2d.h
+++ b/Code/CryEngine/CryCommon/LyShine/IDraw2d.h
@@ -11,7 +11,6 @@
*/
#pragma once
-#include
#include
#include
#include
@@ -84,7 +83,7 @@ public: // types
//! If this is not passed then the defaults below are used
struct TextOptions
{
- IFFont* font; //!< default is "default"
+ AZStd::string fontName; //!< default is "default"
unsigned int effectIndex; //!< default is 0
AZ::Vector3 color; //!< default is (1,1,1)
HAlign horizontalAlignment; //!< default is HAlign::Left
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/System.h b/Code/CryEngine/CrySystem/System.h
index b91b1ba059..a258030f70 100644
--- a/Code/CryEngine/CrySystem/System.h
+++ b/Code/CryEngine/CrySystem/System.h
@@ -729,7 +729,7 @@ protected: // -------------------------------------------------------------
CCmdLine* m_pCmdLine;
string m_currentLanguageAudio;
- string m_systemConfigName; // computed from system_(hardwareplatform)_(assetsPlatform) - eg, system_android_es3.cfg or system_android_opengl.cfg or system_windows_pc.cfg
+ string m_systemConfigName; // computed from system_(hardwareplatform)_(assetsPlatform) - eg, system_android_android.cfg or system_windows_pc.cfg
std::vector< std::pair > m_updateTimes;
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/AzAndroid/java/com/amazon/lumberyard/LumberyardActivity.java b/Code/Framework/AzAndroid/java/com/amazon/lumberyard/LumberyardActivity.java
index b5d3de8164..5c1a120df6 100644
--- a/Code/Framework/AzAndroid/java/com/amazon/lumberyard/LumberyardActivity.java
+++ b/Code/Framework/AzAndroid/java/com/amazon/lumberyard/LumberyardActivity.java
@@ -244,7 +244,7 @@ public class LumberyardActivity extends NativeActivity
boolean useMainObb = GetBooleanResource("use_main_obb");
boolean usePatchObb = GetBooleanResource("use_patch_obb");
- if (IsBootstrapInAPK() && (useMainObb || usePatchObb))
+ if (AreAssetsInAPK() && (useMainObb || usePatchObb))
{
Log.d(TAG, "Using OBB expansion files for game assets");
@@ -421,12 +421,12 @@ public class LumberyardActivity extends NativeActivity
}
////////////////////////////////////////////////////////////////
- private boolean IsBootstrapInAPK()
+ private boolean AreAssetsInAPK()
{
try
{
- InputStream bootstrap = getAssets().open("bootstrap.cfg", AssetManager.ACCESS_UNKNOWN);
- bootstrap.close();
+ InputStream engine = getAssets().open("engine.json", AssetManager.ACCESS_UNKNOWN);
+ engine.close();
return true;
}
catch (IOException exception)
diff --git a/Code/Framework/AzCore/AzCore/Android/Utils.cpp b/Code/Framework/AzCore/AzCore/Android/Utils.cpp
index efbbf50d1d..d6435c67be 100644
--- a/Code/Framework/AzCore/AzCore/Android/Utils.cpp
+++ b/Code/Framework/AzCore/AzCore/Android/Utils.cpp
@@ -148,7 +148,7 @@ namespace AZ
}
}
- AZ_Assert(false, "Failed to locate the bootstrap.cfg path");
+ AZ_Assert(false, "Failed to locate the engine.json path");
return nullptr;
}
diff --git a/Code/Framework/AzCore/AzCore/Android/Utils.h b/Code/Framework/AzCore/AzCore/Android/Utils.h
index 222fac80ad..0862d53aa4 100644
--- a/Code/Framework/AzCore/AzCore/Android/Utils.h
+++ b/Code/Framework/AzCore/AzCore/Android/Utils.h
@@ -73,8 +73,8 @@ namespace AZ
//! \return The pointer position of the relative asset path
AZ::IO::FixedMaxPath StripApkPrefix(const char* filePath);
- //! Searches application storage and the APK for bootstrap.cfg. Will return nullptr
- //! if bootstrap.cfg is not found.
+ //! Searches application storage and the APK for engine.json. Will return nullptr
+ //! if engine.json is not found.
const char* FindAssetsDirectory();
//! Calls into Java to show the splash screen on the main UI (Java) thread
diff --git a/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp b/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp
index 8a170f5d89..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
{
@@ -462,8 +483,6 @@ namespace AZ
// for the application root.
CalculateAppRoot();
- // Merge the bootstrap.cfg file into the Settings Registry as soon as the OSAllocator has been created.
- SettingsRegistryMergeUtils::MergeSettingsToRegistry_Bootstrap(*m_settingsRegistry);
SettingsRegistryMergeUtils::MergeSettingsToRegistry_O3deUserRegistry(*m_settingsRegistry, AZ_TRAIT_OS_PLATFORM_CODENAME, {});
SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(*m_settingsRegistry, m_commandLine, executeRegDumpCommands);
SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*m_settingsRegistry);
@@ -1262,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)
{
@@ -1332,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 be18593d54..2a8d82c34c 100644
--- a/Code/Framework/AzCore/AzCore/Component/TransformBus.h
+++ b/Code/Framework/AzCore/AzCore/Component/TransformBus.h
@@ -172,78 +172,10 @@ namespace AZ
//! Rotation modifiers
//! @{
- //! @deprecated Use SetLocalRotation()
- //! Sets the entity's rotation in the world.
- //! The origin of the axes is the entity's position in world space.
- //! @param eulerAnglesRadians A three-dimensional vector, containing Euler angles in radians, to rotate the entity by.
- virtual void SetRotation([[maybe_unused]] const AZ::Vector3& eulerAnglesRadians) {}
-
- //! @deprecated Use SetLocalRotation()
- //! Sets the entity's rotation around the world's X axis.
- //! The origin of the axis is the entity's position in world space.
- //! @param eulerAngleRadians The X coordinate Euler angle in radians to use for the entity's rotation.
- virtual void SetRotationX([[maybe_unused]] float eulerAngleRadian) {}
-
- //! @deprecated Use SetLocalRotation()
- //! Sets the entity's rotation around the world's Y axis.
- //! The origin of the axis is the entity's position in world space.
- //! @param eulerAngleRadians The Y coordinate Euler angle in radians to use for the entity's rotation.
- virtual void SetRotationY([[maybe_unused]] float eulerAngleRadian) {}
-
- //! @deprecated Use SetLocalRotation()
- //! Sets the entity's rotation around the world's Z axis.
- //! The origin of the axis is the entity's position in world space.
- //! @param eulerAngleRadians The Z coordinate Euler angle in radians to use for the entity's rotation.
- virtual void SetRotationZ([[maybe_unused]] float eulerAngleRadian) {}
-
- //! @deprecated Use SetLocalRotationQuaternion()
//! Sets the entity's rotation in the world in quaternion notation.
//! The origin of the axes is the entity's position in world space.
//! @param quaternion A quaternion that represents the rotation to use for the entity.
- virtual void SetRotationQuaternion([[maybe_unused]] const AZ::Quaternion& quaternion) {}
-
- //! @deprecated Use RotateAroundLocalX()
- //! Rotates the entity around the world's X axis.
- //! The origin of the axis is the entity's position in world space.
- //! @param eulerAngleRadians The Euler angle in radians by which to rotate the entity around the X axis.
- virtual void RotateByX([[maybe_unused]] float eulerAngleRadian) {}
-
- //! @deprecated Use RotateAroundLocalY()
- //! Rotates the entity around the world's Y axis.
- //! The origin of the axis is the entity's position in world space.
- //! @param eulerAngleRadians The Euler angle in radians by which to rotate the entity around the Y axis.
- virtual void RotateByY([[maybe_unused]] float eulerAngleRadian) {}
-
- //! @deprecated Use RotateAroundLocalZ()
- //! Rotates the entity around the world's Z axis.
- //! The origin of the axis is the entity's position in world space.
- //! @param eulerAngleRadians The Euler angle in radians by which to rotate the entity around the Z axis.
- virtual void RotateByZ([[maybe_unused]] float eulerAngleRadian) {}
-
- //! @deprecated Use GetLocalRotation()
- //! Gets the entity's rotation in the world in Euler angles rotation in radians.
- //! @return A three-dimensional vector, containing Euler angles in radians, that represents the entity's rotation.
- virtual AZ::Vector3 GetRotationEulerRadians() { return AZ::Vector3(FLT_MAX); }
-
- //! @deprecated Use GetLocalRotationQuaternion()
- //! Gets the entity's rotation in the world in quaternion format.
- //! @return A quaternion that represents the entity's rotation in world space.
- virtual AZ::Quaternion GetRotationQuaternion() { return AZ::Quaternion::CreateZero(); }
-
- //! @deprecated Use GetLocalRotation()
- //! Gets the entity's rotation around the world's X axis.
- //! @return The Euler angle in radians by which the the entity is rotated around the X axis in world space.
- virtual float GetRotationX() { return FLT_MAX; }
-
- //! @deprecated Use GetLocalRotation()
- //! Gets the entity's rotation around the world's Y axis.
- //! @return The Euler angle in radians by which the the entity is rotated around the Y axis in world space.
- virtual float GetRotationY() { return FLT_MAX; }
-
- //! @deprecated Use GetLocalRotation()
- //! Gets the entity's rotation around the world's Z axis.
- //! @return The Euler angle in radians by which the the entity is rotated around the Z axis in world space.
- virtual float GetRotationZ() { return FLT_MAX; }
+ virtual void SetWorldRotationQuaternion([[maybe_unused]] const AZ::Quaternion& quaternion) {}
//! Get angles in radian for each principle axis around which the world transform is
//! rotated in the order of z-axis and y-axis and then x-axis.
@@ -287,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/Random.h b/Code/Framework/AzCore/AzCore/Math/Random.h
index 8b28f6aaad..8b2763df50 100644
--- a/Code/Framework/AzCore/AzCore/Math/Random.h
+++ b/Code/Framework/AzCore/AzCore/Math/Random.h
@@ -126,17 +126,16 @@ namespace AZ
m_offsets.fill(1); // Halton sequences start at index 1.
m_increments.fill(1); // By default increment by 1 between each number.
}
-
- //! Returns a Halton sequence in an array of N length
- template
- AZStd::array, N> GetHaltonSequence()
+
+ //! Fills a provided container from begin to end with a Halton sequence.
+ //! Entries are expected to be, or implicitly converted to, AZStd::array.
+ template
+ void FillHaltonSequence(Iterator begin, Iterator end)
{
- AZStd::array, N> result;
-
AZStd::array indices = m_offsets;
// Generator that returns the Halton number for all bases for a single entry.
- auto f = [&] ()
+ auto f = [&]()
{
AZStd::array item;
for (auto d = 0; d < Dimensions; ++d)
@@ -147,12 +146,20 @@ namespace AZ
return item;
};
- AZStd::generate(result.begin(), result.end(), f);
+ AZStd::generate(begin, end, f);
+ }
+
+ //! Returns a Halton sequence in an array of N length.
+ template
+ AZStd::array, N> GetHaltonSequence()
+ {
+ AZStd::array, N> result;
+ FillHaltonSequence(result.begin(), result.end());
return result;
}
//! Sets the offsets per dimension to start generating a sequence from.
- //! By default, there is no offset (offset of 0 corresponds to starting at index 1)
+ //! By default, there is no offset (offset of 0 corresponds to starting at index 1).
void SetOffsets(AZStd::array offsets)
{
m_offsets = offsets;
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/PlatformId/PlatformDefaults.cpp b/Code/Framework/AzCore/AzCore/PlatformId/PlatformDefaults.cpp
index 63aad1ecf4..e31c3b0a1e 100644
--- a/Code/Framework/AzCore/AzCore/PlatformId/PlatformDefaults.cpp
+++ b/Code/Framework/AzCore/AzCore/PlatformId/PlatformDefaults.cpp
@@ -19,7 +19,7 @@ namespace AZ
{
inline namespace PlatformDefaults
{
- static const char* PlatformNames[PlatformId::NumPlatformIds] = { PlatformPC, PlatformES3, PlatformIOS, PlatformOSX, PlatformProvo, PlatformSalem, PlatformJasper, PlatformServer, PlatformAll, PlatformAllClient };
+ static const char* PlatformNames[PlatformId::NumPlatformIds] = { PlatformPC, PlatformAndroid, PlatformIOS, PlatformMac, PlatformProvo, PlatformSalem, PlatformJasper, PlatformServer, PlatformAll, PlatformAllClient };
const char* PlatformIdToPalFolder(AZ::PlatformId platform)
{
@@ -31,11 +31,11 @@ namespace AZ
{
case AZ::PC:
return "PC";
- case AZ::ES3:
+ case AZ::ANDROID_ID:
return "Android";
case AZ::IOS:
return "iOS";
- case AZ::OSX:
+ case AZ::MAC_ID:
return "Mac";
case AZ::PROVO:
return "Provo";
@@ -66,11 +66,11 @@ namespace AZ
}
else if (osPlatform == PlatformCodeNameMac)
{
- return PlatformOSX;
+ return PlatformMac;
}
else if (osPlatform == PlatformCodeNameAndroid)
{
- return PlatformES3;
+ return PlatformAndroid;
}
else if (osPlatform == PlatformCodeNameiOS)
{
@@ -207,13 +207,13 @@ namespace AZ
platformCodes.emplace_back(PlatformCodeNameWindows);
platformCodes.emplace_back(PlatformCodeNameLinux);
break;
- case PlatformId::ES3:
+ case PlatformId::ANDROID_ID:
platformCodes.emplace_back(PlatformCodeNameAndroid);
break;
case PlatformId::IOS:
platformCodes.emplace_back(PlatformCodeNameiOS);
break;
- case PlatformId::OSX:
+ case PlatformId::MAC_ID:
platformCodes.emplace_back(PlatformCodeNameMac);
break;
case PlatformId::PROVO:
diff --git a/Code/Framework/AzCore/AzCore/PlatformId/PlatformDefaults.h b/Code/Framework/AzCore/AzCore/PlatformId/PlatformDefaults.h
index 2d67c860cd..ba8c55f5f5 100644
--- a/Code/Framework/AzCore/AzCore/PlatformId/PlatformDefaults.h
+++ b/Code/Framework/AzCore/AzCore/PlatformId/PlatformDefaults.h
@@ -27,9 +27,9 @@ namespace AZ
inline namespace PlatformDefaults
{
constexpr char PlatformPC[] = "pc";
- constexpr char PlatformES3[] = "es3";
+ constexpr char PlatformAndroid[] = "android";
constexpr char PlatformIOS[] = "ios";
- constexpr char PlatformOSX[] = "osx_gl";
+ constexpr char PlatformMac[] = "mac";
constexpr char PlatformProvo[] = "provo";
constexpr char PlatformSalem[] = "salem";
constexpr char PlatformJasper[] = "jasper";
@@ -54,9 +54,9 @@ namespace AZ
AZ_ENUM_WITH_UNDERLYING_TYPE(PlatformId, int,
(Invalid, -1),
PC,
- ES3,
+ ANDROID_ID,
IOS,
- OSX,
+ MAC_ID,
PROVO,
SALEM,
JASPER,
@@ -73,9 +73,9 @@ namespace AZ
{
Platform_NONE = 0x00,
Platform_PC = 1 << PlatformId::PC,
- Platform_ES3 = 1 << PlatformId::ES3,
+ Platform_ANDROID = 1 << PlatformId::ANDROID_ID,
Platform_IOS = 1 << PlatformId::IOS,
- Platform_OSX = 1 << PlatformId::OSX,
+ Platform_MAC = 1 << PlatformId::MAC_ID,
Platform_PROVO = 1 << PlatformId::PROVO,
Platform_SALEM = 1 << PlatformId::SALEM,
Platform_JASPER = 1 << PlatformId::JASPER,
@@ -87,7 +87,7 @@ namespace AZ
// A special platform that will always correspond to all non-server platforms, even if new ones are added
Platform_ALL_CLIENT = 1ULL << 31,
- AllNamedPlatforms = Platform_PC | Platform_ES3 | Platform_IOS | Platform_OSX | Platform_PROVO | Platform_SALEM | Platform_JASPER | Platform_SERVER,
+ AllNamedPlatforms = Platform_PC | Platform_ANDROID | Platform_IOS | Platform_MAC | Platform_PROVO | Platform_SALEM | Platform_JASPER | Platform_SERVER,
};
AZ_DEFINE_ENUM_BITWISE_OPERATORS(PlatformFlags);
diff --git a/Code/Framework/AzCore/AzCore/PlatformId/PlatformId.cpp b/Code/Framework/AzCore/AzCore/PlatformId/PlatformId.cpp
index 0258869a0c..d56140be28 100644
--- a/Code/Framework/AzCore/AzCore/PlatformId/PlatformId.cpp
+++ b/Code/Framework/AzCore/AzCore/PlatformId/PlatformId.cpp
@@ -28,8 +28,8 @@ namespace AZ
return "Android64";
case PlatformID::PLATFORM_APPLE_IOS:
return "iOS";
- case PlatformID::PLATFORM_APPLE_OSX:
- return "OSX";
+ case PlatformID::PLATFORM_APPLE_MAC:
+ return "Mac";
#if defined(AZ_EXPAND_FOR_RESTRICTED_PLATFORM) || defined(AZ_TOOLS_EXPAND_FOR_RESTRICTED_PLATFORMS)
#define AZ_RESTRICTED_PLATFORM_EXPANSION(CodeName, CODENAME, codename, PrivateName, PRIVATENAME, privatename, PublicName, PUBLICNAME, publicname, PublicAuxName1, PublicAuxName2, PublicAuxName3)\
case PlatformID::PLATFORM_##PUBLICNAME:\
diff --git a/Code/Framework/AzCore/AzCore/PlatformId/PlatformId.h b/Code/Framework/AzCore/AzCore/PlatformId/PlatformId.h
index ce1a11d8ce..e8e7cef6dd 100644
--- a/Code/Framework/AzCore/AzCore/PlatformId/PlatformId.h
+++ b/Code/Framework/AzCore/AzCore/PlatformId/PlatformId.h
@@ -23,7 +23,7 @@ namespace AZ
PLATFORM_WINDOWS_64,
PLATFORM_LINUX_64,
PLATFORM_APPLE_IOS,
- PLATFORM_APPLE_OSX,
+ PLATFORM_APPLE_MAC,
PLATFORM_ANDROID_64, // ARMv8 / 64-bit
#if defined(AZ_EXPAND_FOR_RESTRICTED_PLATFORM) || defined(AZ_TOOLS_EXPAND_FOR_RESTRICTED_PLATFORMS)
#define AZ_RESTRICTED_PLATFORM_EXPANSION(CodeName, CODENAME, codename, PrivateName, PRIVATENAME, privatename, PublicName, PUBLICNAME, publicname, PublicAuxName1, PublicAuxName2, PublicAuxName3)\
diff --git a/Code/Framework/AzCore/AzCore/Script/ScriptSystemComponent.cpp b/Code/Framework/AzCore/AzCore/Script/ScriptSystemComponent.cpp
index 015554538f..11d4db2e07 100644
--- a/Code/Framework/AzCore/AzCore/Script/ScriptSystemComponent.cpp
+++ b/Code/Framework/AzCore/AzCore/Script/ScriptSystemComponent.cpp
@@ -937,7 +937,7 @@ void ScriptSystemComponent::Reflect(ReflectContext* reflection)
->Enum(PlatformID::PLATFORM_LINUX_64)>("Linux")
->Enum(PlatformID::PLATFORM_ANDROID_64)>("Android64")
->Enum(PlatformID::PLATFORM_APPLE_IOS)>("iOS")
- ->Enum(PlatformID::PLATFORM_APPLE_OSX)>("OSX")
+ ->Enum(PlatformID::PLATFORM_APPLE_MAC)>("Mac")
#if defined(AZ_EXPAND_FOR_RESTRICTED_PLATFORM) || defined(AZ_TOOLS_EXPAND_FOR_RESTRICTED_PLATFORMS)
#define AZ_RESTRICTED_PLATFORM_EXPANSION(CodeName, CODENAME, codename, PrivateName, PRIVATENAME, privatename, PublicName, PUBLICNAME, publicname, PublicAuxName1, PublicAuxName2, PublicAuxName3)\
->Enum(PlatformID::PLATFORM_##PUBLICNAME)>(#CodeName)
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 82bf1db484..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{};
};
@@ -494,13 +523,6 @@ namespace AZ::SettingsRegistryMergeUtils
return configFileParsed;
}
- void MergeSettingsToRegistry_Bootstrap(SettingsRegistryInterface& registry)
- {
- ConfigParserSettings parserSettings;
- parserSettings.m_registryRootPointerPath = BootstrapSettingsRootKey;
- MergeSettingsToRegistry_ConfigFile(registry, "bootstrap.cfg", parserSettings);
- }
-
void MergeSettingsToRegistry_AddRuntimeFilePaths(SettingsRegistryInterface& registry)
{
using FixedValueString = AZ::SettingsRegistryInterface::FixedValueString;
diff --git a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.h b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.h
index 576066c29f..b482530d24 100644
--- a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.h
+++ b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.h
@@ -172,9 +172,6 @@ namespace AZ::SettingsRegistryMergeUtils
bool MergeSettingsToRegistry_ConfigFile(SettingsRegistryInterface& registry, AZStd::string_view filePath,
const ConfigParserSettings& configParserSettings);
- //! Loads bootstrap.cfg into the Settings Registry. This file does not support specializations.
- void MergeSettingsToRegistry_Bootstrap(SettingsRegistryInterface& registry);
-
//! Extracts file path information from the environment and bootstrap to calculate the various file paths and adds those
//! to the Settings Registry under the FilePathsRootKey.
void MergeSettingsToRegistry_AddRuntimeFilePaths(SettingsRegistryInterface& registry);
diff --git a/Code/Framework/AzCore/Platform/Mac/AzCore/PlatformId/PlatformId_Mac.h b/Code/Framework/AzCore/Platform/Mac/AzCore/PlatformId/PlatformId_Mac.h
index d361e79f05..42dcd3e2f7 100644
--- a/Code/Framework/AzCore/Platform/Mac/AzCore/PlatformId/PlatformId_Mac.h
+++ b/Code/Framework/AzCore/Platform/Mac/AzCore/PlatformId/PlatformId_Mac.h
@@ -13,5 +13,5 @@
namespace AZ
{
- static const PlatformID g_currentPlatform = PlatformID::PLATFORM_APPLE_OSX;
+ static const PlatformID g_currentPlatform = PlatformID::PLATFORM_APPLE_MAC;
}
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/Math/RandomTests.cpp b/Code/Framework/AzCore/Tests/Math/RandomTests.cpp
index ace7d99704..7fe3acff54 100644
--- a/Code/Framework/AzCore/Tests/Math/RandomTests.cpp
+++ b/Code/Framework/AzCore/Tests/Math/RandomTests.cpp
@@ -24,7 +24,7 @@ namespace UnitTest
EXPECT_FLOAT_EQ(5981.0f / 15625.0f, GetHaltonNumber(4321, 5));
}
- TEST(MATH_Random, HaltonSequence)
+ TEST(MATH_Random, HaltonSequenceStandard)
{
HaltonSequence<3> sequence({ 2, 3, 5 });
auto regularSequence = sequence.GetHaltonSequence<5>();
@@ -48,7 +48,11 @@ namespace UnitTest
EXPECT_FLOAT_EQ(5.0f / 8.0f, regularSequence[4][0]);
EXPECT_FLOAT_EQ(7.0f / 9.0f, regularSequence[4][1]);
EXPECT_FLOAT_EQ(1.0f / 25.0f, regularSequence[4][2]);
-
+ }
+
+ TEST(MATH_Random, HaltonSequenceOffsets)
+ {
+ HaltonSequence<3> sequence({ 2, 3, 5 });
sequence.SetOffsets({ 1, 2, 3 });
auto offsetSequence = sequence.GetHaltonSequence<2>();
@@ -59,10 +63,15 @@ namespace UnitTest
EXPECT_FLOAT_EQ(3.0f / 4.0f, offsetSequence[1][0]);
EXPECT_FLOAT_EQ(4.0f / 9.0f, offsetSequence[1][1]);
EXPECT_FLOAT_EQ(1.0f / 25.0f, offsetSequence[1][2]);
-
+ }
+
+ TEST(MATH_Random, HaltonSequenceIncrements)
+ {
+ HaltonSequence<3> sequence({ 2, 3, 5 });
+ sequence.SetOffsets({ 1, 2, 3 });
sequence.SetIncrements({ 1, 2, 3 });
auto incrementedSequence = sequence.GetHaltonSequence<2>();
-
+
EXPECT_FLOAT_EQ(1.0f / 4.0f, incrementedSequence[0][0]);
EXPECT_FLOAT_EQ(1.0f / 9.0f, incrementedSequence[0][1]);
EXPECT_FLOAT_EQ(4.0f / 5.0f, incrementedSequence[0][2]);
@@ -71,4 +80,35 @@ namespace UnitTest
EXPECT_FLOAT_EQ(7.0f / 9.0f, incrementedSequence[1][1]);
EXPECT_FLOAT_EQ(11.0f / 25.0f, incrementedSequence[1][2]);
}
+
+ TEST(MATH_Random, FillHaltonSequence)
+ {
+ HaltonSequence<3> sequence({ 2, 3, 5 });
+ auto regularSequence = sequence.GetHaltonSequence<5>();
+
+ struct Point
+ {
+ Point() = default;
+ Point(AZStd::array arr)
+ :x(arr[0])
+ ,y(arr[1])
+ ,z(arr[2])
+ {}
+
+ float x = 0.0f;
+ float y = 0.0f;
+ float z = 0.0f;
+ };
+
+ AZStd::array ownedContainer;
+ sequence.FillHaltonSequence(ownedContainer.begin(), ownedContainer.end());
+
+ for (size_t i = 0; i < regularSequence.size(); ++i)
+ {
+ EXPECT_FLOAT_EQ(regularSequence[i][0], ownedContainer[i].x);
+ EXPECT_FLOAT_EQ(regularSequence[i][1], ownedContainer[i].y);
+ EXPECT_FLOAT_EQ(regularSequence[i][2], ownedContainer[i].z);
+ }
+ }
+
}
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/AzCore/Tests/SettingsRegistryMergeUtilsTests.cpp b/Code/Framework/AzCore/Tests/SettingsRegistryMergeUtilsTests.cpp
index 36b9757ce3..751d9ded6c 100644
--- a/Code/Framework/AzCore/Tests/SettingsRegistryMergeUtilsTests.cpp
+++ b/Code/Framework/AzCore/Tests/SettingsRegistryMergeUtilsTests.cpp
@@ -372,15 +372,15 @@ mac_remote_filesystem=0
-- We need to know this before we establish VFS because different platform assets
-- are stored in different root folders in the cache. These correspond to the names
-- In the asset processor config file. This value also controls what config file is read
--- when you read system_xxxx_xxxx.cfg (for example, system_windows_pc.cfg or system_android_es3.cfg)
+-- when you read system_xxxx_xxxx.cfg (for example, system_windows_pc.cfg or system_android_android.cfg)
-- by default, pc assets (in the 'pc' folder) are used, with RC being fed 'pc' as the platform
-- by default on console we use the default assets=pc for better iteration times
-- we should turn on console specific assets only when in release and/or testing assets and/or loading performance
-- that way most people will not need to have 3 different caches taking up disk space
assets = pc
-android_assets = es3
+android_assets = android
ios_assets = ios
-mac_assets = osx_gl
+mac_assets = mac
-- Add the IP address of your console to the white list that will connect to the asset processor here
-- You can list addresses or CIDR's. CIDR's are helpful if you are using DHCP. A CIDR looks like an ip address with
@@ -438,9 +438,9 @@ mac_wait_for_connect=0
ConfigFileParams::SettingsKeyValuePair{"/ios_remote_filesystem", AZ::s64{0}},
ConfigFileParams::SettingsKeyValuePair{"/mac_remote_filesystem", AZ::s64{0}},
ConfigFileParams::SettingsKeyValuePair{"/assets", AZStd::string_view{"pc"}},
- ConfigFileParams::SettingsKeyValuePair{"/android_assets", AZStd::string_view{"es3"}},
+ ConfigFileParams::SettingsKeyValuePair{"/android_assets", AZStd::string_view{"android"}},
ConfigFileParams::SettingsKeyValuePair{"/ios_assets", AZStd::string_view{"ios"}},
- ConfigFileParams::SettingsKeyValuePair{"/mac_assets", AZStd::string_view{"osx_gl"}},
+ ConfigFileParams::SettingsKeyValuePair{"/mac_assets", AZStd::string_view{"mac"}},
ConfigFileParams::SettingsKeyValuePair{"/connect_to_remote", AZ::s64{0}},
ConfigFileParams::SettingsKeyValuePair{"/windows_connect_to_remote", AZ::s64{1}},
ConfigFileParams::SettingsKeyValuePair{"/android_connect_to_remote", AZ::s64{0}},
@@ -478,20 +478,20 @@ test_asset_processor_tag = test_value
[Platform pc]
tags=tools,renderer,dx12,vulkan
-[Platform es3]
+[Platform android]
tags=android,mobile,renderer,vulkan ; With Comments at the end
[Platform ios]
tags=mobile,renderer,metal
-[Platform osx_gl]
+[Platform mac]
tags=tools,renderer,metal)"
, AZStd::fixed_vector{
ConfigFileParams::SettingsKeyValuePair{"/test_asset_processor_tag", AZStd::string_view{"test_value"}},
ConfigFileParams::SettingsKeyValuePair{"/Platform pc/tags", AZStd::string_view{"tools,renderer,dx12,vulkan"}},
- ConfigFileParams::SettingsKeyValuePair{"/Platform es3/tags", AZStd::string_view{"android,mobile,renderer,vulkan"}},
+ ConfigFileParams::SettingsKeyValuePair{"/Platform android/tags", AZStd::string_view{"android,mobile,renderer,vulkan"}},
ConfigFileParams::SettingsKeyValuePair{"/Platform ios/tags", AZStd::string_view{"mobile,renderer,metal"}},
- ConfigFileParams::SettingsKeyValuePair{"/Platform osx_gl/tags", AZStd::string_view{"tools,renderer,metal"}},
+ ConfigFileParams::SettingsKeyValuePair{"/Platform mac/tags", AZStd::string_view{"tools,renderer,metal"}},
}}
)
);
diff --git a/Code/Framework/AzFramework/AzFramework/Application/Application.cpp b/Code/Framework/AzFramework/AzFramework/Application/Application.cpp
index e02892de4e..c65ba373f8 100644
--- a/Code/Framework/AzFramework/AzFramework/Application/Application.cpp
+++ b/Code/Framework/AzFramework/AzFramework/Application/Application.cpp
@@ -679,8 +679,6 @@ namespace AzFramework
{
auto fileIoBase = m_archiveFileIO.get();
// Set up the default file aliases based on the settings registry
- fileIoBase->SetAlias("@assets@", "");
- fileIoBase->SetAlias("@root@", GetEngineRoot());
fileIoBase->SetAlias("@engroot@", GetEngineRoot());
fileIoBase->SetAlias("@projectroot@", GetEngineRoot());
fileIoBase->SetAlias("@exefolder@", GetExecutableFolder());
@@ -694,8 +692,8 @@ namespace AzFramework
pathAliases.clear();
if (m_settingsRegistry->Get(pathAliases.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_CacheRootFolder))
{
- fileIoBase->SetAlias("@projectplatformcache@", pathAliases.c_str());
fileIoBase->SetAlias("@assets@", pathAliases.c_str());
+ fileIoBase->SetAlias("@projectplatformcache@", pathAliases.c_str());
fileIoBase->SetAlias("@root@", pathAliases.c_str()); // Deprecated Use @projectplatformcache@
}
pathAliases.clear();
diff --git a/Code/Framework/AzFramework/AzFramework/Archive/Archive.cpp b/Code/Framework/AzFramework/AzFramework/Archive/Archive.cpp
index b0285616df..4a80db2b24 100644
--- a/Code/Framework/AzFramework/AzFramework/Archive/Archive.cpp
+++ b/Code/Framework/AzFramework/AzFramework/Archive/Archive.cpp
@@ -2008,13 +2008,12 @@ namespace AZ::IO
// if no bind root is specified, compute one:
strBindRoot = !bindRoot.empty() ? bindRoot : szFullPath->ParentPath().Native();
- // Check if archive file disk exist on disk or inside of pak.
- bool bFileExists = IsFileExist(szFullPath->Native());
-
- if (!bFileExists && (nFactoryFlags & ZipDir::CacheFactory::FLAGS_READ_ONLY))
+ // Check if archive file disk exist on disk.
+ const bool pakOnDisk = FileIOBase::GetDirectInstance()->Exists(szFullPath->c_str());
+ if (!pakOnDisk && (nFactoryFlags & ZipDir::CacheFactory::FLAGS_READ_ONLY))
{
// Archive file not found.
- AZ_TracePrintf("Archive", "Cannot open Archive file %s\n", szFullPath->c_str());
+ AZ_TracePrintf("Archive", "Archive file %s does not exist\n", szFullPath->c_str());
return nullptr;
}
@@ -2492,8 +2491,6 @@ namespace AZ::IO
void Archive::FindCompressionInfo(bool& found, AZ::IO::CompressionInfo& info, const AZStd::string_view filename)
{
- constexpr uint32_t s_compressionTag = static_cast('Z') << 24 | static_cast('C') << 16 | static_cast('R') << 8 | static_cast('Y');
-
if (!found)
{
auto correctedFilename = AZ::IO::FileIOBase::GetDirectInstance()->ResolvePath(filename);
@@ -2519,7 +2516,6 @@ namespace AZ::IO
found = true;
info.m_archiveFilename.InitFromRelativePath(archive->GetFilePath());
- info.m_compressionTag.m_code = s_compressionTag;
info.m_offset = pFileData->GetFileDataOffset();
info.m_compressedSize = entry->desc.lSizeCompressed;
info.m_uncompressedSize = entry->desc.lSizeUncompressed;
@@ -2539,9 +2535,8 @@ namespace AZ::IO
break;
}
- info.m_decompressor = [&s_compressionTag]([[maybe_unused]] const AZ::IO::CompressionInfo& info, const void* compressed, size_t compressedSize, void* uncompressed, size_t uncompressedBufferSize)->bool
+ info.m_decompressor = []([[maybe_unused]] const AZ::IO::CompressionInfo& info, const void* compressed, size_t compressedSize, void* uncompressed, size_t uncompressedBufferSize)->bool
{
- AZ_Assert(info.m_compressionTag.m_code == s_compressionTag, "Provided compression info isn't supported by this decompressor.");
size_t nSizeUncompressed = uncompressedBufferSize;
return ZipDir::ZipRawUncompress(uncompressed, &nSizeUncompressed, compressed, compressedSize) == 0;
};
diff --git a/Code/Framework/AzFramework/AzFramework/Archive/ArchiveFindData.cpp b/Code/Framework/AzFramework/AzFramework/Archive/ArchiveFindData.cpp
index 678f4e40bf..1794ae90e7 100644
--- a/Code/Framework/AzFramework/AzFramework/Archive/ArchiveFindData.cpp
+++ b/Code/Framework/AzFramework/AzFramework/Archive/ArchiveFindData.cpp
@@ -50,6 +50,7 @@ namespace AZ::IO
, tWrite{ writeTime }
{
}
+
ArchiveFileIterator::ArchiveFileIterator(FindData* findData, AZStd::string_view filename, const FileDesc& fileDesc)
: m_findData{ findData }
, m_filename{ filename }
@@ -108,13 +109,10 @@ namespace AZ::IO
AZ::StringFunc::Path::GetFullPath(directory.c_str(), searchDirectory);
AZ::StringFunc::Path::GetFullFileName(directory.c_str(), pattern);
}
-
AZ::IO::FileIOBase::GetDirectInstance()->FindFiles(searchDirectory.c_str(), pattern.c_str(), [&](const char* filePath) -> bool
{
AZ::IO::FileDesc fileDesc;
-
- AZStd::string fullFilePath;
- AZ::StringFunc::Path::GetFullFileName(filePath, fullFilePath);
+ AZStd::string filePathEntry{filePath};
if (AZ::IO::FileIOBase::GetDirectInstance()->IsDirectory(filePath))
{
@@ -135,9 +133,8 @@ namespace AZ::IO
fileDesc.tAccess = fileDesc.tWrite;
fileDesc.tCreate = fileDesc.tWrite;
}
- [[maybe_unused]] auto result = m_mapFiles.emplace(AZStd::move(fullFilePath), fileDesc);
- AZ_Assert(result.second, "Failed to insert FindData entry for %s", fullFilePath.c_str());
-
+ [[maybe_unused]] auto result = m_mapFiles.emplace(AZStd::move(filePathEntry), fileDesc);
+ AZ_Assert(result.second, "Failed to insert FindData entry for filePath %s", filePath);
return true;
});
}
@@ -273,7 +270,9 @@ namespace AZ::IO
}
auto pakFileIter = m_mapFiles.begin();
- fileIterator.m_filename = pakFileIter->first;
+ AZStd::string fullFilePath;
+ AZ::StringFunc::Path::GetFullFileName(pakFileIter->first.c_str(), fullFilePath);
+ fileIterator.m_filename = AZStd::move(fullFilePath);
fileIterator.m_fileDesc = pakFileIter->second;
fileIterator.m_lastFetchValid = true;
diff --git a/Code/Framework/AzFramework/AzFramework/Asset/AssetProcessorMessages.cpp b/Code/Framework/AzFramework/AzFramework/Asset/AssetProcessorMessages.cpp
index 020feffc47..7280c4af5c 100644
--- a/Code/Framework/AzFramework/AzFramework/Asset/AssetProcessorMessages.cpp
+++ b/Code/Framework/AzFramework/AzFramework/Asset/AssetProcessorMessages.cpp
@@ -308,6 +308,56 @@ namespace AzFramework
}
}
+ //---------------------------------------------------------------------
+ GenerateRelativeSourcePathRequest::GenerateRelativeSourcePathRequest(const AZ::OSString& sourcePath)
+ {
+ AZ_Assert(!sourcePath.empty(), "GenerateRelativeSourcePathRequest: asset path is empty");
+ m_sourcePath = sourcePath;
+ }
+
+ unsigned int GenerateRelativeSourcePathRequest::GetMessageType() const
+ {
+ return MessageType;
+ }
+
+ void GenerateRelativeSourcePathRequest::Reflect(AZ::ReflectContext* context)
+ {
+ auto serialize = azrtti_cast(context);
+ if (serialize)
+ {
+ serialize->Class()
+ ->Version(1)
+ ->Field("SourcePath", &GenerateRelativeSourcePathRequest::m_sourcePath);
+ }
+ }
+
+ //---------------------------------------------------------------------
+ GenerateRelativeSourcePathResponse::GenerateRelativeSourcePathResponse(
+ bool resolved, const AZ::OSString& relativeSourcePath, const AZ::OSString& rootFolder)
+ {
+ m_relativeSourcePath = relativeSourcePath;
+ m_resolved = resolved;
+ m_rootFolder = rootFolder;
+ }
+
+ unsigned int GenerateRelativeSourcePathResponse::GetMessageType() const
+ {
+ return GenerateRelativeSourcePathRequest::MessageType;
+ }
+
+ void GenerateRelativeSourcePathResponse::Reflect(AZ::ReflectContext* context)
+ {
+ auto serialize = azrtti_cast(context);
+ if (serialize)
+ {
+ serialize->Class()
+ ->Version(1)
+ ->Field("RelativeSourcePath", &GenerateRelativeSourcePathResponse::m_relativeSourcePath)
+ ->Field("RootFolder", &GenerateRelativeSourcePathResponse::m_rootFolder)
+ ->Field("Resolved", &GenerateRelativeSourcePathResponse::m_resolved);
+ }
+ }
+
//---------------------------------------------------------------------
GetFullSourcePathFromRelativeProductPathRequest::GetFullSourcePathFromRelativeProductPathRequest(const AZ::OSString& relativeProductPath)
{
diff --git a/Code/Framework/AzFramework/AzFramework/Asset/AssetProcessorMessages.h b/Code/Framework/AzFramework/AzFramework/Asset/AssetProcessorMessages.h
index 9661e61828..c15f75e3e7 100644
--- a/Code/Framework/AzFramework/AzFramework/Asset/AssetProcessorMessages.h
+++ b/Code/Framework/AzFramework/AzFramework/Asset/AssetProcessorMessages.h
@@ -288,6 +288,45 @@ namespace AzFramework
bool m_resolved;
};
+ //////////////////////////////////////////////////////////////////////////
+ class GenerateRelativeSourcePathRequest : public BaseAssetProcessorMessage
+ {
+ public:
+ AZ_CLASS_ALLOCATOR(GenerateRelativeSourcePathRequest, AZ::OSAllocator, 0);
+ AZ_RTTI(GenerateRelativeSourcePathRequest, "{B3865033-F5A3-4749-8147-7B1AB04D5F6D}",
+ BaseAssetProcessorMessage);
+ static void Reflect(AZ::ReflectContext* context);
+
+ // For people that are debugging the network messages and just see MessageType as a value,
+ // the CRC value below is 739777771 (0x2C181CEB)
+ static constexpr unsigned int MessageType =
+ AZ_CRC_CE("AssetSystem::GenerateRelativeSourcePathRequest");
+
+ GenerateRelativeSourcePathRequest() = default;
+ GenerateRelativeSourcePathRequest(const AZ::OSString& sourcePath);
+ unsigned int GetMessageType() const override;
+
+ AZ::OSString m_sourcePath;
+ };
+
+ class GenerateRelativeSourcePathResponse : public BaseAssetProcessorMessage
+ {
+ public:
+ AZ_CLASS_ALLOCATOR(GenerateRelativeSourcePathResponse, AZ::OSAllocator, 0);
+ AZ_RTTI(GenerateRelativeSourcePathResponse, "{938D33DB-C8F6-4FA4-BC81-2F139A9BE1D7}",
+ BaseAssetProcessorMessage);
+ static void Reflect(AZ::ReflectContext* context);
+
+ GenerateRelativeSourcePathResponse() = default;
+ GenerateRelativeSourcePathResponse(
+ bool resolved, const AZ::OSString& relativeSourcePath, const AZ::OSString& rootFolder);
+ unsigned int GetMessageType() const override;
+
+ AZ::OSString m_relativeSourcePath;
+ AZ::OSString m_rootFolder; ///< This is the folder it was found in (the watched/scanned folder, such as gems /assets/ folder)
+ bool m_resolved;
+ };
+
//////////////////////////////////////////////////////////////////////////
class GetFullSourcePathFromRelativeProductPathRequest
: public BaseAssetProcessorMessage
diff --git a/Code/Framework/AzFramework/AzFramework/Asset/AssetSystemComponent.cpp b/Code/Framework/AzFramework/AzFramework/Asset/AssetSystemComponent.cpp
index 83c4907468..6b19084c2a 100644
--- a/Code/Framework/AzFramework/AzFramework/Asset/AssetSystemComponent.cpp
+++ b/Code/Framework/AzFramework/AzFramework/Asset/AssetSystemComponent.cpp
@@ -202,6 +202,7 @@ namespace AzFramework
// Requests
GetUnresolvedDependencyCountsRequest::Reflect(context);
GetRelativeProductPathFromFullSourceOrProductPathRequest::Reflect(context);
+ GenerateRelativeSourcePathRequest::Reflect(context);
GetFullSourcePathFromRelativeProductPathRequest::Reflect(context);
SourceAssetInfoRequest::Reflect(context);
AssetInfoRequest::Reflect(context);
@@ -234,6 +235,7 @@ namespace AzFramework
// Responses
GetUnresolvedDependencyCountsResponse::Reflect(context);
GetRelativeProductPathFromFullSourceOrProductPathResponse::Reflect(context);
+ GenerateRelativeSourcePathResponse::Reflect(context);
GetFullSourcePathFromRelativeProductPathResponse::Reflect(context);
SourceAssetInfoResponse::Reflect(context);
AssetInfoResponse::Reflect(context);
diff --git a/Code/Framework/AzFramework/AzFramework/Components/ComponentAdapter.h b/Code/Framework/AzFramework/AzFramework/Components/ComponentAdapter.h
index 682e886061..ee8b79bf06 100644
--- a/Code/Framework/AzFramework/AzFramework/Components/ComponentAdapter.h
+++ b/Code/Framework/AzFramework/AzFramework/Components/ComponentAdapter.h
@@ -1,22 +1,22 @@
/*
-* 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.
-*
-*/
+ * 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 AzFramework
{
@@ -64,15 +64,13 @@ namespace AzFramework
the EditContext. TController can friend itself to the editor component to make this work if required.
*/
template
- class ComponentAdapter
- : public AZ::Component
+ class ComponentAdapter : public AZ::Component
{
public:
-
AZ_RTTI((ComponentAdapter, "{644A9187-4FDB-42C1-9D59-DD75304B551A}", TController, TConfiguration), AZ::Component);
ComponentAdapter() = default;
- ComponentAdapter(const TConfiguration& configuration);
+ explicit ComponentAdapter(const TConfiguration& configuration);
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services);
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& services);
@@ -85,7 +83,6 @@ namespace AzFramework
void Deactivate() override;
protected:
-
static void Reflect(AZ::ReflectContext* context);
// AZ::Component overrides ...
diff --git a/Code/Framework/AzFramework/AzFramework/Components/ComponentAdapter.inl b/Code/Framework/AzFramework/AzFramework/Components/ComponentAdapter.inl
index a1b0826193..5c36ff0bf7 100644
--- a/Code/Framework/AzFramework/AzFramework/Components/ComponentAdapter.inl
+++ b/Code/Framework/AzFramework/AzFramework/Components/ComponentAdapter.inl
@@ -1,14 +1,14 @@
/*
-* 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.
-*
-*/
+ * 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
@@ -32,10 +32,12 @@ namespace AzFramework
if (auto serializeContext = azrtti_cast(context))
{
+ // clang-format off
serializeContext->Class()
->Version(1)
->Field("Controller", &ComponentAdapter::m_controller)
;
+ // clang-format on
}
}
@@ -66,9 +68,6 @@ namespace AzFramework
GetDependentServicesHelper(services, typename AZ::HasComponentDependentServices::type());
}
- //////////////////////////////////////////////////////////////////////////
- // AZ::Component interface implementation
-
template
void ComponentAdapter::Init()
{
@@ -78,7 +77,7 @@ namespace AzFramework
template
void ComponentAdapter::Activate()
{
- m_controller.Activate(GetEntityId());
+ ComponentActivateHelper::Activate(m_controller, AZ::EntityComponentIdPair(GetEntityId(), GetId()));
}
template
diff --git a/Code/Framework/AzFramework/AzFramework/Components/ComponentAdapterHelpers.h b/Code/Framework/AzFramework/AzFramework/Components/ComponentAdapterHelpers.h
index 158ee95f39..f0ef262a71 100644
--- a/Code/Framework/AzFramework/AzFramework/Components/ComponentAdapterHelpers.h
+++ b/Code/Framework/AzFramework/AzFramework/Components/ComponentAdapterHelpers.h
@@ -13,6 +13,7 @@
#pragma once
#include
+#include
namespace AzFramework
{
@@ -27,18 +28,43 @@ namespace AzFramework
template
struct ComponentInitHelper
{
- static void Init(T& common)
+ static void Init([[maybe_unused]] T& controller)
{
- AZ_UNUSED(common);
}
};
template
struct ComponentInitHelper().Init())>>
{
- static void Init(T& common)
+ static void Init(T& controller)
{
- common.Init();
+ controller.Init();
+ }
+ };
+
+ template
+ struct ComponentActivateHelper
+ {
+ static void Activate([[maybe_unused]] T& controller, [[maybe_unused]] const AZ::EntityComponentIdPair& entityComponentIdPair)
+ {
+ }
+ };
+
+ template
+ struct ComponentActivateHelper().Activate(AZ::EntityId()))>>
+ {
+ static void Activate(T& controller, const AZ::EntityComponentIdPair& entityComponentIdPair)
+ {
+ controller.Activate(entityComponentIdPair.GetEntityId());
+ }
+ };
+
+ template
+ struct ComponentActivateHelper().Activate(AZ::EntityComponentIdPair()))>>
+ {
+ static void Activate(T& controller, const AZ::EntityComponentIdPair& entityComponentIdPair)
+ {
+ controller.Activate(entityComponentIdPair);
}
};
diff --git a/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.cpp b/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.cpp
index 3dafc7c717..b3c4f1b256 100644
--- a/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.cpp
+++ b/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.cpp
@@ -327,99 +327,13 @@ namespace AzFramework
return localZ;
}
- void TransformComponent::SetRotation(const AZ::Vector3& eulerAnglesRadian)
+ void TransformComponent::SetWorldRotationQuaternion(const AZ::Quaternion& quaternion)
{
- AZ_Warning("TransformComponent", false, "SetRotation is deprecated, please use SetLocalRotation");
-
- AZ::Transform newWorldTransform = m_worldTM;
- newWorldTransform.SetRotation(AZ::ConvertEulerRadiansToQuaternion(eulerAnglesRadian));
- SetWorldTM(newWorldTransform);
- }
-
- void TransformComponent::SetRotationQuaternion(const AZ::Quaternion& quaternion)
- {
- AZ_Warning("TransformComponent", false, "SetRotationQuaternion is deprecated, please use SetLocalRotationQuaternion");
-
AZ::Transform newWorldTransform = m_worldTM;
newWorldTransform.SetRotation(quaternion);
SetWorldTM(newWorldTransform);
}
- void TransformComponent::SetRotationX(float eulerAngleRadian)
- {
- AZ_Warning("TransformComponent", false, "SetRotationX is deprecated, please use SetLocalRotation");
-
- AZ::Transform newWorldTransform = m_worldTM;
- newWorldTransform.SetRotation(AZ::Quaternion::CreateRotationX(eulerAngleRadian));
- SetWorldTM(newWorldTransform);
- }
-
- void TransformComponent::SetRotationY(float eulerAngleRadian)
- {
- AZ_Warning("TransformComponent", false, "SetRotationY is deprecated, please use SetLocalRotation");
-
- AZ::Transform newWorldTransform = m_worldTM;
- newWorldTransform.SetRotation(AZ::Quaternion::CreateRotationY(eulerAngleRadian));
- SetWorldTM(newWorldTransform);
- }
-
- void TransformComponent::SetRotationZ(float eulerAngleRadian)
- {
- AZ_Warning("TransformComponent", false, "SetRotationZ is deprecated, please use SetLocalRotation");
-
- AZ::Transform newWorldTransform = m_worldTM;
- newWorldTransform.SetRotation(AZ::Quaternion::CreateRotationZ(eulerAngleRadian));
- SetWorldTM(newWorldTransform);
- }
-
- void TransformComponent::RotateByX(float eulerAngleRadian)
- {
- AZ_Warning("TransformComponent", false, "RotateByX is deprecated, please use RotateAroundLocalX");
- RotateAroundLocalX(eulerAngleRadian);
- }
-
- void TransformComponent::RotateByY(float eulerAngleRadian)
- {
- AZ_Warning("TransformComponent", false, "RotateByY is deprecated, please use RotateAroundLocalY");
- RotateAroundLocalY(eulerAngleRadian);
- }
-
- void TransformComponent::RotateByZ(float eulerAngleRadian)
- {
- AZ_Warning("TransformComponent", false, "RotateByZ is deprecated, please use RotateAroundLocalZ");
- RotateAroundLocalZ(eulerAngleRadian);
- }
-
- AZ::Vector3 TransformComponent::GetRotationEulerRadians()
- {
- AZ_Warning("TransformComponent", false, "GetRotationEulerRadians is deprecated, please use GetWorldRotation");
- return m_worldTM.GetRotation().GetEulerRadians();
- }
-
- AZ::Quaternion TransformComponent::GetRotationQuaternion()
- {
- AZ_Warning("TransformComponent", false, "GetRotationQuaternion is deprecated, please use GetWorldRotationQuaternion");
- return m_worldTM.GetRotation();
- }
-
- float TransformComponent::GetRotationX()
- {
- AZ_Warning("TransformComponent", false, "GetRotationX is deprecated, please use GetWorldRotation");
- return GetRotationEulerRadians().GetX();
- }
-
- float TransformComponent::GetRotationY()
- {
- AZ_Warning("TransformComponent", false, "GetRotationY is deprecated, please use GetWorldRotation");
- return GetRotationEulerRadians().GetY();
- }
-
- float TransformComponent::GetRotationZ()
- {
- AZ_Warning("TransformComponent", false, "GetRotationZ is deprecated, please use GetWorldRotation");
- return GetRotationEulerRadians().GetZ();
- }
-
AZ::Vector3 TransformComponent::GetWorldRotation()
{
return m_worldTM.GetRotation().GetEulerRadians();
@@ -492,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)
@@ -830,45 +733,7 @@ namespace AzFramework
->Event("GetLocalX", &AZ::TransformBus::Events::GetLocalX)
->Event("GetLocalY", &AZ::TransformBus::Events::GetLocalY)
->Event("GetLocalZ", &AZ::TransformBus::Events::GetLocalZ)
- ->Event("RotateByX", &AZ::TransformBus::Events::RotateByX)
- ->Attribute(AZ::Script::Attributes::Deprecated, true)
- ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
- ->Event("RotateByY", &AZ::TransformBus::Events::RotateByY)
- ->Attribute(AZ::Script::Attributes::Deprecated, true)
- ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
- ->Event("RotateByZ", &AZ::TransformBus::Events::RotateByZ)
- ->Attribute(AZ::Script::Attributes::Deprecated, true)
- ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
- ->Event("SetEulerRotation", &AZ::TransformBus::Events::SetRotation)
- ->Attribute(AZ::Script::Attributes::Deprecated, true)
- ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
- ->Event("SetRotationQuaternion", &AZ::TransformBus::Events::SetRotationQuaternion)
- ->Attribute(AZ::Script::Attributes::Deprecated, true)
- ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
- ->Event("SetRotationX", &AZ::TransformBus::Events::SetRotationX)
- ->Attribute(AZ::Script::Attributes::Deprecated, true)
- ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
- ->Event("SetRotationY", &AZ::TransformBus::Events::SetRotationY)
- ->Attribute(AZ::Script::Attributes::Deprecated, true)
- ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
- ->Event("SetRotationZ", &AZ::TransformBus::Events::SetRotationZ)
- ->Attribute(AZ::Script::Attributes::Deprecated, true)
- ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
- ->Event("GetEulerRotation", &AZ::TransformBus::Events::GetRotationEulerRadians)
- ->Attribute(AZ::Script::Attributes::Deprecated, true)
- ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
- ->Event("GetRotationQuaternion", &AZ::TransformBus::Events::GetRotationQuaternion)
- ->Attribute(AZ::Script::Attributes::Deprecated, true)
- ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
- ->Event("GetRotationX", &AZ::TransformBus::Events::GetRotationX)
- ->Attribute(AZ::Script::Attributes::Deprecated, true)
- ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
- ->Event("GetRotationY", &AZ::TransformBus::Events::GetRotationY)
- ->Attribute(AZ::Script::Attributes::Deprecated, true)
- ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
- ->Event("GetRotationZ", &AZ::TransformBus::Events::GetRotationZ)
- ->Attribute(AZ::Script::Attributes::Deprecated, true)
- ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
+ ->Event("SetWorldRotationQuaternion", &AZ::TransformBus::Events::SetWorldRotationQuaternion)
->Event("GetWorldRotation", &AZ::TransformBus::Events::GetWorldRotation)
->Event("GetWorldRotationQuaternion", &AZ::TransformBus::Events::GetWorldRotationQuaternion)
->Event("SetLocalRotation", &AZ::TransformBus::Events::SetLocalRotation)
@@ -880,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 e3a647d39f..0301334a0d 100644
--- a/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.h
+++ b/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.h
@@ -112,22 +112,7 @@ namespace AzFramework
float GetLocalZ() override;
// Rotation modifiers
- void SetRotation(const AZ::Vector3& eulerAnglesRadian) override;
- void SetRotationQuaternion(const AZ::Quaternion& quaternion) override;
- void SetRotationX(float eulerAngleRadian) override;
- void SetRotationY(float eulerAngleRadian) override;
- void SetRotationZ(float eulerAngleRadian) override;
-
- void RotateByX(float eulerAngleRadian) override;
- void RotateByY(float eulerAngleRadian) override;
- void RotateByZ(float eulerAngleRadian) override;
-
- AZ::Vector3 GetRotationEulerRadians() override;
- AZ::Quaternion GetRotationQuaternion() override;
-
- float GetRotationX() override;
- float GetRotationY() override;
- float GetRotationZ() override;
+ void SetWorldRotationQuaternion(const AZ::Quaternion& quaternion) override;
AZ::Vector3 GetWorldRotation() override;
AZ::Quaternion GetWorldRotationQuaternion() override;
@@ -143,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 b64b61e22c..dae5fa9fe7 100644
--- a/Code/Framework/AzFramework/AzFramework/Font/FontInterface.h
+++ b/Code/Framework/AzFramework/AzFramework/Font/FontInterface.h
@@ -15,6 +15,7 @@
#include
#include
#include
+#include
#include
#include
@@ -42,14 +43,18 @@ namespace AzFramework
{
ViewportId m_drawViewportId = InvalidViewportId; //!< Viewport to draw into
AZ::Vector3 m_position; //!< world space position for 3d draws, screen space x,y,depth for 2d.
- AZ::Color m_color = AZ::Colors::White; //!< Color to draw the text
+ AZ::Color m_color = AZ::Colors::White; //!< Color to draw the text
+ unsigned int m_effectIndex = 0; //!< effect index to apply
AZ::Vector2 m_scale = AZ::Vector2(1.0f); //!< font scale
- float m_lineSpacing; //!< Spacing between new lines, as a percentage of m_scale.
+ float m_textSizeFactor = 12.0f; //!< font size in pixels
+ float m_lineSpacing = 1.0f; //!< Spacing between new lines, as a percentage of m_scale.
TextHorizontalAlignment m_hAlign = TextHorizontalAlignment::Left; //!< Horizontal text alignment
TextVerticalAlignment m_vAlign = TextVerticalAlignment::Top; //!< Vertical text alignment
+ bool m_useTransform = false; //!< Use specified transform
+ 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
- {
- return GetMaterialLibraryAsset().GetId();
- }
-
- const Physics::MaterialLibraryAsset* MaterialSelection::GetMaterialLibraryAssetData() const
- {
- return GetMaterialLibraryAsset().Get();
- }
-
- const AZStd::string& MaterialSelection::GetMaterialLibraryAssetHint() const
+ void MaterialSelection::OnMaterialLibraryChanged([[maybe_unused]] const AZ::Data::AssetId& defaultMaterialLibraryId)
{
- 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);
-
- 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).
+ return;
+ }
- 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 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 RegisterOnDefaultMaterialLibraryChangedEventHandler(SystemEvents::OnDefaultMaterialLibraryChangedEvent::Handler& handler) { handler.Connect(m_onDefaultMaterialLibraryChangedEvent); }
+ 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/Ragdoll.h b/Code/Framework/AzFramework/AzFramework/Physics/Ragdoll.h
index 239d93cf32..97c841e8f8 100644
--- a/Code/Framework/AzFramework/AzFramework/Physics/Ragdoll.h
+++ b/Code/Framework/AzFramework/AzFramework/Physics/Ragdoll.h
@@ -102,7 +102,7 @@ namespace Physics
/// Is the ragdoll currently simulated?
/// @result True in case the ragdoll is simulated, false if not.
- virtual bool IsSimulated() = 0;
+ virtual bool IsSimulated() const = 0;
/// Writes the state for all of the bodies in the ragdoll to the provided output.
/// The caller owns the output state and can safely manipulate it without affecting the physics simulation.
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 bdbfe6197f..22598595fc 100644
--- a/Code/Framework/AzFramework/AzFramework/ProjectManager/ProjectManager.cpp
+++ b/Code/Framework/AzFramework/AzFramework/ProjectManager/ProjectManager.cpp
@@ -46,7 +46,6 @@ namespace AzFramework::ProjectManager
// Store the Command line to the Setting Registry
AZ::SettingsRegistryImpl settingsRegistry;
AZ::SettingsRegistryMergeUtils::StoreCommandLineToRegistry(settingsRegistry, commandLine);
- AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_Bootstrap(settingsRegistry);
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_O3deUserRegistry(settingsRegistry, AZ_TRAIT_OS_PLATFORM_CODENAME, {});
// Retrieve Command Line from Settings Registry, it may have been updated by the call to FindEngineRoot()
// in MergeSettingstoRegistry_ConfigFile
@@ -79,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;
@@ -88,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)
@@ -110,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 5e507b3498..677f0326cf 100644
--- a/Code/Framework/AzFramework/AzFramework/Spawnable/Spawnable.h
+++ b/Code/Framework/AzFramework/AzFramework/Spawnable/Spawnable.h
@@ -19,10 +19,13 @@
#include
#include
-namespace AzFramework
+namespace AZ
{
class ReflectContext;
+}
+namespace AzFramework
+{
class Spawnable final
: public AZ::Data::AssetData
{
@@ -38,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 a528797f63..ad1bf032c8 100644
--- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.cpp
+++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.cpp
@@ -14,6 +14,10 @@
namespace AzFramework
{
+ //
+ // SpawnableEntityContainerView
+ //
+
SpawnableEntityContainerView::SpawnableEntityContainerView(AZ::Entity** begin, size_t length)
: m_begin(begin)
, m_end(begin + length)
@@ -52,6 +56,9 @@ namespace AzFramework
}
+ //
+ // SpawnableConstEntityContainerView
+ //
SpawnableConstEntityContainerView::SpawnableConstEntityContainerView(AZ::Entity** begin, size_t length)
: m_begin(begin)
@@ -91,6 +98,136 @@ namespace AzFramework
}
+ //
+ // SpawnableIndexEntityPair
+ //
+
+ SpawnableIndexEntityPair::SpawnableIndexEntityPair(AZ::Entity** entityIterator, size_t* indexIterator)
+ : m_entity(entityIterator)
+ , m_index(indexIterator)
+ {
+ }
+
+ AZ::Entity* SpawnableIndexEntityPair::GetEntity()
+ {
+ return *m_entity;
+ }
+
+ const AZ::Entity* SpawnableIndexEntityPair::GetEntity() const
+ {
+ return *m_entity;
+ }
+
+ size_t SpawnableIndexEntityPair::GetIndex() const
+ {
+ return *m_index;
+ }
+
+ //
+ // SpawnableIndexEntityIterator
+ //
+
+ SpawnableIndexEntityIterator::SpawnableIndexEntityIterator(AZ::Entity** entityIterator, size_t* indexIterator)
+ : m_value(entityIterator, indexIterator)
+ {
+ }
+
+ SpawnableIndexEntityIterator& SpawnableIndexEntityIterator::operator++()
+ {
+ ++m_value.m_entity;
+ ++m_value.m_index;
+ return *this;
+ }
+
+ SpawnableIndexEntityIterator SpawnableIndexEntityIterator::operator++(int)
+ {
+ SpawnableIndexEntityIterator result = *this;
+ ++m_value.m_entity;
+ ++m_value.m_index;
+ return result;
+ }
+
+ SpawnableIndexEntityIterator& SpawnableIndexEntityIterator::operator--()
+ {
+ --m_value.m_entity;
+ --m_value.m_index;
+ return *this;
+ }
+
+ SpawnableIndexEntityIterator SpawnableIndexEntityIterator::operator--(int)
+ {
+ SpawnableIndexEntityIterator result = *this;
+ --m_value.m_entity;
+ --m_value.m_index;
+ return result;
+ }
+
+ bool SpawnableIndexEntityIterator::operator==(const SpawnableIndexEntityIterator& rhs)
+ {
+ return m_value.m_entity == rhs.m_value.m_entity && m_value.m_index == rhs.m_value.m_index;
+ }
+
+ bool SpawnableIndexEntityIterator::operator!=(const SpawnableIndexEntityIterator& rhs)
+ {
+ return m_value.m_entity != rhs.m_value.m_entity || m_value.m_index != rhs.m_value.m_index;
+ }
+
+ SpawnableIndexEntityPair& SpawnableIndexEntityIterator::operator*()
+ {
+ return m_value;
+ }
+
+ const SpawnableIndexEntityPair& SpawnableIndexEntityIterator::operator*() const
+ {
+ return m_value;
+ }
+
+ SpawnableIndexEntityPair* SpawnableIndexEntityIterator::operator->()
+ {
+ return &m_value;
+ }
+
+ const SpawnableIndexEntityPair* SpawnableIndexEntityIterator::operator->() const
+ {
+ return &m_value;
+ }
+
+
+ //
+ // SpawnableConstIndexEntityContainerView
+ //
+
+ SpawnableConstIndexEntityContainerView::SpawnableConstIndexEntityContainerView(
+ AZ::Entity** beginEntity, size_t* beginIndices, size_t length)
+ : m_begin(beginEntity, beginIndices)
+ , m_end(beginEntity + length, beginIndices + length)
+ {
+ }
+
+ const SpawnableIndexEntityIterator& SpawnableConstIndexEntityContainerView::begin()
+ {
+ return m_begin;
+ }
+
+ const SpawnableIndexEntityIterator& SpawnableConstIndexEntityContainerView::end()
+ {
+ return m_end;
+ }
+
+ const SpawnableIndexEntityIterator& SpawnableConstIndexEntityContainerView::cbegin()
+ {
+ return m_begin;
+ }
+
+ const SpawnableIndexEntityIterator& SpawnableConstIndexEntityContainerView::cend()
+ {
+ return m_end;
+ }
+
+
+ //
+ // EntitySpawnTicket
+ //
EntitySpawnTicket::EntitySpawnTicket(EntitySpawnTicket&& rhs)
: m_payload(rhs.m_payload)
@@ -102,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()
@@ -113,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;
}
}
@@ -126,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 ac66288ff2..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:
@@ -58,16 +67,84 @@ namespace AzFramework
AZ::Entity** 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
+ class SpawnableIndexEntityPair
+ {
+ public:
+ friend class SpawnableIndexEntityIterator;
+
+ AZ::Entity* GetEntity();
+ const AZ::Entity* GetEntity() const;
+ size_t GetIndex() const;
+
+ private:
+ SpawnableIndexEntityPair() = default;
+ SpawnableIndexEntityPair(const SpawnableIndexEntityPair&) = default;
+ SpawnableIndexEntityPair(SpawnableIndexEntityPair&&) = default;
+ SpawnableIndexEntityPair(AZ::Entity** entityIterator, size_t* indexIterator);
+
+ SpawnableIndexEntityPair& operator=(const SpawnableIndexEntityPair&) = default;
+ SpawnableIndexEntityPair& operator=(SpawnableIndexEntityPair&&) = default;
+
+ AZ::Entity** m_entity { nullptr };
+ size_t* m_index { nullptr };
+ };
+
+ class SpawnableIndexEntityIterator
+ {
+ public:
+ // Limited to bidirectional iterator as there's no use case for extending it further, but can be extended if a use case is found.
+ using iterator_category = AZStd::bidirectional_iterator_tag;
+ using value_type = SpawnableIndexEntityPair;
+ using difference_type = size_t;
+ using pointer = SpawnableIndexEntityPair*;
+ using reference = SpawnableIndexEntityPair&;
+
+ SpawnableIndexEntityIterator(AZ::Entity** entityIterator, size_t* indexIterator);
+
+ SpawnableIndexEntityIterator& operator++();
+ SpawnableIndexEntityIterator operator++(int);
+ SpawnableIndexEntityIterator& operator--();
+ SpawnableIndexEntityIterator operator--(int);
+
+ bool operator==(const SpawnableIndexEntityIterator& rhs);
+ bool operator!=(const SpawnableIndexEntityIterator& rhs);
+
+ SpawnableIndexEntityPair& operator*();
+ const SpawnableIndexEntityPair& operator*() const;
+ SpawnableIndexEntityPair* operator->();
+ const SpawnableIndexEntityPair* operator->() const;
+
+ private:
+ SpawnableIndexEntityPair m_value;
+ };
+
+ class SpawnableConstIndexEntityContainerView
+ {
+ public:
+ SpawnableConstIndexEntityContainerView(AZ::Entity** beginEntity, size_t* beginIndices, size_t length);
+
+ const SpawnableIndexEntityIterator& begin();
+ const SpawnableIndexEntityIterator& end();
+ const SpawnableIndexEntityIterator& cbegin();
+ const SpawnableIndexEntityIterator& cend();
+
+ private:
+ SpawnableIndexEntityIterator m_begin;
+ SpawnableIndexEntityIterator m_end;
+ };
+
+ //! 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);
@@ -77,25 +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 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:
@@ -106,49 +195,72 @@ 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
+ //! both the entities and this index. The index can be used with SpawnEntities to create the same entities again. Note that
+ //! 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, 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;
@@ -157,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 8045766686..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,102 +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::ClaimEntities(EntitySpawnTicket& ticket, ClaimEntitiesCallback 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_ticketId = ticket.GetId();
+ queueEntry.m_listCallback = AZStd::move(listCallback);
+ QueueRequest(ticket, priority, AZStd::move(queueEntry));
+ }
+
+ 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)
@@ -130,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
@@ -171,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();
}
@@ -179,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)
@@ -200,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));
}
}
@@ -225,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;
@@ -274,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()));
}
@@ -288,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
@@ -305,8 +349,8 @@ namespace AzFramework
bool SpawnableEntitiesManager::ProcessRequest(SpawnEntitiesCommand& request, AZ::SerializeContext& serializeContext)
{
- Ticket& ticket = GetTicketPayload