Merge remote-tracking branch 'origin' into MultiplayerComponents
This commit is contained in:
+2
-1
@@ -13,6 +13,7 @@ AllowShortFunctionsOnASingleLine: None
|
||||
AllowShortLambdasOnASingleLine: None
|
||||
AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakTemplateDeclarations: true
|
||||
BinPackParameters: false
|
||||
BreakBeforeBraces: Custom
|
||||
BraceWrapping:
|
||||
AfterClass: true
|
||||
@@ -46,7 +47,7 @@ SortIncludes: true
|
||||
SpaceAfterLogicalNot: false
|
||||
SpaceAfterTemplateKeyword: false
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCpp11BracedList: true
|
||||
SpaceBeforeCpp11BracedList: false
|
||||
SpaceBeforeCtorInitializerColon: true
|
||||
SpaceBeforeInheritanceColon: true
|
||||
SpaceBeforeParens: ControlStatements
|
||||
|
||||
@@ -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/
|
||||
|
||||
+214
-278
File diff suppressed because it is too large
Load Diff
@@ -20,31 +20,49 @@ if(json_error)
|
||||
message(FATAL_ERROR "Unable to read key 'engine' from 'project.json', error: ${json_error}")
|
||||
endif()
|
||||
|
||||
# Read the list of paths from ~.o3de/o3de_manifest.json
|
||||
file(TO_CMAKE_PATH "$ENV{USERPROFILE}" home_directory) # Windows
|
||||
if((NOT home_directory) OR (NOT EXISTS ${home_directory}))
|
||||
file(TO_CMAKE_PATH "$ENV{HOME}" home_directory)# Unix
|
||||
if(DEFINED ENV{USERPROFILE} AND EXISTS $ENV{USERPROFILE})
|
||||
set(manifest_path $ENV{USERPROFILE}/.o3de/o3de_manifest.json) # Windows
|
||||
else()
|
||||
set(manifest_path $ENV{HOME}/.o3de/o3de_manifest.json) # Unix
|
||||
endif()
|
||||
|
||||
if (NOT home_directory)
|
||||
message(FATAL_ERROR "Cannot find user home directory, the o3de manifest cannot be found")
|
||||
endif()
|
||||
# Set manifest path to path in the user home directory
|
||||
set(manifest_path ${home_directory}/.o3de/o3de_manifest.json)
|
||||
|
||||
# Read the ~/.o3de/o3de_manifest.json file and look through the 'engines_path' object.
|
||||
# Find a key that matches LY_ENGINE_NAME_TO_USE and use that as the engine path.
|
||||
if(EXISTS ${manifest_path})
|
||||
file(READ ${manifest_path} manifest_json)
|
||||
string(JSON engines_count ERROR_VARIABLE json_error LENGTH ${manifest_json} engines)
|
||||
|
||||
string(JSON engines_path_count ERROR_VARIABLE json_error LENGTH ${manifest_json} engines_path)
|
||||
if(json_error)
|
||||
message(FATAL_ERROR "Unable to read key 'engines' from '${manifest_path}', error: ${json_error}")
|
||||
message(FATAL_ERROR "Unable to read key 'engines_path' from '${manifest_path}', error: ${json_error}")
|
||||
endif()
|
||||
|
||||
math(EXPR engines_count "${engines_count}-1")
|
||||
foreach(engine_path_index RANGE ${engines_count})
|
||||
string(JSON engine_path ERROR_VARIABLE json_error GET ${manifest_json} engines ${engine_path_index})
|
||||
if(${json_error})
|
||||
message(FATAL_ERROR "Unable to read engines[${engine_path_index}] '${manifest_path}', error: ${json_error}")
|
||||
string(JSON engines_path_type ERROR_VARIABLE json_error TYPE ${manifest_json} engines_path)
|
||||
if(json_error OR NOT ${engines_path_type} STREQUAL "OBJECT")
|
||||
message(FATAL_ERROR "Type of 'engines_path' in '${manifest_path}' is not a JSON Object, error: ${json_error}")
|
||||
endif()
|
||||
|
||||
math(EXPR engines_path_count "${engines_path_count}-1")
|
||||
foreach(engine_path_index RANGE ${engines_path_count})
|
||||
string(JSON engine_name ERROR_VARIABLE json_error MEMBER ${manifest_json} engines_path ${engine_path_index})
|
||||
if(json_error)
|
||||
message(FATAL_ERROR "Unable to read 'engines_path/${engine_path_index}' from '${manifest_path}', error: ${json_error}")
|
||||
endif()
|
||||
|
||||
if(LY_ENGINE_NAME_TO_USE STREQUAL engine_name)
|
||||
string(JSON engine_path ERROR_VARIABLE json_error GET ${manifest_json} engines_path ${engine_name})
|
||||
if(json_error)
|
||||
message(FATAL_ERROR "Unable to read value from 'engines_path/${engine_name}', error: ${json_error}")
|
||||
endif()
|
||||
|
||||
if(engine_path)
|
||||
list(APPEND CMAKE_MODULE_PATH "${engine_path}/cmake")
|
||||
break()
|
||||
endif()
|
||||
endif()
|
||||
list(APPEND CMAKE_MODULE_PATH "${engine_path}/cmake")
|
||||
endforeach()
|
||||
else()
|
||||
# If the user is passing CMAKE_MODULE_PATH we assume thats where we will find the engine
|
||||
if(NOT CMAKE_MODULE_PATH)
|
||||
message(FATAL_ERROR "Engine registration is required before configuring a project. Please register an engine by running 'scripts/o3de register --this-engine'")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
)
|
||||
@@ -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
|
||||
)
|
||||
@@ -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
|
||||
)
|
||||
@@ -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.
|
||||
"""
|
||||
+237
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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'
|
||||
)
|
||||
@@ -16,12 +16,15 @@ import boto3
|
||||
import ly_test_tools.environment.process_utils as process_utils
|
||||
from typing import List
|
||||
|
||||
BOOTSTRAP_STACK_NAME = 'CDKToolkit'
|
||||
BOOTSTRAP_STAGING_BUCKET_LOGIC_ID = 'StagingBucket'
|
||||
|
||||
class Cdk:
|
||||
"""
|
||||
Cdk class that provides methods to run cdk application commands.
|
||||
Expects system to have NodeJS, AWS CLI and CDK installed globally and have their paths setup as env variables.
|
||||
"""
|
||||
|
||||
def __init__(self, cdk_path: str, project: str, account_id: str,
|
||||
workspace: pytest.fixture, session: boto3.session.Session):
|
||||
"""
|
||||
@@ -49,12 +52,24 @@ class Cdk:
|
||||
env=self._cdk_env,
|
||||
shell=True)
|
||||
|
||||
def bootstrap(self) -> None:
|
||||
"""
|
||||
Deploy the bootstrap stack.
|
||||
"""
|
||||
bootstrap_cmd = ['cdk', 'bootstrap',
|
||||
f'aws://{self._cdk_env["O3DE_AWS_DEPLOY_ACCOUNT"]}/{self._cdk_env["O3DE_AWS_DEPLOY_REGION"]}']
|
||||
|
||||
process_utils.check_call(
|
||||
bootstrap_cmd,
|
||||
cwd=self._cdk_path,
|
||||
env=self._cdk_env,
|
||||
shell=True)
|
||||
|
||||
def list(self) -> List[str]:
|
||||
"""
|
||||
lists cdk stack names
|
||||
:return List of cdk stack names
|
||||
"""
|
||||
|
||||
if not self._cdk_path:
|
||||
return []
|
||||
|
||||
@@ -123,6 +138,38 @@ class Cdk:
|
||||
self._stacks = []
|
||||
self._cdk_path = ''
|
||||
|
||||
@staticmethod
|
||||
def remove_bootstrap_stack(aws_utils: pytest.fixture) -> None:
|
||||
"""
|
||||
Remove the CDK bootstrap stack.
|
||||
:param aws_utils: aws_utils fixture.
|
||||
"""
|
||||
# Check if the bootstrap stack exists.
|
||||
response = aws_utils.client('cloudformation').describe_stacks(
|
||||
StackName=BOOTSTRAP_STACK_NAME
|
||||
)
|
||||
stacks = response.get('Stacks', [])
|
||||
if not stacks:
|
||||
return
|
||||
|
||||
# Clear the bootstrap staging bucket before deleting the bootstrap stack.
|
||||
response = aws_utils.client('cloudformation').describe_stack_resource(
|
||||
StackName=BOOTSTRAP_STACK_NAME,
|
||||
LogicalResourceId=BOOTSTRAP_STAGING_BUCKET_LOGIC_ID
|
||||
)
|
||||
|
||||
staging_bucket_name = response.get('StackResourceDetail', {}).get('PhysicalResourceId', '')
|
||||
if staging_bucket_name:
|
||||
s3 = aws_utils.resource('s3')
|
||||
bucket = s3.Bucket(staging_bucket_name)
|
||||
for key in bucket.objects.all():
|
||||
key.delete()
|
||||
|
||||
# Delete the bootstrap stack.
|
||||
aws_utils.client('cloudformation').delete_stack(
|
||||
StackName=BOOTSTRAP_STACK_NAME
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def cdk(
|
||||
@@ -131,6 +178,7 @@ def cdk(
|
||||
feature_name: str,
|
||||
workspace: pytest.fixture,
|
||||
aws_utils: pytest.fixture,
|
||||
bootstrap_required: bool = True,
|
||||
destroy_stacks_on_teardown: bool = True) -> Cdk:
|
||||
"""
|
||||
Fixture for setting up a Cdk
|
||||
@@ -140,6 +188,8 @@ def cdk(
|
||||
:param feature_name: Feature gem name to expect cdk folder in.
|
||||
:param workspace: ly_test_tools workspace fixture.
|
||||
:param aws_utils: aws_utils fixture.
|
||||
:param bootstrap_required: Whether the bootstrap stack needs to be created to
|
||||
provision resources the AWS CDK needs to perform the deployment.
|
||||
:param destroy_stacks_on_teardown: option to control calling destroy ot the end of test.
|
||||
:return Cdk class object.
|
||||
"""
|
||||
@@ -147,9 +197,14 @@ def cdk(
|
||||
cdk_path = f'{workspace.paths.engine_root()}/Gems/{feature_name}/cdk'
|
||||
cdk_obj = Cdk(cdk_path, project, aws_utils.assume_account_id(), workspace, aws_utils.assume_session())
|
||||
|
||||
if bootstrap_required:
|
||||
cdk_obj.bootstrap()
|
||||
|
||||
def teardown():
|
||||
if destroy_stacks_on_teardown:
|
||||
cdk_obj.destroy()
|
||||
cdk_obj.remove_bootstrap_stack(aws_utils)
|
||||
|
||||
request.addfinalizer(teardown)
|
||||
|
||||
return cdk_obj
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
+5
-5
@@ -162,7 +162,7 @@ def bundler_batch_setup_fixture(request, workspace, asset_processor, timeout) ->
|
||||
else:
|
||||
cmd.append(f"--{key}")
|
||||
if append_defaults:
|
||||
cmd.append(f"--project={workspace.project}")
|
||||
cmd.append(f"--project-path={workspace.project}")
|
||||
return cmd
|
||||
|
||||
# ******
|
||||
@@ -300,9 +300,9 @@ def bundler_batch_setup_fixture(request, workspace, asset_processor, timeout) ->
|
||||
workspace.paths.engine_root(),
|
||||
"Code",
|
||||
"Framework",
|
||||
"AzFramework",
|
||||
"AzFramework",
|
||||
"Platform",
|
||||
"AzCore",
|
||||
"AzCore",
|
||||
"PlatformId",
|
||||
"PlatformDefaults.h",
|
||||
)
|
||||
|
||||
@@ -318,7 +318,7 @@ def bundler_batch_setup_fixture(request, workspace, asset_processor, timeout) ->
|
||||
if start_gathering:
|
||||
result = get_platform.match(line) # Try the regex
|
||||
if result:
|
||||
platform_values[result.group(1).lower()] = counter
|
||||
platform_values[result.group(1).replace("_ID", "").lower()] = counter
|
||||
counter = counter << 1
|
||||
elif "(Invalid, -1)" in line: # The line right before the first platform
|
||||
start_gathering = True
|
||||
|
||||
@@ -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()
|
||||
|
||||
+9
@@ -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
|
||||
|
||||
+123
-15
@@ -80,6 +80,8 @@ class TestsAssetBundlerBatch_WindowsAndMac(object):
|
||||
def test_WindowsAndMac_RunHelpCmd_ZeroExitCode(self, workspace, bundler_batch_helper):
|
||||
"""
|
||||
Simple calls to all AssetBundlerBatch --help to make sure a non-zero exit codes are returned.
|
||||
|
||||
Test will call each Asset Bundler Batch sub-command with help and will error on a non-0 exit code
|
||||
"""
|
||||
bundler_batch_helper.call_bundlerbatch(help="")
|
||||
bundler_batch_helper.call_seeds(help="")
|
||||
@@ -98,6 +100,12 @@ class TestsAssetBundlerBatch_WindowsAndMac(object):
|
||||
r"""
|
||||
Tests that an asset list created maps dependencies correctly.
|
||||
testdependencieslevel\level.pak and lists of known dependencies are used for validation
|
||||
|
||||
Test Steps:
|
||||
1. Create an asset list from the level.pak
|
||||
2. Create Lists of expected assets in the level.pak
|
||||
3. Add lists of expected assets to a single list
|
||||
4. Compare list of expected assets to actual assets
|
||||
"""
|
||||
helper = bundler_batch_helper
|
||||
|
||||
@@ -300,9 +308,18 @@ class TestsAssetBundlerBatch_WindowsAndMac(object):
|
||||
"""
|
||||
Validates destructive overwriting for asset lists and
|
||||
that generating debug information does not affect asset list creation
|
||||
|
||||
1. Create an asset list from seed_list
|
||||
2. Validate asset list was created
|
||||
3. Read and store contents of asset list into memory
|
||||
4. Attempt to create a new asset list in without using --allowOverwrites
|
||||
5. Verify that Asset Bundler returns false
|
||||
6. Verify that file contents of the orignally created asset list did not change from what was stored in memory
|
||||
7. Attempt to create a new asset list without debug while allowing overwrites
|
||||
8. Verify that file contents of the orignally created asset list changed from what was stored in memory
|
||||
"""
|
||||
helper = bundler_batch_helper
|
||||
seed_list = os.path.join(workspace.paths.engine_root(), "Engine", "SeedAssetList.seed") # Engine seed list
|
||||
seed_list = os.path.join(workspace.paths.engine_root(), "Assets", "Engine", "SeedAssetList.seed") # Engine seed list
|
||||
asset = r"levels\testdependencieslevel\level.pak"
|
||||
|
||||
# Create Asset list
|
||||
@@ -375,9 +392,17 @@ class TestsAssetBundlerBatch_WindowsAndMac(object):
|
||||
"""
|
||||
Validates bundle creation both through the 'bundles' and 'bundlesettings'
|
||||
subcommands.
|
||||
|
||||
Test Steps:
|
||||
1. Create an asset list
|
||||
2. Create a bundle with the asset list and without a bundle settings file
|
||||
3. Create a bundle with the asset list and a bundle settings file
|
||||
4. Validate calling bundle doesn't perform destructive overwrite without --allowOverwrites
|
||||
5. Calling bundle again with --alowOverwrites performs destructive overwrite
|
||||
6. Validate contents of original bundle and overwritten bundle
|
||||
"""
|
||||
helper = bundler_batch_helper
|
||||
seed_list = os.path.join(workspace.paths.engine_root(), "Engine", "SeedAssetList.seed") # Engine seed list
|
||||
seed_list = os.path.join(workspace.paths.engine_root(), "Assets", "Engine", "SeedAssetList.seed") # Engine seed list
|
||||
asset = r"levels\testdependencieslevel\level.pak"
|
||||
|
||||
# Useful bundle locations / names (2 for comparing contents)
|
||||
@@ -457,6 +482,16 @@ class TestsAssetBundlerBatch_WindowsAndMac(object):
|
||||
"""
|
||||
Creates bundles using the same asset list and compares that they are created equally. Also
|
||||
validates that platform bundles exclude/include an expected file. (excluded for WIN, included for MAC)
|
||||
|
||||
Test Steps:
|
||||
1. Create an asset list
|
||||
2. Create bundles for both PC & Mac
|
||||
3. Validate that bundles were created
|
||||
4. Verify that expected missing file is not in windows bundle
|
||||
5. Verify that expected file is in the mac bundle
|
||||
6. Create duplicate bundles with allowOverwrites
|
||||
7. Verify that files were generated
|
||||
8. Verify original bundle checksums are equal to new bundle checksums
|
||||
"""
|
||||
helper = bundler_batch_helper
|
||||
# fmt:off
|
||||
@@ -465,7 +500,7 @@ class TestsAssetBundlerBatch_WindowsAndMac(object):
|
||||
"Please rerun with commandline option: '--bundle_platforms=pc,mac'"
|
||||
# fmt:on
|
||||
|
||||
seed_list = os.path.join(workspace.paths.engine_root(), "Engine", "SeedAssetList.seed") # Engine seed list
|
||||
seed_list = os.path.join(workspace.paths.engine_root(), "Assets", "Engine", "SeedAssetList.seed") # Engine seed list
|
||||
|
||||
# Useful bundle / asset list locations
|
||||
bundle_dir = os.path.dirname(helper["bundle_file"])
|
||||
@@ -502,13 +537,13 @@ class TestsAssetBundlerBatch_WindowsAndMac(object):
|
||||
for bundle_file in bundle_files.values():
|
||||
assert os.path.isfile(bundle_file)
|
||||
|
||||
# This asset is created on mac platform but not on windows
|
||||
file_to_check = b"engineassets/shading/defaultprobe_cm.dds.5" # [use byte str because file is in binary]
|
||||
# This asset is created both on mac and windows platform
|
||||
file_to_check = b"engineassets/shading/defaultprobe_cm_ibldiffuse.tif.streamingimage" # [use byte str because file is in binary]
|
||||
|
||||
# Extract the delta catalog file from pc archive. {file_to_check} SHOULD NOT be present for PC
|
||||
file_contents = helper.extract_file_content(bundle_files["pc"], "DeltaCatalog.xml")
|
||||
# fmt:off
|
||||
assert file_to_check not in file_contents, \
|
||||
assert file_to_check in file_contents, \
|
||||
f"{file_to_check} was found in DeltaCatalog.xml in pc bundle file {bundle_files['pc']}"
|
||||
# fmt:on
|
||||
|
||||
@@ -571,6 +606,24 @@ class TestsAssetBundlerBatch_WindowsAndMac(object):
|
||||
"""
|
||||
Validates that the 'seeds' subcommand can add and remove seeds and seed platforms properly.
|
||||
Also checks that destructive overwrites require the --allowOverwrites flag
|
||||
|
||||
Test Steps:
|
||||
|
||||
1. Create a PC Seed List from a test asset
|
||||
2. Validate that seed list was generated with proper platform flag
|
||||
3. Add Mac & PC as platforms to the seed list
|
||||
4. Verify that seed has both Mac & PC platform flags
|
||||
5. Remove Mac as a platform from the seed list
|
||||
6. Verify that seed only has PC as a platform flag
|
||||
7. Attempt to add a platform without using the --platform argument
|
||||
8. Verify that asset bundler returns False and file contents did not change
|
||||
9. Add Mac platform via --addPlatformToSeeds
|
||||
10. Validate that seed has both Mac & PC platform flags
|
||||
11. Attempt to remove platform without specifying a platform
|
||||
12. Validate that seed has both Mac & PC platform flags
|
||||
13. Validate that seed list contents did not change
|
||||
14. Remove seed
|
||||
15. Validate that seed was removed from the seed list
|
||||
"""
|
||||
helper = bundler_batch_helper
|
||||
|
||||
@@ -619,7 +672,7 @@ class TestsAssetBundlerBatch_WindowsAndMac(object):
|
||||
# Validate both mac and pc are activated for seed
|
||||
# fmt:off
|
||||
check_seed_platform(helper["seed_list_file"], test_asset,
|
||||
helper["platform_values"]["pc"] + helper["platform_values"]["osx"])
|
||||
helper["platform_values"]["pc"] + helper["platform_values"]["mac"])
|
||||
# fmt:on
|
||||
|
||||
# Remove MAC platform
|
||||
@@ -651,7 +704,7 @@ class TestsAssetBundlerBatch_WindowsAndMac(object):
|
||||
# Validate Mac platform was added back on. Save file contents
|
||||
# fmt:off
|
||||
all_lines = check_seed_platform(helper["seed_list_file"], test_asset,
|
||||
helper["platform_values"]["pc"] + helper["platform_values"]["osx"])
|
||||
helper["platform_values"]["pc"] + helper["platform_values"]["mac"])
|
||||
# fmt:on
|
||||
|
||||
# Try to remove platform without specifying a platform to remove (should fail)
|
||||
@@ -692,6 +745,12 @@ class TestsAssetBundlerBatch_WindowsAndMac(object):
|
||||
"""
|
||||
Tests asset list comparison, both by file and by comparison type. Uses a set
|
||||
of controlled test assets to compare resulting output asset lists
|
||||
|
||||
1. Create comparison rules files
|
||||
2. Create seed files for different sets of test assets
|
||||
3. Create assetlist files for seed files
|
||||
4. Validate assetlists were created properly
|
||||
5. Compare using comparison rules files and just command line arguments
|
||||
"""
|
||||
helper = bundler_batch_helper
|
||||
env = ap_setup_fixture
|
||||
@@ -1021,6 +1080,16 @@ class TestsAssetBundlerBatch_WindowsAndMac(object):
|
||||
"""
|
||||
Tests that assetlists are created equivalent to the output while being created, and
|
||||
makes sure overwriting an existing file without the --allowOverwrites fails
|
||||
|
||||
Test Steps:
|
||||
1. Check that Asset List creation requires PC platform flag
|
||||
2. Create a PC Asset List using asset info file and default seed lists using --print
|
||||
3. Validate all assets output are present in the asset list
|
||||
4. Create a seed file
|
||||
5. Attempt to overwrite Asset List without using --allowOverwrites
|
||||
6. Validate that command returned an error and file contents did not change
|
||||
7. Specifying platform but not "add" or "remove" should fail
|
||||
8. Verify file Has changed
|
||||
"""
|
||||
helper = bundler_batch_helper
|
||||
|
||||
@@ -1046,7 +1115,7 @@ class TestsAssetBundlerBatch_WindowsAndMac(object):
|
||||
"--addDefaultSeedListFiles",
|
||||
"--platform=pc",
|
||||
"--print",
|
||||
f"--project={workspace.project}"
|
||||
f"--project-path={workspace.project}"
|
||||
],
|
||||
universal_newlines=True,
|
||||
)
|
||||
@@ -1102,7 +1171,16 @@ class TestsAssetBundlerBatch_WindowsAndMac(object):
|
||||
def test_WindowsAndMac_AP_BundleProcessing_BundleProcessedAtRuntime(self, workspace, bundler_batch_helper,
|
||||
asset_processor, request):
|
||||
# fmt:on
|
||||
"""Test to make sure the AP GUI will process a newly created bundle file"""
|
||||
"""
|
||||
Test to make sure the AP GUI will process a newly created bundle file
|
||||
|
||||
Test Steps:
|
||||
1. Make asset list file (used for bundle creation)
|
||||
2. Start Asset Processor GUI
|
||||
3. Make bundle in <project_folder>/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'],
|
||||
|
||||
+17
@@ -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]:
|
||||
|
||||
+9
@@ -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()
|
||||
|
||||
+155
@@ -100,6 +100,14 @@ class TestsAssetProcessorBatch_AllPlatforms(object):
|
||||
@pytest.mark.BAT
|
||||
@pytest.mark.assetpipeline
|
||||
def test_RunAPBatch_TwoPlatforms_ExitCodeZero(self, asset_processor):
|
||||
"""
|
||||
Tests Process assets for PC & Mac and verifies that processing exited without error
|
||||
|
||||
Test Steps:
|
||||
1. Add Mac and PC as enabled platforms
|
||||
2. Process Assets
|
||||
3. Validate that AP exited cleanly
|
||||
"""
|
||||
asset_processor.create_temp_asset_root()
|
||||
asset_processor.enable_asset_processor_platform("pc")
|
||||
asset_processor.enable_asset_processor_platform("mac")
|
||||
@@ -111,6 +119,14 @@ class TestsAssetProcessorBatch_AllPlatforms(object):
|
||||
@pytest.mark.assetpipeline
|
||||
@pytest.mark.test_case_id('C1571826')
|
||||
def test_RunAPBatch_OnlyIncludeInvalidAssets_NoAssetsAdded(self, asset_processor, ap_setup_fixture):
|
||||
"""
|
||||
Tests processing invalid assets and validating that no assets were moved to the cache
|
||||
|
||||
Test Steps:
|
||||
1. Create a test environment with invalid assets
|
||||
2. Run asset processor
|
||||
3. Validate that no assets were found in the cache
|
||||
"""
|
||||
asset_processor.prepare_test_environment(ap_setup_fixture["tests_dir"], "test_ProcessAssets_OnlyIncludeInvalidAssets_NoAssetsAdded")
|
||||
|
||||
result, _ = asset_processor.batch_process()
|
||||
@@ -127,6 +143,16 @@ class TestsAssetProcessorBatch_AllPlatforms(object):
|
||||
"recognized as failing in the logs. There appears to be a window where the AutoFailJob doesn't complete"
|
||||
"before the shutdown completes and the failure doesn't end up counting")
|
||||
def test_ProcessAssets_IncludeTwoAssetsWithSameProduct_FailingOnSecondAsset(self, asset_processor, ap_setup_fixture):
|
||||
"""
|
||||
Tests processing two source assets with the same product file and validates that the second source will error
|
||||
|
||||
Test Steps:
|
||||
1. Create a test environment that has two source files with the same product
|
||||
2. Run asset processor
|
||||
3. Validate that 1 asset failed to process
|
||||
4. Validate that only one product file with the expected name is found in the cache
|
||||
"""
|
||||
|
||||
asset_processor.prepare_test_environment(ap_setup_fixture["tests_dir"], "test_ProcessAssets_IncludeTwoAssetsWithSameProduct_FailingOnSecondAsset")
|
||||
result, output = asset_processor.batch_process(capture_output = True, expect_failure = True)
|
||||
|
||||
@@ -143,6 +169,17 @@ class TestsAssetProcessorBatch_AllPlatforms(object):
|
||||
@pytest.mark.assetpipeline
|
||||
@pytest.mark.test_case_id('C1587615')
|
||||
def test_ProcessAndDeleteCache_APBatchShouldReprocess(self, asset_processor, ap_setup_fixture):
|
||||
"""
|
||||
Tests processing once, deleting the generated cache, then processing again and validates the cache is created
|
||||
|
||||
Test Steps:
|
||||
1. Run asset processor
|
||||
2. Compare the cache with expected output
|
||||
3. Delete Cache
|
||||
4. Compare the cache with expected output to verify that cache is gone
|
||||
5. Run asset processor with fastscan disabled
|
||||
6. Compare the cache with expected output
|
||||
"""
|
||||
# Deleting assets from Cache will make them re-processed in AP (after start)
|
||||
|
||||
# Copying test assets to project folder and deleting them from cache to make sure APBatch will process them
|
||||
@@ -174,6 +211,18 @@ class TestsAssetProcessorBatch_AllPlatforms(object):
|
||||
@pytest.mark.assetpipeline
|
||||
@pytest.mark.test_case_id('C1591564')
|
||||
def test_ProcessAndChangeSource_APBatchShouldReprocess(self, asset_processor, ap_setup_fixture):
|
||||
"""
|
||||
Tests reprocessing of a modified asset and verifies that it was reprocessed
|
||||
|
||||
Test Steps:
|
||||
1. Prepare test environment and copy test asset over
|
||||
2. Run asset processor
|
||||
3. Verify asset processed
|
||||
4. Verify asset is in cache
|
||||
4. Modify asset
|
||||
5. Re-run asset processor
|
||||
6. Verify asset was processed
|
||||
"""
|
||||
# AP Batch Processing changed files (after start)
|
||||
|
||||
# Copying test assets to project folder and deleting them from cache to make sure APBatch will process them
|
||||
@@ -208,6 +257,18 @@ class TestsAssetProcessorBatch_AllPlatforms(object):
|
||||
@pytest.mark.BAT
|
||||
@pytest.mark.assetpipeline
|
||||
def test_ProcessByBothApAndBatch_Md5ShouldMatch(self, asset_processor, ap_setup_fixture):
|
||||
"""
|
||||
Tests that a cache generated by AP GUI is the same as AP Batch
|
||||
|
||||
Test Steps:
|
||||
1. Create test environment with test assets
|
||||
2. Call asset processor batch
|
||||
3. Get checksum for file cache
|
||||
4. Clean up test environment
|
||||
5. Call asset processor gui with quitonidle
|
||||
6. Get checksum for file cache
|
||||
7. Verify that checksums are equal
|
||||
"""
|
||||
# AP Batch and AP app processed assets MD5 sums should be the same
|
||||
|
||||
# Copying test assets to project folder and deleting them from cache to make sure APBatch will process them
|
||||
@@ -240,6 +301,16 @@ class TestsAssetProcessorBatch_AllPlatforms(object):
|
||||
@pytest.mark.assetpipeline
|
||||
@pytest.mark.test_case_id('C1612446')
|
||||
def test_AddSameAssetsDifferentNames_ShouldProcess(self, asset_processor, ap_setup_fixture):
|
||||
"""
|
||||
Tests Asset Processing of duplicate assets with different names and verifies that both assets are processed
|
||||
|
||||
Test Steps:
|
||||
1. Create test environment with two identical source assets with different names
|
||||
2. Run asset processor
|
||||
3. Verify that assets didn't fail to process
|
||||
4. Verify the correct number of jobs were performed
|
||||
5. Verify that product files are in the cache
|
||||
"""
|
||||
# Feed two similar slices and texture with different names - should process without any issues
|
||||
|
||||
# Copying test assets to project folder and deleting them from cache to make sure APBatch will process them
|
||||
@@ -277,6 +348,19 @@ class TestsAssetProcessorBatch_AllPlatforms(object):
|
||||
"recognized as failing in the logs. There appears to be a window where the AutoFailJob doesn't complete"
|
||||
"before the shutdown completes and the failure doesn't end up counting")
|
||||
def test_AddTwoTexturesWithSameName_ShouldProcessAfterRename(self, asset_processor, ap_setup_fixture):
|
||||
"""
|
||||
Tests processing of two textures with the same name then verifies that AP will successfully process after
|
||||
renaming one of the textures
|
||||
|
||||
Test Steps:
|
||||
1. Create test environment with two textures that have the same name
|
||||
2. Launch Asset Processor
|
||||
3. Validate that Asset Processor generates an error
|
||||
4. Rename texture files
|
||||
5. Run asset processor
|
||||
6. Verify that asset processor does not error
|
||||
7. Verify that expected product files are in the cache
|
||||
"""
|
||||
# Feed two different textures with same name (but different extensions) - ap will fail
|
||||
# Rename one of textures and failure should go away
|
||||
|
||||
@@ -312,6 +396,15 @@ class TestsAssetProcessorBatch_AllPlatforms(object):
|
||||
@pytest.mark.BAT
|
||||
@pytest.mark.assetpipeline
|
||||
def test_InvalidServerAddress_Warning_Logs(self, asset_processor):
|
||||
"""
|
||||
Tests running Asset Processor with an invalid server address and verifies that AP returns a warning about
|
||||
an invalid server address
|
||||
|
||||
Test Steps:
|
||||
1. Launch asset processor while providing an invalid server address
|
||||
2. Verify asset processor does not fail
|
||||
3. Verify that asset processor generated a warning informing the user about an invalid server address
|
||||
"""
|
||||
|
||||
asset_processor.create_temp_asset_root()
|
||||
# Launching AP and making sure that the warning exists
|
||||
@@ -327,6 +420,12 @@ class TestsAssetProcessorBatch_AllPlatforms(object):
|
||||
def test_AllSupportedPlatforms_IncludeValidAssets_AssetsProcessed(self, asset_processor, ap_setup_fixture):
|
||||
"""
|
||||
AssetProcessorBatch is successfully processing newly added assets
|
||||
|
||||
Test Steps:
|
||||
1. Create a test environment with test assets
|
||||
2. Launch Asset Processor
|
||||
3. Verify that asset processor does not fail to process
|
||||
4. Verify assets are not missing from the cache
|
||||
"""
|
||||
env = ap_setup_fixture
|
||||
|
||||
@@ -350,6 +449,14 @@ class TestsAssetProcessorBatch_AllPlatforms(object):
|
||||
def test_AllSupportedPlatforms_DeletedAssets_DeletedFromCache(self, asset_processor, ap_setup_fixture):
|
||||
"""
|
||||
AssetProcessor successfully deletes cached items when removed from project
|
||||
|
||||
Test Steps:
|
||||
1. Create a test environment with test assets
|
||||
2. Run asset processor
|
||||
3. Verify expected assets are in the cache
|
||||
4. Delete test assets
|
||||
5. Run asset processor
|
||||
6. Verify expected assets are in the cache
|
||||
"""
|
||||
env = ap_setup_fixture
|
||||
|
||||
@@ -385,6 +492,10 @@ class TestsAssetProcessorBatch_AllPlatforms(object):
|
||||
"""
|
||||
Tests that when cache is deleted (no cache) and AssetProcessorBatch runs,
|
||||
it successfully starts and processes assets.
|
||||
|
||||
Test Steps:
|
||||
1. Run asset processor
|
||||
2. Verify asset processor exits cleanly
|
||||
"""
|
||||
asset_processor.create_temp_asset_root()
|
||||
|
||||
@@ -402,6 +513,14 @@ class TestsAssetProcessorBatch_AllPlatforms(object):
|
||||
# fmt:on
|
||||
"""
|
||||
AssetProcessor successfully recovers assets from cache when deleted.
|
||||
|
||||
Test Steps:
|
||||
1. Create test enviornment with test assets
|
||||
2. Run Asset Processor and verify it exits cleanly
|
||||
3. Make sure cache folder was generated
|
||||
4. Delete temp cache assets but leave database behind
|
||||
5. Run asset processor and verify it exits cleanly
|
||||
6. Verify expected files were generated in the cache
|
||||
"""
|
||||
env = ap_setup_fixture
|
||||
|
||||
@@ -434,6 +553,14 @@ class TestsAssetProcessorBatch_AllPlatforms(object):
|
||||
@pytest.mark.assetpipeline
|
||||
# fmt:off
|
||||
def test_AllSupportedPlatforms_RunFastScanOnEmptyCache_FullScanRuns(self, ap_setup_fixture, asset_processor):
|
||||
"""
|
||||
Tests fast scan processing on an empty cache and verifies that a full analyis will be peformed
|
||||
|
||||
Test Steps:
|
||||
1. Create a test environment
|
||||
2. Execute asset processor batch with fast scan enabled
|
||||
3. Verify that a full analysis is performed
|
||||
"""
|
||||
# fmt:on
|
||||
env = ap_setup_fixture
|
||||
asset_processor.create_temp_asset_root()
|
||||
@@ -455,6 +582,11 @@ class TestsAssetProcessorBatch_AllPlatforms(object):
|
||||
"""
|
||||
After running the APBatch and AP GUI, Logs directory should exist (C1564055),
|
||||
JobLogs, Batch log, and GUI log should exist in the logs directory (C1564056)
|
||||
|
||||
Test Steps:
|
||||
1. Run asset processor batch
|
||||
2. Run asset processor gui with quit on idle
|
||||
3. Verify that logs exist for both AP Batch & AP GUI
|
||||
"""
|
||||
asset_processor.create_temp_asset_root()
|
||||
LOG_PATH = {
|
||||
@@ -536,6 +668,11 @@ class TestsAssetProcessorBatch_AllPlatforms(object):
|
||||
"""
|
||||
Utilizing corrupted test assets, run the batch process to verify the
|
||||
AP logs the failure to process the corrupted file.
|
||||
|
||||
Test Steps:
|
||||
1. Create test environment with corrupted slice
|
||||
2. Launch Asset Processor
|
||||
3. Verify that asset processor fails to process corrupted slice
|
||||
"""
|
||||
env = ap_setup_fixture
|
||||
error_line_found = False
|
||||
@@ -552,6 +689,15 @@ class TestsAssetProcessorBatch_AllPlatforms(object):
|
||||
@pytest.mark.BAT
|
||||
@pytest.mark.assetpipeline
|
||||
def test_validateDirectPreloadDependency_Found(self, asset_processor, ap_setup_fixture, workspace):
|
||||
"""
|
||||
Tests processing an asset with a circular dependency and verifies that Asset Processor will return an error
|
||||
notifying the user about a circular dependency.
|
||||
|
||||
Test Steps:
|
||||
1. Create test environment with an asset that has a circular dependency
|
||||
2. Launch asset processor
|
||||
3. Verify that error is returned informing the user that the asset has a circular dependency
|
||||
"""
|
||||
env = ap_setup_fixture
|
||||
error_line_found = False
|
||||
|
||||
@@ -567,6 +713,15 @@ class TestsAssetProcessorBatch_AllPlatforms(object):
|
||||
@pytest.mark.BAT
|
||||
@pytest.mark.assetpipeline
|
||||
def test_validateNestedPreloadDependency_Found(self, asset_processor, ap_setup_fixture, workspace):
|
||||
"""
|
||||
Tests processing of a nested circular dependency and verifies that Asset Processor will return an error
|
||||
notifying the user about a circular depdency
|
||||
|
||||
Test Steps:
|
||||
1. Create test environment with an asset that has a nested circular dependency
|
||||
2. Launch asset processor
|
||||
3. Verify that error is returned informing the user that the asset has a circular dependency
|
||||
"""
|
||||
|
||||
env = ap_setup_fixture
|
||||
error_line_found = False
|
||||
|
||||
+33
-64
@@ -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 ' \
|
||||
|
||||
+75
-4
@@ -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
|
||||
|
||||
+47
-1
@@ -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
|
||||
|
||||
|
||||
+89
-3
@@ -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)
|
||||
|
||||
+129
-13
@@ -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
|
||||
|
||||
+10
@@ -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()
|
||||
|
||||
@@ -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 '"
|
||||
|
||||
+172
-37
@@ -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')
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
+131
@@ -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)
|
||||
+210
@@ -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)
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"])
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:804193a2afd68cd1e6bec8155ea11400566f2941fbd6eb0c324839ebcd10192d
|
||||
size 8492
|
||||
oid sha256:302d6172156e8ed665e44e206d81f54f1b0f1008d73327300ea92f8c1159780b
|
||||
size 11820
|
||||
|
||||
@@ -119,6 +119,9 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"DefaultMaterial": {
|
||||
"SurfaceType": "Default_1"
|
||||
},
|
||||
"MaterialLibrary": {
|
||||
"assetId": {
|
||||
"guid": "{3A055A3F-8CB7-5FEE-B437-EB365FACD0D4}"
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -107,6 +107,9 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"DefaultMaterial": {
|
||||
"SurfaceType": "Default_1"
|
||||
},
|
||||
"MaterialLibrary": {
|
||||
"assetId": {
|
||||
"guid": "{3A055A3F-8CB7-5FEE-B437-EB365FACD0D4}"
|
||||
|
||||
+3
@@ -119,6 +119,9 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"DefaultMaterial": {
|
||||
"SurfaceType": "Default_1"
|
||||
},
|
||||
"MaterialLibrary": {
|
||||
"assetId": {
|
||||
"guid": "{3A055A3F-8CB7-5FEE-B437-EB365FACD0D4}"
|
||||
|
||||
@@ -119,6 +119,9 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"DefaultMaterial": {
|
||||
"SurfaceType": "Default_1"
|
||||
},
|
||||
"MaterialLibrary": {
|
||||
"assetId": {
|
||||
"guid": "{3A055A3F-8CB7-5FEE-B437-EB365FACD0D4}"
|
||||
|
||||
@@ -119,6 +119,9 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"DefaultMaterial": {
|
||||
"SurfaceType": "Default_1"
|
||||
},
|
||||
"MaterialLibrary": {
|
||||
"assetId": {
|
||||
"guid": "{3A055A3F-8CB7-5FEE-B437-EB365FACD0D4}"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
{
|
||||
"AWSCore":
|
||||
{
|
||||
"ProfileName": "default",
|
||||
"ProfileName": "AWSAutomationTest",
|
||||
"ResourceMappingConfigFileName": "aws_resource_mappings.json"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,6 +101,9 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"DefaultMaterial": {
|
||||
"SurfaceType": "Default_1"
|
||||
},
|
||||
"MaterialLibrary": {
|
||||
"assetId": {
|
||||
"guid": "{3A055A3F-8CB7-5FEE-B437-EB365FACD0D4}"
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:40949893ed7009eeaa90b7ce6057cb6be9dfaf7b162e3c26ba9dadf985939d7d
|
||||
size 2038
|
||||
oid sha256:b9cd9d6f67440c193a85969ec5c082c6343e6d1fff3b6f209a0a6931eb22dd47
|
||||
size 2949
|
||||
|
||||
@@ -1,19 +1,155 @@
|
||||
<ObjectStream version="3">
|
||||
<Class name="MaterialLibraryAsset" version="2" type="{9E366D8C-33BB-4825-9A1F-FA3ADBE11D0F}">
|
||||
<Class name="Physics::MaterialLibraryAsset" version="2" type="{9E366D8C-33BB-4825-9A1F-FA3ADBE11D0F}">
|
||||
<Class name="AssetData" field="BaseClass1" version="1" type="{AF3F7D32-1536-422A-89F3-A11E1F5B5A9C}"/>
|
||||
<Class name="AZStd::vector" field="Properties" type="{A8E59F8C-2F9A-525A-B549-A9E197EB9632}">
|
||||
<Class name="MaterialFromAssetConfiguration" field="element" version="1" type="{FBD76628-DE57-435E-BE00-6FFAE64DDF1D}">
|
||||
<Class name="MaterialConfiguration" field="Configuration" version="2" type="{8807CAA1-AD08-4238-8FDB-2154ADD084A1}">
|
||||
<Class name="AZStd::string" field="SurfaceType" value="" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
|
||||
<Class name="float" field="DynamicFriction" value="0.0000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
|
||||
<Class name="float" field="StaticFriction" value="0.0000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
|
||||
<Class name="float" field="Restitution" value="0.0000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
|
||||
<Class name="Physics::MaterialFromAssetConfiguration" field="element" version="1" type="{FBD76628-DE57-435E-BE00-6FFAE64DDF1D}">
|
||||
<Class name="Physics::MaterialConfiguration" field="Configuration" version="3" type="{8807CAA1-AD08-4238-8FDB-2154ADD084A1}">
|
||||
<Class name="AZStd::string" field="SurfaceType" value="Character" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
|
||||
<Class name="float" field="DynamicFriction" value="0.7000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
|
||||
<Class name="float" field="StaticFriction" value="0.8000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
|
||||
<Class name="float" field="Restitution" value="0.3000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
|
||||
<Class name="unsigned char" field="FrictionCombine" value="2" type="{72B9409A-7D1A-4831-9CFE-FCB3FADD3426}"/>
|
||||
<Class name="unsigned char" field="RestitutionCombine" value="3" type="{72B9409A-7D1A-4831-9CFE-FCB3FADD3426}"/>
|
||||
<Class name="float" field="Density" value="985.0000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
|
||||
<Class name="Color" field="DebugColor" value="0.9183642 0.6973526 0.4447700 1.0000000" type="{7894072A-9050-4F0F-901B-34B1A0D29417}"/>
|
||||
</Class>
|
||||
<Class name="Physics::MaterialId" field="UID" version="1" type="{744CCE6C-9F69-4E2F-B950-DAB8514F870B}">
|
||||
<Class name="AZ::Uuid" field="MaterialId" value="{FDECD8B6-5BAF-42CB-AEFE-C66E1E1CF557}" type="{E152C105-A133-4D03-BBF8-3D4B2FBA3E2A}"/>
|
||||
</Class>
|
||||
</Class>
|
||||
<Class name="Physics::MaterialFromAssetConfiguration" field="element" version="1" type="{FBD76628-DE57-435E-BE00-6FFAE64DDF1D}">
|
||||
<Class name="Physics::MaterialConfiguration" field="Configuration" version="3" type="{8807CAA1-AD08-4238-8FDB-2154ADD084A1}">
|
||||
<Class name="AZStd::string" field="SurfaceType" value="Concrete" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
|
||||
<Class name="float" field="DynamicFriction" value="0.8000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
|
||||
<Class name="float" field="StaticFriction" value="0.9000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
|
||||
<Class name="float" field="Restitution" value="0.3800000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
|
||||
<Class name="unsigned char" field="FrictionCombine" value="3" type="{72B9409A-7D1A-4831-9CFE-FCB3FADD3426}"/>
|
||||
<Class name="unsigned char" field="RestitutionCombine" value="0" type="{72B9409A-7D1A-4831-9CFE-FCB3FADD3426}"/>
|
||||
<Class name="float" field="Density" value="2400.0000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
|
||||
<Class name="Color" field="DebugColor" value="0.5918365 0.4927596 0.3795224 1.0000000" type="{7894072A-9050-4F0F-901B-34B1A0D29417}"/>
|
||||
</Class>
|
||||
<Class name="Physics::MaterialId" field="UID" version="1" type="{744CCE6C-9F69-4E2F-B950-DAB8514F870B}">
|
||||
<Class name="AZ::Uuid" field="MaterialId" value="{A9CACCFF-E0D2-4149-8891-E92319229B2D}" type="{E152C105-A133-4D03-BBF8-3D4B2FBA3E2A}"/>
|
||||
</Class>
|
||||
</Class>
|
||||
<Class name="Physics::MaterialFromAssetConfiguration" field="element" version="1" type="{FBD76628-DE57-435E-BE00-6FFAE64DDF1D}">
|
||||
<Class name="Physics::MaterialConfiguration" field="Configuration" version="3" type="{8807CAA1-AD08-4238-8FDB-2154ADD084A1}">
|
||||
<Class name="AZStd::string" field="SurfaceType" value="Glass" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
|
||||
<Class name="float" field="DynamicFriction" value="0.4000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
|
||||
<Class name="float" field="StaticFriction" value="0.5000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
|
||||
<Class name="float" field="Restitution" value="0.7000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
|
||||
<Class name="unsigned char" field="FrictionCombine" value="3" type="{72B9409A-7D1A-4831-9CFE-FCB3FADD3426}"/>
|
||||
<Class name="unsigned char" field="RestitutionCombine" value="0" type="{72B9409A-7D1A-4831-9CFE-FCB3FADD3426}"/>
|
||||
<Class name="float" field="Density" value="2500.0000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
|
||||
<Class name="Color" field="DebugColor" value="0.4825971 0.8975662 0.9523766 1.0000000" type="{7894072A-9050-4F0F-901B-34B1A0D29417}"/>
|
||||
</Class>
|
||||
<Class name="Physics::MaterialId" field="UID" version="1" type="{744CCE6C-9F69-4E2F-B950-DAB8514F870B}">
|
||||
<Class name="AZ::Uuid" field="MaterialId" value="{FD59CBE9-D1C4-4119-81CB-CD7AD72FC295}" type="{E152C105-A133-4D03-BBF8-3D4B2FBA3E2A}"/>
|
||||
</Class>
|
||||
</Class>
|
||||
<Class name="Physics::MaterialFromAssetConfiguration" field="element" version="1" type="{FBD76628-DE57-435E-BE00-6FFAE64DDF1D}">
|
||||
<Class name="Physics::MaterialConfiguration" field="Configuration" version="3" type="{8807CAA1-AD08-4238-8FDB-2154ADD084A1}">
|
||||
<Class name="AZStd::string" field="SurfaceType" value="Metal" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
|
||||
<Class name="float" field="DynamicFriction" value="0.4200000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
|
||||
<Class name="float" field="StaticFriction" value="0.7800000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
|
||||
<Class name="float" field="Restitution" value="0.4000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
|
||||
<Class name="unsigned char" field="FrictionCombine" value="0" type="{72B9409A-7D1A-4831-9CFE-FCB3FADD3426}"/>
|
||||
<Class name="unsigned char" field="RestitutionCombine" value="0" type="{72B9409A-7D1A-4831-9CFE-FCB3FADD3426}"/>
|
||||
<Class name="Color" field="DebugColor" value="1.0000000 1.0000000 1.0000000 1.0000000" type="{7894072A-9050-4F0F-901B-34B1A0D29417}"/>
|
||||
<Class name="float" field="Density" value="8050.0000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
|
||||
<Class name="Color" field="DebugColor" value="0.2312963 0.2312963 0.2312963 1.0000000" type="{7894072A-9050-4F0F-901B-34B1A0D29417}"/>
|
||||
</Class>
|
||||
<Class name="MaterialId" field="UID" version="1" type="{744CCE6C-9F69-4E2F-B950-DAB8514F870B}">
|
||||
<Class name="AZ::Uuid" field="MaterialId" value="{B072A405-BAFA-4B0A-9164-B3A424E642A9}" type="{E152C105-A133-4D03-BBF8-3D4B2FBA3E2A}"/>
|
||||
<Class name="Physics::MaterialId" field="UID" version="1" type="{744CCE6C-9F69-4E2F-B950-DAB8514F870B}">
|
||||
<Class name="AZ::Uuid" field="MaterialId" value="{76CDC778-ACA9-449F-BFD7-C361F89F3207}" type="{E152C105-A133-4D03-BBF8-3D4B2FBA3E2A}"/>
|
||||
</Class>
|
||||
</Class>
|
||||
<Class name="Physics::MaterialFromAssetConfiguration" field="element" version="1" type="{FBD76628-DE57-435E-BE00-6FFAE64DDF1D}">
|
||||
<Class name="Physics::MaterialConfiguration" field="Configuration" version="3" type="{8807CAA1-AD08-4238-8FDB-2154ADD084A1}">
|
||||
<Class name="AZStd::string" field="SurfaceType" value="Plastic" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
|
||||
<Class name="float" field="DynamicFriction" value="0.3500000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
|
||||
<Class name="float" field="StaticFriction" value="0.3000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
|
||||
<Class name="float" field="Restitution" value="0.6900000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
|
||||
<Class name="unsigned char" field="FrictionCombine" value="3" type="{72B9409A-7D1A-4831-9CFE-FCB3FADD3426}"/>
|
||||
<Class name="unsigned char" field="RestitutionCombine" value="0" type="{72B9409A-7D1A-4831-9CFE-FCB3FADD3426}"/>
|
||||
<Class name="float" field="Density" value="900.0000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
|
||||
<Class name="Color" field="DebugColor" value="0.9394675 1.0000000 0.2735485 1.0000000" type="{7894072A-9050-4F0F-901B-34B1A0D29417}"/>
|
||||
</Class>
|
||||
<Class name="Physics::MaterialId" field="UID" version="1" type="{744CCE6C-9F69-4E2F-B950-DAB8514F870B}">
|
||||
<Class name="AZ::Uuid" field="MaterialId" value="{E2FFB000-D15B-4760-A819-9E490D1D3741}" type="{E152C105-A133-4D03-BBF8-3D4B2FBA3E2A}"/>
|
||||
</Class>
|
||||
</Class>
|
||||
<Class name="Physics::MaterialFromAssetConfiguration" field="element" version="1" type="{FBD76628-DE57-435E-BE00-6FFAE64DDF1D}">
|
||||
<Class name="Physics::MaterialConfiguration" field="Configuration" version="3" type="{8807CAA1-AD08-4238-8FDB-2154ADD084A1}">
|
||||
<Class name="AZStd::string" field="SurfaceType" value="Rubber" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
|
||||
<Class name="float" field="DynamicFriction" value="1.0000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
|
||||
<Class name="float" field="StaticFriction" value="1.0000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
|
||||
<Class name="float" field="Restitution" value="0.8500000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
|
||||
<Class name="unsigned char" field="FrictionCombine" value="3" type="{72B9409A-7D1A-4831-9CFE-FCB3FADD3426}"/>
|
||||
<Class name="unsigned char" field="RestitutionCombine" value="0" type="{72B9409A-7D1A-4831-9CFE-FCB3FADD3426}"/>
|
||||
<Class name="float" field="Density" value="1200.0000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
|
||||
<Class name="Color" field="DebugColor" value="0.1088426 0.1088426 0.1088426 1.0000000" type="{7894072A-9050-4F0F-901B-34B1A0D29417}"/>
|
||||
</Class>
|
||||
<Class name="Physics::MaterialId" field="UID" version="1" type="{744CCE6C-9F69-4E2F-B950-DAB8514F870B}">
|
||||
<Class name="AZ::Uuid" field="MaterialId" value="{8C7A6011-61C2-46B7-9BF4-8D4DD2A624F1}" type="{E152C105-A133-4D03-BBF8-3D4B2FBA3E2A}"/>
|
||||
</Class>
|
||||
</Class>
|
||||
<Class name="Physics::MaterialFromAssetConfiguration" field="element" version="1" type="{FBD76628-DE57-435E-BE00-6FFAE64DDF1D}">
|
||||
<Class name="Physics::MaterialConfiguration" field="Configuration" version="3" type="{8807CAA1-AD08-4238-8FDB-2154ADD084A1}">
|
||||
<Class name="AZStd::string" field="SurfaceType" value="Terrain_Dirt" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
|
||||
<Class name="float" field="DynamicFriction" value="0.4000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
|
||||
<Class name="float" field="StaticFriction" value="0.4000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
|
||||
<Class name="float" field="Restitution" value="0.3000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
|
||||
<Class name="unsigned char" field="FrictionCombine" value="0" type="{72B9409A-7D1A-4831-9CFE-FCB3FADD3426}"/>
|
||||
<Class name="unsigned char" field="RestitutionCombine" value="3" type="{72B9409A-7D1A-4831-9CFE-FCB3FADD3426}"/>
|
||||
<Class name="float" field="Density" value="1600.0000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
|
||||
<Class name="Color" field="DebugColor" value="0.3333333 0.2619974 0.1973144 1.0000000" type="{7894072A-9050-4F0F-901B-34B1A0D29417}"/>
|
||||
</Class>
|
||||
<Class name="Physics::MaterialId" field="UID" version="1" type="{744CCE6C-9F69-4E2F-B950-DAB8514F870B}">
|
||||
<Class name="AZ::Uuid" field="MaterialId" value="{303C5A49-22F2-45A8-B24C-9F2C3CA13402}" type="{E152C105-A133-4D03-BBF8-3D4B2FBA3E2A}"/>
|
||||
</Class>
|
||||
</Class>
|
||||
<Class name="Physics::MaterialFromAssetConfiguration" field="element" version="1" type="{FBD76628-DE57-435E-BE00-6FFAE64DDF1D}">
|
||||
<Class name="Physics::MaterialConfiguration" field="Configuration" version="3" type="{8807CAA1-AD08-4238-8FDB-2154ADD084A1}">
|
||||
<Class name="AZStd::string" field="SurfaceType" value="Terrain_Grass" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
|
||||
<Class name="float" field="DynamicFriction" value="0.2500000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
|
||||
<Class name="float" field="StaticFriction" value="0.3500000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
|
||||
<Class name="float" field="Restitution" value="0.2000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
|
||||
<Class name="unsigned char" field="FrictionCombine" value="3" type="{72B9409A-7D1A-4831-9CFE-FCB3FADD3426}"/>
|
||||
<Class name="unsigned char" field="RestitutionCombine" value="0" type="{72B9409A-7D1A-4831-9CFE-FCB3FADD3426}"/>
|
||||
<Class name="float" field="Density" value="1400.0000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
|
||||
<Class name="Color" field="DebugColor" value="0.1483177 0.5986419 0.1073777 1.0000000" type="{7894072A-9050-4F0F-901B-34B1A0D29417}"/>
|
||||
</Class>
|
||||
<Class name="Physics::MaterialId" field="UID" version="1" type="{744CCE6C-9F69-4E2F-B950-DAB8514F870B}">
|
||||
<Class name="AZ::Uuid" field="MaterialId" value="{53733840-A095-40C4-B653-C40D233B3BE1}" type="{E152C105-A133-4D03-BBF8-3D4B2FBA3E2A}"/>
|
||||
</Class>
|
||||
</Class>
|
||||
<Class name="Physics::MaterialFromAssetConfiguration" field="element" version="1" type="{FBD76628-DE57-435E-BE00-6FFAE64DDF1D}">
|
||||
<Class name="Physics::MaterialConfiguration" field="Configuration" version="3" type="{8807CAA1-AD08-4238-8FDB-2154ADD084A1}">
|
||||
<Class name="AZStd::string" field="SurfaceType" value="Vehicle" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
|
||||
<Class name="float" field="DynamicFriction" value="0.2000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
|
||||
<Class name="float" field="StaticFriction" value="0.5000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
|
||||
<Class name="float" field="Restitution" value="0.3000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
|
||||
<Class name="unsigned char" field="FrictionCombine" value="1" type="{72B9409A-7D1A-4831-9CFE-FCB3FADD3426}"/>
|
||||
<Class name="unsigned char" field="RestitutionCombine" value="1" type="{72B9409A-7D1A-4831-9CFE-FCB3FADD3426}"/>
|
||||
<Class name="float" field="Density" value="140.0000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
|
||||
<Class name="Color" field="DebugColor" value="1.0000000 0.0000000 0.0000000 1.0000000" type="{7894072A-9050-4F0F-901B-34B1A0D29417}"/>
|
||||
</Class>
|
||||
<Class name="Physics::MaterialId" field="UID" version="1" type="{744CCE6C-9F69-4E2F-B950-DAB8514F870B}">
|
||||
<Class name="AZ::Uuid" field="MaterialId" value="{4080A6D4-AF4E-41CE-B7C9-7699C07123E7}" type="{E152C105-A133-4D03-BBF8-3D4B2FBA3E2A}"/>
|
||||
</Class>
|
||||
</Class>
|
||||
<Class name="Physics::MaterialFromAssetConfiguration" field="element" version="1" type="{FBD76628-DE57-435E-BE00-6FFAE64DDF1D}">
|
||||
<Class name="Physics::MaterialConfiguration" field="Configuration" version="3" type="{8807CAA1-AD08-4238-8FDB-2154ADD084A1}">
|
||||
<Class name="AZStd::string" field="SurfaceType" value="Wood" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
|
||||
<Class name="float" field="DynamicFriction" value="0.5000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
|
||||
<Class name="float" field="StaticFriction" value="0.6000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
|
||||
<Class name="float" field="Restitution" value="0.6000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
|
||||
<Class name="unsigned char" field="FrictionCombine" value="3" type="{72B9409A-7D1A-4831-9CFE-FCB3FADD3426}"/>
|
||||
<Class name="unsigned char" field="RestitutionCombine" value="0" type="{72B9409A-7D1A-4831-9CFE-FCB3FADD3426}"/>
|
||||
<Class name="float" field="Density" value="540.0000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
|
||||
<Class name="Color" field="DebugColor" value="1.0000000 0.7318379 0.3004501 1.0000000" type="{7894072A-9050-4F0F-901B-34B1A0D29417}"/>
|
||||
</Class>
|
||||
<Class name="Physics::MaterialId" field="UID" version="1" type="{744CCE6C-9F69-4E2F-B950-DAB8514F870B}">
|
||||
<Class name="AZ::Uuid" field="MaterialId" value="{6ACE67AA-CB32-41CD-8740-58371CCCD3F3}" type="{E152C105-A133-4D03-BBF8-3D4B2FBA3E2A}"/>
|
||||
</Class>
|
||||
</Class>
|
||||
</Class>
|
||||
|
||||
+63
-69
@@ -25,34 +25,13 @@ include(cmake/LySet.cmake)
|
||||
include(cmake/Version.cmake)
|
||||
include(cmake/OutputDirectory.cmake)
|
||||
|
||||
# Set the engine_path and engine_json
|
||||
set(o3de_engine_path ${CMAKE_CURRENT_LIST_DIR})
|
||||
set(o3de_engine_json ${o3de_engine_path}/engine.json)
|
||||
|
||||
if(NOT PROJECT_NAME)
|
||||
project(O3DE
|
||||
LANGUAGES C CXX
|
||||
VERSION ${LY_VERSION_STRING}
|
||||
)
|
||||
|
||||
# o3de manifest
|
||||
include(cmake/o3de_manifest.cmake)
|
||||
endif()
|
||||
|
||||
################################################################################
|
||||
# Resolve this engines name and restricted path
|
||||
################################################################################
|
||||
o3de_engine_name(${o3de_engine_json} o3de_engine_name)
|
||||
o3de_restricted_path(${o3de_engine_json} o3de_engine_restricted_path)
|
||||
message(STATUS "O3DE Engine Name: ${o3de_engine_name}")
|
||||
message(STATUS "O3DE Engine Path: ${o3de_engine_path}")
|
||||
if(o3de_engine_restricted_path)
|
||||
message(STATUS "O3DE Engine Restricted Path: ${o3de_engine_restricted_path}")
|
||||
endif()
|
||||
|
||||
# add the engines cmake folder to the CMAKE_MODULE_PATH
|
||||
list(APPEND CMAKE_MODULE_PATH "${o3de_engine_path}/cmake")
|
||||
|
||||
################################################################################
|
||||
# Initialize
|
||||
################################################################################
|
||||
@@ -60,6 +39,7 @@ include(cmake/GeneralSettings.cmake)
|
||||
include(cmake/FileUtil.cmake)
|
||||
include(cmake/PAL.cmake)
|
||||
include(cmake/PALTools.cmake)
|
||||
include(cmake/RuntimeDependencies.cmake)
|
||||
include(cmake/Install.cmake)
|
||||
include(cmake/Configurations.cmake) # Requires to be after PAL so we get platform variable definitions
|
||||
include(cmake/Dependencies.cmake)
|
||||
@@ -67,92 +47,106 @@ include(cmake/Deployment.cmake)
|
||||
include(cmake/3rdParty.cmake)
|
||||
include(cmake/LYPython.cmake)
|
||||
include(cmake/LYWrappers.cmake)
|
||||
include(cmake/Gems.cmake)
|
||||
include(cmake/UnitTest.cmake)
|
||||
include(cmake/LYTestWrappers.cmake)
|
||||
include(cmake/Monolithic.cmake)
|
||||
include(cmake/SettingsRegistry.cmake)
|
||||
include(cmake/TestImpactFramework/LYTestImpactFramework.cmake)
|
||||
include(cmake/CMakeFiles.cmake)
|
||||
include(cmake/O3DEJson.cmake)
|
||||
|
||||
################################################################################
|
||||
# Subdirectory processing
|
||||
################################################################################
|
||||
|
||||
function(add_engine_json_external_subdirectories)
|
||||
read_json_external_subdirs(external_subdis ${LY_ROOT_FOLDER}/engine.json)
|
||||
foreach(external_subdir ${external_subdis})
|
||||
file(REAL_PATH ${external_subdir} real_external_subdir BASE_DIRECTORY ${LY_ROOT_FOLDER})
|
||||
list(APPEND engine_external_subdirs ${real_external_subdir})
|
||||
endforeach()
|
||||
|
||||
set_property(GLOBAL APPEND PROPERTY LY_EXTERNAL_SUBDIRS ${engine_external_subdirs})
|
||||
endfunction()
|
||||
|
||||
# Add the projects first so the Launcher can find them
|
||||
include(cmake/Projects.cmake)
|
||||
|
||||
if(NOT INSTALLED_ENGINE)
|
||||
|
||||
# Add the rest of the targets
|
||||
add_subdirectory(Code)
|
||||
add_subdirectory(scripts)
|
||||
|
||||
# SPEC-1417 will investigate and fix this
|
||||
if(NOT PAL_PLATFORM_NAME STREQUAL "Mac")
|
||||
add_subdirectory(Tools/LyTestTools/tests/)
|
||||
add_subdirectory(Tools/RemoteConsole/ly_remote_console/tests/)
|
||||
endif()
|
||||
|
||||
# Add external subdirectories listed in the engine.json. LY_EXTERNAL_SUBDIRS is a cache variable so the user can add extra
|
||||
# external subdirectories
|
||||
add_engine_json_external_subdirectories()
|
||||
get_property(external_subdirs GLOBAL PROPERTY LY_EXTERNAL_SUBDIRS)
|
||||
list(APPEND LY_EXTERNAL_SUBDIRS ${external_subdirs})
|
||||
|
||||
# Loop over the additional external subdirectories and invoke add_subdirectory on them
|
||||
foreach(external_directory ${LY_EXTERNAL_SUBDIRS})
|
||||
# Hash the extenal_directory name and append it to the Binary Directory section of add_subdirectory
|
||||
# This is to deal with potential situations where multiple external directories has the same last directory name
|
||||
# For example if D:/Company1/RayTracingGem and F:/Company2/Path/RayTracingGem were both added as a subdirectory
|
||||
file(REAL_PATH ${external_directory} full_directory_path)
|
||||
string(SHA256 full_directory_hash ${full_directory_path})
|
||||
# Truncate the full_directory_hash down to 8 characters to avoid hitting the Windows 260 character path limit
|
||||
# when the external subdirectory contains relative paths of significant length
|
||||
string(SUBSTRING ${full_directory_hash} 0 8 full_directory_hash)
|
||||
# Use the last directory as the suffix path to use for the Binary Directory
|
||||
get_filename_component(directory_name ${external_directory} NAME)
|
||||
add_subdirectory(${external_directory} ${CMAKE_BINARY_DIR}/External/${directory_name}-${full_directory_hash})
|
||||
endforeach()
|
||||
|
||||
else()
|
||||
ly_find_o3de_packages()
|
||||
endif()
|
||||
|
||||
# Add external subdirectories listed in the manifest
|
||||
list(APPEND LY_EXTERNAL_SUBDIRS ${o3de_engine_external_subdirectories})
|
||||
|
||||
set(enabled_platforms
|
||||
${PAL_PLATFORM_NAME}
|
||||
${LY_PAL_TOOLS_ENABLED})
|
||||
|
||||
# Add any engine restricted platforms as external subdirs
|
||||
o3de_add_engine_restricted_platform_external_subdirs()
|
||||
|
||||
if(NOT INSTALLED_ENGINE)
|
||||
add_subdirectory(scripts)
|
||||
endif()
|
||||
|
||||
# SPEC-1417 will investigate and fix this
|
||||
if(NOT PAL_PLATFORM_NAME STREQUAL "Mac")
|
||||
add_subdirectory(Tools/LyTestTools/tests/)
|
||||
add_subdirectory(Tools/RemoteConsole/ly_remote_console/tests/)
|
||||
endif()
|
||||
|
||||
################################################################################
|
||||
# Post-processing
|
||||
################################################################################
|
||||
|
||||
# Loop over the additional external subdirectories and invoke add_subdirectory on them
|
||||
foreach(external_directory ${LY_EXTERNAL_SUBDIRS})
|
||||
# Hash the extenal_directory name and append it to the Binary Directory section of add_subdirectory
|
||||
# This is to deal with potential situations where multiple external directories has the same last directory name
|
||||
# For example if D:/Company1/RayTracingGem and F:/Company2/Path/RayTracingGem were both added as a subdirectory
|
||||
file(REAL_PATH ${external_directory} full_directory_path)
|
||||
string(SHA256 full_directory_hash ${full_directory_path})
|
||||
# Truncate the full_directory_hash down to 8 characters to avoid hitting the Windows 260 character path limit
|
||||
# when the external subdirectory contains relative paths of significant length
|
||||
string(SUBSTRING ${full_directory_hash} 0 8 full_directory_hash)
|
||||
# Use the last directory as the suffix path to use for the Binary Directory
|
||||
get_filename_component(directory_name ${external_directory} NAME)
|
||||
add_subdirectory(${external_directory} ${CMAKE_BINARY_DIR}/External/${directory_name}-${full_directory_hash})
|
||||
endforeach()
|
||||
|
||||
# The following steps have to be done after all targets are registered:
|
||||
# Defer generation of the StaticModules.inl file which is needed to create the AZ::Module derived class in monolithic
|
||||
# builds until after all the targets are known
|
||||
ly_delayed_generate_static_modules_inl()
|
||||
|
||||
# 1. generate a settings registry .setreg file for all ly_add_project_dependencies() and ly_add_target_dependencies() calls
|
||||
# 1. Add any dependencies registered via ly_enable_gems
|
||||
ly_enable_gems_delayed()
|
||||
|
||||
# 2. generate a settings registry .setreg file for all ly_add_project_dependencies() and ly_add_target_dependencies() calls
|
||||
# to provide applications with the filenames of gem modules to load
|
||||
# This must be done before ly_delayed_target_link_libraries() as that inserts BUILD_DEPENDENCIES as MANUALLY_ADDED_DEPENDENCIES
|
||||
# if the build dependency is a MODULE_LIBRARY. That would cause a false load dependency to be generated
|
||||
ly_delayed_generate_settings_registry()
|
||||
# 2. link targets where the dependency was yet not declared, we need to have the declaration so we do different
|
||||
|
||||
# 3. link targets where the dependency was yet not declared, we need to have the declaration so we do different
|
||||
# linking logic depending on the type of target
|
||||
ly_delayed_target_link_libraries()
|
||||
# 3. generate a registry file for unit testing for platforms that support unit testing
|
||||
|
||||
# 4. generate a registry file for unit testing for platforms that support unit testing
|
||||
if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
|
||||
ly_delayed_generate_unit_test_module_registry()
|
||||
endif()
|
||||
# 4. inject runtime dependencies to the targets. We need to do this after (1) since we are going to walk through
|
||||
# the dependencies
|
||||
include(cmake/RuntimeDependencies.cmake)
|
||||
# 5. Perform test impact framework post steps once all of the targets have been enumerated
|
||||
ly_test_impact_post_step()
|
||||
# 6. Generate the O3DE find file and setup install locations for scripts, tools, assets etc., required by the engine
|
||||
if(NOT INSTALLED_ENGINE)
|
||||
ly_setup_o3de_install()
|
||||
|
||||
# IMPORTANT: must be included last
|
||||
# 5. inject runtime dependencies to the targets. We need to do this after (1) since we are going to walk through
|
||||
# the dependencies
|
||||
ly_delayed_generate_runtime_dependencies()
|
||||
|
||||
# 6. Perform test impact framework post steps once all of the targets have been enumerated
|
||||
ly_test_impact_post_step()
|
||||
|
||||
# 7. Generate the O3DE find file and setup install locations for scripts, tools, assets etc., required by the engine
|
||||
if(NOT INSTALLED_ENGINE)
|
||||
# 8. Generate the O3DE find file and setup install locations for scripts, tools, assets etc., required by the engine
|
||||
ly_setup_o3de_install()
|
||||
# 9. CPack information (to be included after install)
|
||||
include(cmake/Packaging.cmake)
|
||||
endif()
|
||||
|
||||
@@ -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())
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -77,6 +77,27 @@
|
||||
#endif // defined(AZ_ENABLE_DEBUG_TOOLS)
|
||||
|
||||
#include <AzCore/Module/Environment.h>
|
||||
#include <AzCore/std/string/conversions.h>
|
||||
|
||||
static void PrintEntityName(const AZ::ConsoleCommandContainer& arguments)
|
||||
{
|
||||
if (arguments.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto entityIdStr = AZStd::string(arguments.front());
|
||||
const auto entityIdValue = AZStd::stoull(entityIdStr);
|
||||
|
||||
AZStd::string entityName;
|
||||
AZ::ComponentApplicationBus::BroadcastResult(
|
||||
entityName, &AZ::ComponentApplicationBus::Events::GetEntityName, AZ::EntityId(entityIdValue));
|
||||
|
||||
AZ_Printf("Entity Debug", "EntityId: %" PRIu64 ", Entity Name: %s", entityIdValue, entityName.c_str());
|
||||
}
|
||||
|
||||
AZ_CONSOLEFREEFUNC(
|
||||
PrintEntityName, AZ::ConsoleFunctorFlags::Null, "Parameter: EntityId value, Prints the name of the entity to the console");
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
@@ -1260,7 +1281,7 @@ namespace AZ
|
||||
// So auto load is turned off if option "AutoLoad" key is bool that is false
|
||||
if (valueName == "AutoLoad" && !value)
|
||||
{
|
||||
// Strip off the AutoLoead entry from the path
|
||||
// Strip off the AutoLoad entry from the path
|
||||
auto autoLoadKey = AZ::StringFunc::TokenizeLast(path, "/");
|
||||
if (!autoLoadKey)
|
||||
{
|
||||
@@ -1330,7 +1351,7 @@ namespace AZ
|
||||
{
|
||||
auto CompareDynamicModuleDescriptor = [&dynamicLibraryPath](const DynamicModuleDescriptor& entry)
|
||||
{
|
||||
return entry.m_dynamicLibraryPath.contains(dynamicLibraryPath);
|
||||
return AZ::IO::PathView(entry.m_dynamicLibraryPath).Stem() == AZ::IO::PathView(dynamicLibraryPath).Stem();
|
||||
};
|
||||
if (auto moduleIter = AZStd::find_if(gemModules.begin(), gemModules.end(), CompareDynamicModuleDescriptor);
|
||||
moduleIter == gemModules.end())
|
||||
|
||||
@@ -219,18 +219,11 @@ namespace AZ
|
||||
|
||||
//! Scale modifiers
|
||||
//! @{
|
||||
//! Set local scale of the transform.
|
||||
//! @param scale The new scale to set.
|
||||
virtual void SetLocalScale([[maybe_unused]] const AZ::Vector3& scale) {}
|
||||
|
||||
//! Get the scale value in local space.
|
||||
//! @deprecated GetLocalScale is deprecated, and is left only to allow migration of legacy vector scale.
|
||||
//! Get the legacy vector scale value in local space.
|
||||
//! @return The scale value in local space.
|
||||
virtual AZ::Vector3 GetLocalScale() { return AZ::Vector3(FLT_MAX); }
|
||||
|
||||
//! Get the scale value in world space.
|
||||
//! @return The scale value in world space.
|
||||
virtual AZ::Vector3 GetWorldScale() { return AZ::Vector3(FLT_MAX); }
|
||||
|
||||
//! Set the uniform scale value in local space.
|
||||
virtual void SetLocalUniformScale([[maybe_unused]] float scale) {}
|
||||
|
||||
|
||||
@@ -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<MaxPathLength> 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"
|
||||
|
||||
@@ -915,6 +915,11 @@ namespace AZ::IO
|
||||
return compare_string_view(path);
|
||||
}
|
||||
|
||||
constexpr AZStd::fixed_string<MaxPathLength> PathView::FixedMaxPathString() const noexcept
|
||||
{
|
||||
return AZStd::fixed_string<MaxPathLength>(m_path.begin(), m_path.end());
|
||||
}
|
||||
|
||||
// decomposition
|
||||
constexpr auto PathView::RootName() const -> PathView
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,8 +130,8 @@ namespace AZ
|
||||
const Transform* transform = reinterpret_cast<const Transform*>(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<float, dataBufferSize> 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<void*>(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<Transform*>(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<Transform*>(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<Transform>()
|
||||
->Version(1)
|
||||
->Version(2)
|
||||
->Serializer<TransformSerializer>();
|
||||
}
|
||||
|
||||
@@ -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<const Vector3&, const Quaternion&, const Vector3&>()->
|
||||
Constructor<const Vector3&, const Quaternion&, float>()->
|
||||
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<void (Transform::*)(const Quaternion&)>("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;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ namespace AZ
|
||||
|
||||
result.Combine(loadResult);
|
||||
|
||||
transformInstance->SetScale(AZ::Vector3(scale));
|
||||
transformInstance->SetUniformScale(scale);
|
||||
}
|
||||
|
||||
return context.Report(
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<EngineInfo> m_enginePaths{};
|
||||
};
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -44,7 +44,7 @@ namespace JsonSerializationTests
|
||||
AZStd::shared_ptr<AZ::Transform> CreateFullySetInstance() override
|
||||
{
|
||||
return AZStd::make_shared<AZ::Transform>(
|
||||
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 })");
|
||||
|
||||
@@ -406,21 +406,10 @@ namespace AzFramework
|
||||
return m_localTM.GetRotation();
|
||||
}
|
||||
|
||||
void TransformComponent::SetLocalScale(const AZ::Vector3& scale)
|
||||
{
|
||||
AZ::Transform newLocalTM = m_localTM;
|
||||
newLocalTM.SetScale(scale);
|
||||
SetLocalTM(newLocalTM);
|
||||
}
|
||||
|
||||
AZ::Vector3 TransformComponent::GetLocalScale()
|
||||
{
|
||||
return m_localTM.GetScale();
|
||||
}
|
||||
|
||||
AZ::Vector3 TransformComponent::GetWorldScale()
|
||||
{
|
||||
return m_worldTM.GetScale();
|
||||
AZ_WarningOnce("TransformComponent", false, "GetLocalScale is deprecated, please use GetLocalUniformScale instead");
|
||||
return AZ::Vector3(m_localTM.GetUniformScale());
|
||||
}
|
||||
|
||||
void TransformComponent::SetLocalUniformScale(float scale)
|
||||
@@ -756,11 +745,11 @@ namespace AzFramework
|
||||
->Event("GetLocalRotationQuaternion", &AZ::TransformBus::Events::GetLocalRotationQuaternion)
|
||||
->Attribute("Rotation", AZ::Edit::Attributes::PropertyRotation)
|
||||
->VirtualProperty("Rotation", "GetLocalRotationQuaternion", "SetLocalRotationQuaternion")
|
||||
->Event("SetLocalScale", &AZ::TransformBus::Events::SetLocalScale)
|
||||
->Event("GetLocalScale", &AZ::TransformBus::Events::GetLocalScale)
|
||||
->Attribute("Scale", AZ::Edit::Attributes::PropertyScale)
|
||||
->VirtualProperty("Scale", "GetLocalScale", "SetLocalScale")
|
||||
->Event("GetWorldScale", &AZ::TransformBus::Events::GetWorldScale)
|
||||
->Event("SetLocalUniformScale", &AZ::TransformBus::Events::SetLocalUniformScale)
|
||||
->Event("GetLocalUniformScale", &AZ::TransformBus::Events::GetLocalUniformScale)
|
||||
->VirtualProperty("Uniform Scale", "GetLocalUniformScale", "SetLocalUniformScale")
|
||||
->Event("GetChildren", &AZ::TransformBus::Events::GetChildren)
|
||||
->Event("GetAllDescendants", &AZ::TransformBus::Events::GetAllDescendants)
|
||||
->Event("GetEntityAndAllDescendants", &AZ::TransformBus::Events::GetEntityAndAllDescendants)
|
||||
|
||||
@@ -128,9 +128,7 @@ namespace AzFramework
|
||||
AZ::Quaternion GetLocalRotationQuaternion() override;
|
||||
|
||||
// Scale Modifiers
|
||||
void SetLocalScale(const AZ::Vector3& scale) override;
|
||||
AZ::Vector3 GetLocalScale() override;
|
||||
AZ::Vector3 GetWorldScale() override;
|
||||
|
||||
void SetLocalUniformScale(float scale) override;
|
||||
float GetLocalUniformScale() override;
|
||||
|
||||
@@ -54,7 +54,7 @@ namespace AzFramework
|
||||
AZ::Matrix3x4 m_transform = AZ::Matrix3x4::Identity(); //!< Transform to apply to text quads
|
||||
bool m_monospace = false; //!< disable character proportional spacing
|
||||
bool m_depthTest = false; //!< Test character against the depth buffer
|
||||
bool m_virtual800x600ScreenSize = true; //!< Text placement and size are scaled relative to a virtual 800x600 resolution
|
||||
bool m_virtual800x600ScreenSize = false; //!< Text placement and size are scaled relative to a virtual 800x600 resolution
|
||||
bool m_scaleWithWindow = false; //!< Font gets bigger as the window gets bigger
|
||||
bool m_multiline = true; //!< text respects ascii newline characters
|
||||
};
|
||||
|
||||
@@ -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<Physics::MaterialId> { 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
|
||||
|
||||
@@ -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<AzPhysics::SceneHandle>;
|
||||
|
||||
//! 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<const AZ::Data::AssetId&>;
|
||||
using OnMaterialLibraryChangedEvent = AZ::Event<const AZ::Data::AssetId&>;
|
||||
|
||||
enum class MaterialLibraryLoadErrorType : uint8_t
|
||||
{
|
||||
InvalidId,
|
||||
ErrorLoading
|
||||
};
|
||||
|
||||
//! Event that triggers when the default material library has loaded with errors.
|
||||
using OnMaterialLibraryLoadErrorEvent = AZ::Event<MaterialLibraryLoadErrorType>;
|
||||
|
||||
//! Event that triggers when the default scene configuration changes.
|
||||
//! When triggered the event will send the new default scene configuration.
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <AzCore/RTTI/RTTI.h>
|
||||
#include <AzCore/std/containers/variant.h>
|
||||
#include <AzCore/std/containers/vector.h>
|
||||
#include <AzCore/std/limits.h>
|
||||
#include <AzFramework/Physics/Common/PhysicsSimulatedBodyEvents.h>
|
||||
#include <AzFramework/Physics/Common/PhysicsSceneQueries.h>
|
||||
#include <AzFramework/Physics/Common/PhysicsTypes.h>
|
||||
@@ -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<uint32_t>::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();
|
||||
|
||||
+5
-1
@@ -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
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
#include <AzCore/RTTI/RTTI.h>
|
||||
#include <AzFramework/Physics/Configuration/CollisionConfiguration.h>
|
||||
#include <AzFramework/Physics/Material.h>
|
||||
|
||||
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<Physics::MaterialLibraryAsset> 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;
|
||||
|
||||
@@ -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<AZ::SerializeContext*>(context);
|
||||
if (serializeContext)
|
||||
{
|
||||
serializeContext->Class<Physics::MaterialLibraryAssetReflectionWrapper>()
|
||||
->Version(1)
|
||||
->Field("Asset", &MaterialLibraryAssetReflectionWrapper::m_asset)
|
||||
;
|
||||
|
||||
AZ::EditContext* editContext = serializeContext->GetEditContext();
|
||||
if (editContext)
|
||||
{
|
||||
editContext->Class<Physics::MaterialLibraryAssetReflectionWrapper>("", "")
|
||||
->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<AZ::SerializeContext*>(context);
|
||||
if (serializeContext)
|
||||
{
|
||||
serializeContext->Class<Physics::DefaultMaterialLibraryAssetReflectionWrapper>()
|
||||
serializeContext->Class<Physics::MaterialInfoReflectionWrapper>()
|
||||
->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<Physics::DefaultMaterialLibraryAssetReflectionWrapper>("", "")
|
||||
editContext->Class<Physics::MaterialInfoReflectionWrapper>("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<AZ::SerializeContext*>(context))
|
||||
{
|
||||
serializeContext->Class<Physics::MaterialSelection>()
|
||||
->Version(2, &ClassConverters::MaterialSelectionConverter)
|
||||
->Version(3, &ClassConverters::MaterialSelectionConverter)
|
||||
->EventHandler<MaterialSelectionEventHandler>()
|
||||
->Field("Material", &MaterialSelection::m_materialLibrary)
|
||||
->Field("MaterialIds", &MaterialSelection::m_materialIdsAssignedToSlots)
|
||||
;
|
||||
|
||||
@@ -381,14 +380,8 @@ namespace Physics
|
||||
editContext->Class<Physics::MaterialSelection>("Physics Material", "Select physics material library and which materials to use for the object")
|
||||
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
|
||||
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
|
||||
->DataElement(AZ::Edit::UIHandlers::Default, &MaterialSelection::m_materialLibrary, "Library", "Physics material library to use for this object")
|
||||
->Attribute(AZ::Edit::Attributes::ContainerCanBeModified, true)
|
||||
->Attribute("EditButton", "")
|
||||
->Attribute("EditDescription", "Open in Asset Editor")
|
||||
->Attribute(AZ::Edit::Attributes::DefaultAsset, &MaterialSelection::GetDefaultMaterialLibraryId)
|
||||
->Attribute(AZ::Edit::Attributes::ChangeNotify, &MaterialSelection::OnMaterialLibraryChanged)
|
||||
->DataElement(AZ::Edit::UIHandlers::Default, &MaterialSelection::m_materialIdsAssignedToSlots, "Mesh Surfaces", "Specify which Physics Material to use for each element of this object")
|
||||
->ElementAttribute(Attributes::MaterialLibraryAssetId, &MaterialSelection::GetMaterialLibraryAssetId)
|
||||
->ElementAttribute(Attributes::MaterialLibraryAssetId, &MaterialSelection::GetMaterialLibraryId)
|
||||
->Attribute(AZ::Edit::Attributes::IndexedChildNameLabelOverride, &MaterialSelection::GetMaterialSlotLabel)
|
||||
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
|
||||
->ElementAttribute(AZ::Edit::Attributes::ReadOnly, &MaterialSelection::AreMaterialSlotsReadOnly)
|
||||
@@ -398,12 +391,6 @@ namespace Physics
|
||||
}
|
||||
}
|
||||
|
||||
AZ::u32 MaterialSelection::OnMaterialLibraryChanged()
|
||||
{
|
||||
SyncSelectionToMaterialLibrary();
|
||||
return AZ::Edit::PropertyRefreshLevels::EntireTree;
|
||||
}
|
||||
|
||||
AZStd::string MaterialSelection::GetMaterialSlotLabel(int index)
|
||||
{
|
||||
if (index < m_materialSlots.size())
|
||||
@@ -425,28 +412,9 @@ namespace Physics
|
||||
}
|
||||
}
|
||||
|
||||
AZ::Data::AssetId MaterialSelection::GetMaterialLibraryAssetId() const
|
||||
void MaterialSelection::OnMaterialLibraryChanged([[maybe_unused]] const AZ::Data::AssetId& defaultMaterialLibraryId)
|
||||
{
|
||||
return GetMaterialLibraryAsset().GetId();
|
||||
}
|
||||
|
||||
const Physics::MaterialLibraryAsset* MaterialSelection::GetMaterialLibraryAssetData() const
|
||||
{
|
||||
return GetMaterialLibraryAsset().Get();
|
||||
}
|
||||
|
||||
const AZStd::string& MaterialSelection::GetMaterialLibraryAssetHint() const
|
||||
{
|
||||
return m_materialLibrary.GetHint();
|
||||
}
|
||||
|
||||
void MaterialSelection::OnDefaultMaterialLibraryChanged(const AZ::Data::AssetId& defaultMaterialLibraryId)
|
||||
{
|
||||
AZ_UNUSED(defaultMaterialLibraryId);
|
||||
if (IsDefaultMaterialLibraryAsset())
|
||||
{
|
||||
OnMaterialLibraryChanged();
|
||||
}
|
||||
SyncSelectionToMaterialLibrary();
|
||||
}
|
||||
|
||||
void MaterialSelection::SetSlotsReadOnly(bool readOnly)
|
||||
@@ -454,45 +422,6 @@ namespace Physics
|
||||
m_slotsReadOnly = readOnly;
|
||||
}
|
||||
|
||||
bool MaterialSelection::IsMaterialLibraryValid() const
|
||||
{
|
||||
if (GetMaterialLibraryAssetId().IsValid())
|
||||
{
|
||||
auto materialAsset = LoadAsset();
|
||||
const auto& materialsData = materialAsset.Get()->GetMaterialsData();
|
||||
|
||||
if (materialsData.size() != 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MaterialSelection::GetMaterialConfiguration(Physics::MaterialFromAssetConfiguration& configuration, const Physics::MaterialId& materialId) const
|
||||
{
|
||||
if (IsMaterialLibraryValid())
|
||||
{
|
||||
auto materialAsset = LoadAsset();
|
||||
if (materialAsset.Get())
|
||||
{
|
||||
return materialAsset.Get()->GetDataForMaterialId(materialId, configuration);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void MaterialSelection::SetMaterialLibrary(const AZ::Data::AssetId& assetId)
|
||||
{
|
||||
m_materialLibrary = AZ::Data::AssetManager::Instance().GetAsset<Physics::MaterialLibraryAsset>(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<Physics::MaterialLibraryAsset> MaterialSelection::LoadAsset() const
|
||||
{
|
||||
AZ::Data::Asset<MaterialLibraryAsset> asset = AZ::Data::AssetManager::Instance()
|
||||
.GetAsset<Physics::MaterialLibraryAsset>(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<Physics::MaterialLibraryAsset>(GetMaterialLibraryAssetId(), AZ::Data::AssetLoadBehavior::Default);
|
||||
return;
|
||||
}
|
||||
|
||||
materialLibraryAsset.BlockUntilLoadComplete();
|
||||
|
||||
// We try to check whether existing selection matches any materials in the newly assigned library and do one of the following:
|
||||
// 1. If previous MaterialId is invalid for this material library, and it is not the Default material, we set it to the Default material from the library.
|
||||
// 2. If it's valid, or it is the Default material, we don't change it (useful when user accidentally re-assigns the same library: previous selection won't go away).
|
||||
|
||||
if (materialLibraryAsset.Get())
|
||||
for (Physics::MaterialId& materialId : m_materialIdsAssignedToSlots)
|
||||
{
|
||||
// Leave nulls (default) unchanged.
|
||||
if (materialId.IsNull())
|
||||
{
|
||||
for (Physics::MaterialId& materialId : m_materialIdsAssignedToSlots)
|
||||
{
|
||||
if (!materialLibraryAsset.Get()->HasDataForMaterialId(materialId)
|
||||
&& !materialId.IsNull()) // Null materialId is the Default material.
|
||||
{
|
||||
materialId = MaterialId();
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
else
|
||||
|
||||
// If the material id is not present in the library anymore, set it to default
|
||||
if (!materialLibrary->HasDataForMaterialId(materialId))
|
||||
{
|
||||
AZ_Warning("PhysX", false, "MaterialSelection: invalid material library");
|
||||
materialId = MaterialId();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const AZ::Data::Asset<Physics::MaterialLibraryAsset>& MaterialSelection::GetMaterialLibraryAsset() const
|
||||
{
|
||||
if (IsDefaultMaterialLibraryAsset())
|
||||
{
|
||||
const AZ::Data::Asset<Physics::MaterialLibraryAsset>& defaultMaterialLibrary = GetDefaultMaterialLibrary();
|
||||
return defaultMaterialLibrary;
|
||||
}
|
||||
|
||||
return m_materialLibrary;
|
||||
}
|
||||
|
||||
bool MaterialSelection::IsDefaultMaterialLibraryAsset() const
|
||||
{
|
||||
return !m_materialLibrary.GetId().IsValid();
|
||||
}
|
||||
|
||||
const AZ::Data::Asset<Physics::MaterialLibraryAsset>& MaterialSelection::GetDefaultMaterialLibrary()
|
||||
const AZ::Data::Asset<Physics::MaterialLibraryAsset>& MaterialSelection::GetMaterialLibrary()
|
||||
{
|
||||
if (auto* physicsSystem = AZ::Interface<AzPhysics::SystemInterface>::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
|
||||
|
||||
@@ -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<Physics::Material> newMaterial = AZ::Interface<Physics::System>::Get()->CreateMaterial(materialProperties);
|
||||
///
|
||||
/// To get PxMaterial use GetNativePointer function
|
||||
///
|
||||
/// physx::PxMaterial* material = static_cast<physx::PxMaterial*>(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<MaterialFromAssetConfiguration> 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<Physics::MaterialLibraryAsset> m_asset =
|
||||
Physics::MaterialConfiguration m_defaultMaterialConfiguration;
|
||||
AZ::Data::Asset<Physics::MaterialLibraryAsset> 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<Physics::MaterialLibraryAsset> 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<Physics::MaterialId>& 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<Physics::MaterialLibraryAsset> m_materialLibrary { AZ::Data::AssetLoadBehavior::NoLoad };
|
||||
AZStd::vector<Physics::MaterialId> m_materialIdsAssignedToSlots;
|
||||
SlotsArray m_materialSlots;
|
||||
bool m_slotsReadOnly = false;
|
||||
|
||||
const AZ::Data::Asset<Physics::MaterialLibraryAsset>& GetMaterialLibraryAsset() const;
|
||||
AZ::Data::Asset<Physics::MaterialLibraryAsset> LoadAsset() const;
|
||||
bool IsDefaultMaterialLibraryAsset() const;
|
||||
void SyncSelectionToMaterialLibrary();
|
||||
|
||||
static const AZ::Data::Asset<Physics::MaterialLibraryAsset>& GetDefaultMaterialLibrary();
|
||||
static const AZ::Data::AssetId& GetDefaultMaterialLibraryId();
|
||||
static const AZ::Data::Asset<Physics::MaterialLibraryAsset>& GetMaterialLibrary();
|
||||
static const AZ::Data::AssetId& GetMaterialLibraryId();
|
||||
|
||||
bool AreMaterialSlotsReadOnly() const;
|
||||
|
||||
// EditorContext callbacks
|
||||
AZ::u32 OnMaterialLibraryChanged();
|
||||
AZStd::string GetMaterialSlotLabel(int index);
|
||||
};
|
||||
|
||||
|
||||
@@ -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<Physics::Material> 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<AZStd::weak_ptr<Physics::Material>>& outMaterials) = 0;
|
||||
, AZStd::vector<AZStd::shared_ptr<Physics::Material>>& outMaterials) = 0;
|
||||
|
||||
/// Returns a weak pointer to physics material with the given id.
|
||||
virtual AZStd::shared_ptr<Physics::Material> GetMaterialById(Physics::MaterialId id) = 0;
|
||||
|
||||
/// Returns a weak pointer to physics material with the given name.
|
||||
virtual AZStd::weak_ptr<Physics::Material> GetMaterialByName(const AZStd::string& name) = 0;
|
||||
virtual AZStd::shared_ptr<Physics::Material> 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<PhysicsMaterialRequests>;
|
||||
|
||||
|
||||
@@ -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<Physics::MaterialLibraryAsset>& materialLibrary) = 0;
|
||||
|
||||
//! Accessor to get the current Material Library. This is also available in the PhysXSystemConfiguration.
|
||||
virtual const AZ::Data::Asset<Physics::MaterialLibraryAsset>& GetDefaultMaterialLibrary() const = 0;
|
||||
|
||||
//! Update the current default scene configuration.
|
||||
//! This is the configuration used to to create scenes without a custom configuration.
|
||||
//! @param sceneConfiguration The new configuration to apply.
|
||||
@@ -169,9 +162,12 @@ namespace AzPhysics
|
||||
//! Register to receive notifications when the SystemConfiguration changes.
|
||||
//! @param handler The handler to receive the event.
|
||||
void RegisterSystemConfigurationChangedEvent(SystemEvents::OnConfigurationChangedEvent::Handler& handler) { handler.Connect(m_configChangeEvent); }
|
||||
//! Register a handler to receive an event when the default material library changes.
|
||||
//! Register a handler to receive an event when the material library changes.
|
||||
//! @param handler The handler to receive the event.
|
||||
void RegisterOnDefaultMaterialLibraryChangedEventHandler(SystemEvents::OnDefaultMaterialLibraryChangedEvent::Handler& handler) { handler.Connect(m_onDefaultMaterialLibraryChangedEvent); }
|
||||
void RegisterOnMaterialLibraryChangedEventHandler(SystemEvents::OnMaterialLibraryChangedEvent::Handler& handler) { handler.Connect(m_onMaterialLibraryChangedEvent); }
|
||||
//! Register a handler to receive an event when the material library fails to load on startup.
|
||||
//! @param handler The handler to receive the event.
|
||||
void RegisterOnMaterialLibraryLoadErrorEventHandler(SystemEvents::OnMaterialLibraryLoadErrorEvent::Handler& handler) { handler.Connect(m_onMaterialLibraryLoadErrorEvent); }
|
||||
//! Register a handler to receive an event when the default SceneConfiguration changes.
|
||||
//! @param handler The handler to receive the event.
|
||||
void RegisterOnDefaultSceneConfigurationChangedEventHandler(SystemEvents::OnDefaultSceneConfigurationChangedEvent::Handler& handler) { handler.Connect(m_onDefaultSceneConfigurationChangedEvent); }
|
||||
@@ -185,7 +181,8 @@ namespace AzPhysics
|
||||
SystemEvents::OnSceneAddedEvent m_sceneAddedEvent;
|
||||
SystemEvents::OnSceneRemovedEvent m_sceneRemovedEvent;
|
||||
SystemEvents::OnConfigurationChangedEvent m_configChangeEvent;
|
||||
SystemEvents::OnDefaultMaterialLibraryChangedEvent m_onDefaultMaterialLibraryChangedEvent;
|
||||
SystemEvents::OnMaterialLibraryChangedEvent m_onMaterialLibraryChangedEvent;
|
||||
SystemEvents::OnMaterialLibraryLoadErrorEvent m_onMaterialLibraryLoadErrorEvent;
|
||||
SystemEvents::OnDefaultSceneConfigurationChangedEvent m_onDefaultSceneConfigurationChangedEvent;
|
||||
};
|
||||
} // namespace AzPhysics
|
||||
|
||||
@@ -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<AZ::SerializeContext*>(context))
|
||||
@@ -166,10 +181,9 @@ namespace Physics
|
||||
->RegisterGenericType<AZStd::shared_ptr<PhysicsAssetShapeConfiguration>>();
|
||||
|
||||
serializeContext->Class<PhysicsAssetShapeConfiguration, ShapeConfiguration>()
|
||||
->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")
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,7 +140,7 @@ namespace Physics
|
||||
|
||||
AZ::Data::Asset<AZ::Data::AssetData> 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.
|
||||
};
|
||||
|
||||
|
||||
@@ -142,24 +142,12 @@ namespace Physics
|
||||
|
||||
virtual AZStd::shared_ptr<Shape> CreateShape(const ColliderConfiguration& colliderConfiguration, const ShapeConfiguration& configuration) = 0;
|
||||
|
||||
virtual AZStd::shared_ptr<Material> 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<Material> CreateMaterial(const Physics::MaterialConfiguration& materialConfiguration) = 0;
|
||||
virtual AZStd::shared_ptr<Material> GetDefaultMaterial() = 0;
|
||||
virtual AZStd::vector<AZStd::shared_ptr<Material>> 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
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -78,7 +78,7 @@ namespace AzFramework::ProjectManager
|
||||
projectJsonPath.c_str());
|
||||
}
|
||||
|
||||
if (LaunchProjectManager(engineRootPath))
|
||||
if (LaunchProjectManager())
|
||||
{
|
||||
AZ_TracePrintf("ProjectManager", "Project Manager launched successfully, requesting exit.");
|
||||
return ProjectPathCheckResult::ProjectManagerLaunched;
|
||||
@@ -87,7 +87,7 @@ namespace AzFramework::ProjectManager
|
||||
return ProjectPathCheckResult::ProjectManagerLaunchFailed;
|
||||
}
|
||||
|
||||
bool LaunchProjectManager([[maybe_unused]] const AZ::IO::FixedMaxPath& engineRootPath)
|
||||
bool LaunchProjectManager(const AZStd::string& commandLineArgs)
|
||||
{
|
||||
bool launchSuccess = false;
|
||||
#if (AZ_TRAIT_AZFRAMEWORK_USE_PROJECT_MANAGER)
|
||||
@@ -109,7 +109,7 @@ namespace AzFramework::ProjectManager
|
||||
}
|
||||
|
||||
AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo;
|
||||
processLaunchInfo.m_commandlineParameters = executablePath.String();
|
||||
processLaunchInfo.m_commandlineParameters = executablePath.String() + commandLineArgs;
|
||||
launchSuccess = AzFramework::ProcessLauncher::LaunchUnwatchedProcess(processLaunchInfo);
|
||||
}
|
||||
if (ownsSystemAllocator)
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/IO/Path/Path_fwd.h>
|
||||
#include <AzCore/std/string/string.h>
|
||||
|
||||
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
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/EBus/EBus.h>
|
||||
#include <AzFramework/Entity/EntityContextBus.h>
|
||||
#include <AzFramework/Render/GeometryIntersectionStructures.h>
|
||||
|
||||
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<class Bus>
|
||||
struct IntersectionRequestsConnectionPolicy
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/RTTI/RTTI.h>
|
||||
#include <AzCore/std/string/string.h>
|
||||
|
||||
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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -41,11 +41,11 @@ namespace AzFramework
|
||||
Spawnable() = default;
|
||||
explicit Spawnable(const AZ::Data::AssetId& id, AssetStatus status = AssetStatus::NotLoaded);
|
||||
Spawnable(const Spawnable& rhs) = delete;
|
||||
Spawnable(Spawnable&& other);
|
||||
Spawnable(Spawnable&& other) = delete;
|
||||
~Spawnable() override = default;
|
||||
|
||||
Spawnable& operator=(const Spawnable& rhs) = delete;
|
||||
Spawnable& operator=(Spawnable&& other);
|
||||
Spawnable& operator=(Spawnable&& other) = delete;
|
||||
|
||||
const EntityList& GetEntities() const;
|
||||
EntityList& GetEntities();
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzCore/Casting/lossy_cast.h>
|
||||
#include <AzCore/Serialization/Utils.h>
|
||||
#include <AzCore/std/string/string.h>
|
||||
#include <AzFramework/Spawnable/Spawnable.h>
|
||||
@@ -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
|
||||
|
||||
@@ -47,6 +47,7 @@ namespace AzFramework
|
||||
const char* GetGroup() const override;
|
||||
const char* GetBrowserIcon() const override;
|
||||
void GetAssetTypeExtensions(AZStd::vector<AZStd::string>& extensions) override;
|
||||
static uint32_t BuildSubId(AZStd::string_view id);
|
||||
|
||||
protected:
|
||||
LoadResult LoadAssetData(
|
||||
|
||||
@@ -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<size_t> 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> 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
|
||||
|
||||
@@ -239,7 +239,9 @@ namespace AzFramework
|
||||
{
|
||||
auto manager = SpawnableEntitiesInterface::Get();
|
||||
AZ_Assert(manager, "Attempting to create an entity spawn ticket while the SpawnableEntitiesInterface has no implementation.");
|
||||
m_payload = manager->CreateTicket(AZStd::move(spawnable));
|
||||
AZStd::pair<EntitySpawnTicket::Id, void*> result = manager->CreateTicket(AZStd::move(spawnable));
|
||||
m_id = result.first;
|
||||
m_payload = result.second;
|
||||
}
|
||||
|
||||
EntitySpawnTicket::~EntitySpawnTicket()
|
||||
@@ -250,6 +252,7 @@ namespace AzFramework
|
||||
AZ_Assert(manager, "Attempting to destroy an entity spawn ticket while the SpawnableEntitiesInterface has no implementation.");
|
||||
manager->DestroyTicket(m_payload);
|
||||
m_payload = nullptr;
|
||||
m_id = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,12 +266,20 @@ namespace AzFramework
|
||||
AZ_Assert(manager, "Attempting to destroy an entity spawn ticket while the SpawnableEntitiesInterface has no implementation.");
|
||||
manager->DestroyTicket(m_payload);
|
||||
}
|
||||
m_id = rhs.m_id;
|
||||
rhs.m_id = 0;
|
||||
|
||||
m_payload = rhs.m_payload;
|
||||
rhs.m_payload = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto EntitySpawnTicket::GetId() const -> Id
|
||||
{
|
||||
return m_id;
|
||||
}
|
||||
|
||||
bool EntitySpawnTicket::IsValid() const
|
||||
{
|
||||
return m_payload != nullptr;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
#include <AzCore/Asset/AssetCommon.h>
|
||||
#include <AzCore/Interface/Interface.h>
|
||||
#include <AzCore/RTTI/TypeSafeIntegral.h>
|
||||
#include <AzCore/std/functional.h>
|
||||
#include <AzFramework/Spawnable/Spawnable.h>
|
||||
|
||||
@@ -24,6 +25,14 @@ namespace AZ
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
AZ_TYPE_SAFE_INTEGRAL(SpawnablePriority, uint8_t);
|
||||
|
||||
inline static constexpr SpawnablePriority SpawnablePriority_Highest { 0 };
|
||||
inline static constexpr SpawnablePriority SpawnablePriority_High { 32 };
|
||||
inline static constexpr SpawnablePriority SpawnablePriority_Default { 128 };
|
||||
inline static constexpr SpawnablePriority SpawnablePriority_Low { 192 };
|
||||
inline static constexpr SpawnablePriority SpawnablePriority_Lowest { 255 };
|
||||
|
||||
class SpawnableEntityContainerView
|
||||
{
|
||||
public:
|
||||
@@ -124,16 +133,18 @@ namespace AzFramework
|
||||
SpawnableIndexEntityIterator m_end;
|
||||
};
|
||||
|
||||
//! Requests to the SpawnableEntitiesInterface require a ticket with a valid spawnable that be used as a template. A ticket can
|
||||
//! be reused for multiple calls on the same spawnable and is safe to use by multiple threads at the same time. Entities created
|
||||
//! Requests to the SpawnableEntitiesInterface require a ticket with a valid spawnable that is used as a template. A ticket can
|
||||
//! be reused for multiple calls on the same spawnable and is safe to be used by multiple threads at the same time. Entities created
|
||||
//! from the spawnable may be tracked by the ticket and so using the same ticket is needed to despawn the exact entities created
|
||||
//! by a call so spawn entities. The life cycle of the spawned entities is tied to the ticket and all entities spawned using a
|
||||
//! by a call to spawn entities. The life cycle of the spawned entities is tied to the ticket and all entities spawned using a
|
||||
//! ticket will be despawned when it's deleted.
|
||||
class EntitySpawnTicket
|
||||
{
|
||||
public:
|
||||
friend class SpawnableEntitiesDefinition;
|
||||
|
||||
using Id = uint64_t;
|
||||
|
||||
EntitySpawnTicket() = default;
|
||||
EntitySpawnTicket(const EntitySpawnTicket&) = delete;
|
||||
EntitySpawnTicket(EntitySpawnTicket&& rhs);
|
||||
@@ -143,26 +154,37 @@ namespace AzFramework
|
||||
EntitySpawnTicket& operator=(const EntitySpawnTicket&) = delete;
|
||||
EntitySpawnTicket& operator=(EntitySpawnTicket&& rhs);
|
||||
|
||||
Id GetId() const;
|
||||
bool IsValid() const;
|
||||
|
||||
private:
|
||||
void* m_payload{ nullptr };
|
||||
Id m_id { 0 }; //!< An id that uniquely identifies a ticket.
|
||||
};
|
||||
|
||||
using EntitySpawnCallback = AZStd::function<void(EntitySpawnTicket&, SpawnableConstEntityContainerView)>;
|
||||
using EntityPreInsertionCallback = AZStd::function<void(EntitySpawnTicket&, SpawnableEntityContainerView)>;
|
||||
using EntityDespawnCallback = AZStd::function<void(EntitySpawnTicket&)>;
|
||||
using ReloadSpawnableCallback = AZStd::function<void(EntitySpawnTicket&, SpawnableConstEntityContainerView)>;
|
||||
using ListEntitiesCallback = AZStd::function<void(EntitySpawnTicket&, SpawnableConstEntityContainerView)>;
|
||||
using ListIndicesEntitiesCallback = AZStd::function<void(EntitySpawnTicket&, SpawnableConstIndexEntityContainerView)>;
|
||||
using ClaimEntitiesCallback = AZStd::function<void(EntitySpawnTicket&, SpawnableEntityContainerView)>;
|
||||
using BarrierCallback = AZStd::function<void(EntitySpawnTicket&)>;
|
||||
using EntitySpawnCallback = AZStd::function<void(EntitySpawnTicket::Id, SpawnableConstEntityContainerView)>;
|
||||
using EntityPreInsertionCallback = AZStd::function<void(EntitySpawnTicket::Id, SpawnableEntityContainerView)>;
|
||||
using EntityDespawnCallback = AZStd::function<void(EntitySpawnTicket::Id)>;
|
||||
using ReloadSpawnableCallback = AZStd::function<void(EntitySpawnTicket::Id, SpawnableConstEntityContainerView)>;
|
||||
using ListEntitiesCallback = AZStd::function<void(EntitySpawnTicket::Id, SpawnableConstEntityContainerView)>;
|
||||
using ListIndicesEntitiesCallback = AZStd::function<void(EntitySpawnTicket::Id, SpawnableConstIndexEntityContainerView)>;
|
||||
using ClaimEntitiesCallback = AZStd::function<void(EntitySpawnTicket::Id, SpawnableEntityContainerView)>;
|
||||
using BarrierCallback = AZStd::function<void(EntitySpawnTicket::Id)>;
|
||||
|
||||
//! Interface definition to (de)spawn entities from a spawnable into the game world.
|
||||
//!
|
||||
//! While the callbacks of the individual calls are being processed they will block processing any other request. Callbacks can be
|
||||
//! issued from threads other than the one that issued the call, including the main thread.
|
||||
//!
|
||||
//! Calls on the same ticket are guaranteed to be executed in the order they are issued. Note that when issuing requests from
|
||||
//! multiple threads on the same ticket the order in which the requests are assigned to the ticket is not guaranteed.
|
||||
//!
|
||||
//! Most calls have a priority with values that range from 0 (highest priority) to 255 (lowest priority). The implementation of this
|
||||
//! interface may choose to use priority lanes which doesn't guarantee that higher priority requests happen before lower priority
|
||||
//! requests if they don't pass the priority lane threshold. Priority lanes and their thresholds are implementation specific and may
|
||||
//! differ between platforms. Note that if a call happened on a ticket with lower priority followed by a one with a higher priority
|
||||
//! the first lower priority call will still need to complete before the second higher priority call can be executed and the priority
|
||||
//! of the first call will not be updated.
|
||||
class SpawnableEntitiesDefinition
|
||||
{
|
||||
public:
|
||||
@@ -173,40 +195,48 @@ namespace AzFramework
|
||||
virtual ~SpawnableEntitiesDefinition() = default;
|
||||
|
||||
//! Spawn instances of all entities in the spawnable.
|
||||
//! @param spawnable The Spawnable asset that will be used to create entity instances from.
|
||||
//! @param ticket Stores the results of the call. Use this ticket to spawn additional entities or to despawn them.
|
||||
//! @param priority The priority at which this call will be executed.
|
||||
//! @param completionCallback Optional callback that's called when spawning entities has completed. This can be called from
|
||||
//! a different thread than the one that made the function call. The returned list of entities contains all the newly
|
||||
//! created entities.
|
||||
virtual void SpawnAllEntities(EntitySpawnTicket& ticket, EntityPreInsertionCallback preInsertionCallback = {},
|
||||
virtual void SpawnAllEntities(
|
||||
EntitySpawnTicket& ticket, SpawnablePriority priority, EntityPreInsertionCallback preInsertionCallback = {},
|
||||
EntitySpawnCallback completionCallback = {}) = 0;
|
||||
//! Spawn instances of some entities in the spawnable.
|
||||
//! @param ticket Stores the results of the call. Use this ticket to spawn additional entities or to despawn them.
|
||||
//! @param priority The priority at which this call will be executed.
|
||||
//! @param entityIndices The indices into the template entities stored in the spawnable that will be used to spawn entities from.
|
||||
//! @param completionCallback Optional callback that's called when spawning entities has completed. This can be called from
|
||||
//! a different thread than the one that made this function call. The returned list of entities contains all the newly
|
||||
//! created entities.
|
||||
virtual void SpawnEntities(EntitySpawnTicket& ticket, AZStd::vector<size_t> entityIndices,
|
||||
virtual void SpawnEntities(
|
||||
EntitySpawnTicket& ticket, SpawnablePriority priority, AZStd::vector<size_t> 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> spawnable,
|
||||
virtual void ReloadSpawnable(
|
||||
EntitySpawnTicket& ticket, SpawnablePriority priority, AZ::Data::Asset<Spawnable> spawnable,
|
||||
ReloadSpawnableCallback completionCallback = {}) = 0;
|
||||
|
||||
//! List all entities that are spawned using this ticket.
|
||||
//! @param ticket Only the entities associated with this ticket will be listed.
|
||||
//! @param priority The priority at which this call will be executed.
|
||||
//! @param listCallback Required callback that will be called to list the entities on.
|
||||
virtual void ListEntities(EntitySpawnTicket& ticket, ListEntitiesCallback listCallback) = 0;
|
||||
virtual void ListEntities(EntitySpawnTicket& ticket, SpawnablePriority priority, ListEntitiesCallback listCallback) = 0;
|
||||
//! List all entities that are spawned using this ticket with their spawnable index.
|
||||
//! Spawnables contain a flat list of entities, which are used as templates to spawn entities from. For every spawned entity
|
||||
//! the index of the entity in the spawnable that was used as a template is stored. This version of ListEntities will return
|
||||
@@ -214,17 +244,23 @@ namespace AzFramework
|
||||
//! the same index may appear multiple times as there are no restriction on how many instance of a specific entity can be
|
||||
//! created.
|
||||
//! @param ticket Only the entities associated with this ticket will be listed.
|
||||
//! @param priority The priority at which this call will be executed.
|
||||
//! @param listCallback Required callback that will be called to list the entities and indices on.
|
||||
virtual void ListIndicesAndEntities(EntitySpawnTicket& ticket, ListIndicesEntitiesCallback listCallback) = 0;
|
||||
virtual void ListIndicesAndEntities(
|
||||
EntitySpawnTicket& ticket, SpawnablePriority priority, ListIndicesEntitiesCallback listCallback) = 0;
|
||||
//! Claim all entities that are spawned using this ticket. Ownership of the entities is transferred from the ticket to the
|
||||
//! caller through the callback. After this call the ticket will have no entities associated with it. The caller of
|
||||
//! this function will need to manage the entities after this call.
|
||||
//! @param ticket Only the entities associated with this ticket will be released.
|
||||
//! @param priority The priority at which this call will be executed.
|
||||
//! @param listCallback Required callback that will be called to transfer the entities through.
|
||||
virtual void ClaimEntities(EntitySpawnTicket& ticket, ClaimEntitiesCallback listCallback) = 0;
|
||||
virtual void ClaimEntities(EntitySpawnTicket& ticket, SpawnablePriority priority, ClaimEntitiesCallback listCallback) = 0;
|
||||
|
||||
//! Blocks until all operations made on the provided ticket before the barrier call have completed.
|
||||
virtual void Barrier(EntitySpawnTicket& ticket, BarrierCallback completionCallback) = 0;
|
||||
//! @param ticket The ticket to monitor.
|
||||
//! @param priority The priority at which this call will be executed.
|
||||
//! @param completionCallback Required callback that will be called as soon as the barrier has been reached.
|
||||
virtual void Barrier(EntitySpawnTicket& ticket, SpawnablePriority priority, BarrierCallback completionCallback) = 0;
|
||||
|
||||
//! Register a handler for OnSpawned events.
|
||||
virtual void AddOnSpawnedHandler(AZ::Event<AZ::Data::Asset<Spawnable>>::Handler& handler) = 0;
|
||||
@@ -233,7 +269,7 @@ namespace AzFramework
|
||||
virtual void AddOnDespawnedHandler(AZ::Event<AZ::Data::Asset<Spawnable>>::Handler& handler) = 0;
|
||||
|
||||
protected:
|
||||
[[nodiscard]] virtual void* CreateTicket(AZ::Data::Asset<Spawnable>&& spawnable) = 0;
|
||||
[[nodiscard]] virtual AZStd::pair<EntitySpawnTicket::Id, void*> CreateTicket(AZ::Data::Asset<Spawnable>&& spawnable) = 0;
|
||||
virtual void DestroyTicket(void* ticket) = 0;
|
||||
|
||||
template<typename T>
|
||||
|
||||
@@ -10,9 +10,11 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzCore/Casting/numeric_cast.h>
|
||||
#include <AzCore/Component/ComponentApplicationBus.h>
|
||||
#include <AzCore/Serialization/IdUtils.h>
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
#include <AzCore/Settings/SettingsRegistry.h>
|
||||
#include <AzCore/std/parallel/scoped_lock.h>
|
||||
#include <AzCore/std/smart_ptr/make_shared.h>
|
||||
#include <AzFramework/Components/TransformComponent.h>
|
||||
@@ -22,128 +24,122 @@
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
void SpawnableEntitiesManager::SpawnAllEntities(EntitySpawnTicket& ticket, EntityPreInsertionCallback preInsertionCallback,
|
||||
template<typename T>
|
||||
void SpawnableEntitiesManager::QueueRequest(EntitySpawnTicket& ticket, SpawnablePriority priority, T&& request)
|
||||
{
|
||||
request.m_ticket = &GetTicketPayload<Ticket>(ticket);
|
||||
Queue& queue = priority <= m_highPriorityThreshold ? m_highPriorityQueue : m_regularPriorityQueue;
|
||||
{
|
||||
AZStd::scoped_lock queueLock(queue.m_pendingRequestMutex);
|
||||
request.m_requestId = GetTicketPayload<Ticket>(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<SpawnablePriority>(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>(ticket).m_nextTicketId++;
|
||||
m_pendingRequestQueue.push(AZStd::move(queueEntry));
|
||||
}
|
||||
QueueRequest(ticket, priority, AZStd::move(queueEntry));
|
||||
}
|
||||
|
||||
void SpawnableEntitiesManager::SpawnEntities(
|
||||
EntitySpawnTicket& ticket, AZStd::vector<size_t> entityIndices,
|
||||
EntitySpawnTicket& ticket, SpawnablePriority priority, AZStd::vector<size_t> 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>(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>(ticket).m_nextTicketId++;
|
||||
m_pendingRequestQueue.push(AZStd::move(queueEntry));
|
||||
}
|
||||
QueueRequest(ticket, priority, AZStd::move(queueEntry));
|
||||
}
|
||||
|
||||
void SpawnableEntitiesManager::ReloadSpawnable(EntitySpawnTicket& ticket, AZ::Data::Asset<Spawnable> spawnable,
|
||||
void SpawnableEntitiesManager::ReloadSpawnable(
|
||||
EntitySpawnTicket& ticket, SpawnablePriority priority, AZ::Data::Asset<Spawnable> 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>(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>(ticket).m_nextTicketId++;
|
||||
m_pendingRequestQueue.push(AZStd::move(queueEntry));
|
||||
}
|
||||
QueueRequest(ticket, priority, AZStd::move(queueEntry));
|
||||
}
|
||||
|
||||
void SpawnableEntitiesManager::ListIndicesAndEntities(EntitySpawnTicket& ticket, ListIndicesEntitiesCallback listCallback)
|
||||
void SpawnableEntitiesManager::ListIndicesAndEntities(
|
||||
EntitySpawnTicket& ticket, SpawnablePriority priority, ListIndicesEntitiesCallback listCallback)
|
||||
{
|
||||
AZ_Assert(listCallback, "ListEntities called on spawnable entities without a valid callback to use.");
|
||||
AZ_Assert(ticket.IsValid(), "Ticket provided to ListEntities hasn't been initialized.");
|
||||
|
||||
ListIndicesEntitiesCommand queueEntry;
|
||||
queueEntry.m_ticket = &ticket;
|
||||
queueEntry.m_ticketId = ticket.GetId();
|
||||
queueEntry.m_listCallback = AZStd::move(listCallback);
|
||||
{
|
||||
AZStd::scoped_lock queueLock(m_pendingRequestQueueMutex);
|
||||
queueEntry.m_ticketId = GetTicketPayload<Ticket>(ticket).m_nextTicketId++;
|
||||
m_pendingRequestQueue.push(AZStd::move(queueEntry));
|
||||
}
|
||||
QueueRequest(ticket, priority, AZStd::move(queueEntry));
|
||||
}
|
||||
|
||||
void SpawnableEntitiesManager::ClaimEntities(EntitySpawnTicket& ticket, ClaimEntitiesCallback listCallback)
|
||||
void SpawnableEntitiesManager::ClaimEntities(EntitySpawnTicket& ticket, SpawnablePriority priority, ClaimEntitiesCallback listCallback)
|
||||
{
|
||||
AZ_Assert(listCallback, "ClaimEntities called on spawnable entities without a valid callback to use.");
|
||||
AZ_Assert(ticket.IsValid(), "Ticket provided to ClaimEntities hasn't been initialized.");
|
||||
|
||||
ClaimEntitiesCommand queueEntry;
|
||||
queueEntry.m_ticket = &ticket;
|
||||
queueEntry.m_ticketId = ticket.GetId();
|
||||
queueEntry.m_listCallback = AZStd::move(listCallback);
|
||||
{
|
||||
AZStd::scoped_lock queueLock(m_pendingRequestQueueMutex);
|
||||
queueEntry.m_ticketId = GetTicketPayload<Ticket>(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>(ticket).m_nextTicketId++;
|
||||
m_pendingRequestQueue.push(AZStd::move(queueEntry));
|
||||
}
|
||||
QueueRequest(ticket, priority, AZStd::move(queueEntry));
|
||||
}
|
||||
|
||||
void SpawnableEntitiesManager::AddOnSpawnedHandler(AZ::Event<AZ::Data::Asset<Spawnable>>::Handler& handler)
|
||||
@@ -156,34 +152,54 @@ namespace AzFramework
|
||||
handler.Connect(m_onDespawnedEvent);
|
||||
}
|
||||
|
||||
auto SpawnableEntitiesManager::ProcessQueue() -> CommandQueueStatus
|
||||
auto SpawnableEntitiesManager::ProcessQueue(CommandQueuePriority priority) -> CommandQueueStatus
|
||||
{
|
||||
CommandQueueStatus result = CommandQueueStatus::NoCommandsLeft;
|
||||
if ((priority & CommandQueuePriority::High) == CommandQueuePriority::High)
|
||||
{
|
||||
if (ProcessQueue(m_highPriorityQueue) == CommandQueueStatus::HasCommandsLeft)
|
||||
{
|
||||
result = CommandQueueStatus::HasCommandsLeft;
|
||||
}
|
||||
}
|
||||
if ((priority & CommandQueuePriority::Regular) == CommandQueuePriority::Regular)
|
||||
{
|
||||
if (ProcessQueue(m_regularPriorityQueue) == CommandQueueStatus::HasCommandsLeft)
|
||||
{
|
||||
result = CommandQueueStatus::HasCommandsLeft;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
auto SpawnableEntitiesManager::ProcessQueue(Queue& queue) -> CommandQueueStatus
|
||||
{
|
||||
AZStd::queue<Requests> pendingRequestQueue;
|
||||
{
|
||||
AZStd::scoped_lock queueLock(m_pendingRequestQueueMutex);
|
||||
m_pendingRequestQueue.swap(pendingRequestQueue);
|
||||
AZStd::scoped_lock queueLock(queue.m_pendingRequestMutex);
|
||||
queue.m_pendingRequest.swap(pendingRequestQueue);
|
||||
}
|
||||
|
||||
if (!pendingRequestQueue.empty() || !m_delayedQueue.empty())
|
||||
if (!pendingRequestQueue.empty() || !queue.m_delayed.empty())
|
||||
{
|
||||
AZ::SerializeContext* serializeContext = nullptr;
|
||||
AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext);
|
||||
AZ_Assert(serializeContext, "Failed to retrieve serialization context.");
|
||||
|
||||
// Only process the requests that are currently in this queue, not the ones that could be re-added if they still can't complete.
|
||||
size_t delayedSize = m_delayedQueue.size();
|
||||
size_t delayedSize = queue.m_delayed.size();
|
||||
for (size_t i = 0; i < delayedSize; ++i)
|
||||
{
|
||||
Requests& request = m_delayedQueue.front();
|
||||
Requests& request = queue.m_delayed.front();
|
||||
bool result = AZStd::visit([this, serializeContext](auto&& args) -> bool
|
||||
{
|
||||
return ProcessRequest(args, *serializeContext);
|
||||
}, request);
|
||||
if (!result)
|
||||
{
|
||||
m_delayedQueue.emplace_back(AZStd::move(request));
|
||||
queue.m_delayed.emplace_back(AZStd::move(request));
|
||||
}
|
||||
m_delayedQueue.pop_front();
|
||||
queue.m_delayed.pop_front();
|
||||
}
|
||||
|
||||
do
|
||||
@@ -197,7 +213,7 @@ namespace AzFramework
|
||||
}, request);
|
||||
if (!result)
|
||||
{
|
||||
m_delayedQueue.emplace_back(AZStd::move(request));
|
||||
queue.m_delayed.emplace_back(AZStd::move(request));
|
||||
}
|
||||
pendingRequestQueue.pop();
|
||||
}
|
||||
@@ -205,20 +221,22 @@ namespace AzFramework
|
||||
// Spawning entities can result in more entities being queued to spawn. Repeat spawning until the queue is
|
||||
// empty to avoid a chain of entity spawning getting dragged out over multiple frames.
|
||||
{
|
||||
AZStd::scoped_lock queueLock(m_pendingRequestQueueMutex);
|
||||
m_pendingRequestQueue.swap(pendingRequestQueue);
|
||||
AZStd::scoped_lock queueLock(queue.m_pendingRequestMutex);
|
||||
queue.m_pendingRequest.swap(pendingRequestQueue);
|
||||
}
|
||||
} while (!pendingRequestQueue.empty());
|
||||
}
|
||||
|
||||
return m_delayedQueue.empty() ? CommandQueueStatus::NoCommandLeft : CommandQueueStatus::HasCommandsLeft;
|
||||
return queue.m_delayed.empty() ? CommandQueueStatus::NoCommandsLeft : CommandQueueStatus::HasCommandsLeft;
|
||||
}
|
||||
|
||||
void* SpawnableEntitiesManager::CreateTicket(AZ::Data::Asset<Spawnable>&& spawnable)
|
||||
AZStd::pair<uint64_t, void*> SpawnableEntitiesManager::CreateTicket(AZ::Data::Asset<Spawnable>&& spawnable)
|
||||
{
|
||||
static AZStd::atomic_uint64_t idCounter { 1 };
|
||||
|
||||
auto result = aznew Ticket();
|
||||
result->m_spawnable = AZStd::move(spawnable);
|
||||
return result;
|
||||
return AZStd::make_pair<EntitySpawnTicket::Id, void*>(idCounter++, result);
|
||||
}
|
||||
|
||||
void SpawnableEntitiesManager::DestroyTicket(void* ticket)
|
||||
@@ -226,9 +244,9 @@ namespace AzFramework
|
||||
DestroyTicketCommand queueEntry;
|
||||
queueEntry.m_ticket = reinterpret_cast<Ticket*>(ticket);
|
||||
{
|
||||
AZStd::scoped_lock queueLock(m_pendingRequestQueueMutex);
|
||||
queueEntry.m_ticketId = reinterpret_cast<Ticket*>(ticket)->m_nextTicketId++;
|
||||
m_pendingRequestQueue.push(AZStd::move(queueEntry));
|
||||
AZStd::scoped_lock queueLock(m_regularPriorityQueue.m_pendingRequestMutex);
|
||||
queueEntry.m_requestId = reinterpret_cast<Ticket*>(ticket)->m_nextRequestId++;
|
||||
m_regularPriorityQueue.m_pendingRequest.push(AZStd::move(queueEntry));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,8 +269,8 @@ namespace AzFramework
|
||||
|
||||
bool SpawnableEntitiesManager::ProcessRequest(SpawnAllEntitiesCommand& request, AZ::SerializeContext& serializeContext)
|
||||
{
|
||||
Ticket& ticket = GetTicketPayload<Ticket>(*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<AZ::Entity*>& spawnedEntities = ticket.m_spawnedEntities;
|
||||
AZStd::vector<size_t>& spawnedEntityIndices = ticket.m_spawnedEntityIndices;
|
||||
@@ -300,7 +318,7 @@ namespace AzFramework
|
||||
// Let other systems know about newly spawned entities for any pre-processing before adding to the scene/game context.
|
||||
if (request.m_preInsertionCallback)
|
||||
{
|
||||
request.m_preInsertionCallback(*request.m_ticket, SpawnableEntityContainerView(
|
||||
request.m_preInsertionCallback(request.m_ticketId, SpawnableEntityContainerView(
|
||||
ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end()));
|
||||
}
|
||||
|
||||
@@ -314,13 +332,13 @@ namespace AzFramework
|
||||
// Let other systems know about newly spawned entities for any post-processing after adding to the scene/game context.
|
||||
if (request.m_completionCallback)
|
||||
{
|
||||
request.m_completionCallback(*request.m_ticket, SpawnableConstEntityContainerView(
|
||||
request.m_completionCallback(request.m_ticketId, SpawnableConstEntityContainerView(
|
||||
ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end()));
|
||||
}
|
||||
|
||||
m_onSpawnedEvent.Signal(ticket.m_spawnable);
|
||||
|
||||
ticket.m_currentTicketId++;
|
||||
ticket.m_currentRequestId++;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
@@ -331,8 +349,8 @@ namespace AzFramework
|
||||
|
||||
bool SpawnableEntitiesManager::ProcessRequest(SpawnEntitiesCommand& request, AZ::SerializeContext& serializeContext)
|
||||
{
|
||||
Ticket& ticket = GetTicketPayload<Ticket>(*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<AZ::Entity*>& spawnedEntities = ticket.m_spawnedEntities;
|
||||
AZStd::vector<size_t>& spawnedEntityIndices = ticket.m_spawnedEntityIndices;
|
||||
@@ -367,9 +385,7 @@ namespace AzFramework
|
||||
// Let other systems know about newly spawned entities for any pre-processing before adding to the scene/game context.
|
||||
if (request.m_preInsertionCallback)
|
||||
{
|
||||
request.m_preInsertionCallback(
|
||||
*request.m_ticket,
|
||||
SpawnableEntityContainerView(
|
||||
request.m_preInsertionCallback(request.m_ticketId, SpawnableEntityContainerView(
|
||||
ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end()));
|
||||
}
|
||||
|
||||
@@ -382,13 +398,13 @@ namespace AzFramework
|
||||
|
||||
if (request.m_completionCallback)
|
||||
{
|
||||
request.m_completionCallback(*request.m_ticket, SpawnableConstEntityContainerView(
|
||||
request.m_completionCallback(request.m_ticketId, SpawnableConstEntityContainerView(
|
||||
ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end()));
|
||||
}
|
||||
|
||||
m_onSpawnedEvent.Signal(ticket.m_spawnable);
|
||||
|
||||
ticket.m_currentTicketId++;
|
||||
ticket.m_currentRequestId++;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
@@ -400,8 +416,8 @@ namespace AzFramework
|
||||
bool SpawnableEntitiesManager::ProcessRequest(DespawnAllEntitiesCommand& request,
|
||||
[[maybe_unused]] AZ::SerializeContext& serializeContext)
|
||||
{
|
||||
Ticket& ticket = GetTicketPayload<Ticket>(*request.m_ticket);
|
||||
if (request.m_ticketId == ticket.m_currentTicketId)
|
||||
Ticket& ticket = *request.m_ticket;
|
||||
if (request.m_requestId == ticket.m_currentRequestId)
|
||||
{
|
||||
for (AZ::Entity* entity : ticket.m_spawnedEntities)
|
||||
{
|
||||
@@ -417,12 +433,12 @@ namespace AzFramework
|
||||
|
||||
if (request.m_completionCallback)
|
||||
{
|
||||
request.m_completionCallback(*request.m_ticket);
|
||||
request.m_completionCallback(request.m_ticketId);
|
||||
}
|
||||
|
||||
m_onDespawnedEvent.Signal(ticket.m_spawnable);
|
||||
|
||||
ticket.m_currentTicketId++;
|
||||
ticket.m_currentRequestId++;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
@@ -433,11 +449,11 @@ namespace AzFramework
|
||||
|
||||
bool SpawnableEntitiesManager::ProcessRequest(ReloadSpawnableCommand& request, AZ::SerializeContext& serializeContext)
|
||||
{
|
||||
Ticket& ticket = GetTicketPayload<Ticket>(*request.m_ticket);
|
||||
Ticket& ticket = *request.m_ticket;
|
||||
AZ_Assert(ticket.m_spawnable.GetId() == request.m_spawnable.GetId(),
|
||||
"Spawnable is being reloaded, but the provided spawnable has a different asset id. "
|
||||
"This will likely result in unexpected entities being created.");
|
||||
if (ticket.m_spawnable.IsReady() && request.m_ticketId == ticket.m_currentTicketId)
|
||||
if (ticket.m_spawnable.IsReady() && request.m_requestId == ticket.m_currentRequestId)
|
||||
{
|
||||
// Delete the original entities.
|
||||
for (AZ::Entity* entity : ticket.m_spawnedEntities)
|
||||
@@ -493,11 +509,11 @@ namespace AzFramework
|
||||
|
||||
if (request.m_completionCallback)
|
||||
{
|
||||
request.m_completionCallback(*request.m_ticket, SpawnableConstEntityContainerView(
|
||||
request.m_completionCallback(request.m_ticketId, SpawnableConstEntityContainerView(
|
||||
ticket.m_spawnedEntities.begin(), ticket.m_spawnedEntities.end()));
|
||||
}
|
||||
|
||||
ticket.m_currentTicketId++;
|
||||
ticket.m_currentRequestId++;
|
||||
|
||||
m_onSpawnedEvent.Signal(ticket.m_spawnable);
|
||||
|
||||
@@ -511,12 +527,12 @@ namespace AzFramework
|
||||
|
||||
bool SpawnableEntitiesManager::ProcessRequest(ListEntitiesCommand& request, [[maybe_unused]] AZ::SerializeContext& serializeContext)
|
||||
{
|
||||
Ticket& ticket = GetTicketPayload<Ticket>(*request.m_ticket);
|
||||
if (request.m_ticketId == ticket.m_currentTicketId)
|
||||
Ticket& ticket = *request.m_ticket;
|
||||
if (request.m_requestId == ticket.m_currentRequestId)
|
||||
{
|
||||
request.m_listCallback(*request.m_ticket, SpawnableConstEntityContainerView(
|
||||
request.m_listCallback(request.m_ticketId, SpawnableConstEntityContainerView(
|
||||
ticket.m_spawnedEntities.begin(), ticket.m_spawnedEntities.end()));
|
||||
ticket.m_currentTicketId++;
|
||||
ticket.m_currentRequestId++;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
@@ -527,17 +543,15 @@ namespace AzFramework
|
||||
|
||||
bool SpawnableEntitiesManager::ProcessRequest(ListIndicesEntitiesCommand& request, [[maybe_unused]] AZ::SerializeContext& serializeContext)
|
||||
{
|
||||
Ticket& ticket = GetTicketPayload<Ticket>(*request.m_ticket);
|
||||
if (request.m_ticketId == ticket.m_currentTicketId)
|
||||
Ticket& ticket = *request.m_ticket;
|
||||
if (request.m_requestId == ticket.m_currentRequestId)
|
||||
{
|
||||
AZ_Assert(
|
||||
ticket.m_spawnedEntities.size() == ticket.m_spawnedEntityIndices.size(),
|
||||
"Entities and indices on spawnable ticket have gone out of sync.");
|
||||
request.m_listCallback(
|
||||
*request.m_ticket,
|
||||
SpawnableConstIndexEntityContainerView(
|
||||
request.m_listCallback(request.m_ticketId, SpawnableConstIndexEntityContainerView(
|
||||
ticket.m_spawnedEntities.begin(), ticket.m_spawnedEntityIndices.begin(), ticket.m_spawnedEntities.size()));
|
||||
ticket.m_currentTicketId++;
|
||||
ticket.m_currentRequestId++;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
@@ -548,16 +562,16 @@ namespace AzFramework
|
||||
|
||||
bool SpawnableEntitiesManager::ProcessRequest(ClaimEntitiesCommand& request, [[maybe_unused]] AZ::SerializeContext& serializeContext)
|
||||
{
|
||||
Ticket& ticket = GetTicketPayload<Ticket>(*request.m_ticket);
|
||||
if (request.m_ticketId == ticket.m_currentTicketId)
|
||||
Ticket& ticket = *request.m_ticket;
|
||||
if (request.m_requestId == ticket.m_currentRequestId)
|
||||
{
|
||||
request.m_listCallback(*request.m_ticket, SpawnableEntityContainerView(
|
||||
request.m_listCallback(request.m_ticketId, SpawnableEntityContainerView(
|
||||
ticket.m_spawnedEntities.begin(), ticket.m_spawnedEntities.end()));
|
||||
|
||||
ticket.m_spawnedEntities.clear();
|
||||
ticket.m_spawnedEntityIndices.clear();
|
||||
|
||||
ticket.m_currentTicketId++;
|
||||
ticket.m_currentRequestId++;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
@@ -568,15 +582,15 @@ namespace AzFramework
|
||||
|
||||
bool SpawnableEntitiesManager::ProcessRequest(BarrierCommand& request, [[maybe_unused]] AZ::SerializeContext& serializeContext)
|
||||
{
|
||||
Ticket& ticket = GetTicketPayload<Ticket>(*request.m_ticket);
|
||||
if (request.m_ticketId == ticket.m_currentTicketId)
|
||||
Ticket& ticket = *request.m_ticket;
|
||||
if (request.m_requestId == ticket.m_currentRequestId)
|
||||
{
|
||||
if (request.m_completionCallback)
|
||||
{
|
||||
request.m_completionCallback(*request.m_ticket);
|
||||
request.m_completionCallback(request.m_ticketId);
|
||||
}
|
||||
|
||||
ticket.m_currentTicketId++;
|
||||
ticket.m_currentRequestId++;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
@@ -587,7 +601,7 @@ namespace AzFramework
|
||||
|
||||
bool SpawnableEntitiesManager::ProcessRequest(DestroyTicketCommand& request, [[maybe_unused]] AZ::SerializeContext& serializeContext)
|
||||
{
|
||||
if (request.m_ticketId == request.m_ticket->m_currentTicketId)
|
||||
if (request.m_requestId == request.m_ticket->m_currentRequestId)
|
||||
{
|
||||
for (AZ::Entity* entity : request.m_ticket->m_spawnedEntities)
|
||||
{
|
||||
@@ -606,24 +620,4 @@ namespace AzFramework
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool SpawnableEntitiesManager::IsEqualTicket(const EntitySpawnTicket* lhs, const EntitySpawnTicket* rhs)
|
||||
{
|
||||
return GetTicketPayload<Ticket>(lhs) == GetTicketPayload<Ticket>(rhs);
|
||||
}
|
||||
|
||||
bool SpawnableEntitiesManager::IsEqualTicket(const Ticket* lhs, const EntitySpawnTicket* rhs)
|
||||
{
|
||||
return lhs == GetTicketPayload<Ticket>(rhs);
|
||||
}
|
||||
|
||||
bool SpawnableEntitiesManager::IsEqualTicket(const EntitySpawnTicket* lhs, const Ticket* rhs)
|
||||
{
|
||||
return GetTicketPayload<Ticket>(lhs) == rhs;
|
||||
}
|
||||
|
||||
bool SpawnableEntitiesManager::IsEqualTicket(const Ticket* lhs, const Ticket* rhs)
|
||||
{
|
||||
return lhs = rhs;
|
||||
}
|
||||
} // namespace AzFramework
|
||||
|
||||
@@ -29,8 +29,6 @@ namespace AZ
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
using EntityIdMap = AZStd::unordered_map<AZ::EntityId, AZ::EntityId>;
|
||||
|
||||
class SpawnableEntitiesManager
|
||||
: public SpawnableEntitiesInterface::Registrar
|
||||
{
|
||||
@@ -38,31 +36,47 @@ namespace AzFramework
|
||||
AZ_RTTI(AzFramework::SpawnableEntitiesManager, "{6E14333F-128C-464C-94CA-A63B05A5E51C}");
|
||||
AZ_CLASS_ALLOCATOR(SpawnableEntitiesManager, AZ::SystemAllocator, 0);
|
||||
|
||||
using EntityIdMap = AZStd::unordered_map<AZ::EntityId, AZ::EntityId>;
|
||||
|
||||
enum class CommandQueueStatus : bool
|
||||
{
|
||||
HasCommandsLeft,
|
||||
NoCommandLeft
|
||||
NoCommandsLeft
|
||||
};
|
||||
|
||||
enum class CommandQueuePriority
|
||||
{
|
||||
High = 1 << 0,
|
||||
Regular = 1 << 1
|
||||
};
|
||||
|
||||
SpawnableEntitiesManager();
|
||||
~SpawnableEntitiesManager() override = default;
|
||||
|
||||
//
|
||||
// The following functions are thread safe
|
||||
//
|
||||
|
||||
void SpawnAllEntities(EntitySpawnTicket& ticket, EntityPreInsertionCallback preInsertionCallback = {}, EntitySpawnCallback completionCallback = {}) override;
|
||||
void SpawnEntities(EntitySpawnTicket& ticket, AZStd::vector<size_t> entityIndices, EntityPreInsertionCallback preInsertionCallback = {},
|
||||
void SpawnAllEntities(
|
||||
EntitySpawnTicket& ticket, SpawnablePriority priority, EntityPreInsertionCallback preInsertionCallback = {},
|
||||
EntitySpawnCallback completionCallback = {}) override;
|
||||
void DespawnAllEntities(EntitySpawnTicket& ticket, EntityDespawnCallback completionCallback = {}) override;
|
||||
void SpawnEntities(
|
||||
EntitySpawnTicket& ticket, SpawnablePriority priority, AZStd::vector<size_t> entityIndices,
|
||||
EntityPreInsertionCallback preInsertionCallback = {},
|
||||
EntitySpawnCallback completionCallback = {}) override;
|
||||
void DespawnAllEntities(
|
||||
EntitySpawnTicket& ticket, SpawnablePriority priority, EntityDespawnCallback completionCallback = {}) override;
|
||||
|
||||
void ReloadSpawnable(EntitySpawnTicket& ticket, AZ::Data::Asset<Spawnable> spawnable,
|
||||
void ReloadSpawnable(
|
||||
EntitySpawnTicket& ticket, SpawnablePriority priority, AZ::Data::Asset<Spawnable> spawnable,
|
||||
ReloadSpawnableCallback completionCallback = {}) override;
|
||||
|
||||
void ListEntities(EntitySpawnTicket& ticket, ListEntitiesCallback listCallback) override;
|
||||
void ListIndicesAndEntities(EntitySpawnTicket& ticket, ListIndicesEntitiesCallback listCallback) override;
|
||||
void ClaimEntities(EntitySpawnTicket& ticket, ClaimEntitiesCallback listCallback) override;
|
||||
void ListEntities(EntitySpawnTicket& ticket, SpawnablePriority priority, ListEntitiesCallback listCallback) override;
|
||||
void ListIndicesAndEntities(
|
||||
EntitySpawnTicket& ticket, SpawnablePriority priority, ListIndicesEntitiesCallback listCallback) override;
|
||||
void ClaimEntities(EntitySpawnTicket& ticket, SpawnablePriority priority, ClaimEntitiesCallback listCallback) override;
|
||||
|
||||
void Barrier(EntitySpawnTicket& spawnInfo, BarrierCallback completionCallback) override;
|
||||
void Barrier(EntitySpawnTicket& spawnInfo, SpawnablePriority priority, BarrierCallback completionCallback) override;
|
||||
|
||||
void AddOnSpawnedHandler(AZ::Event<AZ::Data::Asset<Spawnable>>::Handler& handler) override;
|
||||
void AddOnDespawnedHandler(AZ::Event<AZ::Data::Asset<Spawnable>>::Handler& handler) override;
|
||||
@@ -71,13 +85,9 @@ namespace AzFramework
|
||||
// The following function is thread safe but intended to be run from the main thread.
|
||||
//
|
||||
|
||||
CommandQueueStatus ProcessQueue();
|
||||
CommandQueueStatus ProcessQueue(CommandQueuePriority priority);
|
||||
|
||||
protected:
|
||||
void* CreateTicket(AZ::Data::Asset<Spawnable>&& spawnable) override;
|
||||
void DestroyTicket(void* ticket) override;
|
||||
|
||||
private:
|
||||
struct Ticket
|
||||
{
|
||||
AZ_CLASS_ALLOCATOR(Ticket, AZ::ThreadPoolAllocator, 0);
|
||||
@@ -86,8 +96,8 @@ namespace AzFramework
|
||||
AZStd::vector<AZ::Entity*> m_spawnedEntities;
|
||||
AZStd::vector<size_t> m_spawnedEntityIndices;
|
||||
AZ::Data::Asset<Spawnable> m_spawnable;
|
||||
uint32_t m_nextTicketId{ 0 }; //!< Next id for this ticket.
|
||||
uint32_t m_currentTicketId{ 0 }; //!< The id for the command that should be executed.
|
||||
uint32_t m_nextRequestId{ 0 }; //!< Next id for this ticket.
|
||||
uint32_t m_currentRequestId { 0 }; //!< The id for the command that should be executed.
|
||||
bool m_loadAll{ true };
|
||||
};
|
||||
|
||||
@@ -95,64 +105,86 @@ namespace AzFramework
|
||||
{
|
||||
EntitySpawnCallback m_completionCallback;
|
||||
EntityPreInsertionCallback m_preInsertionCallback;
|
||||
EntitySpawnTicket* m_ticket;
|
||||
uint32_t m_ticketId;
|
||||
Ticket* m_ticket;
|
||||
EntitySpawnTicket::Id m_ticketId;
|
||||
uint32_t m_requestId;
|
||||
};
|
||||
struct SpawnEntitiesCommand
|
||||
{
|
||||
AZStd::vector<size_t> m_entityIndices;
|
||||
EntitySpawnCallback m_completionCallback;
|
||||
EntityPreInsertionCallback m_preInsertionCallback;
|
||||
EntitySpawnTicket* m_ticket;
|
||||
uint32_t m_ticketId;
|
||||
Ticket* m_ticket;
|
||||
EntitySpawnTicket::Id m_ticketId;
|
||||
uint32_t m_requestId;
|
||||
};
|
||||
struct DespawnAllEntitiesCommand
|
||||
{
|
||||
EntityDespawnCallback m_completionCallback;
|
||||
EntitySpawnTicket* m_ticket;
|
||||
uint32_t m_ticketId;
|
||||
Ticket* m_ticket;
|
||||
EntitySpawnTicket::Id m_ticketId;
|
||||
uint32_t m_requestId;
|
||||
};
|
||||
struct ReloadSpawnableCommand
|
||||
{
|
||||
AZ::Data::Asset<Spawnable> m_spawnable;
|
||||
ReloadSpawnableCallback m_completionCallback;
|
||||
EntitySpawnTicket* m_ticket;
|
||||
uint32_t m_ticketId;
|
||||
Ticket* m_ticket;
|
||||
EntitySpawnTicket::Id m_ticketId;
|
||||
uint32_t m_requestId;
|
||||
};
|
||||
struct ListEntitiesCommand
|
||||
{
|
||||
ListEntitiesCallback m_listCallback;
|
||||
EntitySpawnTicket* m_ticket;
|
||||
uint32_t m_ticketId;
|
||||
Ticket* m_ticket;
|
||||
EntitySpawnTicket::Id m_ticketId;
|
||||
uint32_t m_requestId;
|
||||
};
|
||||
struct ListIndicesEntitiesCommand
|
||||
{
|
||||
ListIndicesEntitiesCallback m_listCallback;
|
||||
EntitySpawnTicket* m_ticket;
|
||||
uint32_t m_ticketId;
|
||||
Ticket* m_ticket;
|
||||
EntitySpawnTicket::Id m_ticketId;
|
||||
uint32_t m_requestId;
|
||||
};
|
||||
struct ClaimEntitiesCommand
|
||||
{
|
||||
ClaimEntitiesCallback m_listCallback;
|
||||
EntitySpawnTicket* m_ticket;
|
||||
uint32_t m_ticketId;
|
||||
Ticket* m_ticket;
|
||||
EntitySpawnTicket::Id m_ticketId;
|
||||
uint32_t m_requestId;
|
||||
};
|
||||
struct BarrierCommand
|
||||
{
|
||||
BarrierCallback m_completionCallback;
|
||||
EntitySpawnTicket* m_ticket;
|
||||
uint32_t m_ticketId;
|
||||
Ticket* m_ticket;
|
||||
EntitySpawnTicket::Id m_ticketId;
|
||||
uint32_t m_requestId;
|
||||
};
|
||||
struct DestroyTicketCommand
|
||||
{
|
||||
Ticket* m_ticket;
|
||||
uint32_t m_ticketId;
|
||||
uint32_t m_requestId;
|
||||
};
|
||||
|
||||
using Requests = AZStd::variant<
|
||||
SpawnAllEntitiesCommand, SpawnEntitiesCommand, DespawnAllEntitiesCommand, ReloadSpawnableCommand, ListEntitiesCommand,
|
||||
ListIndicesEntitiesCommand, ClaimEntitiesCommand, BarrierCommand, DestroyTicketCommand>;
|
||||
|
||||
struct Queue
|
||||
{
|
||||
AZStd::deque<Requests> m_delayed; //!< Requests that were processed before, but couldn't be completed.
|
||||
AZStd::queue<Requests> m_pendingRequest; //!< Requests waiting to be processed for the first time.
|
||||
AZStd::mutex m_pendingRequestMutex;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
void QueueRequest(EntitySpawnTicket& ticket, SpawnablePriority priority, T&& request);
|
||||
AZStd::pair<EntitySpawnTicket::Id, void*> CreateTicket(AZ::Data::Asset<Spawnable>&& spawnable) override;
|
||||
void DestroyTicket(void* ticket) override;
|
||||
|
||||
CommandQueueStatus ProcessQueue(Queue& queue);
|
||||
|
||||
AZ::Entity* SpawnSingleEntity(const AZ::Entity& entityTemplate,
|
||||
AZ::SerializeContext& serializeContext);
|
||||
|
||||
@@ -169,16 +201,18 @@ namespace AzFramework
|
||||
bool ProcessRequest(BarrierCommand& request, AZ::SerializeContext& serializeContext);
|
||||
bool ProcessRequest(DestroyTicketCommand& request, AZ::SerializeContext& serializeContext);
|
||||
|
||||
[[nodiscard]] static bool IsEqualTicket(const EntitySpawnTicket* lhs, const EntitySpawnTicket* rhs);
|
||||
[[nodiscard]] static bool IsEqualTicket(const Ticket* lhs, const EntitySpawnTicket* rhs);
|
||||
[[nodiscard]] static bool IsEqualTicket(const EntitySpawnTicket* lhs, const Ticket* rhs);
|
||||
[[nodiscard]] static bool IsEqualTicket(const Ticket* lhs, const Ticket* rhs);
|
||||
|
||||
AZStd::deque<Requests> m_delayedQueue; //!< Requests that were processed before, but couldn't be completed.
|
||||
AZStd::queue<Requests> m_pendingRequestQueue;
|
||||
AZStd::mutex m_pendingRequestQueueMutex;
|
||||
Queue m_highPriorityQueue;
|
||||
Queue m_regularPriorityQueue;
|
||||
|
||||
AZ::Event<AZ::Data::Asset<Spawnable>> m_onSpawnedEvent;
|
||||
AZ::Event<AZ::Data::Asset<Spawnable>> m_onDespawnedEvent;
|
||||
|
||||
//! The threshold used to determine if a request goes in the regular (if bigger than the value) or high priority queue (if smaller
|
||||
//! or equal to this value). The starting value of 64 is chosen as it's between default values SpawnablePriority_High and
|
||||
//! SpawnablePriority_Default which gives users a bit of room to fine tune the priorities as this value can be configured
|
||||
//! through the Settings Registry under the key "/O3DE/AzFramework/Spawnables/HighPriorityThreshold".
|
||||
SpawnablePriority m_highPriorityThreshold { 64 };
|
||||
};
|
||||
|
||||
AZ_DEFINE_ENUM_BITWISE_OPERATORS(AzFramework::SpawnableEntitiesManager::CommandQueuePriority);
|
||||
} // namespace AzFramework
|
||||
|
||||
@@ -48,10 +48,23 @@ namespace AzFramework
|
||||
|
||||
void SpawnableSystemComponent::OnTick(float /*deltaTime*/, AZ::ScriptTimePoint /*time*/)
|
||||
{
|
||||
m_entitiesManager.ProcessQueue();
|
||||
m_entitiesManager.ProcessQueue(
|
||||
SpawnableEntitiesManager::CommandQueuePriority::High | SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
RootSpawnableNotificationBus::ExecuteQueuedEvents();
|
||||
}
|
||||
|
||||
int SpawnableSystemComponent::GetTickOrder()
|
||||
{
|
||||
return AZ::ComponentTickBus::TICK_GAME;
|
||||
}
|
||||
|
||||
void SpawnableSystemComponent::OnSystemTick()
|
||||
{
|
||||
// Handle only high priority spawning events such as those created from network. These need to happen even if the client
|
||||
// doesn't have focus to avoid time-out issues for instance.
|
||||
m_entitiesManager.ProcessQueue(SpawnableEntitiesManager::CommandQueuePriority::High);
|
||||
}
|
||||
|
||||
void SpawnableSystemComponent::OnCatalogLoaded([[maybe_unused]] const char* catalogFile)
|
||||
{
|
||||
if (!m_catalogAvailable)
|
||||
@@ -168,7 +181,8 @@ namespace AzFramework
|
||||
SpawnableEntitiesManager::CommandQueueStatus queueStatus;
|
||||
do
|
||||
{
|
||||
queueStatus = m_entitiesManager.ProcessQueue();
|
||||
queueStatus = m_entitiesManager.ProcessQueue(
|
||||
SpawnableEntitiesManager::CommandQueuePriority::High | SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
} while (queueStatus == SpawnableEntitiesManager::CommandQueueStatus::HasCommandsLeft);
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ namespace AzFramework
|
||||
class SpawnableSystemComponent
|
||||
: public AZ::Component
|
||||
, public AZ::TickBus::Handler
|
||||
, public AZ::SystemTickBus::Handler
|
||||
, public AssetCatalogEventBus::Handler
|
||||
, public RootSpawnableInterface::Registrar
|
||||
, public RootSpawnableNotificationBus::Handler
|
||||
@@ -58,6 +59,13 @@ namespace AzFramework
|
||||
//
|
||||
|
||||
void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
|
||||
int GetTickOrder() override;
|
||||
|
||||
//
|
||||
// SystemTickBus
|
||||
//
|
||||
|
||||
void OnSystemTick() override;
|
||||
|
||||
//
|
||||
// AssetCatalogEventBus
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace AzFramework
|
||||
AZ_CVAR(float, ed_cameraSystemOrbitDollyScrollSpeed, 0.02f, nullptr, AZ::ConsoleFunctorFlags::Null, "");
|
||||
AZ_CVAR(float, ed_cameraSystemOrbitDollyCursorSpeed, 0.01f, nullptr, AZ::ConsoleFunctorFlags::Null, "");
|
||||
AZ_CVAR(float, ed_cameraSystemScrollTranslateSpeed, 0.02f, nullptr, AZ::ConsoleFunctorFlags::Null, "");
|
||||
AZ_CVAR(float, ed_cameraSystemMinOrbitDistance, 6.0f, nullptr, AZ::ConsoleFunctorFlags::Null, "");
|
||||
AZ_CVAR(float, ed_cameraSystemMinOrbitDistance, 10.0f, nullptr, AZ::ConsoleFunctorFlags::Null, "");
|
||||
AZ_CVAR(float, ed_cameraSystemMaxOrbitDistance, 50.0f, nullptr, AZ::ConsoleFunctorFlags::Null, "");
|
||||
AZ_CVAR(float, ed_cameraSystemLookSmoothness, 5.0f, nullptr, AZ::ConsoleFunctorFlags::Null, "");
|
||||
AZ_CVAR(float, ed_cameraSystemTranslateSmoothness, 5.0f, nullptr, AZ::ConsoleFunctorFlags::Null, "");
|
||||
@@ -37,7 +37,6 @@ namespace AzFramework
|
||||
AZ_CVAR(float, ed_cameraSystemPanSpeed, 0.01f, nullptr, AZ::ConsoleFunctorFlags::Null, "");
|
||||
AZ_CVAR(bool, ed_cameraSystemPanInvertX, true, nullptr, AZ::ConsoleFunctorFlags::Null, "");
|
||||
AZ_CVAR(bool, ed_cameraSystemPanInvertY, true, nullptr, AZ::ConsoleFunctorFlags::Null, "");
|
||||
AZ_CVAR(float, ed_cameraSystemLookDeadzone, 2.0f, nullptr, AZ::ConsoleFunctorFlags::Null, "");
|
||||
|
||||
AZ_CVAR(
|
||||
AZ::CVarFixedString, ed_cameraSystemTranslateForwardKey, "keyboard_key_alphanumeric_W", nullptr, AZ::ConsoleFunctorFlags::Null, "");
|
||||
@@ -144,7 +143,7 @@ namespace AzFramework
|
||||
z = AZStd::atan2(-orientation.GetElement(1, 2), orientation.GetElement(1, 1));
|
||||
}
|
||||
|
||||
return {x, y, z};
|
||||
return { x, y, z };
|
||||
}
|
||||
|
||||
void UpdateCameraFromTransform(Camera& camera, const AZ::Transform& transform)
|
||||
@@ -179,7 +178,7 @@ namespace AzFramework
|
||||
{
|
||||
const auto nextCamera = m_cameras.StepCamera(targetCamera, m_motionDelta, m_scrollDelta, deltaTime);
|
||||
|
||||
m_motionDelta = ScreenVector{0, 0};
|
||||
m_motionDelta = ScreenVector{ 0, 0 };
|
||||
m_scrollDelta = 0.0f;
|
||||
|
||||
return nextCamera;
|
||||
@@ -213,7 +212,10 @@ namespace AzFramework
|
||||
auto& cameraInput = m_idleCameraInputs[i];
|
||||
const bool canBegin = cameraInput->Beginning() &&
|
||||
AZStd::all_of(m_activeCameraInputs.cbegin(), m_activeCameraInputs.cend(),
|
||||
[](const auto& input) { return !input->Exclusive(); }) &&
|
||||
[](const auto& input)
|
||||
{
|
||||
return !input->Exclusive();
|
||||
}) &&
|
||||
(!cameraInput->Exclusive() || (cameraInput->Exclusive() && m_activeCameraInputs.empty()));
|
||||
|
||||
if (canBegin)
|
||||
@@ -231,7 +233,8 @@ namespace AzFramework
|
||||
|
||||
const Camera nextCamera = AZStd::accumulate(
|
||||
AZStd::begin(m_activeCameraInputs), AZStd::end(m_activeCameraInputs), targetCamera,
|
||||
[cursorDelta, scrollDelta, deltaTime](Camera acc, auto& camera) {
|
||||
[cursorDelta, scrollDelta, deltaTime](Camera acc, auto& camera)
|
||||
{
|
||||
acc = camera->StepCamera(acc, cursorDelta, scrollDelta, deltaTime);
|
||||
return acc;
|
||||
});
|
||||
@@ -284,7 +287,8 @@ namespace AzFramework
|
||||
|
||||
bool RotateCameraInput::HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, [[maybe_unused]] float scrollDelta)
|
||||
{
|
||||
const ClickDetector::ClickEvent clickEvent = [&event, this] {
|
||||
const ClickDetector::ClickEvent clickEvent = [&event, this]
|
||||
{
|
||||
if (const auto& input = AZStd::get_if<DiscreteInputEvent>(&event))
|
||||
{
|
||||
if (input->m_channelId == m_rotateChannelId)
|
||||
@@ -330,7 +334,10 @@ namespace AzFramework
|
||||
nextCamera.m_pitch -= float(cursorDelta.m_y) * ed_cameraSystemRotateSpeed;
|
||||
nextCamera.m_yaw -= float(cursorDelta.m_x) * ed_cameraSystemRotateSpeed;
|
||||
|
||||
const auto clampRotation = [](const float angle) { return AZStd::fmod(angle + AZ::Constants::TwoPi, AZ::Constants::TwoPi); };
|
||||
const auto clampRotation = [](const float angle)
|
||||
{
|
||||
return AZStd::fmod(angle + AZ::Constants::TwoPi, AZ::Constants::TwoPi);
|
||||
};
|
||||
|
||||
nextCamera.m_yaw = clampRotation(nextCamera.m_yaw);
|
||||
// clamp pitch to be +-90 degrees
|
||||
@@ -377,9 +384,10 @@ namespace AzFramework
|
||||
const auto deltaPanX = float(cursorDelta.m_x) * panAxes.m_horizontalAxis * ed_cameraSystemPanSpeed;
|
||||
const auto deltaPanY = float(cursorDelta.m_y) * panAxes.m_verticalAxis * ed_cameraSystemPanSpeed;
|
||||
|
||||
const auto inv = [](const bool invert) {
|
||||
constexpr float Dir[] = {1.0f, -1.0f};
|
||||
return Dir[static_cast<int>(invert)];
|
||||
const auto inv = [](const bool invert)
|
||||
{
|
||||
constexpr float Dir[] = { 1.0f, -1.0f };
|
||||
return Dir[aznumeric_cast<int>(invert)];
|
||||
};
|
||||
|
||||
nextCamera.m_lookAt += deltaPanX * inv(ed_cameraSystemPanInvertX);
|
||||
@@ -475,7 +483,8 @@ namespace AzFramework
|
||||
const auto axisY = translationBasis.GetBasisY();
|
||||
const auto axisZ = translationBasis.GetBasisZ();
|
||||
|
||||
const float speed = [boost = m_boost]() {
|
||||
const float speed = [boost = m_boost]()
|
||||
{
|
||||
return ed_cameraSystemTranslateSpeed * (boost ? ed_cameraSystemBoostMultiplier : 1.0f);
|
||||
}();
|
||||
|
||||
@@ -555,10 +564,12 @@ namespace AzFramework
|
||||
|
||||
if (Beginning())
|
||||
{
|
||||
const auto hasLookAt = [&nextCamera, &targetCamera, &lookAtFn = m_lookAtFn] {
|
||||
const auto hasLookAt = [&nextCamera, &targetCamera, &lookAtFn = m_lookAtFn]
|
||||
{
|
||||
if (lookAtFn)
|
||||
{
|
||||
if (const auto lookAt = lookAtFn())
|
||||
// pass through the camera's position and look vector for use in the lookAt function
|
||||
if (const auto lookAt = lookAtFn(targetCamera.Translation(), targetCamera.Rotation().GetBasisY()))
|
||||
{
|
||||
auto transform = AZ::Transform::CreateLookAt(targetCamera.m_lookAt, *lookAt);
|
||||
nextCamera.m_lookDist = -lookAt->GetDistance(targetCamera.m_lookAt);
|
||||
@@ -692,14 +703,20 @@ namespace AzFramework
|
||||
|
||||
Camera SmoothCamera(const Camera& currentCamera, const Camera& targetCamera, const float deltaTime)
|
||||
{
|
||||
const auto clamp_rotation = [](const float angle) { return AZStd::fmod(angle + AZ::Constants::TwoPi, AZ::Constants::TwoPi); };
|
||||
const auto clamp_rotation = [](const float angle)
|
||||
{
|
||||
return AZStd::fmod(angle + AZ::Constants::TwoPi, AZ::Constants::TwoPi);
|
||||
};
|
||||
|
||||
// keep yaw in 0 - 360 range
|
||||
float targetYaw = clamp_rotation(targetCamera.m_yaw);
|
||||
const float currentYaw = clamp_rotation(currentCamera.m_yaw);
|
||||
|
||||
// return the sign of the float input (-1, 0, 1)
|
||||
const auto sign = [](const float value) { return aznumeric_cast<float>((0.0f < value) - (value < 0.0f)); };
|
||||
const auto sign = [](const float value)
|
||||
{
|
||||
return aznumeric_cast<float>((0.0f < value) - (value < 0.0f));
|
||||
};
|
||||
|
||||
// ensure smooth transition when moving across 0 - 360 boundary
|
||||
const float yawDelta = targetYaw - currentYaw;
|
||||
@@ -727,26 +744,28 @@ namespace AzFramework
|
||||
const auto& inputChannelId = inputChannel.GetInputChannelId();
|
||||
const auto& inputDeviceId = inputChannel.GetInputDevice().GetInputDeviceId();
|
||||
|
||||
const bool wasMouseButton =
|
||||
AZStd::any_of(InputDeviceMouse::Button::All.begin(), InputDeviceMouse::Button::All.end(), [inputChannelId](const auto& button) {
|
||||
const bool wasMouseButton = AZStd::any_of(
|
||||
InputDeviceMouse::Button::All.begin(), InputDeviceMouse::Button::All.end(),
|
||||
[inputChannelId](const auto& button)
|
||||
{
|
||||
return button == inputChannelId;
|
||||
});
|
||||
|
||||
if (inputChannelId == InputDeviceMouse::Movement::X)
|
||||
{
|
||||
return HorizontalMotionEvent{(int)inputChannel.GetValue()};
|
||||
return HorizontalMotionEvent{ aznumeric_cast<int>(inputChannel.GetValue()) };
|
||||
}
|
||||
else if (inputChannelId == InputDeviceMouse::Movement::Y)
|
||||
{
|
||||
return VerticalMotionEvent{(int)inputChannel.GetValue()};
|
||||
return VerticalMotionEvent{ aznumeric_cast<int>(inputChannel.GetValue()) };
|
||||
}
|
||||
else if (inputChannelId == InputDeviceMouse::Movement::Z)
|
||||
{
|
||||
return ScrollEvent{inputChannel.GetValue()};
|
||||
return ScrollEvent{ inputChannel.GetValue() };
|
||||
}
|
||||
else if (wasMouseButton || InputDeviceKeyboard::IsKeyboardDevice(inputDeviceId))
|
||||
{
|
||||
return DiscreteInputEvent{inputChannelId, inputChannel.GetState()};
|
||||
return DiscreteInputEvent{ inputChannelId, inputChannel.GetState() };
|
||||
}
|
||||
|
||||
return AZStd::monostate{};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user