diff --git a/.clang-format b/.clang-format
index 2a9205219a..565f28130e 100644
--- a/.clang-format
+++ b/.clang-format
@@ -7,28 +7,33 @@ AlignConsecutiveDeclarations: false
AlignEscapedNewlines: Right
AlignOperands: false
AlignTrailingComments: false
+AllowAllArgumentsOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortFunctionsOnASingleLine: None
+AllowShortLambdasOnASingleLine: None
AlwaysBreakAfterReturnType: None
AlwaysBreakTemplateDeclarations: true
BreakBeforeBraces: Custom
BraceWrapping:
AfterClass: true
+ AfterControlStatement: true
AfterEnum: true
AfterFunction: true
AfterNamespace: true
+ BeforeLambdaBody: true
AfterStruct: true
- SplitEmptyFunction: true
- AfterControlStatement: true
BeforeElse: true
+ SplitEmptyFunction: true
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: BeforeComma
+BreakInheritanceList: BeforeComma
ColumnLimit: 140
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
-Cpp11BracedListStyle: true
+Cpp11BracedListStyle: false
FixNamespaceComments: true
IncludeBlocks: Preserve
+IndentCaseBlocks: true
IndentCaseLabels: false
IndentPPDirectives: None
IndentWidth: 4
@@ -38,27 +43,17 @@ NamespaceIndentation: All
PenaltyReturnTypeOnItsOwnLine: 1000
PointerAlignment: Left
SortIncludes: true
+SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: false
SpaceBeforeAssignmentOperators: true
+SpaceBeforeCpp11BracedList: true
+SpaceBeforeCtorInitializerColon: true
+SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
+SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyParentheses: false
SpacesInAngles: false
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
+Standard: c++17
UseTab: Never
-
-# Not available in clang-format version 6.0.0
-# BasedOnStyle: Microsoft
-# Standard: c++17
-# AllowAllArgumentsOnNextLine: true
-# AllowShortLambdasOnASingleLine: None
-# BreakInheritanceList: BeforeComma
-# SpaceAfterLogicalNot: false
-# SpaceBeforeCpp11BracedList: false
-# SpaceBeforeCtorInitializerColon: true
-# SpaceBeforeInheritanceColon: true
-# SpaceBeforeRangeBasedForLoopColon: true
-
-# Not available in clang-format version 10.0.0
-# BeforeLambdaBody: true (BraceWrapping)
-# IndentCaseBlocks: true
diff --git a/AutomatedTesting/Gem/Code/runtime_dependencies.cmake b/AutomatedTesting/Gem/Code/runtime_dependencies.cmake
index 33c2bf8d5f..280c25bcf7 100644
--- a/AutomatedTesting/Gem/Code/runtime_dependencies.cmake
+++ b/AutomatedTesting/Gem/Code/runtime_dependencies.cmake
@@ -45,7 +45,4 @@ set(GEM_DEPENDENCIES
Gem::Atom_AtomBridge
Gem::NvCloth
Gem::Blast
- Gem::AWSCore
- Gem::AWSClientAuth
- Gem::AWSMetrics
)
diff --git a/AutomatedTesting/Gem/Code/tool_dependencies.cmake b/AutomatedTesting/Gem/Code/tool_dependencies.cmake
index c8eccab947..e2e57d4012 100644
--- a/AutomatedTesting/Gem/Code/tool_dependencies.cmake
+++ b/AutomatedTesting/Gem/Code/tool_dependencies.cmake
@@ -57,7 +57,4 @@ set(GEM_DEPENDENCIES
Gem::Atom_AtomBridge.Editor
Gem::NvCloth.Editor
Gem::Blast.Editor
- Gem::AWSCore.Editor
- Gem::AWSClientAuth
- Gem::AWSMetrics
)
diff --git a/AutomatedTesting/Gem/PythonTests/AWS/Windows/client_auth/test_password_signin.py b/AutomatedTesting/Gem/PythonTests/AWS/Windows/client_auth/test_password_signin.py
new file mode 100644
index 0000000000..da4898b8a9
--- /dev/null
+++ b/AutomatedTesting/Gem/PythonTests/AWS/Windows/client_auth/test_password_signin.py
@@ -0,0 +1,97 @@
+"""
+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 pytest
+import os
+import logging
+import ly_test_tools.log.log_monitor
+
+from AWS.Windows.resource_mappings.resource_mappings import resource_mappings
+from AWS.Windows.cdk.cdk import cdk
+from AWS.common.aws_utils import aws_utils
+from assetpipeline.ap_fixtures.asset_processor_fixture import asset_processor as asset_processor
+
+AWS_PROJECT_NAME = 'AWS-AutomationTest'
+AWS_CLIENT_AUTH_FEATURE_NAME = 'AWSClientAuth'
+AWS_CLIENT_AUTH_DEFAULT_PROFILE_NAME = 'default'
+
+GAME_LOG_NAME = 'Game.log'
+
+logger = logging.getLogger(__name__)
+
+
+@pytest.mark.SUITE_periodic
+@pytest.mark.usefixtures('automatic_process_killer')
+@pytest.mark.usefixtures('asset_processor')
+@pytest.mark.usefixtures('workspace')
+@pytest.mark.parametrize('project', ['AutomatedTesting'])
+@pytest.mark.usefixtures('cdk')
+@pytest.mark.parametrize('feature_name', [AWS_CLIENT_AUTH_FEATURE_NAME])
+@pytest.mark.usefixtures('resource_mappings')
+@pytest.mark.parametrize('resource_mappings_filename', ['aws_resource_mappings.json'])
+@pytest.mark.usefixtures('aws_utils')
+@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 TestAWSClientAuthPasswordSignIn(object):
+ """
+ Test class to verify AWS Cognito IDP Password sign in and Cognito Identity pool authenticated authorization.
+ """
+
+ def test_password_signin_credentials(self,
+ launcher: pytest.fixture,
+ cdk: pytest.fixture,
+ resource_mappings: pytest.fixture,
+ workspace: pytest.fixture,
+ asset_processor: pytest.fixture,
+ aws_utils: pytest.fixture
+ ):
+ """
+ Setup: Deploys cdk and updates resource mapping file.
+ Tests: Sign up new test user, admin confirm the user, sign in and get aws credentials.
+ Verification: Log monitor looks for success credentials log.
+ """
+ logger.info(f'Cdk stack names:\n{cdk.list()}')
+ stacks = cdk.deploy()
+ resource_mappings.populate_output_keys(stacks)
+ asset_processor.start()
+ asset_processor.wait_for_idle()
+
+ file_to_monitor = os.path.join(launcher.workspace.paths.project_log(), GAME_LOG_NAME)
+ log_monitor = ly_test_tools.log.log_monitor.LogMonitor(launcher=launcher, log_file_path=file_to_monitor)
+
+ launcher.args = ['+LoadLevel', 'AWS/ClientAuthPasswordSignUp']
+
+ with launcher.start(launch_ap=False):
+ result = log_monitor.monitor_log_for_lines(
+ expected_lines=['(Script) - Signup Success'],
+ unexpected_lines=['(Script) - Signup Fail'],
+ halt_on_unexpected=True,
+ )
+ assert result, 'Sign Up Success.'
+
+ launcher.stop()
+
+ cognito_idp = aws_utils.client('cognito-idp')
+ user_pool_id = resource_mappings.get_resource_name_id(f'{AWS_CLIENT_AUTH_FEATURE_NAME}.CognitoUserPoolId')
+ print(f'UserPoolId:{user_pool_id}')
+ cognito_idp.admin_confirm_sign_up(
+ UserPoolId=user_pool_id,
+ Username='test1'
+ )
+
+ launcher.args = ['+LoadLevel', 'AWS/ClientAuthPasswordSignIn']
+
+ with launcher.start(launch_ap=False):
+ result = log_monitor.monitor_log_for_lines(
+ expected_lines=['(Script) - SignIn Success', '(Script) - Success credentials'],
+ unexpected_lines=['(Script) - SignIn Fail', '(Script) - Fail credentials'],
+ halt_on_unexpected=True,
+ )
+ assert result, 'Sign in Success, fetched authenticated AWS temp credentials.'
diff --git a/AutomatedTesting/Gem/PythonTests/AWS/Windows/resource_mappings/resource_mappings.py b/AutomatedTesting/Gem/PythonTests/AWS/Windows/resource_mappings/resource_mappings.py
index c8d8cff828..b3fa3011ce 100644
--- a/AutomatedTesting/Gem/PythonTests/AWS/Windows/resource_mappings/resource_mappings.py
+++ b/AutomatedTesting/Gem/PythonTests/AWS/Windows/resource_mappings/resource_mappings.py
@@ -39,6 +39,7 @@ class ResourceMappings:
self._region = region
self._feature_name = feature_name
self._account_id = account_id
+ self._resource_mappings = {}
assert os.path.exists(self._resource_mapping_file_path), \
f'Invalid resource mapping file path {self._resource_mapping_file_path}'
@@ -79,6 +80,7 @@ class ResourceMappings:
resource_mappings[AWS_RESOURCE_MAPPINGS_KEY][resource_key]['Name/ID'] = output.get('OutputValue',
'InvalidId')
+ self._resource_mappings = resource_mappings
with open(self._resource_mapping_file_path, 'w') as file_content:
json.dump(resource_mappings, file_content, indent=4)
@@ -103,6 +105,9 @@ class ResourceMappings:
self._region = ''
self._client = None
+ def get_resource_name_id(self, resource_key: str):
+ return self._resource_mappings[AWS_RESOURCE_MAPPINGS_KEY][resource_key]['Name/ID']
+
@pytest.fixture(scope='function')
def resource_mappings(
diff --git a/AutomatedTesting/Gem/PythonTests/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/CMakeLists.txt
index c6ed6c7538..8142691464 100644
--- a/AutomatedTesting/Gem/PythonTests/CMakeLists.txt
+++ b/AutomatedTesting/Gem/PythonTests/CMakeLists.txt
@@ -60,4 +60,5 @@ add_subdirectory(streaming)
add_subdirectory(smoke)
## AWS ##
-add_subdirectory(AWS)
+# Enable when AWS Gems work on Linux and Android.
+# add_subdirectory(AWS)
diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/missing_dependency_tests.py b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/missing_dependency_tests.py
index 6cc3484d96..432b6cdfc8 100755
--- a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/missing_dependency_tests.py
+++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/missing_dependency_tests.py
@@ -84,10 +84,11 @@ class TestsMissingDependencies_WindowsAndMac(object):
"""Run a single test"""
for asset_platform in platforms:
- db_product = db_utils.get_product_id_from_relative(self._workspace, source_product, asset_platform)
+ db_product_path = db_utils.get_db_product_path(self._workspace, source_product, asset_platform)
+ db_product = db_utils.get_product_id(self._missing_dep_helper.asset_db, db_product_path)
if db_product:
db_utils.clear_missing_dependencies(self._missing_dep_helper.asset_db, db_product)
- expected_product = os.path.join(self._workspace.project, source_product).lower()
+ expected_product = source_product.lower()
dependency_search_params = [f"--dsp={dsp_param}", "--zeroAnalysisMode"]
if max_iterations:
@@ -112,17 +113,26 @@ class TestsMissingDependencies_WindowsAndMac(object):
# Expected missing dependencies
expected_dependencies = [
# String Asset #
- ("06E9D6633C875400A532BCB2C0CA19D6", "{06E9D663-3C87-5400-A532-BCB2C0CA19D6}:0"),
- ("1CB10C43F3245B93A294C602ADEF95F9:[0", "{1CB10C43-F324-5B93-A294-C602ADEF95F9}:0"),
- ("58BE9DA51F1753B98CEEEEB10E63454D", "{58BE9DA5-1F17-53B9-8CEE-EEB10E63454D}:914f19b7"),
- ("6BDE282B49C957F7B0714B26579BCA9A", "{6BDE282B-49C9-57F7-B071-4B26579BCA9A}:0"),
- ("747D31D71E62553592226173C49CF97E", "{747D31D7-1E62-5535-9222-6173C49CF97E}:1"),
- ("747D31D71E62553592226173C49CF97E", "{747D31D7-1E62-5535-9222-6173C49CF97E}:2"),
- ("9886E132-572D-5746-9377-E629AB6C1981", "{9886E132-572D-5746-9377-E629AB6C1981}:0"),
- ("33bcee02F3225688ABEE534F6058593F", "{33BCEE02-F322-5688-ABEE-534F6058593F}:0"),
- ("B92667DC-9F5B-5D72-A29D-99219DD9B691", "{B92667DC-9F5B-5D72-A29D-99219DD9B691}:0"),
- ("D92C4661C8985E19BD3597CB2318CFA6:[0", "{D92C4661-C898-5E19-BD35-97CB2318CFA6}:0"),
- ("7364AB2B092F5B0B80601BBC6E53087C", "{7364AB2B-092F-5B0B-8060-1BBC6E53087C}:0"),
+ ('1CB10C43F3245B93A294C602ADEF95F9:[0', '{1CB10C43-F324-5B93-A294-C602ADEF95F9}:0'),
+ ('33bcee02F3225688ABEE534F6058593F', '{33BCEE02-F322-5688-ABEE-534F6058593F}:0'),
+ ('345E5C660D6254FF8D0F7C8EE66A2249', '{345E5C66-0D62-54FF-8D0F-7C8EE66A2249}:3e8'),
+ ('345E5C660D6254FF8D0F7C8EE66A2249', '{345E5C66-0D62-54FF-8D0F-7C8EE66A2249}:3ea'),
+ ('345E5C660D6254FF8D0F7C8EE66A2249', '{345E5C66-0D62-54FF-8D0F-7C8EE66A2249}:3eb'),
+ ('37108522F50459499CD6C8D47A960CF1', '{37108522-F504-5949-9CD6-C8D47A960CF1}:3e8'),
+ ('37108522F50459499CD6C8D47A960CF1', '{37108522-F504-5949-9CD6-C8D47A960CF1}:3ea'),
+ ('37108522F50459499CD6C8D47A960CF1', '{37108522-F504-5949-9CD6-C8D47A960CF1}:3eb'),
+ ('6BDE282B49C957F7B0714B26579BCA9A', '{6BDE282B-49C9-57F7-B071-4B26579BCA9A}:0'),
+ ('747D31D71E62553592226173C49CF97E', '{747D31D7-1E62-5535-9222-6173C49CF97E}:1'),
+ ('747D31D71E62553592226173C49CF97E', '{747D31D7-1E62-5535-9222-6173C49CF97E}:2'),
+ ('A26C73D1837E5AE59E68F916FA7C3699', '{A26C73D1-837E-5AE5-9E68-F916FA7C3699}:3e8'),
+ ('A26C73D1837E5AE59E68F916FA7C3699', '{A26C73D1-837E-5AE5-9E68-F916FA7C3699}:3ea'),
+ ('A26C73D1837E5AE59E68F916FA7C3699', '{A26C73D1-837E-5AE5-9E68-F916FA7C3699}:3eb'),
+ ('B076CDDC-14DF-50F4-A5E9-7518ABB3E851', '{B076CDDC-14DF-50F4-A5E9-7518ABB3E851}:0'),
+ ('C67BEA9F-09FF-59AA-A7F0-A52B8F987508', '{C67BEA9F-09FF-59AA-A7F0-A52B8F987508}:3e8'),
+ ('C67BEA9F-09FF-59AA-A7F0-A52B8F987508', '{C67BEA9F-09FF-59AA-A7F0-A52B8F987508}:3ea'),
+ ('C67BEA9F-09FF-59AA-A7F0-A52B8F987508', '{C67BEA9F-09FF-59AA-A7F0-A52B8F987508}:3eb'),
+ ('C67BEA9F-09FF-59AA-A7F0-A52B8F987508', '{C67BEA9F-09FF-59AA-A7F0-A52B8F987508}:3ec'),
+ ('D92C4661C8985E19BD3597CB2318CFA6:[0', '{D92C4661-C898-5E19-BD35-97CB2318CFA6}:0'),
]
self.do_missing_dependency_test(expected_product, expected_dependencies,
"%ValidUUIDsNotDependency.txt")
@@ -151,9 +161,9 @@ class TestsMissingDependencies_WindowsAndMac(object):
# Expected missing dependencies
expected_dependencies = [
# String Asset #
- ("2ef92b8D044E5C278E2BB1AC0374A4E7:131072", "{2EF92B8D-044E-5C27-8E2B-B1AC0374A4E7}:20000"),
- ("A2482826-053D-5634-A27B-084B1326AAE5}:[196608", "{A2482826-053D-5634-A27B-084B1326AAE5}:30000"),
- ("D83B36F1-61A6-5001-B191-4D0CE282E236}-327680", "{D83B36F1-61A6-5001-B191-4D0CE282E236}:50000"),
+ ('2ef92b8D044E5C278E2BB1AC0374A4E7:1003', '{2EF92B8D-044E-5C27-8E2B-B1AC0374A4E7}:3eb'),
+ ('A2482826-053D-5634-A27B-084B1326AAE5}:[1002', '{A2482826-053D-5634-A27B-084B1326AAE5}:3ea'),
+ ('D83B36F1-61A6-5001-B191-4D0CE282E236}-1002', '{D83B36F1-61A6-5001-B191-4D0CE282E236}:3ea'),
]
self.do_missing_dependency_test(expected_product, expected_dependencies,
@@ -186,16 +196,13 @@ class TestsMissingDependencies_WindowsAndMac(object):
# Expected missing dependencies
expected_dependencies = [
# String Asset #
- ("Config/Editor.xml", "{06E9D663-3C87-5400-A532-BCB2C0CA19D6}:0"),
- (r"TestAssets\WildcardScanTest1.txt", "{1CB10C43-F324-5B93-A294-C602ADEF95F9}:0"),
- ("TestAssets/RelativeProductPathsNotDependencies.txt", "{B772953C-A08A-5D20-9491-530E87D11504}:0"),
- ("textures/_dev_Purple.tif", "{A2482826-053D-5634-A27B-084B1326AAE5}:0"),
- ("textures/_dev_Purple.tif", "{A2482826-053D-5634-A27B-084B1326AAE5}:10000"),
- ("textures/_dev_Purple.tif", "{A2482826-053D-5634-A27B-084B1326AAE5}:20000"),
- ("textures/_dev_Purple.tif", "{A2482826-053D-5634-A27B-084B1326AAE5}:30000"),
- ("textures/_dev_Purple.tif", "{A2482826-053D-5634-A27B-084B1326AAE5}:40000"),
- ("textures/_dev_Purple.tif", "{A2482826-053D-5634-A27B-084B1326AAE5}:50000"),
- ("Config/gAME.XML", "{B92667DC-9F5B-5D72-A29D-99219DD9B691}:0"),
+ ('TestAssets\\WildcardScanTest1.txt', '{1CB10C43-F324-5B93-A294-C602ADEF95F9}:0'),
+ ('libs/particles/milestone2PARTICLES.XML', '{6BDE282B-49C9-57F7-B071-4B26579BCA9A}:0'),
+ ('textures/_dev_Purple.tif', '{A2482826-053D-5634-A27B-084B1326AAE5}:3e8'),
+ ('textures/_dev_Purple.tif', '{A2482826-053D-5634-A27B-084B1326AAE5}:3ea'),
+ ('textures/_dev_Purple.tif', '{A2482826-053D-5634-A27B-084B1326AAE5}:3eb'),
+ ('project.json', '{B076CDDC-14DF-50F4-A5E9-7518ABB3E851}:0'),
+ ('TestAssets/RelativeProductPathsNotDependencies.txt', '{B772953C-A08A-5D20-9491-530E87D11504}:0'),
]
self.do_missing_dependency_test(expected_product, expected_dependencies,
@@ -228,24 +235,22 @@ class TestsMissingDependencies_WindowsAndMac(object):
expected_product = f"testassets\\relativeproductpathsnotdependencies.txt"
expected_dependencies = [
# String Asset #
- ("materials/floor_tile.mtl", "{0EFF5E4A-F544-5D87-8696-6DDFA62D6063}:0"),
- ("materials/am_grass1.mtl", "{1151F14D-38A6-5579-888A-BE3139882E68}:0"),
- ("2ef92b8D044E5C278E2BB1AC0374A4E7:131072", "{2EF92B8D-044E-5C27-8E2B-B1AC0374A4E7}:20000"),
- ("ui/milestone2menu.uicanvas", "{445D9AF3-6CA5-5281-82A9-5C570BCD1DB8}:0"),
- ("ui/fonts/lyshineexamples/vera.ttf", "{74F5C29E-4749-5EE8-AEC6-A1C540600CE7}:0"),
- ("materials/am_rockground.mtl", "{A1DA3D05-A020-5BB5-A608-C4812B7BD733}:0"),
- ("textures/_dev_yellow_light.dds.2", "{6C40868F-3FC1-5115-96EA-DD0A9E33DEE4}:20000"),
- (r"automatedtesting\textures\_dev_stucco.dds", "{70114D85-D712-5AEB-A816-8FE3A37087AF}:0"),
- ("textures/milestone2/ama_grey_02.dds", "{3EE80AAD-EB9C-56BD-9E9C-65410578998C}:0"),
- (r"textures\\_dev_tan.dds", "{8F2BCEF5-C8CE-5B80-8103-8C1D694D012C}:0"),
- ("textures/_dev_purple.dds", "{A2482826-053D-5634-A27B-084B1326AAE5}:0"),
- ("TEXTURES/_DEV_WHITE.dds", "{D83B36F1-61A6-5001-B191-4D0CE282E236}:0"),
- ("textures/_dev_woodland.dds", "{F3DD193C-5845-569C-A974-AA338B30CF86}:0"),
- ("A2482826-053D-5634-A27B-084B1326AAE5}:[196608", "{A2482826-053D-5634-A27B-084B1326AAE5}:30000"),
- ("B92667DC-9F5B-5D72-A29D-99219DD9B691", "{B92667DC-9F5B-5D72-A29D-99219DD9B691}:0"),
- ("CEAA362B4E505BCEB827CB92EF40A50E", "{CEAA362B-4E50-5BCE-B827-CB92EF40A50E}:1"),
- ("CEAA362B4E505BCEB827CB92EF40A50E", "{CEAA362B-4E50-5BCE-B827-CB92EF40A50E}:2"),
- ("ui/fonts/lyshineexamples/veramono.ttf", "{BAD7FDC5-7BA6-5490-95AA-89078E2FA876}:0"),
+ ('materials/floor_tile.mtl', '{0EFF5E4A-F544-5D87-8696-6DDFA62D6063}:0'),
+ ('materials/am_grass1.mtl', '{1151F14D-38A6-5579-888A-BE3139882E68}:0'),
+ ('2ef92b8D044E5C278E2BB1AC0374A4E7:1002', '{2EF92B8D-044E-5C27-8E2B-B1AC0374A4E7}:3ea'),
+ ('textures/milestone2/ama_grey_02.tif.streamingimage', '{3EE80AAD-EB9C-56BD-9E9C-65410578998C}:3e8'),
+ ('ui/milestone2menu.uicanvas', '{445D9AF3-6CA5-5281-82A9-5C570BCD1DB8}:0'),
+ ('libs/particles/milestone2particles.xml', '{6BDE282B-49C9-57F7-B071-4B26579BCA9A}:0'),
+ ('textures/_dev_yellow_light.tif.1002.imagemipchain', '{6C40868F-3FC1-5115-96EA-DD0A9E33DEE4}:3ea'),
+ ('textures\\\\_dev_tan.tif.streamingimage', '{8F2BCEF5-C8CE-5B80-8103-8C1D694D012C}:3e8'),
+ ('materials/am_rockground.mtl', '{A1DA3D05-A020-5BB5-A608-C4812B7BD733}:0'),
+ ('textures/_dev_purple.tif.streamingimage', '{A2482826-053D-5634-A27B-084B1326AAE5}:3e8'),
+ ('A2482826-053D-5634-A27B-084B1326AAE5}:[1002', '{A2482826-053D-5634-A27B-084B1326AAE5}:3ea'),
+ ('project.json', '{B076CDDC-14DF-50F4-A5E9-7518ABB3E851}:0'),
+ ('CEAA362B4E505BCEB827CB92EF40A50E', '{CEAA362B-4E50-5BCE-B827-CB92EF40A50E}:1'),
+ ('CEAA362B4E505BCEB827CB92EF40A50E', '{CEAA362B-4E50-5BCE-B827-CB92EF40A50E}:2'),
+ ('TEXTURES/_DEV_WHITE.tif.streamingimage', '{D83B36F1-61A6-5001-B191-4D0CE282E236}:3e8'),
+ ('textures/_dev_woodland.tif.streamingimage', '{F3DD193C-5845-569C-A974-AA338B30CF86}:3e8'),
]
self.do_missing_dependency_test(expected_product, expected_dependencies,
@@ -260,8 +265,8 @@ class TestsMissingDependencies_WindowsAndMac(object):
helper = self._missing_dep_helper
# Relative paths to the txt file with no missing dependencies
- expected_product_1 = f"{self._workspace.project}\\testassets\\wildcardscantest1.txt"
- expected_product_2 = f"{self._workspace.project}\\testassets\\wildcardscantest2.txt"
+ expected_product_1 = f"testassets\\wildcardscantest1.txt"
+ expected_product_2 = f"testassets\\wildcardscantest2.txt"
expected_dependencies = [] # Neither file has expected missing dependencies
# Run missing dependency scanner and validate results for both files
@@ -288,13 +293,13 @@ class TestsMissingDependencies_WindowsAndMac(object):
emitting missing dependencies.
"""
# Relative path to target test file
- expected_product = f"testassets\\dependencyscannerasset.dynamicslice"
+ expected_product = f"testassets\\reportonemissingdependency.txt"
# The only expected missing dependency
- expected_dependencies = [("Config/Game.xml", "{B92667DC-9F5B-5D72-A29D-99219DD9B691}:0")]
+ expected_dependencies = [('6BDE282B49C957F7B0714B26579BCA9A', '{6BDE282B-49C9-57F7-B071-4B26579BCA9A}:0'),]
self.do_missing_dependency_test(expected_product, expected_dependencies,
- "%DependencyScannerAsset%.dynamicslice")
+ "%reportonemissingdependency.txt")
@pytest.mark.BAT
@pytest.mark.assetpipeline
@@ -366,7 +371,7 @@ class TestsMissingDependencies_WindowsAndMac(object):
# Expected missing dependency hiding 31 dependencies deep
expected_dependencies = [
# String Asset #
- ("B92667DC-9F5B-5D72-A29D-99219DD9B691", "{B92667DC-9F5B-5D72-A29D-99219DD9B691}:0")
+ ("6BDE282B-49C9-57F7-B071-4B26579BCA9A", "{6BDE282B-49C9-57F7-B071-4B26579BCA9A}:0")
]
self.do_missing_dependency_test(expected_product, expected_dependencies,
@@ -386,11 +391,11 @@ class TestsMissingDependencies_WindowsAndMac(object):
# Expected dependencies with valid lengths from file
expected_dependencies = [
# String Asset #
- ("D92C4661C8985E19BD3597CB2318CFA6", "{D92C4661-C898-5E19-BD35-97CB2318CFA6}:0"),
- ("58BE9DA51F1753B98CEEEEB10E63454D", "{58BE9DA5-1F17-53B9-8CEE-EEB10E63454D}:914f19b7"),
- ("747D31D71E62553592226173C49CF97E", "{747D31D7-1E62-5535-9222-6173C49CF97E}:1"),
- ("747D31D71E62553592226173C49CF97E", "{747D31D7-1E62-5535-9222-6173C49CF97E}:2"),
- ("1CB10C43-F324-5B93-A294-C602ADEF95F9", "{1CB10C43-F324-5B93-A294-C602ADEF95F9}:0"),
+ ('D1265251CC14584AB1CECB10746A2BA0', '{D1265251-CC14-584A-B1CE-CB10746A2BA0}:2'),
+ ('D1265251CC14584AB1CECB10746A2BA0', '{D1265251-CC14-584A-B1CE-CB10746A2BA0}:1'),
+ ('D92C4661C8985E19BD3597CB2318CFA6', '{D92C4661-C898-5E19-BD35-97CB2318CFA6}:0'),
+ ('837412DFD05F576D81AAACF360463749', '{837412DF-D05F-576D-81AA-ACF360463749}:0'),
+ ('785A05D2483E5B43A2B992ACDAE6E938', '{785A05D2-483E-5B43-A2B9-92ACDAE6E938}:0'),
]
self.do_missing_dependency_test( expected_product, expected_dependencies,
diff --git a/AutomatedTesting/Gem/PythonTests/atom_renderer/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/atom_renderer/CMakeLists.txt
index 7ffc2072f1..91228bc71c 100644
--- a/AutomatedTesting/Gem/PythonTests/atom_renderer/CMakeLists.txt
+++ b/AutomatedTesting/Gem/PythonTests/atom_renderer/CMakeLists.txt
@@ -20,7 +20,7 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_BUILD_TESTS_SUPPORTED AND AutomatedT
TEST_SUITE main
PATH ${CMAKE_CURRENT_LIST_DIR}/test_Atom_MainSuite.py
TEST_SERIAL
- TIMEOUT 300
+ TIMEOUT 400
RUNTIME_DEPENDENCIES
AssetProcessor
AutomatedTesting.Assets
@@ -31,7 +31,7 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_BUILD_TESTS_SUPPORTED AND AutomatedT
TEST_SUITE sandbox
PATH ${CMAKE_CURRENT_LIST_DIR}/test_Atom_SandboxSuite.py
TEST_SERIAL
- TIMEOUT 300
+ TIMEOUT 400
RUNTIME_DEPENDENCIES
AssetProcessor
AutomatedTesting.Assets
diff --git a/AutomatedTesting/Gem/PythonTests/atom_renderer/test_Atom_MainSuite.py b/AutomatedTesting/Gem/PythonTests/atom_renderer/test_Atom_MainSuite.py
index b64a592c1d..3da1c27e67 100644
--- a/AutomatedTesting/Gem/PythonTests/atom_renderer/test_Atom_MainSuite.py
+++ b/AutomatedTesting/Gem/PythonTests/atom_renderer/test_Atom_MainSuite.py
@@ -27,6 +27,7 @@ TEST_DIRECTORY = os.path.join(os.path.dirname(__file__), "atom_hydra_scripts")
@pytest.mark.parametrize("level", ["auto_test"])
class TestAtomEditorComponentsMain(object):
+ @pytest.mark.xfail(reason="Timing out sporadically, LYN-3956")
@pytest.mark.test_case_id(
"C32078130", # Display Mapper
"C32078129", # Light
diff --git a/AutomatedTesting/Gem/PythonTests/automatedtesting_shared/asset_database_utils.py b/AutomatedTesting/Gem/PythonTests/automatedtesting_shared/asset_database_utils.py
index 75df510db1..38df3906a8 100755
--- a/AutomatedTesting/Gem/PythonTests/automatedtesting_shared/asset_database_utils.py
+++ b/AutomatedTesting/Gem/PythonTests/automatedtesting_shared/asset_database_utils.py
@@ -47,7 +47,7 @@ def get_active_platforms_from_db(asset_db_path) -> List[str]:
# Convert a source product path into a db product path
# cache_platform/projectname/product_path
def get_db_product_path(workspace, source_path, cache_platform):
- product_path = os.path.join(cache_platform, workspace.project, source_path)
+ product_path = os.path.join(cache_platform, source_path)
product_path = product_path.replace('\\', '/')
return product_path
diff --git a/AutomatedTesting/Levels/AWS/ClientAuth/ClientAuth.ly b/AutomatedTesting/Levels/AWS/ClientAuth/ClientAuth.ly
deleted file mode 100644
index af8a7f5c8e..0000000000
--- a/AutomatedTesting/Levels/AWS/ClientAuth/ClientAuth.ly
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:f0f4d4e0155feaa76c80a14128000a0fd9570ab76e79f4847eaef9006324a4d2
-size 9084
diff --git a/AutomatedTesting/Levels/AWS/ClientAuth/ConitoAnonymousAuthorization.scriptcanvas b/AutomatedTesting/Levels/AWS/ClientAuth/ConitoAnonymousAuthorization.scriptcanvas
deleted file mode 100644
index ef03c66b16..0000000000
--- a/AutomatedTesting/Levels/AWS/ClientAuth/ConitoAnonymousAuthorization.scriptcanvas
+++ /dev/null
@@ -1,2313 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/AutomatedTesting/Levels/AWS/ClientAuth/LevelData/Environment.xml b/AutomatedTesting/Levels/AWS/ClientAuth/LevelData/Environment.xml
deleted file mode 100644
index d4e3d33551..0000000000
--- a/AutomatedTesting/Levels/AWS/ClientAuth/LevelData/Environment.xml
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/AutomatedTesting/Levels/AWS/ClientAuth/LevelData/TimeOfDay.xml b/AutomatedTesting/Levels/AWS/ClientAuth/LevelData/TimeOfDay.xml
deleted file mode 100644
index d827d4da29..0000000000
--- a/AutomatedTesting/Levels/AWS/ClientAuth/LevelData/TimeOfDay.xml
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/AutomatedTesting/Levels/AWS/ClientAuth/filelist.xml b/AutomatedTesting/Levels/AWS/ClientAuth/filelist.xml
deleted file mode 100644
index f69a99fe37..0000000000
--- a/AutomatedTesting/Levels/AWS/ClientAuth/filelist.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/AutomatedTesting/Levels/AWS/ClientAuth/level.pak b/AutomatedTesting/Levels/AWS/ClientAuth/level.pak
deleted file mode 100644
index 1ae0bb1f7a..0000000000
--- a/AutomatedTesting/Levels/AWS/ClientAuth/level.pak
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:4900bdf28654e21032e69957f2762fa0a3b93a4b82163267a1f10f19f6d78692
-size 3795
diff --git a/AutomatedTesting/Levels/AWS/ClientAuth/tags.txt b/AutomatedTesting/Levels/AWS/ClientAuth/tags.txt
deleted file mode 100644
index 0d6c1880e7..0000000000
--- a/AutomatedTesting/Levels/AWS/ClientAuth/tags.txt
+++ /dev/null
@@ -1,12 +0,0 @@
-0,0,0,0,0,0
-0,0,0,0,0,0
-0,0,0,0,0,0
-0,0,0,0,0,0
-0,0,0,0,0,0
-0,0,0,0,0,0
-0,0,0,0,0,0
-0,0,0,0,0,0
-0,0,0,0,0,0
-0,0,0,0,0,0
-0,0,0,0,0,0
-0,0,0,0,0,0
diff --git a/AutomatedTesting/Registry/authenticationProvider.setreg b/AutomatedTesting/Registry/authenticationProvider.setreg
new file mode 100644
index 0000000000..c90433468c
--- /dev/null
+++ b/AutomatedTesting/Registry/authenticationProvider.setreg
@@ -0,0 +1,5 @@
+{
+ "AWS":
+ {
+ }
+}
\ No newline at end of file
diff --git a/AutomatedTesting/TestAssets/MaxIteration31Deep.txt b/AutomatedTesting/TestAssets/MaxIteration31Deep.txt
index e9e8f94715..d0ac57c883 100644
--- a/AutomatedTesting/TestAssets/MaxIteration31Deep.txt
+++ b/AutomatedTesting/TestAssets/MaxIteration31Deep.txt
@@ -1,3 +1,3 @@
The dependency scanner has a built in limit to how deep it will recurse when multiple results exist on the same line.
This file has a single line with 10 invalid UUIDs, and an 11th valid UUID.
-1: 00000000-0000-0000-0000-000000000000, 2: 00000000-0000-0000-0000-000000000000, 3: 00000000-0000-0000-0000-000000000000, 4: 00000000-0000-0000-0000-000000000000, 5: 00000000-0000-0000-0000-000000000000, 6: 00000000-0000-0000-0000-000000000000, 7: 00000000-0000-0000-0000-000000000000, 8: 00000000-0000-0000-0000-000000000000, 9: 00000000-0000-0000-0000-000000000000, 10: 00000000-0000-0000-0000-000000000000, 11: 00000000-0000-0000-0000-000000000000, 12: 00000000-0000-0000-0000-000000000000, 13: 00000000-0000-0000-0000-000000000000, 14: 00000000-0000-0000-0000-000000000000, 15: 00000000-0000-0000-0000-000000000000, 16: 00000000-0000-0000-0000-000000000000, 17: 00000000-0000-0000-0000-000000000000, 18: 00000000-0000-0000-0000-000000000000, 19: 00000000-0000-0000-0000-000000000000, 20: 00000000-0000-0000-0000-000000000000, 21: 00000000-0000-0000-0000-000000000000, 22: 00000000-0000-0000-0000-000000000000, 23: 00000000-0000-0000-0000-000000000000, 24: 00000000-0000-0000-0000-000000000000, 25: 00000000-0000-0000-0000-000000000000, 26: 00000000-0000-0000-0000-000000000000, 27: 00000000-0000-0000-0000-000000000000, 28: 00000000-0000-0000-0000-000000000000, 29: 00000000-0000-0000-0000-000000000000, 30: 00000000-0000-0000-0000-000000000000, 31 (valid UUID, game . xml): B92667DC-9F5B-5D72-A29D-99219DD9B691
+1: 00000000-0000-0000-0000-000000000000, 2: 00000000-0000-0000-0000-000000000000, 3: 00000000-0000-0000-0000-000000000000, 4: 00000000-0000-0000-0000-000000000000, 5: 00000000-0000-0000-0000-000000000000, 6: 00000000-0000-0000-0000-000000000000, 7: 00000000-0000-0000-0000-000000000000, 8: 00000000-0000-0000-0000-000000000000, 9: 00000000-0000-0000-0000-000000000000, 10: 00000000-0000-0000-0000-000000000000, 11: 00000000-0000-0000-0000-000000000000, 12: 00000000-0000-0000-0000-000000000000, 13: 00000000-0000-0000-0000-000000000000, 14: 00000000-0000-0000-0000-000000000000, 15: 00000000-0000-0000-0000-000000000000, 16: 00000000-0000-0000-0000-000000000000, 17: 00000000-0000-0000-0000-000000000000, 18: 00000000-0000-0000-0000-000000000000, 19: 00000000-0000-0000-0000-000000000000, 20: 00000000-0000-0000-0000-000000000000, 21: 00000000-0000-0000-0000-000000000000, 22: 00000000-0000-0000-0000-000000000000, 23: 00000000-0000-0000-0000-000000000000, 24: 00000000-0000-0000-0000-000000000000, 25: 00000000-0000-0000-0000-000000000000, 26: 00000000-0000-0000-0000-000000000000, 27: 00000000-0000-0000-0000-000000000000, 28: 00000000-0000-0000-0000-000000000000, 29: 00000000-0000-0000-0000-000000000000, 30: 00000000-0000-0000-0000-000000000000, 31 (valid UUID, libs / particles / milestone2particles . xml): 6BDE282B-49C9-57F7-B071-4B26579BCA9A
diff --git a/AutomatedTesting/TestAssets/OnlyMatchesCorrectLengthUUIDs.txt b/AutomatedTesting/TestAssets/OnlyMatchesCorrectLengthUUIDs.txt
index d49108072a..10bdaec686 100644
--- a/AutomatedTesting/TestAssets/OnlyMatchesCorrectLengthUUIDs.txt
+++ b/AutomatedTesting/TestAssets/OnlyMatchesCorrectLengthUUIDs.txt
@@ -1,15 +1,15 @@
The missing dependency scanner was updated to only look for substrings that are the exact length of UUIDs, separated by word boundaries. This avoids problems with very long numbers causing the scan to stall out and take a long time.
-This is the UUID for dev / AutomatedTesting / Config / Game . xml with an extra UUID character at the beginning. It should not show up in scan results.
-aB92667DC-9F5B-5D72-A29D-99219DD9B691
+This is the UUID for libs / particles / milestone2particles . xml with an extra UUID character at the beginning. It should not show up in scan results.
+a6BDE282B49C957F7B0714B26579BCA9A
-This is the UUID for dev / AutomatedTesting / Config / Editor . xml. It has an extra non-UUID character at the end. It should not show up in scan results.
-06E9D6633C875400A532BCB2C0CA19D6t
+This is the UUID for project . json. It has an extra non-UUID character at the end. It should not show up in scan results.
+B076CDDC14DF50F4A5E97518ABB3E851t
-Two UUIDs, the first invalid a6BDE282B49C957F7B0714B26579BCA9A mixed with a second valid one 58BE9DA51F1753B98CEEEEB10E63454D the same line. The second should show up in scan results.
+Two UUIDs, the first invalid a1CB10C43F3245B93A294C602ADEF95F9 mixed with a second valid one D1265251CC14584AB1CECB10746A2BA0 the same line. The second should show up in scan results.
-UUID for slices / MuzzleFlash . slice, after an equal sign should show up in scan results=747D31D71E62553592226173C49CF97E
+UUID for TestAssets / DependencyScannerAsset . slice, after an equal sign should show up in scan results=837412DFD05F576D81AAACF360463749
-UUID for TestsAssets / WildcardScanTest1 . txt in quotes, should show up in scan results"1CB10C43-F324-5B93-A294-C602ADEF95F9"
+UUID for TestAssets / WildcardScanTest2 . txt in quotes, should show up in scan results"D92C4661C8985E19BD3597CB2318CFA6"
-UUID for Objects / Lumbertank_turret . cgf in curly braces, should up show in scan results{D92C4661C8985E19BD3597CB2318CFA6}
+UUID for TestAssets / SelfReferenceAssetID. txt in curly braces, should up show in scan results{785A05D2483E5B43A2B992ACDAE6E938}
diff --git a/AutomatedTesting/TestAssets/RelativeProductPathsNotDependencies.txt b/AutomatedTesting/TestAssets/RelativeProductPathsNotDependencies.txt
index b2f341a15f..d3975ced7c 100644
--- a/AutomatedTesting/TestAssets/RelativeProductPathsNotDependencies.txt
+++ b/AutomatedTesting/TestAssets/RelativeProductPathsNotDependencies.txt
@@ -1,25 +1,25 @@
These tests are mostly done with files that have a different extension between source and product.
The source scan is done first, and will catch files in the source path.
Product path searching is resolved using "endsWith" logic.
-textures/_dev_purple.dds
+textures/_dev_purple.tif.streamingimage
Back slashes, and project name in the path
-automatedtesting\textures\_dev_stucco.dds
+pc/textures/_dev_stucco.tif.streamingimage
Double back slashes
-textures\\_dev_tan.dds
+textures\\_dev_tan.tif.streamingimage
Casing doesn't match
-TEXTURES/_DEV_WHITE.dds
+TEXTURES/_DEV_WHITE.tif.streamingimage
Some files have multiple extensions, this verifies that won't trip up the scanner.
-textures/_dev_yellow_light.dds.2
-Path inline textures/milestone2/ama_grey_02.dds test
-Path after=textures/_dev_woodland.dds equal sign
+textures/_dev_yellow_light.tif.1002.imagemipchain
+Path inline textures/milestone2/ama_grey_02.tif.streamingimage test
+Path after=textures/_dev_woodland.tif.streamingimage equal sign
Multiple paths on one line
Multiple materials/am_grass1.mtl paths materials/am_rockground.mtl on one line
Path before a UUID
Path materials/floor_tile.mtl before B92667DC-9F5B-5D72-A29D-99219DD9B691 a UUID
Path before an asset ID
-Path ui/milestone2menu.uicanvas before an 2ef92b8D044E5C278E2BB1AC0374A4E7:131072 asset ID
+Path ui/milestone2menu.uicanvas before an 2ef92b8D044E5C278E2BB1AC0374A4E7:1002 asset ID
Path after a UUID
-Path after CEAA362B4E505BCEB827CB92EF40A50E a ui/fonts/lyshineexamples/vera.ttf UUID
+Path after CEAA362B4E505BCEB827CB92EF40A50E a project.json UUID
Path after an asset ID
-Path after {A2482826-053D-5634-A27B-084B1326AAE5}:[196608] an ui/fonts/lyshineexamples/veramono.ttf asset ID
+Path after {A2482826-053D-5634-A27B-084B1326AAE5}:[1002] an libs/particles/milestone2particles.xml asset ID
diff --git a/AutomatedTesting/TestAssets/RelativeSourcePathsNotDependencies.txt b/AutomatedTesting/TestAssets/RelativeSourcePathsNotDependencies.txt
index 27ac8f02dd..a5ae046b11 100644
--- a/AutomatedTesting/TestAssets/RelativeSourcePathsNotDependencies.txt
+++ b/AutomatedTesting/TestAssets/RelativeSourcePathsNotDependencies.txt
@@ -3,6 +3,6 @@ TestAssets/RelativeProductPathsNotDependencies.txt
Back slashes
TestAssets\WildcardScanTest1.txt
Casing doesn't match
-Config/gAME.XML
-Path inline Config/Editor.xml test
+libs/particles/milestone2PARTICLES.XML
+Path inline project.json test
Path after=textures/_dev_Purple.tif equal sign
diff --git a/AutomatedTesting/TestAssets/ValidAssetIdNotDependency.txt b/AutomatedTesting/TestAssets/ValidAssetIdNotDependency.txt
index 0d677113e0..4cda459232 100644
--- a/AutomatedTesting/TestAssets/ValidAssetIdNotDependency.txt
+++ b/AutomatedTesting/TestAssets/ValidAssetIdNotDependency.txt
@@ -1,5 +1,5 @@
-dev/AutomatedTesting/textures/_dev_Purple.tif, the product ID is for one of the mips.
-{A2482826-053D-5634-A27B-084B1326AAE5}:[196608]
-_dev_Red.tif, another mip, different formatting.
-2ef92b8D044E5C278E2BB1AC0374A4E7:131072
-_dev_White.tif, {D83B36F1-61A6-5001-B191-4D0CE282E236}-327680 asset ID inline.
+ /textures /_dev_Purple . tif, the product ID is for one of the mips.
+{A2482826-053D-5634-A27B-084B1326AAE5}:[1002]
+_dev_Red . tif, another mip, different formatting.
+2ef92b8D044E5C278E2BB1AC0374A4E7:1003
+_dev_White.tif, {D83B36F1-61A6-5001-B191-4D0CE282E236}-1002 asset ID inline.
diff --git a/AutomatedTesting/TestAssets/ValidUUIDsNotDependency.txt b/AutomatedTesting/TestAssets/ValidUUIDsNotDependency.txt
index c6b2cbb0e7..436d6fb628 100644
--- a/AutomatedTesting/TestAssets/ValidUUIDsNotDependency.txt
+++ b/AutomatedTesting/TestAssets/ValidUUIDsNotDependency.txt
@@ -1,18 +1,18 @@
Paths are broken up to avoid having them show up as relative path results.
-This is the UUID for dev / AutomatedTesting / Config / Game . xml
-B92667DC-9F5B-5D72-A29D-99219DD9B691
-This is the UUID for dev / AutomatedTesting / Config / Editor . xml. This tests UUIDs without separators.
-06E9D6633C875400A532BCB2C0CA19D6
+This is the UUID for Materials / Default / AM_UV_v1_1K_source . png
+C67BEA9F-09FF-59AA-A7F0-A52B8F987508
+This is the UUID for libs / particles / milestone2particles . xml. This tests UUIDs without separators.
+6BDE282B49C957F7B0714B26579BCA9A
This is the UUID for SelfReferenceUUID.txt. This tests UUIDs with mixed casing.
33bcee02F3225688ABEE534F6058593F
-This is a UUID mid-line 9886E132-572D-5746-9377-E629AB6C1981, for gems . json
+This is a UUID mid-line B076CDDC-14DF-50F4-A5E9-7518ABB3E851, for project . json
Two UUIDs on the same line
-Two UUIDs 6BDE282B49C957F7B0714B26579BCA9A mixed on 58BE9DA51F1753B98CEEEEB10E63454D the same line
+Two UUIDs 345E5C660D6254FF8D0F7C8EE66A2249 mixed on A26C73D1837E5AE59E68F916FA7C3699 the same line
Test UUIDs and Asset IDs mixed on the same line. Relative paths are handled in the relative path tests.
UUID: slices / MuzzleFlash . slice, AssetID: TestsAssets / WildcardScanTest1 . txt
This 747D31D71E62553592226173C49CF97E uuid is on the line with 1CB10C43F3245B93A294C602ADEF95F9:[0] a valid asset ID
UUID: Objects / Lumbertank_turret . cgf, AssetID: TestsAssets / WildcardScanTest2 . txt
-This D92C4661C8985E19BD3597CB2318CFA6:[0] uuid is on the line with 7364AB2B092F5B0B80601BBC6E53087C a valid asset ID
+This D92C4661C8985E19BD3597CB2318CFA6:[0] uuid is on the line with 37108522F50459499CD6C8D47A960CF1 a valid asset ID
diff --git a/Code/Framework/AzCore/AzCore/Asset/AssetJsonSerializer.cpp b/Code/Framework/AzCore/AzCore/Asset/AssetJsonSerializer.cpp
index 555eedf034..a72fe4e013 100644
--- a/Code/Framework/AzCore/AzCore/Asset/AssetJsonSerializer.cpp
+++ b/Code/Framework/AzCore/AzCore/Asset/AssetJsonSerializer.cpp
@@ -133,7 +133,15 @@ namespace AZ
if (!id.m_guid.IsNull())
{
*instance = AssetManager::Instance().FindOrCreateAsset(id, instance->GetType(), instance->GetAutoLoadBehavior());
-
+ if (!instance->GetId().IsValid())
+ {
+ // If the asset failed to be created, FindOrCreateAsset returns an asset instance with a null
+ // id. To preserve the asset id in the source json, reset the asset to an empty one, but with
+ // the right id.
+ const auto loadBehavior = instance->GetAutoLoadBehavior();
+ *instance = Asset(id, instance->GetType());
+ instance->SetAutoLoadBehavior(loadBehavior);
+ }
result.Combine(context.Report(result, "Successfully created Asset with id."));
}
diff --git a/Code/Framework/AzCore/AzCore/Math/Random.h b/Code/Framework/AzCore/AzCore/Math/Random.h
index 5ae37433ec..8b28f6aaad 100644
--- a/Code/Framework/AzCore/AzCore/Math/Random.h
+++ b/Code/Framework/AzCore/AzCore/Math/Random.h
@@ -86,4 +86,94 @@ namespace AZ
Normal,
UniformReal
};
+
+ //! Halton sequences are deterministic, quasi-random sequences with low discrepancy. They
+ //! are useful for generating evenly distributed points.
+ //! See https://en.wikipedia.org/wiki/Halton_sequence for more information.
+
+ //! Returns a single halton number.
+ //! @param index The index of the number. Indices start at 1. Using index 0 will return 0.
+ //! @param base The numerical base of the halton number.
+ inline float GetHaltonNumber(uint32_t index, uint32_t base)
+ {
+ float fraction = 1.0f;
+ float result = 0.0f;
+
+ while (index > 0)
+ {
+ fraction = fraction / base;
+ result += fraction * (index % base);
+ index = aznumeric_cast(index / base);
+ }
+
+ return result;
+ }
+
+ //! A helper class for generating arrays of Halton sequences in n dimensions.
+ //! The class holds the state of which bases to use, the starting offset
+ //! of each dimension and how much to increment between each index for each
+ //! dimension.
+ template
+ class HaltonSequence
+ {
+ public:
+
+ //! Initializes a Halton sequence with some bases. By default there is no
+ //! offset and the index increments by 1 between each number.
+ HaltonSequence(AZStd::array bases)
+ : m_bases(bases)
+ {
+ m_offsets.fill(1); // Halton sequences start at index 1.
+ m_increments.fill(1); // By default increment by 1 between each number.
+ }
+
+ //! Returns a Halton sequence in an array of N length
+ template
+ AZStd::array, N> GetHaltonSequence()
+ {
+ AZStd::array, N> result;
+
+ AZStd::array indices = m_offsets;
+
+ // Generator that returns the Halton number for all bases for a single entry.
+ auto f = [&] ()
+ {
+ AZStd::array item;
+ for (auto d = 0; d < Dimensions; ++d)
+ {
+ item[d] = GetHaltonNumber(indices[d], m_bases[d]);
+ indices[d] += m_increments[d];
+ }
+ return item;
+ };
+
+ AZStd::generate(result.begin(), result.end(), f);
+ return result;
+ }
+
+ //! Sets the offsets per dimension to start generating a sequence from.
+ //! By default, there is no offset (offset of 0 corresponds to starting at index 1)
+ void SetOffsets(AZStd::array offsets)
+ {
+ m_offsets = offsets;
+
+ // Halton sequences start at index 1, so increment all the indices.
+ AZStd::for_each(m_offsets.begin(), m_offsets.end(), [](uint32_t &n){ n++; });
+ }
+
+ //! Sets the increment between numbers in the halton sequence per dimension
+ //! By default this is 1, meaning that no numbers are skipped. Can be negative
+ //! to generate numbers in reverse order.
+ void SetIncrements(AZStd::array increments)
+ {
+ m_increments = increments;
+ }
+
+ private:
+
+ AZStd::array m_bases;
+ AZStd::array m_offsets;
+ AZStd::array m_increments;
+
+ };
}
diff --git a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryImpl.cpp b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryImpl.cpp
index 2421c75be3..3dd4931374 100644
--- a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryImpl.cpp
+++ b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryImpl.cpp
@@ -14,6 +14,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -880,6 +881,13 @@ namespace AZ
const Specializations& specializations, const rapidjson::Pointer& historyPointer, AZStd::string_view folderPath)
{
using namespace rapidjson;
+
+ if (&lhs == &rhs)
+ {
+ // Early return to avoid setting the collisionFound reference to true
+ // std::sort is allowed to pass in the same memory address for the left and right elements
+ return false;
+ }
AZ_Assert(!lhs.m_tags.empty(), "Comparing a settings file without at least a name tag.");
AZ_Assert(!rhs.m_tags.empty(), "Comparing a settings file without at least a name tag.");
@@ -1054,15 +1062,23 @@ namespace AZ
jsonPatch.ParseInsitu(scratchBuffer.data());
if (jsonPatch.HasParseError())
{
+ auto nativeUI = AZ::Interface::Get();
if (jsonPatch.GetParseError() == rapidjson::kParseErrorDocumentEmpty)
{
- AZ_Warning("Settings Registry", false, R"(Unable to parse registry file "%s" due to json error "%s" at offset %llu.)",
+ AZ_Warning("Settings Registry", false, R"(Unable to parse registry file "%s" due to json error "%s" at offset %zu.)",
path, GetParseError_En(jsonPatch.GetParseError()), jsonPatch.GetErrorOffset());
}
else
{
- AZ_Error("Settings Registry", false, R"(Unable to parse registry file "%s" due to json error "%s" at offset %llu.)", path,
+ using ErrorString = AZStd::fixed_string<4096>;
+ auto jsonError = ErrorString::format(R"(Unable to parse registry file "%s" due to json error "%s" at offset %zu.)", path,
GetParseError_En(jsonPatch.GetParseError()), jsonPatch.GetErrorOffset());
+ AZ_Error("Settings Registry", false, "%s", jsonError.c_str());
+
+ if (nativeUI)
+ {
+ nativeUI->DisplayOkDialog("Setreg(Patch) Merge Issue", AZStd::string_view(jsonError), false);
+ }
}
pointer.Create(m_settings, m_settings.GetAllocator()).SetObject()
diff --git a/Code/Framework/AzCore/Tests/Math/RandomTests.cpp b/Code/Framework/AzCore/Tests/Math/RandomTests.cpp
new file mode 100644
index 0000000000..ace7d99704
--- /dev/null
+++ b/Code/Framework/AzCore/Tests/Math/RandomTests.cpp
@@ -0,0 +1,74 @@
+/*
+* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
+* its licensors.
+*
+* For complete copyright and license terms please see the LICENSE at the root of this
+* distribution (the "License"). All use of this software is governed by the License,
+* or, if provided, by the license below or the license accompanying this file. Do not
+* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+*
+*/
+
+#include
+#include
+
+using namespace AZ;
+
+namespace UnitTest
+{
+ TEST(MATH_Random, GetHaltonNumber)
+ {
+ EXPECT_FLOAT_EQ(0.5, GetHaltonNumber(1, 2));
+ EXPECT_FLOAT_EQ(898.0f / 2187.0f, GetHaltonNumber(1234, 3));
+ EXPECT_FLOAT_EQ(5981.0f / 15625.0f, GetHaltonNumber(4321, 5));
+ }
+
+ TEST(MATH_Random, HaltonSequence)
+ {
+ HaltonSequence<3> sequence({ 2, 3, 5 });
+ auto regularSequence = sequence.GetHaltonSequence<5>();
+
+ EXPECT_FLOAT_EQ(1.0f / 2.0f, regularSequence[0][0]);
+ EXPECT_FLOAT_EQ(1.0f / 3.0f, regularSequence[0][1]);
+ EXPECT_FLOAT_EQ(1.0f / 5.0f, regularSequence[0][2]);
+
+ EXPECT_FLOAT_EQ(1.0f / 4.0f, regularSequence[1][0]);
+ EXPECT_FLOAT_EQ(2.0f / 3.0f, regularSequence[1][1]);
+ EXPECT_FLOAT_EQ(2.0f / 5.0f, regularSequence[1][2]);
+
+ EXPECT_FLOAT_EQ(3.0f / 4.0f, regularSequence[2][0]);
+ EXPECT_FLOAT_EQ(1.0f / 9.0f, regularSequence[2][1]);
+ EXPECT_FLOAT_EQ(3.0f / 5.0f, regularSequence[2][2]);
+
+ EXPECT_FLOAT_EQ(1.0f / 8.0f, regularSequence[3][0]);
+ EXPECT_FLOAT_EQ(4.0f / 9.0f, regularSequence[3][1]);
+ EXPECT_FLOAT_EQ(4.0f / 5.0f, regularSequence[3][2]);
+
+ EXPECT_FLOAT_EQ(5.0f / 8.0f, regularSequence[4][0]);
+ EXPECT_FLOAT_EQ(7.0f / 9.0f, regularSequence[4][1]);
+ EXPECT_FLOAT_EQ(1.0f / 25.0f, regularSequence[4][2]);
+
+ sequence.SetOffsets({ 1, 2, 3 });
+ auto offsetSequence = sequence.GetHaltonSequence<2>();
+
+ EXPECT_FLOAT_EQ(1.0f / 4.0f, offsetSequence[0][0]);
+ EXPECT_FLOAT_EQ(1.0f / 9.0f, offsetSequence[0][1]);
+ EXPECT_FLOAT_EQ(4.0f / 5.0f, offsetSequence[0][2]);
+
+ EXPECT_FLOAT_EQ(3.0f / 4.0f, offsetSequence[1][0]);
+ EXPECT_FLOAT_EQ(4.0f / 9.0f, offsetSequence[1][1]);
+ EXPECT_FLOAT_EQ(1.0f / 25.0f, offsetSequence[1][2]);
+
+ sequence.SetIncrements({ 1, 2, 3 });
+ auto incrementedSequence = sequence.GetHaltonSequence<2>();
+
+ EXPECT_FLOAT_EQ(1.0f / 4.0f, incrementedSequence[0][0]);
+ EXPECT_FLOAT_EQ(1.0f / 9.0f, incrementedSequence[0][1]);
+ EXPECT_FLOAT_EQ(4.0f / 5.0f, incrementedSequence[0][2]);
+
+ EXPECT_FLOAT_EQ(3.0f / 4.0f, incrementedSequence[1][0]);
+ EXPECT_FLOAT_EQ(7.0f / 9.0f, incrementedSequence[1][1]);
+ EXPECT_FLOAT_EQ(11.0f / 25.0f, incrementedSequence[1][2]);
+ }
+}
diff --git a/Code/Framework/AzCore/Tests/azcoretests_files.cmake b/Code/Framework/AzCore/Tests/azcoretests_files.cmake
index f90717d003..78b2701d92 100644
--- a/Code/Framework/AzCore/Tests/azcoretests_files.cmake
+++ b/Code/Framework/AzCore/Tests/azcoretests_files.cmake
@@ -152,6 +152,7 @@ set(FILES
Math/PlaneTests.cpp
Math/QuaternionPerformanceTests.cpp
Math/QuaternionTests.cpp
+ Math/RandomTests.cpp
Math/ShapeIntersectionPerformanceTests.cpp
Math/ShapeIntersectionTests.cpp
Math/SfmtTests.cpp
diff --git a/Code/Framework/AzFramework/AzFramework/Physics/Configuration/SimulatedBodyConfiguration.cpp b/Code/Framework/AzFramework/AzFramework/Physics/Configuration/SimulatedBodyConfiguration.cpp
index 01bff3ccbb..4aea643c6c 100644
--- a/Code/Framework/AzFramework/AzFramework/Physics/Configuration/SimulatedBodyConfiguration.cpp
+++ b/Code/Framework/AzFramework/AzFramework/Physics/Configuration/SimulatedBodyConfiguration.cpp
@@ -30,6 +30,16 @@ namespace AzPhysics
classElement.AddElementWithData(context, "name", name);
return true;
}
+
+ bool SimulatedBodyVersionConverter([[maybe_unused]] AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement)
+ {
+ if (classElement.GetVersion() <= 1)
+ {
+ classElement.RemoveElementByName(AZ_CRC_CE("scale"));
+ }
+
+ return true;
+ }
}
AZ_CLASS_ALLOCATOR_IMPL(SimulatedBodyConfiguration, AZ::SystemAllocator, 0);
@@ -40,11 +50,10 @@ namespace AzPhysics
{
serializeContext->ClassDeprecate("WorldBodyConfiguration", "{6EEB377C-DC60-4E10-AF12-9626C0763B2D}", &Internal::DeprecateWorldBodyConfiguration);
serializeContext->Class()
- ->Version(1)
+ ->Version(2, &Internal::SimulatedBodyVersionConverter)
->Field("name", &SimulatedBodyConfiguration::m_debugName)
->Field("position", &SimulatedBodyConfiguration::m_position)
->Field("orientation", &SimulatedBodyConfiguration::m_orientation)
- ->Field("scale", &SimulatedBodyConfiguration::m_scale)
->Field("entityId", &SimulatedBodyConfiguration::m_entityId)
->Field("startSimulationEnabled", &SimulatedBodyConfiguration::m_startSimulationEnabled)
;
diff --git a/Code/Framework/AzFramework/AzFramework/Physics/Configuration/SimulatedBodyConfiguration.h b/Code/Framework/AzFramework/AzFramework/Physics/Configuration/SimulatedBodyConfiguration.h
index 6862bfccb8..5ac920ab9d 100644
--- a/Code/Framework/AzFramework/AzFramework/Physics/Configuration/SimulatedBodyConfiguration.h
+++ b/Code/Framework/AzFramework/AzFramework/Physics/Configuration/SimulatedBodyConfiguration.h
@@ -38,7 +38,6 @@ namespace AzPhysics
// Basic initial settings.
AZ::Vector3 m_position = AZ::Vector3::CreateZero();
AZ::Quaternion m_orientation = AZ::Quaternion::CreateIdentity();
- AZ::Vector3 m_scale = AZ::Vector3::CreateOne();
bool m_startSimulationEnabled = true;
// Entity/object association.
diff --git a/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp b/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp
index 79c1a28e5d..e4833ccb3c 100644
--- a/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp
+++ b/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp
@@ -18,7 +18,6 @@
#include
#include
#include
-#include
namespace AzFramework
{
@@ -160,24 +159,27 @@ namespace AzFramework
bool CameraSystem::HandleEvents(const InputEvent& event)
{
- if (const auto& cursor = AZStd::get_if(&event))
+ if (const auto& horizonalMotion = AZStd::get_if(&event))
{
- m_cursorState.SetCurrentPosition(cursor->m_position);
+ m_motionDelta.m_x = horizonalMotion->m_delta;
+ }
+ else if (const auto& verticalMotion = AZStd::get_if(&event))
+ {
+ m_motionDelta.m_y = verticalMotion->m_delta;
}
else if (const auto& scroll = AZStd::get_if(&event))
{
m_scrollDelta = scroll->m_delta;
}
- return m_cameras.HandleEvents(event, m_cursorState.CursorDelta(), m_scrollDelta);
+ return m_cameras.HandleEvents(event, m_motionDelta, m_scrollDelta);
}
Camera CameraSystem::StepCamera(const Camera& targetCamera, const float deltaTime)
{
- const auto nextCamera = m_cameras.StepCamera(targetCamera, m_cursorState.CursorDelta(), m_scrollDelta, deltaTime);
-
- m_cursorState.Update();
+ const auto nextCamera = m_cameras.StepCamera(targetCamera, m_motionDelta, m_scrollDelta, deltaTime);
+ m_motionDelta = ScreenVector{0, 0};
m_scrollDelta = 0.0f;
return nextCamera;
@@ -193,13 +195,12 @@ namespace AzFramework
bool handling = false;
for (auto& cameraInput : m_activeCameraInputs)
{
- cameraInput->HandleEvents(event, cursorDelta, scrollDelta);
- handling = !cameraInput->Idle() || handling;
+ handling = cameraInput->HandleEvents(event, cursorDelta, scrollDelta) || handling;
}
for (auto& cameraInput : m_idleCameraInputs)
{
- cameraInput->HandleEvents(event, cursorDelta, scrollDelta);
+ handling = cameraInput->HandleEvents(event, cursorDelta, scrollDelta) || handling;
}
return handling;
@@ -262,17 +263,26 @@ namespace AzFramework
{
m_activeCameraInputs[i]->Reset();
m_idleCameraInputs.push_back(m_activeCameraInputs[i]);
- m_activeCameraInputs[i] = m_activeCameraInputs[m_activeCameraInputs.size() - 1];
+ using AZStd::swap;
+ swap(m_activeCameraInputs[i], m_activeCameraInputs[m_activeCameraInputs.size() - 1]);
m_activeCameraInputs.pop_back();
}
}
+ void Cameras::Clear()
+ {
+ Reset();
+ AZ_Assert(m_activeCameraInputs.empty(), "Active Camera Inputs is not empty");
+
+ m_idleCameraInputs.clear();
+ }
+
RotateCameraInput::RotateCameraInput(const InputChannelId rotateChannelId)
: m_rotateChannelId(rotateChannelId)
{
}
- void RotateCameraInput::HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, [[maybe_unused]] float scrollDelta)
+ bool RotateCameraInput::HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, [[maybe_unused]] float scrollDelta)
{
const ClickDetector::ClickEvent clickEvent = [&event, this] {
if (const auto& input = AZStd::get_if(&event))
@@ -304,6 +314,11 @@ namespace AzFramework
// noop
break;
}
+
+ // note - must also check !ending to ensure the mouse up (release) event
+ // is not consumed and can be propagated to other systems.
+ // (don't swallow mouse up events)
+ return !Idle() && !Ending();
}
Camera RotateCameraInput::StepCamera(
@@ -330,7 +345,7 @@ namespace AzFramework
{
}
- void PanCameraInput::HandleEvents(
+ bool PanCameraInput::HandleEvents(
const InputEvent& event, [[maybe_unused]] const ScreenVector& cursorDelta, [[maybe_unused]] float scrollDelta)
{
if (const auto& input = AZStd::get_if(&event))
@@ -347,6 +362,8 @@ namespace AzFramework
}
}
}
+
+ return !Idle();
}
Camera PanCameraInput::StepCamera(
@@ -411,7 +428,7 @@ namespace AzFramework
{
}
- void TranslateCameraInput::HandleEvents(
+ bool TranslateCameraInput::HandleEvents(
const InputEvent& event, [[maybe_unused]] const ScreenVector& cursorDelta, [[maybe_unused]] float scrollDelta)
{
if (const auto& input = AZStd::get_if(&event))
@@ -429,7 +446,8 @@ namespace AzFramework
m_boost = true;
}
}
- else if (input->m_state == InputChannel::State::Ended)
+ // ensure we don't process end events in the idle state
+ else if (input->m_state == InputChannel::State::Ended && !Idle())
{
m_translation &= ~(translationFromKey(input->m_channelId));
if (m_translation == TranslationType::Nil)
@@ -442,6 +460,8 @@ namespace AzFramework
}
}
}
+
+ return !Idle();
}
Camera TranslateCameraInput::StepCamera(
@@ -503,7 +523,7 @@ namespace AzFramework
m_boost = false;
}
- void OrbitCameraInput::HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta)
+ bool OrbitCameraInput::HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta)
{
if (const auto* input = AZStd::get_if(&event))
{
@@ -522,8 +542,10 @@ namespace AzFramework
if (Active())
{
- m_orbitCameras.HandleEvents(event, cursorDelta, scrollDelta);
+ return m_orbitCameras.HandleEvents(event, cursorDelta, scrollDelta);
}
+
+ return !Idle();
}
Camera OrbitCameraInput::StepCamera(
@@ -533,7 +555,7 @@ 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())
@@ -585,13 +607,15 @@ namespace AzFramework
return nextCamera;
}
- void OrbitDollyScrollCameraInput::HandleEvents(
+ bool OrbitDollyScrollCameraInput::HandleEvents(
const InputEvent& event, [[maybe_unused]] const ScreenVector& cursorDelta, [[maybe_unused]] float scrollDelta)
{
if (const auto* scroll = AZStd::get_if(&event))
{
BeginActivation();
}
+
+ return !Idle();
}
Camera OrbitDollyScrollCameraInput::StepCamera(
@@ -609,7 +633,7 @@ namespace AzFramework
{
}
- void OrbitDollyCursorMoveCameraInput::HandleEvents(
+ bool OrbitDollyCursorMoveCameraInput::HandleEvents(
const InputEvent& event, [[maybe_unused]] const ScreenVector& cursorDelta, [[maybe_unused]] float scrollDelta)
{
if (const auto& input = AZStd::get_if(&event))
@@ -626,6 +650,8 @@ namespace AzFramework
}
}
}
+
+ return !Idle();
}
Camera OrbitDollyCursorMoveCameraInput::StepCamera(
@@ -637,13 +663,15 @@ namespace AzFramework
return nextCamera;
}
- void ScrollTranslationCameraInput::HandleEvents(
+ bool ScrollTranslationCameraInput::HandleEvents(
const InputEvent& event, [[maybe_unused]] const ScreenVector& cursorDelta, [[maybe_unused]] float scrollDelta)
{
if (const auto* scroll = AZStd::get_if(&event))
{
BeginActivation();
}
+
+ return !Idle();
}
Camera ScrollTranslationCameraInput::StepCamera(
@@ -694,7 +722,7 @@ namespace AzFramework
return camera;
}
- InputEvent BuildInputEvent(const InputChannel& inputChannel, const WindowSize& windowSize)
+ InputEvent BuildInputEvent(const InputChannel& inputChannel)
{
const auto& inputChannelId = inputChannel.GetInputChannelId();
const auto& inputDeviceId = inputChannel.GetInputDevice().GetInputDeviceId();
@@ -704,13 +732,13 @@ namespace AzFramework
return button == inputChannelId;
});
- if (inputChannelId == InputDeviceMouse::Movement::X || inputChannelId == InputDeviceMouse::Movement::Y)
+ if (inputChannelId == InputDeviceMouse::Movement::X)
{
- const auto* position = inputChannel.GetCustomData();
- AZ_Assert(position, "Expected PositionData2D but found nullptr");
-
- return CursorEvent{ScreenPoint(
- position->m_normalizedPosition.GetX() * windowSize.m_width, position->m_normalizedPosition.GetY() * windowSize.m_height)};
+ return HorizontalMotionEvent{(int)inputChannel.GetValue()};
+ }
+ else if (inputChannelId == InputDeviceMouse::Movement::Y)
+ {
+ return VerticalMotionEvent{(int)inputChannel.GetValue()};
}
else if (inputChannelId == InputDeviceMouse::Movement::Z)
{
diff --git a/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.h b/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.h
index b6b2bc1e6a..ec70fc00de 100644
--- a/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.h
+++ b/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.h
@@ -18,7 +18,6 @@
#include
#include
#include
-#include
#include
#include
@@ -72,11 +71,16 @@ namespace AzFramework
void UpdateCameraFromTransform(Camera& camera, const AZ::Transform& transform);
- struct CursorEvent
+ //! Generic motion type
+ template
+ struct MotionEvent
{
- ScreenPoint m_position;
+ int m_delta;
};
+ using HorizontalMotionEvent = MotionEvent;
+ using VerticalMotionEvent = MotionEvent;
+
struct ScrollEvent
{
float m_delta;
@@ -88,7 +92,7 @@ namespace AzFramework
InputChannel::State m_state; //!< Channel state. (e.g. Begin/update/end event).
};
- using InputEvent = AZStd::variant;
+ using InputEvent = AZStd::variant;
class CameraInput
{
@@ -149,7 +153,7 @@ namespace AzFramework
ResetImpl();
}
- virtual void HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) = 0;
+ virtual bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) = 0;
virtual Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) = 0;
virtual bool Exclusive() const
@@ -171,16 +175,30 @@ namespace AzFramework
class Cameras
{
public:
- void AddCamera(AZStd::shared_ptr cameraInput);
bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta);
Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime);
+
+ void AddCamera(AZStd::shared_ptr cameraInput);
+ //! Reset the state of all cameras.
void Reset();
+ //! Remove all cameras that were added.
+ void Clear();
+ //! Is one of the cameras in the active camera inputs marked as 'exclusive'.
+ //! @note This implies no other sibling cameras can begin while the exclusive camera is running.
+ bool Exclusive() const;
private:
AZStd::vector> m_activeCameraInputs;
AZStd::vector> m_idleCameraInputs;
};
+ inline bool Cameras::Exclusive() const
+ {
+ return AZStd::any_of(
+ m_activeCameraInputs.begin(), m_activeCameraInputs.end(), [](const auto& cameraInput) { return cameraInput->Exclusive(); });
+ }
+
+ //! Responsible for updating a series of cameras given various inputs.
class CameraSystem
{
public:
@@ -190,8 +208,8 @@ namespace AzFramework
Cameras m_cameras;
private:
- CursorState m_cursorState;
- float m_scrollDelta = 0.0f;
+ ScreenVector m_motionDelta; //!< The delta used for look/orbit/pan (rotation + translation) - two dimensional.
+ float m_scrollDelta = 0.0f; //!< The delta used for dolly/movement (translation) - one dimensional.
};
class RotateCameraInput : public CameraInput
@@ -200,7 +218,7 @@ namespace AzFramework
explicit RotateCameraInput(InputChannelId rotateChannelId);
// CameraInput overrides ...
- void HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
+ bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) override;
private:
@@ -241,7 +259,7 @@ namespace AzFramework
PanCameraInput(InputChannelId panChannelId, PanAxesFn panAxesFn);
// CameraInput overrides ...
- void HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
+ bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) override;
private:
@@ -282,7 +300,7 @@ namespace AzFramework
explicit TranslateCameraInput(TranslationAxesFn translationAxesFn);
// CameraInput overrides ...
- void HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
+ bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) override;
void ResetImpl() override;
@@ -352,7 +370,7 @@ namespace AzFramework
{
public:
// CameraInput overrides ...
- void HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
+ bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) override;
};
@@ -362,7 +380,7 @@ namespace AzFramework
explicit OrbitDollyCursorMoveCameraInput(InputChannelId dollyChannelId);
// CameraInput overrides ...
- void HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
+ bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) override;
private:
@@ -373,7 +391,7 @@ namespace AzFramework
{
public:
// CameraInput overrides ...
- void HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
+ bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) override;
};
@@ -383,7 +401,7 @@ namespace AzFramework
using LookAtFn = AZStd::function()>;
// CameraInput overrides ...
- void HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
+ bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) override;
bool Exclusive() const override;
@@ -406,8 +424,6 @@ namespace AzFramework
return true;
}
- struct WindowSize;
-
//! Map from a generic InputChannel event to a camera specific InputEvent.
- InputEvent BuildInputEvent(const InputChannel& inputChannel, const WindowSize& windowSize);
+ InputEvent BuildInputEvent(const InputChannel& inputChannel);
} // namespace AzFramework
diff --git a/Code/Framework/AzFramework/AzFramework/Viewport/ClickDetector.cpp b/Code/Framework/AzFramework/AzFramework/Viewport/ClickDetector.cpp
index 4b8fbca36a..c276463554 100644
--- a/Code/Framework/AzFramework/AzFramework/Viewport/ClickDetector.cpp
+++ b/Code/Framework/AzFramework/AzFramework/Viewport/ClickDetector.cpp
@@ -17,6 +17,17 @@ namespace AzFramework
{
ClickDetector::ClickOutcome ClickDetector::DetectClick(const ClickEvent clickEvent, const ScreenVector& cursorDelta)
{
+ const auto previousDetectionState = m_detectionState;
+ if (previousDetectionState == DetectionState::WaitingForMove)
+ {
+ // only allow the action to begin if the mouse has been moved a small amount
+ m_moveAccumulator += ScreenVectorLength(cursorDelta);
+ if (m_moveAccumulator > m_deadZone)
+ {
+ m_detectionState = DetectionState::Moved;
+ }
+ }
+
if (clickEvent == ClickEvent::Down)
{
const auto now = std::chrono::steady_clock::now();
@@ -52,15 +63,9 @@ namespace AzFramework
return clickOutcome;
}
- if (m_detectionState == DetectionState::WaitingForMove)
+ if (previousDetectionState == DetectionState::WaitingForMove && m_detectionState == DetectionState::Moved)
{
- // only allow the action to begin if the mouse has been moved a small amount
- m_moveAccumulator += ScreenVectorLength(cursorDelta);
- if (m_moveAccumulator > m_deadZone)
- {
- m_detectionState = DetectionState::Moved;
- return ClickOutcome::Move;
- }
+ return ClickOutcome::Move;
}
return ClickOutcome::Nil;
diff --git a/Code/Framework/AzFramework/AzFramework/Viewport/ClickDetector.h b/Code/Framework/AzFramework/AzFramework/Viewport/ClickDetector.h
index 997ccd07d9..a595735d28 100644
--- a/Code/Framework/AzFramework/AzFramework/Viewport/ClickDetector.h
+++ b/Code/Framework/AzFramework/AzFramework/Viewport/ClickDetector.h
@@ -50,7 +50,11 @@ namespace AzFramework
//! Called from any type of 'handle event' function.
ClickOutcome DetectClick(ClickEvent clickEvent, const ScreenVector& cursorDelta);
+ //! Override the default double click interval.
+ //! @note Default is 400ms - system default.
void SetDoubleClickInterval(float doubleClickInterval);
+ //! Override the dead zone before a 'move' outcome will be triggered.
+ void SetDeadZone(float deadZone);
private:
//! Internal state of ClickDetector based on incoming events.
@@ -72,4 +76,9 @@ namespace AzFramework
{
m_doubleClickInterval = doubleClickInterval;
}
+
+ inline void ClickDetector::SetDeadZone(const float deadZone)
+ {
+ m_deadZone = deadZone;
+ }
} // namespace AzFramework
diff --git a/Code/Framework/AzNetworking/AzNetworking/Framework/INetworking.h b/Code/Framework/AzNetworking/AzNetworking/Framework/INetworking.h
index 72ffce4202..fb6d217b80 100644
--- a/Code/Framework/AzNetworking/AzNetworking/Framework/INetworking.h
+++ b/Code/Framework/AzNetworking/AzNetworking/Framework/INetworking.h
@@ -19,6 +19,8 @@
namespace AzNetworking
{
+ using NetworkInterfaces = AZStd::unordered_map>;
+
//! @class INetworking
//! @brief The interface for creating and working with network interfaces.
class INetworking
@@ -60,5 +62,25 @@ namespace AzNetworking
//! @param name The name of the Compressor factory to unregister, must match result of factory->GetFactoryName()
//! @return Whether the factory was found and unregistered
virtual bool UnregisterCompressorFactory(AZ::Name name) = 0;
+
+ //! Returns the raw network interfaces owned by the networking instance.
+ //! @return the raw network interfaces owned by the networking instance
+ virtual const NetworkInterfaces& GetNetworkInterfaces() const = 0;
+
+ //! Returns the number of sockets monitored by our TcpListenThread.
+ //! @return the number of sockets monitored by our TcpListenThread
+ virtual uint32_t GetTcpListenThreadSocketCount() const = 0;
+
+ //! Returns the total time spent updating our TcpListenThread.
+ //! @return the total time spent updating our TcpListenThread
+ virtual AZ::TimeMs GetTcpListenThreadUpdateTime() const = 0;
+
+ //! Returns the number of sockets monitored by our UdpReaderThread.
+ //! @return the number of sockets monitored by our UdpReaderThread
+ virtual uint32_t GetUdpReaderThreadSocketCount() const = 0;
+
+ //! Returns the total time spent updating our UdpReaderThread.
+ //! @return the total time spent updating our UdpReaderThread
+ virtual AZ::TimeMs GetUdpReaderThreadUpdateTime() const = 0;
};
}
diff --git a/Code/Framework/AzNetworking/AzNetworking/Framework/NetworkingSystemComponent.cpp b/Code/Framework/AzNetworking/AzNetworking/Framework/NetworkingSystemComponent.cpp
index c275ad9057..1a1476dcc8 100644
--- a/Code/Framework/AzNetworking/AzNetworking/Framework/NetworkingSystemComponent.cpp
+++ b/Code/Framework/AzNetworking/AzNetworking/Framework/NetworkingSystemComponent.cpp
@@ -149,12 +149,37 @@ namespace AzNetworking
return m_compressorFactories.erase(name) > 0;
}
+ const NetworkInterfaces& NetworkingSystemComponent::GetNetworkInterfaces() const
+ {
+ return m_networkInterfaces;
+ }
+
+ uint32_t NetworkingSystemComponent::GetTcpListenThreadSocketCount() const
+ {
+ return m_listenThread->GetSocketCount();
+ }
+
+ AZ::TimeMs NetworkingSystemComponent::GetTcpListenThreadUpdateTime() const
+ {
+ return m_listenThread->GetUpdateTimeMs();
+ }
+
+ uint32_t NetworkingSystemComponent::GetUdpReaderThreadSocketCount() const
+ {
+ return m_readerThread->GetSocketCount();
+ }
+
+ AZ::TimeMs NetworkingSystemComponent::GetUdpReaderThreadUpdateTime() const
+ {
+ return m_readerThread->GetUpdateTimeMs();
+ }
+
void NetworkingSystemComponent::DumpStats([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments)
{
- AZLOG_INFO("Total sockets monitored by TcpListenThread: %u", m_listenThread->GetSocketCount());
- AZLOG_INFO("Total time spent updating TcpListenThread: %lld", aznumeric_cast(m_listenThread->GetUpdateTimeMs()));
- AZLOG_INFO("Total sockets monitored by UdpReaderThread: %u", m_readerThread->GetSocketCount());
- AZLOG_INFO("Total time spent updating UdpReaderThread: %lld", aznumeric_cast(m_readerThread->GetUpdateTimeMs()));
+ AZLOG_INFO("Total sockets monitored by TcpListenThread: %u", GetTcpListenThreadSocketCount());
+ AZLOG_INFO("Total time spent updating TcpListenThread: %lld", aznumeric_cast(GetTcpListenThreadUpdateTime()));
+ AZLOG_INFO("Total sockets monitored by UdpReaderThread: %u", GetUdpReaderThreadSocketCount());
+ AZLOG_INFO("Total time spent updating UdpReaderThread: %lld", aznumeric_cast(GetUdpReaderThreadUpdateTime()));
for (auto& networkInterface : m_networkInterfaces)
{
diff --git a/Code/Framework/AzNetworking/AzNetworking/Framework/NetworkingSystemComponent.h b/Code/Framework/AzNetworking/AzNetworking/Framework/NetworkingSystemComponent.h
index b0b4d83d54..2fdc773fb0 100644
--- a/Code/Framework/AzNetworking/AzNetworking/Framework/NetworkingSystemComponent.h
+++ b/Code/Framework/AzNetworking/AzNetworking/Framework/NetworkingSystemComponent.h
@@ -63,6 +63,11 @@ namespace AzNetworking
void RegisterCompressorFactory(ICompressorFactory* factory) override;
AZStd::unique_ptr CreateCompressor(AZ::Name name) override;
bool UnregisterCompressorFactory(AZ::Name name) override;
+ const NetworkInterfaces& GetNetworkInterfaces() const override;
+ uint32_t GetTcpListenThreadSocketCount() const override;
+ AZ::TimeMs GetTcpListenThreadUpdateTime() const override;
+ uint32_t GetUdpReaderThreadSocketCount() const override;
+ AZ::TimeMs GetUdpReaderThreadUpdateTime() const override;
//! @}
//! Console commands.
@@ -74,7 +79,6 @@ namespace AzNetworking
AZ_CONSOLEFUNC(NetworkingSystemComponent, DumpStats, AZ::ConsoleFunctorFlags::Null, "Dumps stats for all instantiated network interfaces");
- using NetworkInterfaces = AZStd::unordered_map>;
NetworkInterfaces m_networkInterfaces;
AZStd::unique_ptr m_listenThread;
AZStd::unique_ptr m_readerThread;
diff --git a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpSocketManager_Select.cpp b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpSocketManager_Select.cpp
index 4070f74d67..e8b2527638 100644
--- a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpSocketManager_Select.cpp
+++ b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpSocketManager_Select.cpp
@@ -46,6 +46,12 @@ namespace AzNetworking
void TcpSocketManager::ProcessEvents(AZ::TimeMs maxBlockMs, const SocketEventCallback& readCallback, const SocketEventCallback& writeCallback)
{
+ if(static_cast(m_maxFd) <= 0 && m_socketFds.empty())
+ {
+ // There are no available sockets to process
+ return;
+ }
+
m_readerFdSet = m_sourceFdSet;
m_writerFdSet = m_sourceFdSet;
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/API/EditorEntityAPI.h b/Code/Framework/AzToolsFramework/AzToolsFramework/API/EditorEntityAPI.h
index f51af58c51..6c230ff5c7 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/API/EditorEntityAPI.h
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/API/EditorEntityAPI.h
@@ -52,6 +52,21 @@ namespace AzToolsFramework
* Deletes all entities in the provided list, as well as their transform descendants.
*/
virtual void DeleteEntitiesAndAllDescendants(const EntityIdList& entities) = 0;
+
+ /**
+ * Duplicate all currently-selected entities.
+ */
+ virtual void DuplicateSelected() = 0;
+
+ /**
+ * Duplicates the specified entity.
+ */
+ virtual void DuplicateEntityById(AZ::EntityId entityId) = 0;
+
+ /**
+ * Duplicates all specified entities.
+ */
+ virtual void DuplicateEntities(const EntityIdList& entities) = 0;
};
} // namespace AzToolsFramework
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Application/EditorEntityManager.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Application/EditorEntityManager.cpp
index 80d7fc7c5a..880217e807 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/Application/EditorEntityManager.cpp
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Application/EditorEntityManager.cpp
@@ -43,7 +43,7 @@ namespace AzToolsFramework
void EditorEntityManager::DeleteEntityById(AZ::EntityId entityId)
{
- DeleteEntities({entityId});
+ DeleteEntities(EntityIdList{ entityId });
}
void EditorEntityManager::DeleteEntities(const EntityIdList& entities)
@@ -53,12 +53,30 @@ namespace AzToolsFramework
void EditorEntityManager::DeleteEntityAndAllDescendants(AZ::EntityId entityId)
{
- DeleteEntitiesAndAllDescendants({entityId});
+ DeleteEntitiesAndAllDescendants(EntityIdList{ entityId });
}
void EditorEntityManager::DeleteEntitiesAndAllDescendants(const EntityIdList& entities)
{
m_prefabPublicInterface->DeleteEntitiesAndAllDescendantsInInstance(entities);
}
+
+ void EditorEntityManager::DuplicateSelected()
+ {
+ EntityIdList selectedEntities;
+ ToolsApplicationRequestBus::BroadcastResult(selectedEntities, &ToolsApplicationRequests::GetSelectedEntities);
+
+ m_prefabPublicInterface->DuplicateEntitiesInInstance(selectedEntities);
+ }
+
+ void EditorEntityManager::DuplicateEntityById(AZ::EntityId entityId)
+ {
+ DuplicateEntities(EntityIdList{ entityId });
+ }
+
+ void EditorEntityManager::DuplicateEntities(const EntityIdList& entities)
+ {
+ m_prefabPublicInterface->DuplicateEntitiesInInstance(entities);
+ }
}
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Application/EditorEntityManager.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Application/EditorEntityManager.h
index 580ad22bda..939f73729e 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/Application/EditorEntityManager.h
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Application/EditorEntityManager.h
@@ -31,6 +31,9 @@ namespace AzToolsFramework
void DeleteEntities(const EntityIdList& entities) override;
void DeleteEntityAndAllDescendants(AZ::EntityId entityId) override;
void DeleteEntitiesAndAllDescendants(const EntityIdList& entities) override;
+ void DuplicateSelected() override;
+ void DuplicateEntityById(AZ::EntityId entityId) override;
+ void DuplicateEntities(const EntityIdList& entities) override;
private:
Prefab::PrefabPublicInterface* m_prefabPublicInterface = nullptr;
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabDomUtils.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabDomUtils.h
index 5ee91c85ae..4feecb9da3 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabDomUtils.h
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabDomUtils.h
@@ -28,6 +28,7 @@ namespace AzToolsFramework
inline static const char* PatchesName = "Patches";
inline static const char* SourceName = "Source";
inline static const char* LinkIdName = "LinkId";
+ inline static const char* EntityIdName = "Id";
inline static const char* EntitiesName = "Entities";
inline static const char* ContainerEntityName = "ContainerEntity";
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp
index e6bb8c7dee..579f465eb2 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp
@@ -13,6 +13,8 @@
#include
#include
+#include
+#include
#include
#include
@@ -31,6 +33,8 @@
#include
#include
+#include
+
namespace AzToolsFramework
{
namespace Prefab
@@ -83,7 +87,9 @@ namespace AzToolsFramework
commonRootInstanceDomBeforeCreate, commonRootEntityOwningInstance->get());
AZStd::vector entities;
- AZStd::vector> instances;
+ AZStd::vector> instancePtrs;
+ AZStd::vector instances;
+ AZStd::unordered_map nestedInstanceLinkPatchesMap;
// Retrieve all entities affected and identify Instances
if (!RetrieveAndSortPrefabEntitiesAndInstances(inputEntityList, commonRootEntityOwningInstance->get(), entities, instances))
@@ -92,11 +98,31 @@ namespace AzToolsFramework
AZStd::string("Could not create a new prefab out of the entities provided - invalid selection."));
}
+ // Detach the retrieved entities
+ for (AZ::Entity* entity : entities)
+ {
+ commonRootEntityOwningInstance->get().DetachEntity(entity->GetId()).release();
+ }
+
// When we create a prefab with other prefab instances, we have to remove the existing links between the source and
// target templates of the other instances.
for (auto& nestedInstance : instances)
{
- RemoveLink(nestedInstance, commonRootEntityOwningInstance->get().GetTemplateId(), undoBatch.GetUndoBatch());
+ AZStd::unique_ptr outInstance = commonRootEntityOwningInstance->get().DetachNestedInstance(nestedInstance->GetInstanceAlias());
+
+ auto linkRef = m_prefabSystemComponentInterface->FindLink(nestedInstance->GetLinkId());
+
+ if (linkRef.has_value())
+ {
+ PrefabDom oldLinkPatches;
+ oldLinkPatches.CopyFrom(linkRef->get().GetLinkDom(), oldLinkPatches.GetAllocator());
+
+ nestedInstanceLinkPatchesMap.emplace(nestedInstance, AZStd::move(oldLinkPatches));
+ }
+
+ RemoveLink(outInstance, commonRootEntityOwningInstance->get().GetTemplateId(), undoBatch.GetUndoBatch());
+
+ instancePtrs.emplace_back(AZStd::move(outInstance));
}
PrefabUndoHelpers::UpdatePrefabInstance(
@@ -112,7 +138,7 @@ namespace AzToolsFramework
// Create the Prefab
instanceToCreate = prefabEditorEntityOwnershipInterface->CreatePrefab(
- entities, AZStd::move(instances), filePath, commonRootEntityOwningInstance);
+ entities, AZStd::move(instancePtrs), filePath, commonRootEntityOwningInstance);
if (!instanceToCreate)
{
@@ -122,6 +148,9 @@ namespace AzToolsFramework
AZ::EntityId containerEntityId = instanceToCreate->get().GetContainerEntityId();
+ // Apply the correct transform to the container for the new instance, and store the patch for use when creating the link.
+ PrefabDom patch = ApplyContainerTransformAndGeneratePatch(containerEntityId, commonRootEntityId, topLevelEntities);
+
// Parent the non-container top level entities to the container entity.
// Parenting the top level container entities will be done during the creation of links.
for (AZ::Entity* topLevelEntity : topLevelEntities)
@@ -141,35 +170,55 @@ namespace AzToolsFramework
instanceToCreate->get().GetNestedInstances([&](AZStd::unique_ptr& nestedInstance) {
AZ_Assert(nestedInstance, "Invalid nested instance found in the new prefab created.");
+
EntityOptionalReference nestedInstanceContainerEntity = nestedInstance->GetContainerEntity();
AZ_Assert(
nestedInstanceContainerEntity, "Invalid container entity found for the nested instance used in prefab creation.");
- AZ::EntityId parentId;
- AZ::TransformBus::EventResult(
- parentId, nestedInstanceContainerEntity->get().GetId(), &AZ::TransformBus::Events::GetParentId);
+ AZ::EntityId nestedInstanceContainerEntityId = nestedInstanceContainerEntity->get().GetId();
+ PrefabDom previousPatch;
- auto entityIterator = AZStd::find_if(
- entities.begin(), entities.end(), [parentId](AZ::Entity* entity) { return entity->GetId() == parentId; });
-
- // If the previous parent entity of the nested instance is not part of the entities of the newly created prefab,
- // then set the parent of the nested prefab as the container entity of the newly created prefab.
- if (entityIterator == entities.end())
+ // Retrieve the previous patch if it exists
+ if (nestedInstanceLinkPatchesMap.contains(nestedInstance.get()))
{
- parentId = containerEntityId;
+ previousPatch = AZStd::move(nestedInstanceLinkPatchesMap[nestedInstance.get()]);
}
// These link creations shouldn't be undone because that would put the template in a non-usable state if a user
// chooses to instantiate the template after undoing the creation.
- CreateLink(
- {&nestedInstanceContainerEntity->get()}, *nestedInstance, instanceToCreate->get().GetTemplateId(),
- undoBatch.GetUndoBatch(), parentId, false);
+ CreateLink(*nestedInstance, instanceToCreate->get().GetTemplateId(), undoBatch.GetUndoBatch(), AZStd::move(previousPatch), false);
+
+ // If this nested instance's container is a top level entity in the new prefab, re-parent it and apply the change.
+ if (AZStd::find(topLevelEntities.begin(), topLevelEntities.end(), &nestedInstanceContainerEntity->get()) != topLevelEntities.end())
+ {
+ Prefab::PrefabDom containerEntityDomBefore;
+ m_instanceToTemplateInterface->GenerateDomForEntity(containerEntityDomBefore, *nestedInstanceContainerEntity);
+
+ AZ::TransformBus::Event(nestedInstanceContainerEntityId, &AZ::TransformBus::Events::SetParent, containerEntityId);
+
+ PrefabDom containerEntityDomAfter;
+ m_instanceToTemplateInterface->GenerateDomForEntity(containerEntityDomAfter, *nestedInstanceContainerEntity);
+
+ PrefabDom reparentPatch;
+ m_instanceToTemplateInterface->GeneratePatch(reparentPatch, containerEntityDomBefore, containerEntityDomAfter);
+ m_instanceToTemplateInterface->AppendEntityAliasToPatchPaths(reparentPatch, nestedInstanceContainerEntityId);
+
+ // Update the cache - this prevents these changes from being stored in the regular undo/redo nodes as a separate step
+ m_prefabUndoCache.Store(nestedInstanceContainerEntityId, AZStd::move(containerEntityDomAfter));
+
+ // Save these changes as patches to the link
+ PrefabUndoLinkUpdate* linkUpdate = aznew PrefabUndoLinkUpdate(AZStd::to_string(static_cast(nestedInstanceContainerEntityId)));
+ linkUpdate->SetParent(undoBatch.GetUndoBatch());
+ linkUpdate->Capture(reparentPatch, nestedInstance->GetLinkId());
+
+ linkUpdate->Redo();
+ }
});
// Create a link between the templates of the newly created instance and the instance it's being parented under.
CreateLink(
- topLevelEntities, instanceToCreate->get(), commonRootEntityOwningInstance->get().GetTemplateId(),
- undoBatch.GetUndoBatch(), commonRootEntityId);
+ instanceToCreate->get(), commonRootEntityOwningInstance->get().GetTemplateId(), undoBatch.GetUndoBatch(),
+ AZStd::move(patch));
for (AZ::Entity* topLevelEntity : topLevelEntities)
{
@@ -199,6 +248,40 @@ namespace AzToolsFramework
return AZ::Success();
}
+ PrefabDom PrefabPublicHandler::ApplyContainerTransformAndGeneratePatch(AZ::EntityId containerEntityId, AZ::EntityId parentEntityId, const EntityList& childEntities)
+ {
+ AZ::Entity* containerEntity = GetEntityById(containerEntityId);
+ AZ_Assert(containerEntity, "Invalid container entity passed to ApplyContainerTransformAndGeneratePatch.");
+
+ // Generate the transform for the container entity out of the top level entities, and set it
+ // This step needs to be done before anything is parented to the container, else children position will be wrong
+ Prefab::PrefabDom containerEntityDomBefore;
+ m_instanceToTemplateInterface->GenerateDomForEntity(containerEntityDomBefore, *containerEntity);
+
+ AZ::Vector3 containerEntityTranslation(AZ::Vector3::CreateZero());
+ AZ::Quaternion containerEntityRotation(AZ::Quaternion::CreateZero());
+
+ // Set container entity to be child of common root
+ AZ::TransformBus::Event(containerEntityId, &AZ::TransformBus::Events::SetParent, parentEntityId);
+
+ // Set the transform (translation, rotation) of the container entity
+ GenerateContainerEntityTransform(childEntities, containerEntityTranslation, containerEntityRotation);
+ AZ::TransformBus::Event(containerEntityId, &AZ::TransformBus::Events::SetLocalTranslation, containerEntityTranslation);
+ AZ::TransformBus::Event(containerEntityId, &AZ::TransformBus::Events::SetLocalRotationQuaternion, containerEntityRotation);
+
+ PrefabDom containerEntityDomAfter;
+ m_instanceToTemplateInterface->GenerateDomForEntity(containerEntityDomAfter, *containerEntity);
+
+ PrefabDom patch;
+ m_instanceToTemplateInterface->GeneratePatch(patch, containerEntityDomBefore, containerEntityDomAfter);
+ m_instanceToTemplateInterface->AppendEntityAliasToPatchPaths(patch, containerEntityId);
+
+ // Update the cache - this prevents these changes from being stored in the regular undo/redo nodes
+ m_prefabUndoCache.Store(containerEntityId, AZStd::move(containerEntityDomAfter));
+
+ return AZStd::move(patch);
+ }
+
PrefabOperationResult PrefabPublicHandler::InstantiatePrefab(
AZStd::string_view filePath, AZ::EntityId parent, const AZ::Vector3& position)
{
@@ -249,10 +332,10 @@ namespace AzToolsFramework
// Initialize Undo Batch object
ScopedUndoBatch undoBatch("Instantiate Prefab");
+ // Instantiate the Prefab
PrefabDom instanceToParentUnderDomBeforeCreate;
m_instanceToTemplateInterface->GenerateDomForInstance(instanceToParentUnderDomBeforeCreate, instanceToParentUnder->get());
- // Instantiate the Prefab
auto instanceToCreate = prefabEditorEntityOwnershipInterface->InstantiatePrefab(relativePath, instanceToParentUnder);
if (!instanceToCreate)
@@ -264,11 +347,32 @@ namespace AzToolsFramework
PrefabUndoHelpers::UpdatePrefabInstance(
instanceToParentUnder->get(), "Update prefab instance", instanceToParentUnderDomBeforeCreate, undoBatch.GetUndoBatch());
- CreateLink({}, instanceToCreate->get(), instanceToParentUnder->get().GetTemplateId(), undoBatch.GetUndoBatch(), parent);
+ // Create Link with correct container patches
AZ::EntityId containerEntityId = instanceToCreate->get().GetContainerEntityId();
+ AZ::Entity* containerEntity = GetEntityById(containerEntityId);
+ AZ_Assert(containerEntity, "Invalid container entity detected in InstantiatePrefab.");
- // Apply position
+ Prefab::PrefabDom containerEntityDomBefore;
+ m_instanceToTemplateInterface->GenerateDomForEntity(containerEntityDomBefore, *containerEntity);
+
+ // Set container entity's parent
+ AZ::TransformBus::Event(containerEntityId, &AZ::TransformBus::Events::SetParent, parent);
+
+ // Set the position of the container entity
AZ::TransformBus::Event(containerEntityId, &AZ::TransformBus::Events::SetWorldTranslation, position);
+
+ PrefabDom containerEntityDomAfter;
+ m_instanceToTemplateInterface->GenerateDomForEntity(containerEntityDomAfter, *containerEntity);
+
+ // Generate patch to be stored in the link
+ PrefabDom patch;
+ m_instanceToTemplateInterface->GeneratePatch(patch, containerEntityDomBefore, containerEntityDomAfter);
+ m_instanceToTemplateInterface->AppendEntityAliasToPatchPaths(patch, containerEntityId);
+
+ CreateLink(instanceToCreate->get(), instanceToParentUnder->get().GetTemplateId(), undoBatch.GetUndoBatch(), AZStd::move(patch));
+
+ // Update the cache - this prevents these changes from being stored in the regular undo/redo nodes
+ m_prefabUndoCache.Store(containerEntityId, AZStd::move(containerEntityDomAfter));
}
return AZ::Success();
@@ -299,7 +403,7 @@ namespace AzToolsFramework
// Find common root and top level entities
bool entitiesHaveCommonRoot = false;
- AzToolsFramework::ToolsApplicationRequests::Bus::BroadcastResult(
+ AzToolsFramework::ToolsApplicationRequestBus::BroadcastResult(
entitiesHaveCommonRoot, &AzToolsFramework::ToolsApplicationRequests::FindCommonRootInactive, inputEntityList,
commonRootEntityId, &topLevelEntities);
@@ -335,33 +439,9 @@ namespace AzToolsFramework
}
void PrefabPublicHandler::CreateLink(
- const EntityList& topLevelEntities, Instance& sourceInstance, TemplateId targetTemplateId,
- UndoSystem::URSequencePoint* undoBatch, AZ::EntityId commonRootEntityId, const bool isUndoRedoSupportNeeded)
+ Instance& sourceInstance, TemplateId targetTemplateId,
+ UndoSystem::URSequencePoint* undoBatch, PrefabDom patch, const bool isUndoRedoSupportNeeded)
{
- AZ::EntityId containerEntityId = sourceInstance.GetContainerEntityId();
- AZ::Entity* containerEntity = GetEntityById(containerEntityId);
- Prefab::PrefabDom containerEntityDomBefore;
- m_instanceToTemplateInterface->GenerateDomForEntity(containerEntityDomBefore, *containerEntity);
-
- AZ::Vector3 containerEntityTranslation(AZ::Vector3::CreateZero());
- AZ::Quaternion containerEntityRotation(AZ::Quaternion::CreateZero());
-
- // Set the transform (translation, rotation) of the container entity
- GenerateContainerEntityTransform(topLevelEntities, containerEntityTranslation, containerEntityRotation);
-
- // Set container entity to be child of common root
- AZ::TransformBus::Event(containerEntityId, &AZ::TransformBus::Events::SetParent, commonRootEntityId);
-
- AZ::TransformBus::Event(containerEntityId, &AZ::TransformBus::Events::SetLocalTranslation, containerEntityTranslation);
- AZ::TransformBus::Event(containerEntityId, &AZ::TransformBus::Events::SetLocalRotationQuaternion, containerEntityRotation);
-
- PrefabDom containerEntityDomAfter;
- m_instanceToTemplateInterface->GenerateDomForEntity(containerEntityDomAfter, *containerEntity);
-
- PrefabDom patch;
- m_instanceToTemplateInterface->GeneratePatch(patch, containerEntityDomBefore, containerEntityDomAfter);
- m_instanceToTemplateInterface->AppendEntityAliasToPatchPaths(patch, containerEntityId);
-
LinkId linkId;
if (isUndoRedoSupportNeeded)
{
@@ -377,9 +457,6 @@ namespace AzToolsFramework
}
sourceInstance.SetLinkId(linkId);
-
- // Update the cache - this prevents these changes from being stored in the regular undo/redo nodes
- m_prefabUndoCache.Store(containerEntityId, AZStd::move(containerEntityDomAfter));
}
void PrefabPublicHandler::RemoveLink(
@@ -648,6 +725,151 @@ namespace AzToolsFramework
return DeleteFromInstance(entityIds, true);
}
+ PrefabOperationResult PrefabPublicHandler::DuplicateEntitiesInInstance(const EntityIdList& entityIds)
+ {
+ if (entityIds.empty())
+ {
+ return AZ::Failure(AZStd::string("No entities to duplicate."));
+ }
+
+ if (!EntitiesBelongToSameInstance(entityIds))
+ {
+ return AZ::Failure(AZStd::string("Cannot duplicate multiple "
+ "entities belonging to different instances with one operation."));
+ }
+
+ // We've already verified the entities are all owned by the same instance,
+ // so we can just retrieve our instance from the first entity in the list.
+ InstanceOptionalReference commonEntityOwningInstance = GetOwnerInstanceByEntityId(entityIds[0]);
+ AZ_Assert(
+ commonEntityOwningInstance.has_value(),
+ "Failed to duplicate : Couldn't get a valid owning instance for the common root entity of the entities provided");
+
+ // This will cull out any entities that have ancestors in the list, since we will end up duplicating
+ // the full nested hierarchy with what is returned from RetrieveAndSortPrefabEntitiesAndInstances
+ AzToolsFramework::EntityIdSet duplicationSet = AzToolsFramework::GetCulledEntityHierarchy(entityIds);
+
+ AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
+
+ ScopedUndoBatch undoBatch("Duplicate Entities");
+
+ {
+ AZ_PROFILE_SCOPE(AZ::Debug::ProfileCategory::AzToolsFramework, "DuplicateEntitiesInInstance::UndoCaptureAndDuplicateEntities");
+
+ // Take a snapshot of the instance DOM before we manipulate it
+ Prefab::PrefabDom instanceDomBefore;
+ m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomBefore, commonEntityOwningInstance->get());
+
+ AZStd::vector entities;
+ AZStd::vector instances;
+
+ // Gather all entities/instances in the hierarchy, but don't detach them because we are duplicating not deleting.
+ EntityList inputEntityList = EntityIdSetToEntityList(duplicationSet);
+ bool success = RetrieveAndSortPrefabEntitiesAndInstances(inputEntityList, commonEntityOwningInstance->get(), entities, instances);
+
+ if (!success)
+ {
+ return AZ::Failure(AZStd::string("Failed to retrieve entities and instances from the given list of entity ids for duplication"));
+ }
+
+ // Make a copy of our before instance DOM where we will add our duplicated entities
+ Prefab::PrefabDom instanceDomAfter;
+ instanceDomAfter.CopyFrom(instanceDomBefore, instanceDomAfter.GetAllocator());
+
+ AZStd::unordered_map oldAliasToNewAliasMap;
+ AZStd::unordered_map aliasToEntityDomMap;
+
+ for (AZ::Entity* entity : entities)
+ {
+ EntityAliasOptionalReference oldAliasRef = commonEntityOwningInstance->get().GetEntityAlias(entity->GetId());
+ AZ_Assert(oldAliasRef.has_value(), "No alias found for Entity in the DOM");
+ EntityAlias oldAlias = oldAliasRef.value();
+
+ // Give this the outer allocator so that the memory reference will be valid when
+ // it gets used for AddMember
+ Prefab::PrefabDom entityDomBefore(&instanceDomAfter.GetAllocator());
+ m_instanceToTemplateInterface->GenerateDomForEntity(entityDomBefore, *entity);
+
+ // Keep track of the old alias <-> new alias mapping for this duplicated entity
+ // so we can fixup references later
+ EntityAlias newEntityAlias = Instance::GenerateEntityAlias();
+ oldAliasToNewAliasMap.insert(AZStd::make_pair(oldAlias, newEntityAlias));
+
+ rapidjson::StringBuffer buffer;
+ rapidjson::Writer writer(buffer);
+ entityDomBefore.Accept(writer);
+
+ // Store our duplicated Entity DOM with its new alias as a string
+ // so that we can fixup entity alias references before adding it
+ // to the Entities member of our instance DOM
+ QString entityDomString(buffer.GetString());
+ aliasToEntityDomMap.insert(AZStd::make_pair(newEntityAlias, entityDomString));
+ }
+
+ auto entitiesIter = instanceDomAfter.FindMember(PrefabDomUtils::EntitiesName);
+ AZ_Assert(entitiesIter != instanceDomAfter.MemberEnd(), "Instance DOM missing the Entities member.");
+
+ // Now that all the duplicated Entity DOMs have been created, we need to iterate
+ // through them and replace any previous EntityAlias references with the new ones.
+ // These are more than just parent entity references for nested entities, this will
+ // also cover any EntityId references that were made in the components between them.
+ for (auto aliasEntityPair : aliasToEntityDomMap)
+ {
+ EntityAlias newEntityAlias = aliasEntityPair.first;
+ QString newEntityDomString = aliasEntityPair.second;
+
+ // Replace all of the old alias references with the new ones
+ // We bookend the aliases with \" and also with a / as an extra precaution to prevent
+ // inadvertently replacing a matching string vs. where an actual EntityId is expected
+ // This will cover both cases where an alias could be used in a normal entity vs. an instance
+ for (auto aliasMapIter : oldAliasToNewAliasMap)
+ {
+ QString oldAliasQuotes = QString("\"%1\"").arg(aliasMapIter.first.c_str());
+ QString newAliasQuotes = QString("\"%1\"").arg(aliasMapIter.second.c_str());
+
+ newEntityDomString.replace(oldAliasQuotes, newAliasQuotes);
+
+ QString oldAliasPathRef = QString("/%1").arg(aliasMapIter.first.c_str());
+ QString newAliasPathRef = QString("/%1").arg(aliasMapIter.second.c_str());
+
+ newEntityDomString.replace(oldAliasPathRef, newAliasPathRef);
+ }
+
+ // Create the new Entity DOM from parsing the JSON string
+ Prefab::PrefabDom entityDomAfter(&instanceDomAfter.GetAllocator());
+ entityDomAfter.Parse(newEntityDomString.toUtf8().constData());
+
+ // Add the new Entity DOM to the Entities member of the instance
+ rapidjson::Value aliasName(newEntityAlias.c_str(), newEntityAlias.length(), instanceDomAfter.GetAllocator());
+ entitiesIter->value.AddMember(AZStd::move(aliasName), entityDomAfter, instanceDomAfter.GetAllocator());
+ }
+
+ PrefabUndoInstance* command = aznew PrefabUndoInstance("Entity duplication");
+ command->SetParent(undoBatch.GetUndoBatch());
+ command->Capture(instanceDomBefore, instanceDomAfter, commonEntityOwningInstance->get().GetTemplateId());
+ command->RunRedo();
+
+ EntityIdList duplicatedEntityIds;
+ for (auto aliasMapIter : oldAliasToNewAliasMap)
+ {
+ EntityAlias newEntityAlias = aliasMapIter.second;
+
+ AliasPath absoluteEntityPath = commonEntityOwningInstance->get().GetAbsoluteInstanceAliasPath();
+ absoluteEntityPath.Append(newEntityAlias);
+
+ AZ::EntityId newEntityId = InstanceEntityIdMapper::GenerateEntityIdForAliasPath(absoluteEntityPath);
+ duplicatedEntityIds.push_back(newEntityId);
+ }
+
+ // Select the duplicated entities
+ auto selectionUndo = aznew SelectionCommand(duplicatedEntityIds, "Select Duplicated Entities");
+ selectionUndo->SetParent(undoBatch.GetUndoBatch());
+ ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequestBus::Events::RunRedoSeparately, selectionUndo);
+ }
+
+ return AZ::Success();
+ }
+
PrefabOperationResult PrefabPublicHandler::DeleteFromInstance(const EntityIdList& entityIds, bool deleteDescendants)
{
if (entityIds.empty())
@@ -675,17 +897,7 @@ namespace AzToolsFramework
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
- UndoSystem::URSequencePoint* currentUndoBatch = nullptr;
- ToolsApplicationRequests::Bus::BroadcastResult(currentUndoBatch, &ToolsApplicationRequests::Bus::Events::GetCurrentUndoBatch);
-
- bool createdUndo = false;
- if (!currentUndoBatch)
- {
- createdUndo = true;
- ToolsApplicationRequests::Bus::BroadcastResult(
- currentUndoBatch, &ToolsApplicationRequests::Bus::Events::BeginUndoBatch, "Delete Selected");
- AZ_Assert(currentUndoBatch, "Failed to create new undo batch.");
- }
+ ScopedUndoBatch undoBatch("Delete Selected");
// In order to undo DeleteSelected, we have to create a selection command which selects the current selection
// and then add the deletion as children.
@@ -713,7 +925,7 @@ namespace AzToolsFramework
if (deleteDescendants)
{
AZStd::vector entities;
- AZStd::vector> instances;
+ AZStd::vector instances;
bool success = RetrieveAndSortPrefabEntitiesAndInstances(inputEntityList, commonOwningInstance->get(), entities, instances);
@@ -724,13 +936,15 @@ namespace AzToolsFramework
for (AZ::Entity* entity : entities)
{
+ commonOwningInstance->get().DetachEntity(entity->GetId()).release();
AZ::ComponentApplicationBus::Broadcast(&AZ::ComponentApplicationRequests::DeleteEntity, entity->GetId());
}
for (auto& nestedInstance : instances)
{
- RemoveLink(nestedInstance, commonOwningInstance->get().GetTemplateId(), currentUndoBatch);
- nestedInstance.reset();
+ AZStd::unique_ptr outInstance = commonOwningInstance->get().DetachNestedInstance(nestedInstance->GetInstanceAlias());
+ RemoveLink(outInstance, commonOwningInstance->get().GetTemplateId(), undoBatch.GetUndoBatch());
+ outInstance.reset();
}
}
else
@@ -742,7 +956,7 @@ namespace AzToolsFramework
if (owningInstance->get().GetContainerEntityId() == entityId)
{
auto instancePtr = commonOwningInstance->get().DetachNestedInstance(owningInstance->get().GetInstanceAlias());
- RemoveLink(instancePtr, commonOwningInstance->get().GetTemplateId(), currentUndoBatch);
+ RemoveLink(instancePtr, commonOwningInstance->get().GetTemplateId(), undoBatch.GetUndoBatch());
}
else
{
@@ -760,17 +974,12 @@ namespace AzToolsFramework
command->SetParent(selCommand);
}
- selCommand->SetParent(currentUndoBatch);
+ selCommand->SetParent(undoBatch.GetUndoBatch());
{
AZ_PROFILE_SCOPE(AZ::Debug::ProfileCategory::AzToolsFramework, "Internal::DeleteEntities:RunRedo");
selCommand->RunRedo();
}
- if (createdUndo)
- {
- ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequests::EndUndoBatch);
- }
-
return AZ::Success();
}
@@ -882,7 +1091,7 @@ namespace AzToolsFramework
bool PrefabPublicHandler::RetrieveAndSortPrefabEntitiesAndInstances(
const EntityList& inputEntities, Instance& commonRootEntityOwningInstance,
- EntityList& outEntities, AZStd::vector>& outInstances) const
+ EntityList& outEntities, AZStd::vector& outInstances) const
{
if (inputEntities.size() == 0)
{
@@ -966,14 +1175,14 @@ namespace AzToolsFramework
for (AZ::Entity* entity : entities)
{
- outEntities.emplace_back(commonRootEntityOwningInstance.DetachEntity(entity->GetId()).release());
+ outEntities.emplace_back(entity);
}
outInstances.clear();
outInstances.reserve(instances.size());
for (Instance* instancePtr : instances)
{
- outInstances.push_back(AZStd::move(commonRootEntityOwningInstance.DetachNestedInstance(instancePtr->GetInstanceAlias())));
+ outInstances.push_back(instancePtr);
}
return (outEntities.size() + outInstances.size()) > 0;
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h
index d88086dda9..223a725c6c 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h
@@ -60,28 +60,41 @@ namespace AzToolsFramework
PrefabOperationResult DeleteEntitiesInInstance(const EntityIdList& entityIds) override;
PrefabOperationResult DeleteEntitiesAndAllDescendantsInInstance(const EntityIdList& entityIds) override;
+ PrefabOperationResult DuplicateEntitiesInInstance(const EntityIdList& entityIds) override;
private:
PrefabOperationResult DeleteFromInstance(const EntityIdList& entityIds, bool deleteDescendants);
bool RetrieveAndSortPrefabEntitiesAndInstances(const EntityList& inputEntities, Instance& commonRootEntityOwningInstance,
- EntityList& outEntities, AZStd::vector>& outInstances) const;
+ EntityList& outEntities, AZStd::vector& outInstances) const;
InstanceOptionalReference GetOwnerInstanceByEntityId(AZ::EntityId entityId) const;
bool EntitiesBelongToSameInstance(const EntityIdList& entityIds) const;
+
+ /**
+ * Applies the correct transform changes to the container entity based on the parent and child entities provided, and returns an appropriate patch.
+ * The container will be parented to parentId, moved to the average transform of the future direct children and its cache will be updated.
+ * This helper function won't support undo/redo, update the templates or create any links. All that needs to be done by the caller.
+ *
+ * \param containerEntityId The container to apply the changes to.
+ * \param parentEntityId The id of the entity the container should be parented to.
+ * \param childEntities A list of entities that will subsequently be parented to this container.
+ * \return The PrefabDom containing the patches that should be stored in the parent link.
+ */
+ PrefabDom ApplyContainerTransformAndGeneratePatch(
+ AZ::EntityId containerEntityId, AZ::EntityId parentEntityId, const EntityList& childEntities);
/**
* Creates a link between the templates of an instance and its parent.
*
- * \param topLevelEntities The list of entities that are immediate children to the container entity of the instance.
* \param sourceInstance The instance that corresponds to the source template of the link.
* \param targetInstance The id of the target template.
* \param undoBatch The undo batch to set as parent for this create link action.
- * \param commonRootEntityId The id of the entity that the source instance should be parented under.
+ * \param patch The patch to store in the newly created link dom.
* \param isUndoRedoSupportNeeded The flag indicating whether the link should be created with undo/redo support or not.
*/
void CreateLink(
- const EntityList& topLevelEntities, Instance& sourceInstance, TemplateId targetTemplateId,
- UndoSystem::URSequencePoint* undoBatch, AZ::EntityId commonRootEntityId, const bool isUndoRedoSupportNeeded = true);
+ Instance& sourceInstance, TemplateId targetTemplateId, UndoSystem::URSequencePoint* undoBatch,
+ PrefabDom patch, const bool isUndoRedoSupportNeeded = true);
/**
* Removes the link between template of the sourceInstance and the template corresponding to targetTemplateId.
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicInterface.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicInterface.h
index 1a8da0dfe0..0750c4d264 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicInterface.h
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicInterface.h
@@ -143,6 +143,13 @@ namespace AzToolsFramework
* @return An outcome object; on failure, it comes with an error message detailing the cause of the error.
*/
virtual PrefabOperationResult DeleteEntitiesAndAllDescendantsInInstance(const EntityIdList& entityIds) = 0;
+
+ /**
+ * Duplicates all entities in the owning instance. Bails if the entities don't all belong to the same instance.
+ * @param entities The entities to duplicate.
+ * @return An outcome object; on failure, it comes with an error message detailing the cause of the error.
+ */
+ virtual PrefabOperationResult DuplicateEntitiesInInstance(const EntityIdList& entityIds) = 0;
};
} // namespace Prefab
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.cpp
index 5b44594398..4a72afb16b 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.cpp
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.cpp
@@ -63,6 +63,7 @@
#include
#include
#include
+#include
#include
////////////////////////////////////////////////////////////////////////////
@@ -1409,6 +1410,16 @@ namespace AzToolsFramework
{
(void)name;
QueueEntityUpdate(entityId);
+
+ bool isSelected = false;
+ AzToolsFramework::ToolsApplicationRequests::Bus::BroadcastResult(
+ isSelected, &AzToolsFramework::ToolsApplicationRequests::IsSelected, entityId);
+
+ if (isSelected)
+ {
+ // Ask the system to scroll to the entity in case it is off screen after the rename
+ EntityOutlinerModelNotificationBus::Broadcast(&EntityOutlinerModelNotifications::QueueScrollToNewContent, entityId);
+ }
}
void EntityOutlinerListModel::OnEntityInfoUpdatedUnsavedChanges(AZ::EntityId entityId)
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp
index bc7afbf085..3edc190fb7 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp
@@ -93,28 +93,12 @@ namespace AzToolsFramework
EditorContextMenuBus::Handler::BusConnect();
PrefabInstanceContainerNotificationBus::Handler::BusConnect();
AZ::Interface::Register(this);
-
- bool prefabWipFeaturesEnabled = false;
- AzFramework::ApplicationRequests::Bus::BroadcastResult(
- prefabWipFeaturesEnabled, &AzFramework::ApplicationRequests::ArePrefabWipFeaturesEnabled);
-
- if (prefabWipFeaturesEnabled)
- {
- AssetBrowser::AssetBrowserSourceDropBus::Handler::BusConnect(s_prefabFileExtension);
- }
+ AssetBrowser::AssetBrowserSourceDropBus::Handler::BusConnect(s_prefabFileExtension);
}
PrefabIntegrationManager::~PrefabIntegrationManager()
{
- bool prefabWipFeaturesEnabled = false;
- AzFramework::ApplicationRequests::Bus::BroadcastResult(
- prefabWipFeaturesEnabled, &AzFramework::ApplicationRequests::ArePrefabWipFeaturesEnabled);
-
- if (prefabWipFeaturesEnabled)
- {
- AssetBrowser::AssetBrowserSourceDropBus::Handler::BusDisconnect();
- }
-
+ AssetBrowser::AssetBrowserSourceDropBus::Handler::BusDisconnect();
AZ::Interface::Unregister(this);
PrefabInstanceContainerNotificationBus::Handler::BusDisconnect();
EditorContextMenuBus::Handler::BusDisconnect();
@@ -137,66 +121,63 @@ namespace AzToolsFramework
void PrefabIntegrationManager::PopulateEditorGlobalContextMenu(QMenu* menu) const
{
- bool prefabWipFeaturesEnabled = false;
- AzFramework::ApplicationRequests::Bus::BroadcastResult(
- prefabWipFeaturesEnabled, &AzFramework::ApplicationRequests::ArePrefabWipFeaturesEnabled);
-
AzToolsFramework::EntityIdList selectedEntities;
AzToolsFramework::ToolsApplicationRequestBus::BroadcastResult(
selectedEntities, &AzToolsFramework::ToolsApplicationRequests::GetSelectedEntities);
- if (prefabWipFeaturesEnabled)
+ bool prefabWipFeaturesEnabled = false;
+ AzFramework::ApplicationRequests::Bus::BroadcastResult(
+ prefabWipFeaturesEnabled, &AzFramework::ApplicationRequests::ArePrefabWipFeaturesEnabled);
+
+ // Create Prefab
{
- // Create Prefab
+ if (!selectedEntities.empty())
{
- if (!selectedEntities.empty())
+ // Hide if the only selected entity is the Level Container
+ if (selectedEntities.size() > 1 || !s_prefabPublicInterface->IsLevelInstanceContainerEntity(selectedEntities[0]))
{
- // Hide if the only selected entity is the Level Container
- if (selectedEntities.size() > 1 || !s_prefabPublicInterface->IsLevelInstanceContainerEntity(selectedEntities[0]))
+ bool layerInSelection = false;
+
+ for (AZ::EntityId entityId : selectedEntities)
{
- bool layerInSelection = false;
-
- for (AZ::EntityId entityId : selectedEntities)
- {
- if (!layerInSelection)
- {
- AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult(
- layerInSelection, entityId,
- &AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::HasLayer);
-
- if (layerInSelection)
- {
- break;
- }
- }
- }
-
- // Layers can't be in prefabs.
if (!layerInSelection)
{
- QAction* createAction = menu->addAction(QObject::tr("Create Prefab..."));
- createAction->setToolTip(QObject::tr("Creates a prefab out of the currently selected entities."));
+ AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult(
+ layerInSelection, entityId,
+ &AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::HasLayer);
- QObject::connect(createAction, &QAction::triggered, createAction, [this, selectedEntities] {
- ContextMenu_CreatePrefab(selectedEntities);
- });
+ if (layerInSelection)
+ {
+ break;
+ }
}
}
+
+ // Layers can't be in prefabs.
+ if (!layerInSelection)
+ {
+ QAction* createAction = menu->addAction(QObject::tr("Create Prefab..."));
+ createAction->setToolTip(QObject::tr("Creates a prefab out of the currently selected entities."));
+
+ QObject::connect(createAction, &QAction::triggered, createAction, [this, selectedEntities] {
+ ContextMenu_CreatePrefab(selectedEntities);
+ });
+ }
}
}
-
- // Instantiate Prefab
- {
- QAction* instantiateAction = menu->addAction(QObject::tr("Instantiate Prefab..."));
- instantiateAction->setToolTip(QObject::tr("Instantiates a prefab file in the scene."));
-
- QObject::connect(
- instantiateAction, &QAction::triggered, instantiateAction, [this] { ContextMenu_InstantiatePrefab(); });
- }
-
- menu->addSeparator();
}
+ // Instantiate Prefab
+ {
+ QAction* instantiateAction = menu->addAction(QObject::tr("Instantiate Prefab..."));
+ instantiateAction->setToolTip(QObject::tr("Instantiates a prefab file in the scene."));
+
+ QObject::connect(
+ instantiateAction, &QAction::triggered, instantiateAction, [this] { ContextMenu_InstantiatePrefab(); });
+ }
+
+ menu->addSeparator();
+
bool itemWasShown = false;
// Edit/Save Prefab
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h b/Code/Framework/AzToolsFramework/AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h
index 53dab661ce..276982d46d 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h
@@ -138,7 +138,7 @@ namespace UnitTest
if (!GetApplication())
{
// Create & Start a new ToolsApplication if there's no existing one
- m_app = AZStd::make_unique("ToolsApplication");
+ m_app = CreateTestApplication();
m_app->Start(AzFramework::Application::Descriptor());
}
@@ -216,6 +216,12 @@ namespace UnitTest
TestEditorActions m_editorActions;
ToolsApplicationMessageHandler m_messageHandler; // used to suppress trace messages in test output
+ // Override this if your test fixture needs to use a custom TestApplication
+ virtual AZStd::unique_ptr CreateTestApplication()
+ {
+ return AZStd::make_unique("ToolsApplication");
+ }
+
private:
AZStd::unique_ptr m_app;
};
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.h
index ee95412376..8e91dc945d 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.h
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.h
@@ -18,6 +18,7 @@
#include
#include
#include
+#include
#include
#include
@@ -304,4 +305,24 @@ namespace AzToolsFramework
return entityContextId;
}
+
+ //! Maps a mouse interaction event to a ClickDetector event.
+ //! @note Function only cares about up or down events, all other events are mapped to Nil (ignored).
+ inline AzFramework::ClickDetector::ClickEvent ClickDetectorEventFromViewportInteraction(
+ const ViewportInteraction::MouseInteractionEvent& mouseInteraction)
+ {
+ if (mouseInteraction.m_mouseInteraction.m_mouseButtons.Left())
+ {
+ if (mouseInteraction.m_mouseEvent == ViewportInteraction::MouseEvent::Down)
+ {
+ return AzFramework::ClickDetector::ClickEvent::Down;
+ }
+
+ if (mouseInteraction.m_mouseEvent == ViewportInteraction::MouseEvent::Up)
+ {
+ return AzFramework::ClickDetector::ClickEvent::Up;
+ }
+ }
+ return AzFramework::ClickDetector::ClickEvent::Nil;
+ }
} // namespace AzToolsFramework
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorBoxSelect.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorBoxSelect.cpp
index 2e467caa4c..531cffb561 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorBoxSelect.cpp
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorBoxSelect.cpp
@@ -14,6 +14,7 @@
#include
#include
+#include
#include
@@ -27,8 +28,11 @@ namespace AzToolsFramework
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
- if (mouseInteraction.m_mouseInteraction.m_mouseButtons.Left() &&
- mouseInteraction.m_mouseEvent == ViewportInteraction::MouseEvent::Down)
+ m_cursorState.SetCurrentPosition(mouseInteraction.m_mouseInteraction.m_mousePick.m_screenCoordinates);
+
+ const auto selectClickEvent = ClickDetectorEventFromViewportInteraction(mouseInteraction);
+ const auto clickOutcome = m_clickDetector.DetectClick(selectClickEvent, m_cursorState.CursorDelta());
+ if (clickOutcome == AzFramework::ClickDetector::ClickOutcome::Move)
{
if (m_leftMouseDown)
{
@@ -58,8 +62,7 @@ namespace AzToolsFramework
}
}
- if (mouseInteraction.m_mouseInteraction.m_mouseButtons.Left() &&
- mouseInteraction.m_mouseEvent == ViewportInteraction::MouseEvent::Up)
+ if (clickOutcome == AzFramework::ClickDetector::ClickOutcome::Release)
{
if (m_leftMouseUp)
{
@@ -77,6 +80,8 @@ namespace AzToolsFramework
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
+ m_cursorState.Update();
+
if (m_boxSelectRegion)
{
debugDisplay.DepthTestOff();
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorBoxSelect.h b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorBoxSelect.h
index 7f50b16325..c115220755 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorBoxSelect.h
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorBoxSelect.h
@@ -14,6 +14,8 @@
#include
#include
+#include
+#include
#include
#include
@@ -26,49 +28,49 @@ namespace AzFramework
namespace AzToolsFramework
{
- /// Utility to provide box select (click and drag) support for viewport types.
- /// Users can override the mouse event callbacks and display scene function to customize behavior.
+ //! Utility to provide box select (click and drag) support for viewport types.
+ //! Users can override the mouse event callbacks and display scene function to customize behavior.
class EditorBoxSelect
{
public:
EditorBoxSelect() = default;
- /// Return if a box select action is currently taking place.
+ //! Return if a box select action is currently taking place.
bool Active() const { return m_boxSelectRegion.has_value(); }
- /// Update the box select for various mouse events.
- /// Call HandleMouseInteraction from type/system implementing MouseViewportRequests interface.
+ //! Update the box select for various mouse events.
+ //! Call HandleMouseInteraction from type/system implementing MouseViewportRequests interface.
void HandleMouseInteraction(
const ViewportInteraction::MouseInteractionEvent& mouseInteraction);
- /// Responsible for drawing the 2d box representing the selection in screen space.
+ //! Responsible for drawing the 2d box representing the selection in screen space.
void Display2d(const AzFramework::ViewportInfo& viewportInfo, AzFramework::DebugDisplayRequests& debugDisplay);
- /// Custom drawing behavior to happen during a box select.
+ //! Custom drawing behavior to happen during a box select.
void DisplayScene(
const AzFramework::ViewportInfo& viewportInfo, AzFramework::DebugDisplayRequests& debugDisplay);
- /// Set the left mouse down callback.
+ //! Set the left mouse down callback.
void InstallLeftMouseDown(
const AZStd::function& leftMouseDown);
- /// Set the mouse move callback.
+ //! Set the mouse move callback.
void InstallMouseMove(
const AZStd::function& mouseMove);
- /// Set the left mouse up callback.
+ //! Set the left mouse up callback.
void InstallLeftMouseUp(
const AZStd::function& leftMouseUp);
- /// Set the display scene callback.
+ //! Set the display scene callback.
void InstallDisplayScene(
const AZStd::function& displayScene);
- /// Return the box select region.
- /// If a box selection is being made, return the current rectangle representing the area.
- /// If there is currently no active box select, then the Maybe type will be empty (there will be no region/area).
+ //! Return the box select region.
+ //! If a box selection is being made, return the current rectangle representing the area.
+ //! If there is currently no active box select, then the Maybe type will be empty (there will be no region/area).
const AZStd::optional& BoxRegion() const { return m_boxSelectRegion; }
- /// Return the active modifiers from the previous frame.
+ //! Return the active modifiers from the previous frame.
ViewportInteraction::KeyboardModifiers PreviousModifiers() const { return m_previousModifiers; }
private:
@@ -79,7 +81,9 @@ namespace AzToolsFramework
AZStd::function m_displayScene;
- AZStd::optional m_boxSelectRegion; ///< Maybe/optional value to store box select region while active.
- ViewportInteraction::KeyboardModifiers m_previousModifiers; ///< Modifier keys active on the previous frame.
+ AZStd::optional m_boxSelectRegion; //!< Maybe/optional value to store box select region while active.
+ ViewportInteraction::KeyboardModifiers m_previousModifiers; //!< Modifier keys active on the previous frame.
+ AzFramework::ClickDetector m_clickDetector; //!< Utility type to detect if a mouse click or move has occurred.
+ AzFramework::CursorState m_cursorState; //!< Utility type to track the current cursor position (and movement/delta).
};
} // namespace AzToolsFramework
diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp
index 6e49f7c601..91644dc6ac 100644
--- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp
+++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp
@@ -1782,22 +1782,7 @@ namespace AzToolsFramework
m_cachedEntityIdUnderCursor = m_editorHelpers->HandleMouseInteraction(cameraState, mouseInteraction);
- const AzFramework::ClickDetector::ClickEvent selectClickEvent = [&mouseInteraction] {
- if (mouseInteraction.m_mouseInteraction.m_mouseButtons.Left())
- {
- if (mouseInteraction.m_mouseEvent == ViewportInteraction::MouseEvent::Down)
- {
- return AzFramework::ClickDetector::ClickEvent::Down;
- }
-
- if (mouseInteraction.m_mouseEvent == ViewportInteraction::MouseEvent::Up)
- {
- return AzFramework::ClickDetector::ClickEvent::Up;
- }
- }
- return AzFramework::ClickDetector::ClickEvent::Nil;
- }();
-
+ const auto selectClickEvent = ClickDetectorEventFromViewportInteraction(mouseInteraction);
m_cursorState.SetCurrentPosition(mouseInteraction.m_mouseInteraction.m_mousePick.m_screenCoordinates);
const auto clickOutcome = m_clickDetector.DetectClick(selectClickEvent, m_cursorState.CursorDelta());
diff --git a/Code/Framework/AzToolsFramework/Tests/Prefab/PrefabDuplicateTests.cpp b/Code/Framework/AzToolsFramework/Tests/Prefab/PrefabDuplicateTests.cpp
new file mode 100644
index 0000000000..514942166f
--- /dev/null
+++ b/Code/Framework/AzToolsFramework/Tests/Prefab/PrefabDuplicateTests.cpp
@@ -0,0 +1,129 @@
+/*
+* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
+* its licensors.
+*
+* For complete copyright and license terms please see the LICENSE at the root of this
+* distribution (the "License"). All use of this software is governed by the License,
+* or, if provided, by the license below or the license accompanying this file. Do not
+* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+*
+*/
+
+#include
+
+#include
+#include
+#include
+
+namespace UnitTest
+{
+ using PrefabDuplicateTest = PrefabTestFixture;
+
+ TEST_F(PrefabDuplicateTest, PrefabDuplicate_DuplicateSingleEntitySucceeds)
+ {
+ AZStd::string entityName("Same Name");
+ AZ::Entity* entity1 = CreateEntity(entityName.c_str());
+ entity1->Deactivate();
+ entity1->CreateComponent();
+ entity1->Activate();
+
+ AzToolsFramework::EditorEntityContextRequestBus::Broadcast(
+ &AzToolsFramework::EditorEntityContextRequests::HandleEntitiesAdded, AzToolsFramework::EntityList{ entity1 });
+ AZStd::unique_ptr newInstance = m_prefabSystemComponent->CreatePrefab(
+ { entity1 },
+ {},
+ PrefabMockFilePath);
+
+ // We've created a prefab with a single Entity, so there should only be one EntityAlias in our instance
+ EXPECT_EQ(newInstance->GetEntityAliases().size(), 1);
+
+ // Duplicate the Entity and trigger the UpdateTemplateInstancesInQueue so the changes get propagated
+ m_prefabPublicInterface->DuplicateEntitiesInInstance(AzToolsFramework::EntityIdList{ entity1->GetId() });
+ m_instanceUpdateExecutorInterface->UpdateTemplateInstancesInQueue();
+
+ // We duplicated a single Entity, so there should now be two EntityAliases
+ EXPECT_EQ(newInstance->GetEntityAliases().size(), 2);
+
+ newInstance->GetConstEntities([&](const AZ::Entity& entity)
+ {
+ // Both of the entities should have the same name
+ EXPECT_EQ(entity.GetName(), entityName);
+
+ // Both of the entities should have the PrefabTestComponent we added
+ auto testComponent = entity.FindComponent();
+ EXPECT_NE(nullptr, testComponent);
+
+ return true;
+ });
+ }
+
+ TEST_F(PrefabDuplicateTest, PrefabDuplicate_DuplicateMultipleEntitiesAndFixesReferences)
+ {
+ AZ::Entity* parentEntity = CreateEntity("Parent Entity");
+
+ AZ::Entity* childEntity = CreateEntity("Child Entity");
+ childEntity->Deactivate();
+ auto newComponent = childEntity->CreateComponent();
+ childEntity->Activate();
+
+ // Set the EntityId reference property on our PrefabTestComponent so we can
+ // verify that arbitrary EntityId's are fixed up properly
+ newComponent->m_entityIdProperty = parentEntity->GetId();
+
+ AzToolsFramework::EditorEntityContextRequestBus::Broadcast(
+ &AzToolsFramework::EditorEntityContextRequests::HandleEntitiesAdded, AzToolsFramework::EntityList{ parentEntity, childEntity });
+
+ AZStd::unique_ptr newInstance = m_prefabSystemComponent->CreatePrefab(
+ { parentEntity, childEntity },
+ {},
+ PrefabMockFilePath);
+
+ // We've created a prefab with two entities, so there should be two EntityAliases in our instance
+ EXPECT_EQ(newInstance->GetEntityAliases().size(), 2);
+
+ // Duplicate the entities and trigger the UpdateTemplateInstancesInQueue so the changes get propagated
+ m_prefabPublicInterface->DuplicateEntitiesInInstance(AzToolsFramework::EntityIdList{ parentEntity->GetId(), childEntity->GetId() });
+ m_instanceUpdateExecutorInterface->UpdateTemplateInstancesInQueue();
+
+ // We duplicated two entities, so there should now be four EntityAliases
+ EXPECT_EQ(newInstance->GetEntityAliases().size(), 4);
+
+ AzToolsFramework::EntityIdList parentEntityIds;
+ newInstance->GetConstEntities([&](const AZ::Entity& entity)
+ {
+ // Gather the parent EntityIds by tracking which entities don't have a PrefabTestComponent
+ auto testComponent = entity.FindComponent();
+ if (!testComponent)
+ {
+ parentEntityIds.push_back(entity.GetId());
+ }
+
+ return true;
+ });
+
+ // There should only be two parents
+ EXPECT_EQ(parentEntityIds.size(), 2);
+
+ // Verify that the EntityId reference on the PrefabTestComponent on the children correspond
+ // to unique entities, which will verify that the EntityIds are fixed up on duplicate
+ newInstance->GetConstEntities([&](const AZ::Entity& entity)
+ {
+ // Only the child entities have a PrefabTestComponent
+ auto testComponent = entity.FindComponent();
+ if (testComponent)
+ {
+ auto it = AZStd::find(parentEntityIds.begin(), parentEntityIds.end(), testComponent->m_entityIdProperty);
+ EXPECT_NE(it, parentEntityIds.end());
+
+ // Erase when we find it so that the matches will be unique
+ parentEntityIds.erase(it);
+ }
+
+ return true;
+ });
+
+ // Verify we matched each of the parent EntityIds
+ EXPECT_EQ(parentEntityIds.size(), 0);
+ }
+}
diff --git a/Code/Framework/AzToolsFramework/Tests/Prefab/PrefabTestFixture.cpp b/Code/Framework/AzToolsFramework/Tests/Prefab/PrefabTestFixture.cpp
index 3a8d9cc7eb..ace2356732 100644
--- a/Code/Framework/AzToolsFramework/Tests/Prefab/PrefabTestFixture.cpp
+++ b/Code/Framework/AzToolsFramework/Tests/Prefab/PrefabTestFixture.cpp
@@ -20,6 +20,17 @@
namespace UnitTest
{
+ PrefabTestToolsApplication::PrefabTestToolsApplication(AZStd::string appName)
+ : ToolsTestApplication(AZStd::move(appName))
+ {
+ }
+
+ bool PrefabTestToolsApplication::IsPrefabSystemEnabled() const
+ {
+ // Make sure our prefab tests always run with prefabs enabled
+ return true;
+ }
+
void PrefabTestFixture::SetUpEditorFixtureImpl()
{
// Acquire the system entity
@@ -32,6 +43,9 @@ namespace UnitTest
m_prefabLoaderInterface = AZ::Interface::Get();
EXPECT_TRUE(m_prefabLoaderInterface);
+ m_prefabPublicInterface = AZ::Interface::Get();
+ EXPECT_TRUE(m_prefabPublicInterface);
+
m_instanceUpdateExecutorInterface = AZ::Interface::Get();
EXPECT_TRUE(m_instanceUpdateExecutorInterface);
@@ -41,6 +55,11 @@ namespace UnitTest
GetApplication()->RegisterComponentDescriptor(PrefabTestComponent::CreateDescriptor());
}
+ AZStd::unique_ptr PrefabTestFixture::CreateTestApplication()
+ {
+ return AZStd::make_unique("PrefabTestApplication");
+ }
+
AZ::Entity* PrefabTestFixture::CreateEntity(const char* entityName, const bool shouldActivate)
{
// Circumvent the EntityContext system and generate a new entity with a transformcomponent
diff --git a/Code/Framework/AzToolsFramework/Tests/Prefab/PrefabTestFixture.h b/Code/Framework/AzToolsFramework/Tests/Prefab/PrefabTestFixture.h
index af90309867..ee471cf192 100644
--- a/Code/Framework/AzToolsFramework/Tests/Prefab/PrefabTestFixture.h
+++ b/Code/Framework/AzToolsFramework/Tests/Prefab/PrefabTestFixture.h
@@ -31,6 +31,16 @@ namespace UnitTest
using namespace AzToolsFramework::Prefab;
using namespace PrefabTestUtils;
+ class PrefabTestToolsApplication
+ : public ToolsTestApplication
+ {
+ public:
+ PrefabTestToolsApplication(AZStd::string appName);
+
+ // Make sure our prefab tests always run with prefabs enabled
+ bool IsPrefabSystemEnabled() const override;
+ };
+
class PrefabTestFixture
: public ToolsApplicationFixture,
public UnitTest::TraceBusRedirector
@@ -45,6 +55,8 @@ namespace UnitTest
void SetUpEditorFixtureImpl() override;
+ AZStd::unique_ptr CreateTestApplication() override;
+
AZ::Entity* CreateEntity(const char* entityName, const bool shouldActivate = true);
void CompareInstances(const Instance& instanceA, const Instance& instanceB, bool shouldCompareLinkIds = true,
@@ -57,6 +69,7 @@ namespace UnitTest
PrefabSystemComponent* m_prefabSystemComponent = nullptr;
PrefabLoaderInterface* m_prefabLoaderInterface = nullptr;
+ PrefabPublicInterface* m_prefabPublicInterface = nullptr;
InstanceUpdateExecutorInterface* m_instanceUpdateExecutorInterface = nullptr;
InstanceToTemplateInterface* m_instanceToTemplateInterface = nullptr;
};
diff --git a/Code/Framework/AzToolsFramework/Tests/aztoolsframeworktests_files.cmake b/Code/Framework/AzToolsFramework/Tests/aztoolsframeworktests_files.cmake
index e54aa187e4..cd3796a64e 100644
--- a/Code/Framework/AzToolsFramework/Tests/aztoolsframeworktests_files.cmake
+++ b/Code/Framework/AzToolsFramework/Tests/aztoolsframeworktests_files.cmake
@@ -54,6 +54,7 @@ set(FILES
Prefab/Spawnable/SpawnableMetaDataTests.cpp
Prefab/MockPrefabFileIOActionValidator.cpp
Prefab/MockPrefabFileIOActionValidator.h
+ Prefab/PrefabDuplicateTests.cpp
Prefab/PrefabEntityAliasTests.cpp
Prefab/PrefabInstanceToTemplatePropagatorTests.cpp
Prefab/PrefabInstantiateTests.cpp
diff --git a/Code/Framework/Tests/CameraInputTests.cpp b/Code/Framework/Tests/CameraInputTests.cpp
new file mode 100644
index 0000000000..3fc826975e
--- /dev/null
+++ b/Code/Framework/Tests/CameraInputTests.cpp
@@ -0,0 +1,87 @@
+/*
+ * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
+ * its licensors.
+ *
+ * For complete copyright and license terms please see the LICENSE at the root of this
+ * distribution (the "License"). All use of this software is governed by the License,
+ * or, if provided, by the license below or the license accompanying this file. Do not
+ * remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *
+ */
+
+#include
+#include
+#include
+#include
+#include
+
+namespace UnitTest
+{
+ class CameraInputFixture : public AllocatorsTestFixture
+ {
+ public:
+ AzFramework::Camera m_camera;
+ AzFramework::Camera m_targetCamera;
+ AZStd::shared_ptr m_cameraSystem;
+
+ bool HandleEventAndUpdate(const AzFramework::InputEvent& event)
+ {
+ constexpr float deltaTime = 0.01666f; // 60fps
+ const bool consumed = m_cameraSystem->HandleEvents(event);
+ m_camera = m_cameraSystem->StepCamera(m_targetCamera, deltaTime);
+ return consumed;
+ }
+
+ void SetUp() override
+ {
+ AllocatorsTestFixture::SetUp();
+
+ AzFramework::ReloadCameraKeyBindings();
+
+ m_cameraSystem = AZStd::make_shared();
+
+ auto firstPersonRotateCamera = AZStd::make_shared(AzFramework::InputDeviceMouse::Button::Right);
+ auto firstPersonTranslateCamera = AZStd::make_shared(AzFramework::LookTranslation);
+
+ auto orbitCamera = AZStd::make_shared();
+ auto orbitRotateCamera = AZStd::make_shared(AzFramework::InputDeviceMouse::Button::Left);
+ auto orbitTranslateCamera = AZStd::make_shared(AzFramework::OrbitTranslation);
+
+ orbitCamera->m_orbitCameras.AddCamera(orbitRotateCamera);
+ orbitCamera->m_orbitCameras.AddCamera(orbitTranslateCamera);
+
+ m_cameraSystem->m_cameras.AddCamera(firstPersonRotateCamera);
+ m_cameraSystem->m_cameras.AddCamera(firstPersonTranslateCamera);
+ m_cameraSystem->m_cameras.AddCamera(orbitCamera);
+ }
+
+ void TearDown() override
+ {
+ m_cameraSystem->m_cameras.Clear();
+ m_cameraSystem.reset();
+
+ AllocatorsTestFixture::TearDown();
+ }
+ };
+
+ TEST_F(CameraInputFixture, BeginEndOrbitCameraConsumesCorrectEvents)
+ {
+ // begin orbit camera
+ const bool consumed1 = HandleEventAndUpdate(
+ AzFramework::DiscreteInputEvent{AzFramework::InputDeviceKeyboard::Key::ModifierAltL, AzFramework::InputChannel::State::Began});
+ // begin listening for orbit rotate (click detector) - event is not consumed
+ const bool consumed2 = HandleEventAndUpdate(
+ AzFramework::DiscreteInputEvent{AzFramework::InputDeviceMouse::Button::Left, AzFramework::InputChannel::State::Began});
+ // begin orbit rotate (mouse has moved sufficient distance to initiate)
+ const bool consumed3 = HandleEventAndUpdate(AzFramework::HorizontalMotionEvent{5});
+ // end orbit (mouse up) - event is not consumed
+ const bool consumed4 = HandleEventAndUpdate(
+ AzFramework::DiscreteInputEvent{AzFramework::InputDeviceMouse::Button::Left, AzFramework::InputChannel::State::Ended});
+
+ const auto allConsumed = AZStd::vector{consumed1, consumed2, consumed3, consumed4};
+
+ using ::testing::ElementsAre;
+ EXPECT_THAT(allConsumed, ElementsAre(true, false, true, false));
+ }
+} // namespace UnitTest
diff --git a/Code/Framework/Tests/ClickDetectorTests.cpp b/Code/Framework/Tests/ClickDetectorTests.cpp
index 7e6f9634c8..64f06ee66c 100644
--- a/Code/Framework/Tests/ClickDetectorTests.cpp
+++ b/Code/Framework/Tests/ClickDetectorTests.cpp
@@ -139,4 +139,21 @@ namespace UnitTest
EXPECT_THAT(secondaryDownOutcome, Eq(ClickDetector::ClickOutcome::Nil)); // ignored double click
EXPECT_THAT(secondaryUpOutcome, Eq(ClickDetector::ClickOutcome::Nil)); // click not registered
}
+
+ // if the click detector registers a mouse down event, but then all intermediate calls are ignored
+ // (another system may start intercepting events and swallowing them) then when we do receive a mouse
+ // up event we should ensure we take into account the current delta - if the delta is large, then the
+ // outcome will be release
+ TEST_F(ClickDetectorFixture, ClickIsNotRegisteredAfterIgnoringMouseMovesBeforeMouseUpWithLargeDelta)
+ {
+ using ::testing::Eq;
+
+ const ClickDetector::ClickOutcome downOutcome =
+ m_clickDetector.DetectClick(ClickDetector::ClickEvent::Down, ScreenVector(0, 0));
+ const ClickDetector::ClickOutcome upOutcome =
+ m_clickDetector.DetectClick(ClickDetector::ClickEvent::Up, ScreenVector(50, 50));
+
+ EXPECT_THAT(downOutcome, Eq(ClickDetector::ClickOutcome::Nil));
+ EXPECT_THAT(upOutcome, Eq(ClickDetector::ClickOutcome::Release));
+ }
} // namespace UnitTest
diff --git a/Code/Framework/Tests/frameworktests_files.cmake b/Code/Framework/Tests/frameworktests_files.cmake
index e249cf6e64..197bcc9fce 100644
--- a/Code/Framework/Tests/frameworktests_files.cmake
+++ b/Code/Framework/Tests/frameworktests_files.cmake
@@ -17,6 +17,7 @@ set(FILES
BinToTextEncode.cpp
ComponentAddRemove.cpp
ComponentAdapterTests.cpp
+ CameraInputTests.cpp
ClickDetectorTests.cpp
CursorStateTests.cpp
EntityContext.cpp
diff --git a/Code/Sandbox/Editor/EditorViewportWidget.cpp b/Code/Sandbox/Editor/EditorViewportWidget.cpp
index 85c40f2092..f72801a4d3 100644
--- a/Code/Sandbox/Editor/EditorViewportWidget.cpp
+++ b/Code/Sandbox/Editor/EditorViewportWidget.cpp
@@ -457,15 +457,6 @@ void EditorViewportWidget::Update()
return;
}
- static bool sentOnWindowCreated = false;
- if (!sentOnWindowCreated && windowHandle()->isActive())
- {
- sentOnWindowCreated = true;
- AzFramework::WindowSystemNotificationBus::Broadcast(
- &AzFramework::WindowSystemNotificationBus::Handler::OnWindowCreated,
- reinterpret_cast(winId()));
- }
-
m_updatingCameraPosition = true;
if (!ed_useNewCameraSystem)
{
diff --git a/Code/Sandbox/Editor/ModernViewportCameraController.cpp b/Code/Sandbox/Editor/ModernViewportCameraController.cpp
index af161af493..0779542878 100644
--- a/Code/Sandbox/Editor/ModernViewportCameraController.cpp
+++ b/Code/Sandbox/Editor/ModernViewportCameraController.cpp
@@ -97,17 +97,34 @@ namespace SandboxEditor
AzFramework::ViewportDebugDisplayEventBus::Handler::BusDisconnect();
}
+ // should the camera system respond to this particular event
+ static bool ShouldHandle(const AzFramework::ViewportControllerPriority priority, const bool exclusive)
+ {
+ // ModernViewportCameraControllerInstance receives events at all priorities, it should only respond
+ // to normal priority events if it is not in 'exclusive' mode and when in 'exclusive' mode it should
+ // only respond to the highest priority events
+ return !exclusive && priority == AzFramework::ViewportControllerPriority::Normal ||
+ exclusive && priority == AzFramework::ViewportControllerPriority::Highest;
+ }
+
bool ModernViewportCameraControllerInstance::HandleInputChannelEvent(const AzFramework::ViewportControllerInputEvent& event)
{
- AzFramework::WindowSize windowSize;
- AzFramework::WindowRequestBus::EventResult(
- windowSize, event.m_windowHandle, &AzFramework::WindowRequestBus::Events::GetClientAreaSize);
+ if (ShouldHandle(event.m_priority, m_cameraSystem.m_cameras.Exclusive()))
+ {
+ return m_cameraSystem.HandleEvents(AzFramework::BuildInputEvent(event.m_inputChannel));
+ }
- return m_cameraSystem.HandleEvents(AzFramework::BuildInputEvent(event.m_inputChannel, windowSize));
+ return false;
}
void ModernViewportCameraControllerInstance::UpdateViewport(const AzFramework::ViewportControllerUpdateEvent& event)
{
+ // only update for a single priority (normal is the default)
+ if (event.m_priority != AzFramework::ViewportControllerPriority::Normal)
+ {
+ return;
+ }
+
if (auto viewportContext = RetrieveViewportContext(GetViewportId()))
{
m_updatingTransform = true;
diff --git a/Code/Sandbox/Editor/ModernViewportCameraController.h b/Code/Sandbox/Editor/ModernViewportCameraController.h
index 066c8efaa8..39e3c9cbb3 100644
--- a/Code/Sandbox/Editor/ModernViewportCameraController.h
+++ b/Code/Sandbox/Editor/ModernViewportCameraController.h
@@ -22,7 +22,9 @@
namespace SandboxEditor
{
class ModernViewportCameraControllerInstance;
- class ModernViewportCameraController : public AzFramework::MultiViewportController
+ class ModernViewportCameraController
+ : public AzFramework::MultiViewportController<
+ ModernViewportCameraControllerInstance, AzFramework::ViewportControllerPriority::DispatchToAllPriorities>
{
public:
using CameraListBuilder = AZStd::function;
diff --git a/Code/Sandbox/Plugins/ComponentEntityEditorPlugin/SandboxIntegration.cpp b/Code/Sandbox/Plugins/ComponentEntityEditorPlugin/SandboxIntegration.cpp
index 027487a435..d36c20c56a 100644
--- a/Code/Sandbox/Plugins/ComponentEntityEditorPlugin/SandboxIntegration.cpp
+++ b/Code/Sandbox/Plugins/ComponentEntityEditorPlugin/SandboxIntegration.cpp
@@ -35,6 +35,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -192,6 +193,9 @@ void SandboxIntegrationManager::Setup()
(m_prefabIntegrationInterface != nullptr),
"SandboxIntegrationManager requires a PrefabIntegrationInterface instance to be present on Setup().");
+ m_editorEntityAPI = AZ::Interface::Get();
+ AZ_Assert(m_editorEntityAPI, "SandboxIntegrationManager requires an EditorEntityAPI instance to be present on Setup().");
+
AzToolsFramework::Layers::EditorLayerComponentNotificationBus::Handler::BusConnect();
}
@@ -1215,9 +1219,20 @@ void SandboxIntegrationManager::CloneSelection(bool& handled)
if (!duplicationSet.empty())
{
- AZStd::unordered_set clonedEntities;
- handled = AzToolsFramework::CloneInstantiatedEntities(duplicationSet, clonedEntities);
- m_unsavedEntities.insert(clonedEntities.begin(), clonedEntities.end());
+ bool prefabSystemEnabled = false;
+ AzFramework::ApplicationRequests::Bus::BroadcastResult(prefabSystemEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
+
+ if (prefabSystemEnabled)
+ {
+ m_editorEntityAPI->DuplicateSelected();
+ handled = true;
+ }
+ else
+ {
+ AZStd::unordered_set clonedEntities;
+ handled = AzToolsFramework::CloneInstantiatedEntities(duplicationSet, clonedEntities);
+ m_unsavedEntities.insert(clonedEntities.begin(), clonedEntities.end());
+ }
}
else
{
diff --git a/Code/Sandbox/Plugins/ComponentEntityEditorPlugin/SandboxIntegration.h b/Code/Sandbox/Plugins/ComponentEntityEditorPlugin/SandboxIntegration.h
index 14f52591a4..528b93e44e 100644
--- a/Code/Sandbox/Plugins/ComponentEntityEditorPlugin/SandboxIntegration.h
+++ b/Code/Sandbox/Plugins/ComponentEntityEditorPlugin/SandboxIntegration.h
@@ -77,6 +77,7 @@ class CHyperGraph;
namespace AzToolsFramework
{
+ class EditorEntityAPI;
class EditorEntityUiInterface;
namespace AssetBrowser
@@ -371,6 +372,7 @@ private:
AzToolsFramework::EditorEntityUiInterface* m_editorEntityUiInterface = nullptr;
AzToolsFramework::Prefab::PrefabIntegrationInterface* m_prefabIntegrationInterface = nullptr;
+ AzToolsFramework::EditorEntityAPI* m_editorEntityAPI = nullptr;
// Overrides UI styling and behavior for Layer Entities
AzToolsFramework::LayerUiHandler m_layerUiOverrideHandler;
diff --git a/Code/Sandbox/Plugins/ComponentEntityEditorPlugin/UI/Outliner/OutlinerListModel.cpp b/Code/Sandbox/Plugins/ComponentEntityEditorPlugin/UI/Outliner/OutlinerListModel.cpp
index ac9b92adce..11c279c7f7 100644
--- a/Code/Sandbox/Plugins/ComponentEntityEditorPlugin/UI/Outliner/OutlinerListModel.cpp
+++ b/Code/Sandbox/Plugins/ComponentEntityEditorPlugin/UI/Outliner/OutlinerListModel.cpp
@@ -65,6 +65,7 @@
#include "OutlinerTreeView.hxx"
#include "Include/ICommandManager.h"
#include "Include/IObjectManager.h"
+#include "OutlinerCacheBus.h"
#include
#include
@@ -1538,6 +1539,16 @@ void OutlinerListModel::OnEntityInfoUpdatedName(AZ::EntityId entityId, const AZS
{
(void)name;
QueueEntityUpdate(entityId);
+
+ bool isSelected = false;
+ AzToolsFramework::ToolsApplicationRequests::Bus::BroadcastResult(
+ isSelected, &AzToolsFramework::ToolsApplicationRequests::IsSelected, entityId);
+
+ if (isSelected)
+ {
+ // Ask the system to scroll to the entity in case it is off screen after the rename
+ OutlinerModelNotificationBus::Broadcast(&OutlinerModelNotifications::QueueScrollToNewContent, entityId);
+ }
}
void OutlinerListModel::OnEntityInfoUpdatedUnsavedChanges(AZ::EntityId entityId)
diff --git a/Code/Tools/ProjectManager/Resources/Add.svg b/Code/Tools/ProjectManager/Resources/Add.svg
index d2b9b2e0a6..4fa30932fb 100644
--- a/Code/Tools/ProjectManager/Resources/Add.svg
+++ b/Code/Tools/ProjectManager/Resources/Add.svg
@@ -1,4 +1,3 @@
diff --git a/Code/Tools/ProjectManager/Resources/ArrowDownLine.svg b/Code/Tools/ProjectManager/Resources/ArrowDownLine.svg
new file mode 100644
index 0000000000..8418431f11
--- /dev/null
+++ b/Code/Tools/ProjectManager/Resources/ArrowDownLine.svg
@@ -0,0 +1,3 @@
+
diff --git a/Code/Tools/ProjectManager/Resources/ArrowUpLine.svg b/Code/Tools/ProjectManager/Resources/ArrowUpLine.svg
new file mode 100644
index 0000000000..d7f26fdad5
--- /dev/null
+++ b/Code/Tools/ProjectManager/Resources/ArrowUpLine.svg
@@ -0,0 +1,3 @@
+
diff --git a/Code/Tools/ProjectManager/Resources/ProjectManager.qrc b/Code/Tools/ProjectManager/Resources/ProjectManager.qrc
new file mode 100644
index 0000000000..1ffd7cf3e7
--- /dev/null
+++ b/Code/Tools/ProjectManager/Resources/ProjectManager.qrc
@@ -0,0 +1,16 @@
+
+
+ ProjectManager.qss
+
+
+ Add.svg
+ Select_Folder.svg
+ o3de_editor.ico
+ Windows.svg
+ Android.svg
+ iOS.svg
+ Linux.svg
+ macOS.svg
+ Backgrounds/FirstTimeBackgroundImage.jpg
+
+
diff --git a/Code/Tools/ProjectManager/Resources/ProjectManager.qss b/Code/Tools/ProjectManager/Resources/ProjectManager.qss
index e69de29bb2..16ef48ee7c 100644
--- a/Code/Tools/ProjectManager/Resources/ProjectManager.qss
+++ b/Code/Tools/ProjectManager/Resources/ProjectManager.qss
@@ -0,0 +1,73 @@
+/************** General (MainWindow) **************/
+QMainWindow {
+ background-color: #333333;
+}
+
+
+QPushButton:focus {
+ outline: none;
+ border:1px solid #1e70eb;
+}
+
+/************** General (Forms) **************/
+
+#formLineEditWidget,
+#formBrowseEditWidget {
+ max-width: 780px;
+}
+
+#formFrame {
+ max-width: 720px;
+ background-color: #444444;
+ border:1px solid #dddddd;
+ border-radius: 4px;
+ padding: 0px 10px 2px 6px;
+ margin-top:10px;
+ margin-left:30px;
+}
+
+#formFrame[Focus="true"] {
+ border:1px solid #1e70eb;
+}
+
+#formFrame[Valid="false"] {
+ border:1px solid red;
+}
+
+#formFrame QLabel {
+ font-size: 13px;
+ color: #cccccc;
+}
+
+#formFrame QPushButton {
+ background-color: transparent;
+ background:transparent url(:/Select_Folder.svg) no-repeat center;
+ qproperty-flat: true;
+}
+
+#formFrame QPushButton:focus {
+ border:none;
+}
+
+#formFrame QLineEdit {
+ background-color: rgba(0,0,0,0);
+ font-size: 18px;
+ color: #ffffff;
+ border:0;
+ line-height: 30px;
+ height: 1em;
+ padding-top: -4px;
+}
+
+#formErrorLabel {
+ color: #ec3030;
+ font-size: 14px;
+ margin-left: 40px;
+}
+
+#formTitleLabel {
+ font-size:21px;
+ color:#ffffff;
+ margin: 10px 0 10px 30px;
+}
+
diff --git a/Code/Tools/ProjectManager/Resources/Select_Folder.svg b/Code/Tools/ProjectManager/Resources/Select_Folder.svg
index 72dcd3385e..df20a06e76 100644
--- a/Code/Tools/ProjectManager/Resources/Select_Folder.svg
+++ b/Code/Tools/ProjectManager/Resources/Select_Folder.svg
@@ -1,4 +1,3 @@
diff --git a/Code/Tools/ProjectManager/Source/EngineInfo.cpp b/Code/Tools/ProjectManager/Source/EngineInfo.cpp
index 8043a498ff..934d3af9d8 100644
--- a/Code/Tools/ProjectManager/Source/EngineInfo.cpp
+++ b/Code/Tools/ProjectManager/Source/EngineInfo.cpp
@@ -14,8 +14,16 @@
namespace O3DE::ProjectManager
{
- EngineInfo::EngineInfo(const QString& path)
+ EngineInfo::EngineInfo(const QString& path, const QString& name, const QString& version, const QString& thirdPartyPath)
: m_path(path)
+ , m_name(name)
+ , m_version(version)
+ , m_thirdPartyPath(thirdPartyPath)
{
}
+
+ bool EngineInfo::IsValid() const
+ {
+ return !m_path.isEmpty();
+ }
} // namespace O3DE::ProjectManager
diff --git a/Code/Tools/ProjectManager/Source/EngineInfo.h b/Code/Tools/ProjectManager/Source/EngineInfo.h
index ada6e73a15..262c42e56b 100644
--- a/Code/Tools/ProjectManager/Source/EngineInfo.h
+++ b/Code/Tools/ProjectManager/Source/EngineInfo.h
@@ -22,8 +22,20 @@ namespace O3DE::ProjectManager
{
public:
EngineInfo() = default;
- EngineInfo(const QString& path);
+ EngineInfo(const QString& path, const QString& name, const QString& version, const QString& thirdPartyPath);
+ // from engine.json
+ QString m_version;
+ QString m_name;
+ QString m_thirdPartyPath;
+
+ // from o3de_manifest.json
QString m_path;
+ QString m_defaultProjectsFolder;
+ QString m_defaultGemsFolder;
+ QString m_defaultTemplatesFolder;
+ QString m_defaultRestrictedFolder;
+
+ bool IsValid() const;
};
} // namespace O3DE::ProjectManager
diff --git a/Code/Tools/ProjectManager/Source/EngineSettingsScreen.cpp b/Code/Tools/ProjectManager/Source/EngineSettingsScreen.cpp
index 1adab41c0e..f51996bd65 100644
--- a/Code/Tools/ProjectManager/Source/EngineSettingsScreen.cpp
+++ b/Code/Tools/ProjectManager/Source/EngineSettingsScreen.cpp
@@ -11,20 +11,99 @@
*/
#include
-
-#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
namespace O3DE::ProjectManager
{
EngineSettingsScreen::EngineSettingsScreen(QWidget* parent)
: ScreenWidget(parent)
- , m_ui(new Ui::EngineSettingsClass())
{
- m_ui->setupUi(this);
+ auto* layout = new QVBoxLayout(this);
+ layout->setAlignment(Qt::AlignTop);
+
+ setObjectName("engineSettingsScreen");
+
+ EngineInfo engineInfo;
+
+ AZ::Outcome engineInfoResult = PythonBindingsInterface::Get()->GetEngineInfo();
+ if (engineInfoResult.IsSuccess())
+ {
+ engineInfo = engineInfoResult.GetValue();
+ }
+
+ QLabel* formTitleLabel = new QLabel(tr("O3DE Settings"), this);
+ formTitleLabel->setObjectName("formTitleLabel");
+ layout->addWidget(formTitleLabel);
+
+ m_engineVersion = new FormLineEditWidget(tr("Engine Version"), engineInfo.m_version, this);
+ m_engineVersion->lineEdit()->setReadOnly(true);
+ layout->addWidget(m_engineVersion);
+
+ m_thirdParty = new FormBrowseEditWidget(tr("3rd Party Software Folder"), engineInfo.m_thirdPartyPath, this);
+ m_thirdParty->lineEdit()->setValidator(new PathValidator(PathValidator::PathMode::ExistingFolder, this));
+ m_thirdParty->lineEdit()->setReadOnly(true);
+ m_thirdParty->setErrorLabelText(tr("Please provide a valid path to a folder that exists"));
+ connect(m_thirdParty->lineEdit(), &QLineEdit::textChanged, this, &EngineSettingsScreen::OnTextChanged);
+ layout->addWidget(m_thirdParty);
+
+ m_defaultProjects = new FormBrowseEditWidget(tr("Default Projects Folder"), engineInfo.m_defaultProjectsFolder, this);
+ m_defaultProjects->lineEdit()->setValidator(new PathValidator(PathValidator::PathMode::ExistingFolder, this));
+ m_defaultProjects->lineEdit()->setReadOnly(true);
+ m_defaultProjects->setErrorLabelText(tr("Please provide a valid path to a folder that exists"));
+ connect(m_defaultProjects->lineEdit(), &QLineEdit::textChanged, this, &EngineSettingsScreen::OnTextChanged);
+ layout->addWidget(m_defaultProjects);
+
+ m_defaultGems = new FormBrowseEditWidget(tr("Default Gems Folder"), engineInfo.m_defaultGemsFolder, this);
+ m_defaultGems->lineEdit()->setValidator(new PathValidator(PathValidator::PathMode::ExistingFolder, this));
+ m_defaultGems->lineEdit()->setReadOnly(true);
+ m_defaultGems->setErrorLabelText(tr("Please provide a valid path to a folder that exists"));
+ connect(m_defaultGems->lineEdit(), &QLineEdit::textChanged, this, &EngineSettingsScreen::OnTextChanged);
+ layout->addWidget(m_defaultGems);
+
+ m_defaultProjectTemplates = new FormBrowseEditWidget(tr("Default Project Templates Folder"), engineInfo.m_defaultTemplatesFolder, this);
+ m_defaultProjectTemplates->lineEdit()->setValidator(new PathValidator(PathValidator::PathMode::ExistingFolder, this));
+ m_defaultProjectTemplates->lineEdit()->setReadOnly(true);
+ m_defaultProjectTemplates->setErrorLabelText(tr("Please provide a valid path to a folder that exists"));
+ connect(m_defaultProjectTemplates->lineEdit(), &QLineEdit::textChanged, this, &EngineSettingsScreen::OnTextChanged);
+ layout->addWidget(m_defaultProjectTemplates);
+
+ setLayout(layout);
}
ProjectManagerScreen EngineSettingsScreen::GetScreenEnum()
{
return ProjectManagerScreen::EngineSettings;
}
+
+ void EngineSettingsScreen::OnTextChanged()
+ {
+ // save engine settings
+ auto engineInfoResult = PythonBindingsInterface::Get()->GetEngineInfo();
+ if (engineInfoResult.IsSuccess())
+ {
+ EngineInfo engineInfo;
+ engineInfo = engineInfoResult.GetValue();
+ engineInfo.m_thirdPartyPath = m_thirdParty->lineEdit()->text();
+ engineInfo.m_defaultProjectsFolder = m_defaultProjects->lineEdit()->text();
+ engineInfo.m_defaultGemsFolder = m_defaultGems->lineEdit()->text();
+ engineInfo.m_defaultTemplatesFolder = m_defaultProjectTemplates->lineEdit()->text();
+
+ bool result = PythonBindingsInterface::Get()->SetEngineInfo(engineInfo);
+ if (!result)
+ {
+ QMessageBox::critical(this, tr("Engine Settings"), tr("Failed to save engine settings."));
+ }
+ }
+ else
+ {
+ QMessageBox::critical(this, tr("Engine Settings"), tr("Failed to get engine settings."));
+ }
+ }
} // namespace O3DE::ProjectManager
diff --git a/Code/Tools/ProjectManager/Source/EngineSettingsScreen.h b/Code/Tools/ProjectManager/Source/EngineSettingsScreen.h
index 4baa3fb28c..0e91ec2d3b 100644
--- a/Code/Tools/ProjectManager/Source/EngineSettingsScreen.h
+++ b/Code/Tools/ProjectManager/Source/EngineSettingsScreen.h
@@ -15,13 +15,11 @@
#include
#endif
-namespace Ui
-{
- class EngineSettingsClass;
-}
-
namespace O3DE::ProjectManager
{
+ QT_FORWARD_DECLARE_CLASS(FormLineEditWidget)
+ QT_FORWARD_DECLARE_CLASS(FormBrowseEditWidget)
+
class EngineSettingsScreen
: public ScreenWidget
{
@@ -30,8 +28,15 @@ namespace O3DE::ProjectManager
~EngineSettingsScreen() = default;
ProjectManagerScreen GetScreenEnum() override;
+ protected slots:
+ void OnTextChanged();
+
private:
- QScopedPointer m_ui;
+ FormLineEditWidget* m_engineVersion;
+ FormBrowseEditWidget* m_thirdParty;
+ FormBrowseEditWidget* m_defaultProjects;
+ FormBrowseEditWidget* m_defaultGems;
+ FormBrowseEditWidget* m_defaultProjectTemplates;
};
} // namespace O3DE::ProjectManager
diff --git a/Code/Tools/ProjectManager/Source/EngineSettingsScreen.ui b/Code/Tools/ProjectManager/Source/EngineSettingsScreen.ui
deleted file mode 100644
index c8fda8bfd7..0000000000
--- a/Code/Tools/ProjectManager/Source/EngineSettingsScreen.ui
+++ /dev/null
@@ -1,82 +0,0 @@
-
-
- EngineSettingsClass
-
-
-
- 0
- 0
- 839
- 597
-
-
-
- Form
-
-
- -
-
-
- O3DE Settings
-
-
-
- -
-
-
- Engine Version
-
-
-
- -
-
-
- v1.01
-
-
-
- -
-
-
- 3rd Party Software Folder
-
-
-
- -
-
-
- -
-
-
- Restricted Folder
-
-
-
- -
-
-
- -
-
-
- Default Gems Folder
-
-
-
- -
-
-
- -
-
-
- Default Project Templates Folder
-
-
-
- -
-
-
-
-
-
-
-
diff --git a/Code/Tools/ProjectManager/Source/FirstTimeUseScreen.cpp b/Code/Tools/ProjectManager/Source/FirstTimeUseScreen.cpp
index 2c96078d43..a1be7e8ac9 100644
--- a/Code/Tools/ProjectManager/Source/FirstTimeUseScreen.cpp
+++ b/Code/Tools/ProjectManager/Source/FirstTimeUseScreen.cpp
@@ -49,11 +49,11 @@ namespace O3DE::ProjectManager
QHBoxLayout* buttonLayout = new QHBoxLayout();
buttonLayout->setSpacing(s_buttonSpacing);
- m_createProjectButton = CreateLargeBoxButton(QIcon(":/Resources/Add.svg"), tr("Create Project"), this);
+ m_createProjectButton = CreateLargeBoxButton(QIcon(":/Add.svg"), tr("Create Project"), this);
m_createProjectButton->setIconSize(QSize(s_iconSize, s_iconSize));
buttonLayout->addWidget(m_createProjectButton);
- m_addProjectButton = CreateLargeBoxButton(QIcon(":/Resources/Select_Folder.svg"), tr("Add a Project"), this);
+ m_addProjectButton = CreateLargeBoxButton(QIcon(":/Select_Folder.svg"), tr("Add a Project"), this);
m_addProjectButton->setIconSize(QSize(s_iconSize, s_iconSize));
buttonLayout->addWidget(m_addProjectButton);
@@ -66,7 +66,7 @@ namespace O3DE::ProjectManager
vLayout->addItem(verticalSpacer);
// Using border-image allows for scaling options background-image does not support
- setStyleSheet("O3DE--ProjectManager--ScreenWidget { border-image: url(:/Resources/Backgrounds/FirstTimeBackgroundImage.jpg) repeat repeat; }");
+ setStyleSheet("O3DE--ProjectManager--ScreenWidget { border-image: url(:/Backgrounds/FirstTimeBackgroundImage.jpg) repeat repeat; }");
connect(m_createProjectButton, &QPushButton::pressed, this, &FirstTimeUseScreen::HandleNewProjectButton);
connect(m_addProjectButton, &QPushButton::pressed, this, &FirstTimeUseScreen::HandleAddProjectButton);
diff --git a/Code/Tools/ProjectManager/Source/FormBrowseEditWidget.cpp b/Code/Tools/ProjectManager/Source/FormBrowseEditWidget.cpp
new file mode 100644
index 0000000000..c30d6a7b30
--- /dev/null
+++ b/Code/Tools/ProjectManager/Source/FormBrowseEditWidget.cpp
@@ -0,0 +1,49 @@
+/*
+* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
+* its licensors.
+*
+* For complete copyright and license terms please see the LICENSE at the root of this
+* distribution (the "License"). All use of this software is governed by the License,
+* or, if provided, by the license below or the license accompanying this file. Do not
+* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+*
+*/
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace O3DE::ProjectManager
+{
+ FormBrowseEditWidget::FormBrowseEditWidget(const QString& labelText, const QString& valueText, QWidget* parent)
+ : FormLineEditWidget(labelText, valueText, parent)
+ {
+ setObjectName("formBrowseEditWidget");
+
+ QPushButton* browseButton = new QPushButton(this);
+ connect(browseButton, &QPushButton::pressed, this, &FormBrowseEditWidget::HandleBrowseButton);
+ m_frameLayout->addWidget(browseButton);
+ }
+
+ void FormBrowseEditWidget::HandleBrowseButton()
+ {
+ QString defaultPath = m_lineEdit->text();
+ if (defaultPath.isEmpty())
+ {
+ defaultPath = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
+ }
+
+ QString directory = QDir::toNativeSeparators(QFileDialog::getExistingDirectory(this, tr("Browse"), defaultPath));
+ if (!directory.isEmpty())
+ {
+ m_lineEdit->setText(directory);
+ }
+
+ }
+} // namespace O3DE::ProjectManager
diff --git a/Code/Tools/ProjectManager/Source/FormBrowseEditWidget.h b/Code/Tools/ProjectManager/Source/FormBrowseEditWidget.h
new file mode 100644
index 0000000000..887fc29dd9
--- /dev/null
+++ b/Code/Tools/ProjectManager/Source/FormBrowseEditWidget.h
@@ -0,0 +1,33 @@
+/*
+* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
+* its licensors.
+*
+* For complete copyright and license terms please see the LICENSE at the root of this
+* distribution (the "License"). All use of this software is governed by the License,
+* or, if provided, by the license below or the license accompanying this file. Do not
+* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+*
+*/
+
+#pragma once
+
+#if !defined(Q_MOC_RUN)
+#include
+#endif
+
+namespace O3DE::ProjectManager
+{
+ class FormBrowseEditWidget
+ : public FormLineEditWidget
+ {
+ Q_OBJECT
+
+ public:
+ explicit FormBrowseEditWidget(const QString& labelText, const QString& valueText = "", QWidget* parent = nullptr);
+ ~FormBrowseEditWidget() = default;
+
+ private slots:
+ void HandleBrowseButton();
+ };
+} // namespace O3DE::ProjectManager
diff --git a/Code/Tools/ProjectManager/Source/FormLineEditWidget.cpp b/Code/Tools/ProjectManager/Source/FormLineEditWidget.cpp
new file mode 100644
index 0000000000..7ef7e3c7d8
--- /dev/null
+++ b/Code/Tools/ProjectManager/Source/FormLineEditWidget.cpp
@@ -0,0 +1,123 @@
+/*
+* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
+* its licensors.
+*
+* For complete copyright and license terms please see the LICENSE at the root of this
+* distribution (the "License"). All use of this software is governed by the License,
+* or, if provided, by the license below or the license accompanying this file. Do not
+* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+*
+*/
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace O3DE::ProjectManager
+{
+ FormLineEditWidget::FormLineEditWidget(const QString& labelText, const QString& valueText, QWidget* parent)
+ : QWidget(parent)
+ {
+ setObjectName("formLineEditWidget");
+
+ QVBoxLayout* mainLayout = new QVBoxLayout();
+ mainLayout->setAlignment(Qt::AlignTop);
+ {
+ m_frame = new QFrame(this);
+ m_frame->setObjectName("formFrame");
+
+ // use a horizontal box layout so buttons can be added to the right of the field
+ m_frameLayout = new QHBoxLayout();
+ {
+ QVBoxLayout* fieldLayout = new QVBoxLayout();
+
+ QLabel* label = new QLabel(labelText, this);
+ fieldLayout->addWidget(label);
+
+ m_lineEdit = new AzQtComponents::StyledLineEdit(this);
+ m_lineEdit->setFlavor(AzQtComponents::StyledLineEdit::Question);
+ AzQtComponents::LineEdit::setErrorIconEnabled(m_lineEdit, false);
+ m_lineEdit->setText(valueText);
+
+ connect(m_lineEdit, &AzQtComponents::StyledLineEdit::flavorChanged, this, &FormLineEditWidget::flavorChanged);
+ connect(m_lineEdit, &AzQtComponents::StyledLineEdit::onFocus, this, &FormLineEditWidget::onFocus);
+ connect(m_lineEdit, &AzQtComponents::StyledLineEdit::onFocusOut, this, &FormLineEditWidget::onFocusOut);
+
+ m_lineEdit->setFrame(false);
+ fieldLayout->addWidget(m_lineEdit);
+
+ m_frameLayout->addLayout(fieldLayout);
+
+ QWidget* emptyWidget = new QWidget(this);
+ m_frameLayout->addWidget(emptyWidget);
+ }
+
+ m_frame->setLayout(m_frameLayout);
+
+ mainLayout->addWidget(m_frame);
+
+ m_errorLabel = new QLabel(this);
+ m_errorLabel->setObjectName("formErrorLabel");
+ m_errorLabel->setVisible(false);
+ mainLayout->addWidget(m_errorLabel);
+ }
+
+ setLayout(mainLayout);
+ }
+
+ void FormLineEditWidget::setErrorLabelText(const QString& labelText)
+ {
+ m_errorLabel->setText(labelText);
+ }
+
+ QLineEdit* FormLineEditWidget::lineEdit() const
+ {
+ return m_lineEdit;
+ }
+
+ void FormLineEditWidget::flavorChanged()
+ {
+ if (m_lineEdit->flavor() == AzQtComponents::StyledLineEdit::Flavor::Invalid)
+ {
+ m_frame->setProperty("Valid", false);
+ m_errorLabel->setVisible(true);
+ }
+ else
+ {
+ m_frame->setProperty("Valid", true);
+ m_errorLabel->setVisible(false);
+ }
+ refreshStyle();
+ }
+
+ void FormLineEditWidget::onFocus()
+ {
+ m_frame->setProperty("Focus", true);
+ refreshStyle();
+ }
+
+ void FormLineEditWidget::onFocusOut()
+ {
+ m_frame->setProperty("Focus", false);
+ refreshStyle();
+ }
+
+ void FormLineEditWidget::refreshStyle()
+ {
+ // we must unpolish/polish every child after changing a property
+ // or else they won't use the correct stylesheet selector
+ for (auto child : findChildren())
+ {
+ child->style()->unpolish(child);
+ child->style()->polish(child);
+ }
+ }
+} // namespace O3DE::ProjectManager
diff --git a/Code/Tools/ProjectManager/Source/FormLineEditWidget.h b/Code/Tools/ProjectManager/Source/FormLineEditWidget.h
new file mode 100644
index 0000000000..3094442cbd
--- /dev/null
+++ b/Code/Tools/ProjectManager/Source/FormLineEditWidget.h
@@ -0,0 +1,60 @@
+/*
+* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
+* its licensors.
+*
+* For complete copyright and license terms please see the LICENSE at the root of this
+* distribution (the "License"). All use of this software is governed by the License,
+* or, if provided, by the license below or the license accompanying this file. Do not
+* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+*
+*/
+
+#pragma once
+
+#if !defined(Q_MOC_RUN)
+#include
+#endif
+
+QT_FORWARD_DECLARE_CLASS(QLineEdit)
+QT_FORWARD_DECLARE_CLASS(QLabel)
+QT_FORWARD_DECLARE_CLASS(QFrame)
+QT_FORWARD_DECLARE_CLASS(QHBoxLayout)
+
+namespace AzQtComponents
+{
+ class StyledLineEdit;
+}
+
+namespace O3DE::ProjectManager
+{
+ class FormLineEditWidget
+ : public QWidget
+ {
+ Q_OBJECT
+
+ public:
+ explicit FormLineEditWidget(const QString& labelText, const QString& valueText = "", QWidget* parent = nullptr);
+ ~FormLineEditWidget() = default;
+
+ //! Set the error message for to display when invalid.
+ void setErrorLabelText(const QString& labelText);
+
+ //! Returns a pointer to the underlying LineEdit.
+ QLineEdit* lineEdit() const;
+
+ protected:
+ QLabel* m_errorLabel = nullptr;
+ QFrame* m_frame = nullptr;
+ QHBoxLayout* m_frameLayout = nullptr;
+ AzQtComponents::StyledLineEdit* m_lineEdit = nullptr;
+
+ private slots:
+ void flavorChanged();
+ void onFocus();
+ void onFocusOut();
+
+ private:
+ void refreshStyle();
+ };
+} // namespace O3DE::ProjectManager
diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp
index 5737a188de..3c221d6055 100644
--- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp
+++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp
@@ -58,13 +58,6 @@ namespace O3DE::ProjectManager
hLayout->addWidget(m_gemListView);
hLayout->addWidget(m_gemInspector);
-
-
- // Select the first entry after everything got correctly sized
- QTimer::singleShot(100, [=]{
- QModelIndex firstModelIndex = m_gemListView->model()->index(0,0);
- m_gemListView->selectionModel()->select(firstModelIndex, QItemSelectionModel::ClearAndSelect);
- });
}
QVector GemCatalogScreen::GenerateTestData()
diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.cpp
index 729935fc8e..5b7127bdbe 100644
--- a/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.cpp
+++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.cpp
@@ -32,21 +32,36 @@ namespace O3DE::ProjectManager
{
switch (platform)
{
- case O3DE::ProjectManager::GemInfo::Android:
+ case Android:
return "Android";
- case O3DE::ProjectManager::GemInfo::iOS:
+ case iOS:
return "iOS";
- case O3DE::ProjectManager::GemInfo::Linux:
+ case Linux:
return "Linux";
- case O3DE::ProjectManager::GemInfo::macOS:
+ case macOS:
return "macOS";
- case O3DE::ProjectManager::GemInfo::Windows:
+ case Windows:
return "Windows";
default:
return "";
}
}
+ QString GemInfo::GetTypeString(Type type)
+ {
+ switch (type)
+ {
+ case Asset:
+ return "Asset";
+ case Code:
+ return "Code";
+ case Tool:
+ return "Tool";
+ default:
+ return "";
+ }
+ }
+
bool GemInfo::IsPlatformSupported(Platform platform) const
{
return (m_platforms & platform);
diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h
index 7ee619702f..28b2fab451 100644
--- a/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h
+++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h
@@ -36,6 +36,16 @@ namespace O3DE::ProjectManager
Q_DECLARE_FLAGS(Platforms, Platform)
static QString GetPlatformString(Platform platform);
+ enum Type
+ {
+ Asset = 1 << 0,
+ Code = 1 << 1,
+ Tool = 1 << 2,
+ NumTypes = 3
+ };
+ Q_DECLARE_FLAGS(Types, Type)
+ static QString GetTypeString(Type type);
+
GemInfo() = default;
GemInfo(const QString& name, const QString& creator, const QString& summary, Platforms platforms, bool isAdded);
bool IsPlatformSupported(Platform platform) const;
@@ -50,6 +60,7 @@ namespace O3DE::ProjectManager
bool m_isAdded = false; //! Is the gem currently added and enabled in the project?
QString m_summary;
Platforms m_platforms;
+ Types m_types; //! Asset and/or Code and/or Tool
QStringList m_features;
QString m_directoryLink;
QString m_documentationLink;
@@ -62,3 +73,4 @@ namespace O3DE::ProjectManager
} // namespace O3DE::ProjectManager
Q_DECLARE_OPERATORS_FOR_FLAGS(O3DE::ProjectManager::GemInfo::Platforms)
+Q_DECLARE_OPERATORS_FOR_FLAGS(O3DE::ProjectManager::GemInfo::Types)
diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.cpp
index 434a4aeef2..9a45600f70 100644
--- a/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.cpp
+++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.cpp
@@ -22,11 +22,11 @@ namespace O3DE::ProjectManager
: QStyledItemDelegate(parent)
, m_gemModel(gemModel)
{
- AddPlatformIcon(GemInfo::Android, ":/Resources/Android.svg");
- AddPlatformIcon(GemInfo::iOS, ":/Resources/iOS.svg");
- AddPlatformIcon(GemInfo::Linux, ":/Resources/Linux.svg");
- AddPlatformIcon(GemInfo::macOS, ":/Resources/macOS.svg");
- AddPlatformIcon(GemInfo::Windows, ":/Resources/Windows.svg");
+ AddPlatformIcon(GemInfo::Android, ":/Android.svg");
+ AddPlatformIcon(GemInfo::iOS, ":/iOS.svg");
+ AddPlatformIcon(GemInfo::Linux, ":/Linux.svg");
+ AddPlatformIcon(GemInfo::macOS, ":/macOS.svg");
+ AddPlatformIcon(GemInfo::Windows, ":/Windows.svg");
}
void GemItemDelegate::AddPlatformIcon(GemInfo::Platform platform, const QString& iconPath)
diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.cpp
index 1112c656f3..addf59783d 100644
--- a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.cpp
+++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.cpp
@@ -10,7 +10,8 @@
*
*/
-#include "GemModel.h"
+#include
+#include
namespace O3DE::ProjectManager
{
@@ -32,8 +33,11 @@ namespace O3DE::ProjectManager
item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
item->setData(gemInfo.m_name, RoleName);
+ const QString uuidString = gemInfo.m_uuid.ToString().c_str();
+ item->setData(uuidString, RoleUuid);
item->setData(gemInfo.m_creator, RoleCreator);
- item->setData(static_cast(gemInfo.m_platforms), RolePlatforms);
+ item->setData(aznumeric_cast(gemInfo.m_platforms), RolePlatforms);
+ item->setData(aznumeric_cast(gemInfo.m_types), RoleTypes);
item->setData(gemInfo.m_summary, RoleSummary);
item->setData(gemInfo.m_isAdded, RoleIsAdded);
@@ -48,6 +52,8 @@ namespace O3DE::ProjectManager
item->setData(gemInfo.m_features, RoleFeatures);
appendRow(item);
+
+ m_uuidToNameMap[uuidString] = gemInfo.m_displayName;
}
void GemModel::Clear()
@@ -65,11 +71,21 @@ namespace O3DE::ProjectManager
return modelIndex.data(RoleCreator).toString();
}
+ QString GemModel::GetUuidString(const QModelIndex& modelIndex)
+ {
+ return modelIndex.data(RoleUuid).toString();
+ }
+
GemInfo::Platforms GemModel::GetPlatforms(const QModelIndex& modelIndex)
{
return static_cast(modelIndex.data(RolePlatforms).toInt());
}
+ GemInfo::Types GemModel::GetTypes(const QModelIndex& modelIndex)
+ {
+ return static_cast(modelIndex.data(RoleTypes).toInt());
+ }
+
QString GemModel::GetSummary(const QModelIndex& modelIndex)
{
return modelIndex.data(RoleSummary).toString();
@@ -90,9 +106,35 @@ namespace O3DE::ProjectManager
return modelIndex.data(RoleDocLink).toString();
}
+ AZ::Outcome GemModel::FindGemNameByUuidString(const QString& uuidString) const
+ {
+ const auto iterator = m_uuidToNameMap.find(uuidString);
+ if (iterator != m_uuidToNameMap.end())
+ {
+ return AZ::Success(iterator.value());
+ }
+
+ return AZ::Failure();
+ }
+
QStringList GemModel::GetDependingGems(const QModelIndex& modelIndex)
{
- return modelIndex.data(RoleDependingGems).toStringList();
+ QStringList result = modelIndex.data(RoleDependingGems).toStringList();
+ if (result.isEmpty())
+ {
+ return {};
+ }
+
+ for (QString& dependingGemString : result)
+ {
+ AZ::Outcome gemNameOutcome = FindGemNameByUuidString(dependingGemString);
+ if (gemNameOutcome.IsSuccess())
+ {
+ dependingGemString = gemNameOutcome.GetValue();
+ }
+ }
+
+ return result;
}
QStringList GemModel::GetConflictingGems(const QModelIndex& modelIndex)
diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h
index fba65e7009..76211b1f22 100644
--- a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h
+++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h
@@ -13,7 +13,8 @@
#pragma once
#if !defined(Q_MOC_RUN)
-#include "GemInfo.h"
+#include
+#include
#include
#include
#include
@@ -33,14 +34,18 @@ namespace O3DE::ProjectManager
void AddGem(const GemInfo& gemInfo);
void Clear();
+ AZ::Outcome FindGemNameByUuidString(const QString& uuidString) const;
+ QStringList GetDependingGems(const QModelIndex& modelIndex);
+
static QString GetName(const QModelIndex& modelIndex);
static QString GetCreator(const QModelIndex& modelIndex);
+ static QString GetUuidString(const QModelIndex& modelIndex);
static GemInfo::Platforms GetPlatforms(const QModelIndex& modelIndex);
+ static GemInfo::Types GetTypes(const QModelIndex& modelIndex);
static QString GetSummary(const QModelIndex& modelIndex);
static bool IsAdded(const QModelIndex& modelIndex);
static QString GetDirectoryLink(const QModelIndex& modelIndex);
static QString GetDocLink(const QModelIndex& modelIndex);
- static QStringList GetDependingGems(const QModelIndex& modelIndex);
static QStringList GetConflictingGems(const QModelIndex& modelIndex);
static QString GetVersion(const QModelIndex& modelIndex);
static QString GetLastUpdated(const QModelIndex& modelIndex);
@@ -51,6 +56,7 @@ namespace O3DE::ProjectManager
enum UserRole
{
RoleName = Qt::UserRole,
+ RoleUuid,
RoleCreator,
RolePlatforms,
RoleSummary,
@@ -63,8 +69,10 @@ namespace O3DE::ProjectManager
RoleLastUpdated,
RoleBinarySize,
RoleFeatures,
+ RoleTypes
};
+ QHash m_uuidToNameMap;
QItemSelectionModel* m_selectionModel = nullptr;
};
} // namespace O3DE::ProjectManager
diff --git a/Code/Tools/ProjectManager/Source/LinkWidget.cpp b/Code/Tools/ProjectManager/Source/LinkWidget.cpp
index fddc4cd8c9..a6308f6c62 100644
--- a/Code/Tools/ProjectManager/Source/LinkWidget.cpp
+++ b/Code/Tools/ProjectManager/Source/LinkWidget.cpp
@@ -27,7 +27,12 @@ namespace O3DE::ProjectManager
void LinkLabel::mousePressEvent([[maybe_unused]] QMouseEvent* event)
{
- QDesktopServices::openUrl(m_url);
+ if (m_url.isValid())
+ {
+ QDesktopServices::openUrl(m_url);
+ }
+
+ emit clicked();
}
void LinkLabel::enterEvent([[maybe_unused]] QEvent* event)
diff --git a/Code/Tools/ProjectManager/Source/LinkWidget.h b/Code/Tools/ProjectManager/Source/LinkWidget.h
index 7055dce2af..b3a34cd63a 100644
--- a/Code/Tools/ProjectManager/Source/LinkWidget.h
+++ b/Code/Tools/ProjectManager/Source/LinkWidget.h
@@ -26,10 +26,16 @@ namespace O3DE::ProjectManager
class LinkLabel
: public QLabel
{
+ Q_OBJECT // AUTOMOC
+
public:
- LinkLabel(const QString& text, const QUrl& url = {}, QWidget* parent = nullptr);
+ LinkLabel(const QString& text = {}, const QUrl& url = {}, QWidget* parent = nullptr);
void SetUrl(const QUrl& url);
+
+ signals:
+ void clicked();
+
private:
void mousePressEvent(QMouseEvent* event) override;
void enterEvent(QEvent* event) override;
diff --git a/Code/Tools/ProjectManager/Source/PathValidator.cpp b/Code/Tools/ProjectManager/Source/PathValidator.cpp
new file mode 100644
index 0000000000..8b74284b6c
--- /dev/null
+++ b/Code/Tools/ProjectManager/Source/PathValidator.cpp
@@ -0,0 +1,65 @@
+/*
+* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
+* its licensors.
+*
+* For complete copyright and license terms please see the LICENSE at the root of this
+* distribution (the "License"). All use of this software is governed by the License,
+* or, if provided, by the license below or the license accompanying this file. Do not
+* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+*
+*/
+
+#include "PathValidator.h"
+
+#include
+#include
+#include
+
+namespace O3DE::ProjectManager
+{
+ PathValidator::PathValidator(PathMode pathMode, QWidget* parent)
+ : QValidator(parent)
+ , m_pathMode(pathMode)
+ {
+ }
+
+ void PathValidator::setAllowEmpty(bool allowEmpty)
+ {
+ m_allowEmpty = allowEmpty;
+ }
+
+ void PathValidator::setPathMode(PathMode pathMode)
+ {
+ m_pathMode = pathMode;
+ }
+
+ QValidator::State PathValidator::validate(QString &text, int &) const
+ {
+ if(text.isEmpty())
+ {
+ return m_allowEmpty ? QValidator::Acceptable : QValidator::Intermediate;
+ }
+
+ QFileInfo pathInfo(text);
+ if(!pathInfo.dir().exists())
+ {
+ return QValidator::Intermediate;
+ }
+
+ switch(m_pathMode)
+ {
+ case PathMode::AnyFile://acceptable, as long as it's not an directoy
+ return pathInfo.isDir() ? QValidator::Intermediate : QValidator::Acceptable;
+ case PathMode::ExistingFile://must be an existing file
+ return pathInfo.exists() && pathInfo.isFile() ? QValidator::Acceptable : QValidator::Intermediate;
+ case PathMode::ExistingFolder://must be an existing folder
+ return pathInfo.exists() && pathInfo.isDir() ? QValidator::Acceptable : QValidator::Intermediate;
+ default:
+ Q_UNREACHABLE();
+ }
+
+ return QValidator::Invalid;
+ }
+
+} // namespace O3DE::ProjectManager
diff --git a/Code/Tools/ProjectManager/Source/PathValidator.h b/Code/Tools/ProjectManager/Source/PathValidator.h
new file mode 100644
index 0000000000..aeb35571b9
--- /dev/null
+++ b/Code/Tools/ProjectManager/Source/PathValidator.h
@@ -0,0 +1,45 @@
+/*
+* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
+* its licensors.
+*
+* For complete copyright and license terms please see the LICENSE at the root of this
+* distribution (the "License"). All use of this software is governed by the License,
+* or, if provided, by the license below or the license accompanying this file. Do not
+* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+*
+*/
+
+#pragma once
+
+#if !defined(Q_MOC_RUN)
+#include
+#endif
+
+QT_FORWARD_DECLARE_CLASS(QWidget)
+
+namespace O3DE::ProjectManager
+{
+ class PathValidator
+ : public QValidator
+ {
+ public:
+ enum class PathMode {
+ ExistingFile, //!< A single, existings file. Useful for "Open file"
+ ExistingFolder, //!< A single, existing directory. Useful for "Open Folder"
+ AnyFile //!< A single, valid file, doesn't have to exist but the directory must. Useful for "Save File"
+ };
+
+ explicit PathValidator(PathMode pathMode, QWidget* parent = nullptr);
+ ~PathValidator() = default;
+
+ void setAllowEmpty(bool allowEmpty);
+ void setPathMode(PathMode pathMode);
+
+ QValidator::State validate(QString &text, int &) const override;
+
+ private:
+ PathMode m_pathMode = PathMode::AnyFile;
+ bool m_allowEmpty = false;
+ };
+} // namespace O3DE::ProjectManager
diff --git a/Code/Tools/ProjectManager/Source/ProjectManagerWindow.cpp b/Code/Tools/ProjectManager/Source/ProjectManagerWindow.cpp
index 6b9d268564..121add657f 100644
--- a/Code/Tools/ProjectManager/Source/ProjectManagerWindow.cpp
+++ b/Code/Tools/ProjectManager/Source/ProjectManagerWindow.cpp
@@ -44,10 +44,10 @@ namespace O3DE::ProjectManager
QDir rootDir = QString::fromUtf8(engineRootPath.Native().data(), aznumeric_cast(engineRootPath.Native().size()));
const auto pathOnDisk = rootDir.absoluteFilePath("Code/Tools/ProjectManager/Resources");
- const auto qrcPath = QStringLiteral(":/ProjectManagerWindow");
- AzQtComponents::StyleManager::addSearchPaths("projectmanagerwindow", pathOnDisk, qrcPath, engineRootPath);
+ const auto qrcPath = QStringLiteral(":/ProjectManager/style");
+ AzQtComponents::StyleManager::addSearchPaths("style", pathOnDisk, qrcPath, engineRootPath);
- AzQtComponents::StyleManager::setStyleSheet(this, QStringLiteral("projectlauncherwindow:ProjectManagerWindow.qss"));
+ AzQtComponents::StyleManager::setStyleSheet(this, QStringLiteral("style:ProjectManager.qss"));
QVector screenEnums =
{
diff --git a/Code/Tools/ProjectManager/Source/ProjectManagerWindow.ui b/Code/Tools/ProjectManager/Source/ProjectManagerWindow.ui
index a71ed3aabf..4e33511bff 100644
--- a/Code/Tools/ProjectManager/Source/ProjectManagerWindow.ui
+++ b/Code/Tools/ProjectManager/Source/ProjectManagerWindow.ui
@@ -41,8 +41,8 @@
Icon
-
- :/Resources/o3de_editor.ico:/Resources/o3de_editor.ico
+
+ :/o3de_editor.ico:/o3de_editor.ico
-
+
diff --git a/Code/Tools/ProjectManager/Source/ProjectSettingsCtrl.cpp b/Code/Tools/ProjectManager/Source/ProjectSettingsCtrl.cpp
index fd1013c871..95dcec3e18 100644
--- a/Code/Tools/ProjectManager/Source/ProjectSettingsCtrl.cpp
+++ b/Code/Tools/ProjectManager/Source/ProjectSettingsCtrl.cpp
@@ -106,6 +106,7 @@ namespace O3DE::ProjectManager
auto result = PythonBindingsInterface::Get()->CreateProject(m_projectTemplatePath, m_projectInfo);
if (result.IsSuccess())
{
+ // adding gems is not implemented yet because we don't know what targets to add or how to add them
emit ChangeScreenRequest(ProjectManagerScreen::ProjectsHome);
}
else
diff --git a/Code/Tools/ProjectManager/Source/ProjectsHomeScreen.ui b/Code/Tools/ProjectManager/Source/ProjectsHomeScreen.ui
index ea3e34d84b..2ba93ccf90 100644
--- a/Code/Tools/ProjectManager/Source/ProjectsHomeScreen.ui
+++ b/Code/Tools/ProjectManager/Source/ProjectsHomeScreen.ui
@@ -48,8 +48,8 @@
-
- :/Resources/Add.svg:/Resources/Add.svg
+
+ :/Add.svg:/Add.svg
@@ -65,8 +65,8 @@
-
- :/Resources/Select_Folder.svg:/Resources/Select_Folder.svg
+
+ :/Select_Folder.svg:/Select_Folder.svg
@@ -131,7 +131,7 @@
-
+
diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.cpp b/Code/Tools/ProjectManager/Source/PythonBindings.cpp
index e925c81032..9a5e82dafb 100644
--- a/Code/Tools/ProjectManager/Source/PythonBindings.cpp
+++ b/Code/Tools/ProjectManager/Source/PythonBindings.cpp
@@ -328,12 +328,91 @@ namespace O3DE::ProjectManager
AZ::Outcome PythonBindings::GetEngineInfo()
{
+ EngineInfo engineInfo;
+ bool result = ExecuteWithLock([&] {
+ pybind11::str enginePath = m_registration.attr("get_this_engine_path")();
+
+ auto o3deData = m_registration.attr("load_o3de_manifest")();
+ if (pybind11::isinstance(o3deData))
+ {
+ engineInfo.m_path = Py_To_String(enginePath);
+ engineInfo.m_defaultGemsFolder = Py_To_String(o3deData["default_gems_folder"]);
+ engineInfo.m_defaultProjectsFolder = Py_To_String(o3deData["default_projects_folder"]);
+ engineInfo.m_defaultRestrictedFolder = Py_To_String(o3deData["default_restricted_folder"]);
+ engineInfo.m_defaultTemplatesFolder = Py_To_String(o3deData["default_templates_folder"]);
+ engineInfo.m_thirdPartyPath = Py_To_String_Optional(o3deData,"third_party_path","");
+ }
+
+ auto engineData = m_registration.attr("get_engine_data")(pybind11::none(), enginePath);
+ if (pybind11::isinstance(engineData))
+ {
+ try
+ {
+ engineInfo.m_version = Py_To_String_Optional(engineData,"O3DEVersion","0.0.0.0");
+ engineInfo.m_name = Py_To_String_Optional(engineData,"engine_name","O3DE");
+ }
+ catch ([[maybe_unused]] const std::exception& e)
+ {
+ AZ_Warning("PythonBindings", false, "Failed to get EngineInfo from %s", Py_To_String(enginePath));
+ }
+ }
+ });
+
+ if (!result || !engineInfo.IsValid())
+ {
+ return AZ::Failure();
+ }
+ else
+ {
+ return AZ::Success(AZStd::move(engineInfo));
+ }
+
return AZ::Failure();
}
- bool PythonBindings::SetEngineInfo([[maybe_unused]] const EngineInfo& engineInfo)
+ bool PythonBindings::SetEngineInfo(const EngineInfo& engineInfo)
{
- return false;
+ bool result = ExecuteWithLock([&] {
+ pybind11::str enginePath = engineInfo.m_path.toStdString();
+ pybind11::str defaultProjectsFolder = engineInfo.m_defaultProjectsFolder.toStdString();
+ pybind11::str defaultGemsFolder = engineInfo.m_defaultGemsFolder.toStdString();
+ pybind11::str defaultTemplatesFolder = engineInfo.m_defaultTemplatesFolder.toStdString();
+
+ auto registrationResult = m_registration.attr("register")(
+ enginePath, // engine_path
+ pybind11::none(), // project_path
+ pybind11::none(), // gem_path
+ pybind11::none(), // template_path
+ pybind11::none(), // restricted_path
+ pybind11::none(), // repo_uri
+ pybind11::none(), // default_engines_folder
+ defaultProjectsFolder,
+ defaultGemsFolder,
+ defaultTemplatesFolder
+ );
+
+ if (registrationResult.cast() != 0)
+ {
+ result = false;
+ }
+
+ auto manifest = m_registration.attr("load_o3de_manifest")();
+ if (pybind11::isinstance(manifest))
+ {
+ try
+ {
+ manifest["third_party_path"] = engineInfo.m_thirdPartyPath.toStdString();
+ m_registration.attr("save_o3de_manifest")(manifest);
+ }
+ catch ([[maybe_unused]] const std::exception& e)
+ {
+ AZ_Warning("PythonBindings", false, "Failed to set third party path.");
+ }
+ }
+
+ });
+
+ return result;
}
AZ::Outcome PythonBindings::GetGem(const QString& path)
@@ -426,7 +505,7 @@ namespace O3DE::ProjectManager
{
// required
gemInfo.m_name = Py_To_String(data["Name"]);
- gemInfo.m_uuid = AZ::Uuid(Py_To_String(data["Uuid"]));
+ gemInfo.m_uuid = AZ::Uuid(Py_To_String(data["Uuid"]));
// optional
gemInfo.m_displayName = Py_To_String_Optional(data, "DisplayName", gemInfo.m_name);
@@ -437,7 +516,8 @@ namespace O3DE::ProjectManager
{
for (auto dependency : data["Dependencies"])
{
- gemInfo.m_dependingGemUuids.push_back(Py_To_String(dependency["Uuid"]));
+ const AZ::Uuid uuid = Py_To_String(dependency["Uuid"]);
+ gemInfo.m_dependingGemUuids.push_back(uuid.ToString().c_str());
}
}
if (data.contains("Tags"))
@@ -507,6 +587,42 @@ namespace O3DE::ProjectManager
}
}
+ bool PythonBindings::AddGemToProject(const QString& gemPath, const QString& projectPath)
+ {
+ bool result = ExecuteWithLock([&] {
+ pybind11::str pyGemPath = gemPath.toStdString();
+ pybind11::str pyProjectPath = projectPath.toStdString();
+
+ m_registration.attr("add_gem_to_project")(
+ pybind11::none(), // gem_name
+ pyGemPath,
+ pybind11::none(), // gem_target
+ pybind11::none(), // project_name
+ pyProjectPath
+ );
+ });
+
+ return result;
+ }
+
+ bool PythonBindings::RemoveGemFromProject(const QString& gemPath, const QString& projectPath)
+ {
+ bool result = ExecuteWithLock([&] {
+ pybind11::str pyGemPath = gemPath.toStdString();
+ pybind11::str pyProjectPath = projectPath.toStdString();
+
+ m_registration.attr("remove_gem_to_project")(
+ pybind11::none(), // gem_name
+ pyGemPath,
+ pybind11::none(), // gem_target
+ pybind11::none(), // project_name
+ pyProjectPath
+ );
+ });
+
+ return result;
+ }
+
bool PythonBindings::UpdateProject([[maybe_unused]] const ProjectInfo& projectInfo)
{
return false;
diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.h b/Code/Tools/ProjectManager/Source/PythonBindings.h
index ffabf99b49..892e13a65b 100644
--- a/Code/Tools/ProjectManager/Source/PythonBindings.h
+++ b/Code/Tools/ProjectManager/Source/PythonBindings.h
@@ -47,6 +47,8 @@ namespace O3DE::ProjectManager
AZ::Outcome GetProject(const QString& path) override;
AZ::Outcome> GetProjects() override;
bool UpdateProject(const ProjectInfo& projectInfo) override;
+ bool AddGemToProject(const QString& gemPath, const QString& projectPath) override;
+ bool RemoveGemFromProject(const QString& gemPath, const QString& projectPath) override;
// ProjectTemplate
AZ::Outcome> GetProjectTemplates() override;
diff --git a/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h b/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h
index 2377da1461..b5c8f1a76a 100644
--- a/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h
+++ b/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h
@@ -96,6 +96,22 @@ namespace O3DE::ProjectManager
*/
virtual bool UpdateProject(const ProjectInfo& projectInfo) = 0;
+ /**
+ * Add a gem to a project
+ * @param gemPath the absolute path to the gem
+ * @param projectPath the absolute path to the project
+ * @return true on success, false on failure
+ */
+ virtual bool AddGemToProject(const QString& gemPath, const QString& projectPath) = 0;
+
+ /**
+ * Remove gem to a project
+ * @param gemPath the absolute path to the gem
+ * @param projectPath the absolute path to the project
+ * @return true on success, false on failure
+ */
+ virtual bool RemoveGemFromProject(const QString& gemPath, const QString& projectPath) = 0;
+
// Project Templates
diff --git a/Code/Tools/ProjectManager/project_manager.qrc b/Code/Tools/ProjectManager/project_manager.qrc
deleted file mode 100644
index 3c23bc24ff..0000000000
--- a/Code/Tools/ProjectManager/project_manager.qrc
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
- Resources/ProjectManager.qss
- Resources/Add.svg
- Resources/Select_Folder.svg
- Resources/o3de_editor.ico
- Resources/Windows.svg
- Resources/Android.svg
- Resources/iOS.svg
- Resources/Linux.svg
- Resources/macOS.svg
- Resources/Backgrounds/FirstTimeBackgroundImage.jpg
-
-
diff --git a/Code/Tools/ProjectManager/project_manager_files.cmake b/Code/Tools/ProjectManager/project_manager_files.cmake
index 3594d1e079..858fb972aa 100644
--- a/Code/Tools/ProjectManager/project_manager_files.cmake
+++ b/Code/Tools/ProjectManager/project_manager_files.cmake
@@ -10,7 +10,8 @@
#
set(FILES
- project_manager.qrc
+ Resources/ProjectManager.qrc
+ Resources/ProjectManager.qss
Source/main.cpp
Source/ScreenDefs.h
Source/ScreenFactory.h
@@ -22,6 +23,12 @@ set(FILES
Source/EngineInfo.cpp
Source/FirstTimeUseScreen.h
Source/FirstTimeUseScreen.cpp
+ Source/FormLineEditWidget.h
+ Source/FormLineEditWidget.cpp
+ Source/FormBrowseEditWidget.h
+ Source/FormBrowseEditWidget.cpp
+ Source/PathValidator.h
+ Source/PathValidator.cpp
Source/ProjectManagerWindow.h
Source/ProjectManagerWindow.cpp
Source/ProjectTemplateInfo.h
@@ -44,7 +51,6 @@ set(FILES
Source/ProjectSettingsScreen.ui
Source/EngineSettingsScreen.h
Source/EngineSettingsScreen.cpp
- Source/EngineSettingsScreen.ui
Source/LinkWidget.h
Source/LinkWidget.cpp
Source/TagWidget.h
diff --git a/Code/Tools/SceneAPI/FbxSceneBuilder/DllMain.cpp b/Code/Tools/SceneAPI/FbxSceneBuilder/DllMain.cpp
index 3dc14814de..d2818f3653 100644
--- a/Code/Tools/SceneAPI/FbxSceneBuilder/DllMain.cpp
+++ b/Code/Tools/SceneAPI/FbxSceneBuilder/DllMain.cpp
@@ -41,18 +41,6 @@ namespace AZ
static AZ::SceneAPI::FbxSceneImporter::FbxImportRequestHandler* g_fbxImporter = nullptr;
static AZStd::vector g_componentDescriptors;
- void Initialize()
- {
- // Currently it's still needed to explicitly create an instance of this instead of letting
- // it be a normal component. This is because ResourceCompilerScene needs to return
- // the list of available extensions before it can start the application.
- if (!g_fbxImporter)
- {
- g_fbxImporter = aznew AZ::SceneAPI::FbxSceneImporter::FbxImportRequestHandler();
- g_fbxImporter->Activate();
- }
- }
-
void Reflect(AZ::SerializeContext* /*context*/)
{
// Descriptor registration is done in Reflect instead of Initialize because the ResourceCompilerScene initializes the libraries before
@@ -64,6 +52,7 @@ namespace AZ
{
// Global importer and behavior
g_componentDescriptors.push_back(FbxSceneBuilder::FbxImporter::CreateDescriptor());
+ g_componentDescriptors.push_back(FbxSceneImporter::FbxImportRequestHandler::CreateDescriptor());
// Node and attribute importers
g_componentDescriptors.push_back(AssImpBitangentStreamImporter::CreateDescriptor());
@@ -125,7 +114,6 @@ namespace AZ
extern "C" AZ_DLL_EXPORT void InitializeDynamicModule(void* env)
{
AZ::Environment::Attach(static_cast(env));
- AZ::SceneAPI::FbxSceneBuilder::Initialize();
}
extern "C" AZ_DLL_EXPORT void Reflect(AZ::SerializeContext* context)
{
diff --git a/Code/Tools/SceneAPI/FbxSceneBuilder/FbxImportRequestHandler.cpp b/Code/Tools/SceneAPI/FbxSceneBuilder/FbxImportRequestHandler.cpp
index 155209f1b5..a43f1e16b8 100644
--- a/Code/Tools/SceneAPI/FbxSceneBuilder/FbxImportRequestHandler.cpp
+++ b/Code/Tools/SceneAPI/FbxSceneBuilder/FbxImportRequestHandler.cpp
@@ -10,12 +10,16 @@
*
*/
+#include
+#include
#include
-#include