From e256df3f05c778d7ddad16471b18aa87fb504a54 Mon Sep 17 00:00:00 2001 From: brianherrera Date: Fri, 8 Oct 2021 10:05:50 -0700 Subject: [PATCH 1/4] Add retry config to boto3 clients This change makes the inc_build_util script more resilient against transient network issues and issues encountered during node boot-up. Signed-off-by: brianherrera --- .../build/bootstrap/incremental_build_util.py | 30 ++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/scripts/build/bootstrap/incremental_build_util.py b/scripts/build/bootstrap/incremental_build_util.py index 101e31b5db..5c77559085 100644 --- a/scripts/build/bootstrap/incremental_build_util.py +++ b/scripts/build/bootstrap/incremental_build_util.py @@ -18,6 +18,8 @@ from contextlib import contextmanager import threading import _thread +from botocore.config import Config + DEFAULT_REGION = 'us-west-2' DEFAULT_DISK_SIZE = 300 DEFAULT_DISK_TYPE = 'gp2' @@ -173,10 +175,27 @@ def get_region_name(): def get_ec2_client(region): - client = boto3.client('ec2', region_name=region) + client_config = Config( + region_name=region, + retries={ + 'mode': 'standard' + } + ) + client = boto3.client('ec2', config=client_config) return client +def get_ec2_resource(region): + resource_config = Config( + region_name=region, + retries={ + 'mode': 'standard' + } + ) + resource = boto3.resource('ec2', config=resource_config) + return resource + + def get_ec2_instance_id(): try: instance_id = urllib.request.urlopen('http://169.254.169.254/latest/meta-data/instance-id').read() @@ -395,14 +414,11 @@ def detach_volume_from_ec2_instance(volume, ec2_instance_id, force, timeout_dura def mount_ebs(snapshot_hint, repository_name, project, pipeline, branch, platform, build_type, disk_size, disk_type): - session = boto3.session.Session() - region = session.region_name - if region is None: - region = DEFAULT_REGION + region = get_region_name() ec2_client = get_ec2_client(region) ec2_instance_id = get_ec2_instance_id() ec2_availability_zone = get_availability_zone() - ec2_resource = boto3.resource('ec2', region_name=region) + ec2_resource = get_ec2_resource(region) ec2_instance = ec2_resource.Instance(ec2_instance_id) for volume in ec2_instance.volumes.all(): @@ -469,7 +485,7 @@ def mount_ebs(snapshot_hint, repository_name, project, pipeline, branch, platfor def unmount_ebs(): region = get_region_name() ec2_instance_id = get_ec2_instance_id() - ec2_resource = boto3.resource('ec2', region_name=region) + ec2_resource = get_ec2_resource(region) ec2_instance = ec2_resource.Instance(ec2_instance_id) if os.path.isfile('envinject.properties'): From d3f6898ad6e617d5f40e7f45bc7098a1ce098569 Mon Sep 17 00:00:00 2001 From: jromnoa <80134229+jromnoa@users.noreply.github.com> Date: Fri, 8 Oct 2021 15:01:20 -0700 Subject: [PATCH 2/4] Fixes test_MaterialEditorLaunch_AllRHIOptionsSucceed() test by searching for RHI log lines (#4511) * double test timeout from 60 seconds to 120 seconds in attempt to fix it for nightly GPU runs Signed-off-by: jromnoa * add a file to launch with the test to ensure we get a full viewport load completed Signed-off-by: jromnoa * fix import error Signed-off-by: jromnoa * remove python error checks Signed-off-by: jromnoa * add new log line specific to each RHI to check for Signed-off-by: jromnoa * remove the new test script as it is no longer needed with our improved log lines check - the viewport logs don't show up in AR for some reason Signed-off-by: jromnoa --- .../Gem/PythonTests/Atom/TestSuite_Main_GPU.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main_GPU.py b/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main_GPU.py index 3403d8e9b1..220623af8b 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main_GPU.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main_GPU.py @@ -220,20 +220,18 @@ class TestPerformanceBenchmarkSuite(object): @pytest.mark.system class TestMaterialEditor(object): - @pytest.mark.parametrize("cfg_args", ["-rhi=dx12", "-rhi=Vulkan"]) + @pytest.mark.parametrize("cfg_args,expected_lines", [ + pytest.param("-rhi=dx12", ["Registering dx12 RHI"]), + pytest.param("-rhi=Vulkan", ["Registering vulkan RHI"]) + ]) @pytest.mark.parametrize("exe_file_name", ["MaterialEditor"]) def test_MaterialEditorLaunch_AllRHIOptionsSucceed( - self, request, workspace, project, launcher_platform, generic_launcher, exe_file_name, cfg_args): + self, request, workspace, project, launcher_platform, generic_launcher, exe_file_name, cfg_args, + expected_lines): """ Tests each valid RHI option (Null RHI excluded) can be launched with the MaterialEditor. - Checks for the "Finished loading viewport configurations." success message post launch. + Checks for the specific expected_lines messaging for each RHI type. """ - expected_lines = ["Finished loading viewport configurations."] - unexpected_lines = [ - # "Trace::Assert", - # "Trace::Error", - "Traceback (most recent call last):", - ] hydra.launch_and_validate_results( request, @@ -243,7 +241,7 @@ class TestMaterialEditor(object): run_python="--runpython", timeout=60, expected_lines=expected_lines, - unexpected_lines=unexpected_lines, + unexpected_lines=[], halt_on_unexpected=False, null_renderer=False, cfg_args=[cfg_args], From e4d3ab118c02071d5533ea90525cb8b43589ea62 Mon Sep 17 00:00:00 2001 From: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> Date: Fri, 8 Oct 2021 17:59:57 -0500 Subject: [PATCH 3/4] Console changes: Added a new SettingsRegistry root key for executing (#4567) console commands. The new key is "/O3DE/Autoexec/ConsoleCommands" and the only difference with the "/Amazon/AzCore/Runtime/ConosleCommands" key is that it isn't excluded by the SettingsRegistryBuilder. Due to not being excluded by the SettingsRegistryBuilder this key can be used to forward console commands to the aggregate `bootstrap.game...setreg` files. For GameLauncher specific console commands it is recommend to be put them in .setreg file that uses the "game" specialization, such as "autoexec.game.setreg". Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> --- .../AzCore/AzCore/Console/Console.cpp | 32 +++++++++++++------ .../AzCore/AzCore/Console/IConsole.h | 3 +- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/Code/Framework/AzCore/AzCore/Console/Console.cpp b/Code/Framework/AzCore/AzCore/Console/Console.cpp index a1b8a1759d..9f207d9afd 100644 --- a/Code/Framework/AzCore/AzCore/Console/Console.cpp +++ b/Code/Framework/AzCore/AzCore/Console/Console.cpp @@ -476,15 +476,16 @@ namespace AZ // Responsible for using the Json Serialization Issue Callback system // to determine when a JSON Patch or JSON Merge Patch modifies a value - // at a path underneath the IConsole::ConsoleRootCommandKey JSON pointer + // at a path underneath the IConsole::ConsoleRuntimeCommandKey JSON pointer JsonSerializationResult::ResultCode operator()(AZStd::string_view message, JsonSerializationResult::ResultCode result, AZStd::string_view path) { - AZ::IO::PathView consoleRootCommandKey{ IConsole::ConsoleRootCommandKey, AZ::IO::PosixPathSeparator }; + constexpr AZ::IO::PathView consoleRootCommandKey{ IConsole::ConsoleRuntimeCommandKey, AZ::IO::PosixPathSeparator }; + constexpr AZ::IO::PathView consoleAutoexecCommandKey{ IConsole::ConsoleAutoexecCommandKey, AZ::IO::PosixPathSeparator }; AZ::IO::PathView inputKey{ path, AZ::IO::PosixPathSeparator }; if (result.GetTask() == JsonSerializationResult::Tasks::Merge && result.GetProcessing() == JsonSerializationResult::Processing::Completed - && inputKey.IsRelativeTo(consoleRootCommandKey)) + && (inputKey.IsRelativeTo(consoleRootCommandKey) || inputKey.IsRelativeTo(consoleAutoexecCommandKey))) { if (auto type = m_settingsRegistry.GetType(path); type != SettingsRegistryInterface::Type::NoType) { @@ -510,12 +511,24 @@ namespace AZ { using FixedValueString = AZ::SettingsRegistryInterface::FixedValueString; - AZ::IO::PathView consoleRootCommandKey{ IConsole::ConsoleRootCommandKey, AZ::IO::PosixPathSeparator }; + constexpr AZ::IO::PathView consoleRuntimeCommandKey{ IConsole::ConsoleRuntimeCommandKey, AZ::IO::PosixPathSeparator }; + constexpr AZ::IO::PathView consoleAutoexecCommandKey{ IConsole::ConsoleAutoexecCommandKey, AZ::IO::PosixPathSeparator }; AZ::IO::PathView inputKey{ path, AZ::IO::PosixPathSeparator }; - // The ConsoleRootComamndKey is not a command itself so strictly children keys are being examined - if (inputKey.IsRelativeTo(consoleRootCommandKey) && inputKey != consoleRootCommandKey) + + // Abuses the IsRelativeToFuncton function of the path class to extract the console + // command from the settings registry objects + FixedValueString command; + if (inputKey != consoleRuntimeCommandKey && inputKey.IsRelativeTo(consoleRuntimeCommandKey)) + { + command = inputKey.LexicallyRelative(consoleRuntimeCommandKey).Native(); + } + else if (inputKey != consoleAutoexecCommandKey && inputKey.IsRelativeTo(consoleAutoexecCommandKey)) + { + command = inputKey.LexicallyRelative(consoleAutoexecCommandKey).Native(); + } + + if (!command.empty()) { - FixedValueString command = inputKey.LexicallyRelative(consoleRootCommandKey).Native(); ConsoleCommandContainer commandArgs; // Argument string which stores the value from the Settings Registry long enough // to pass into the PerformCommand. The ConsoleCommandContainer stores string_views @@ -603,9 +616,10 @@ namespace AZ void Console::RegisterCommandInvokerWithSettingsRegistry(AZ::SettingsRegistryInterface& settingsRegistry) { - // Make sure the there is a JSON object at the path of AZ::IConsole::ConsoleRootCommandKey + // Make sure the there is a JSON object at the ConsoleRuntimeCommandKey or ConsoleAutoexecKey // So that JSON Patch is able to add values underneath that object (JSON Patch doesn't create intermediate objects) - settingsRegistry.MergeSettings(R"({ "Amazon": { "AzCore": { "Runtime": { "ConsoleCommands": {} } }}})", + settingsRegistry.MergeSettings(R"({ "Amazon": { "AzCore": { "Runtime": { "ConsoleCommands": {} } } })" + R"(,"O3DE": { "Autoexec": { "ConsoleCommands": {} } } })", SettingsRegistryInterface::Format::JsonMergePatch); m_consoleCommandKeyHandler = settingsRegistry.RegisterNotifier(ConsoleCommandKeyNotificationHandler{ settingsRegistry, *this }); diff --git a/Code/Framework/AzCore/AzCore/Console/IConsole.h b/Code/Framework/AzCore/AzCore/Console/IConsole.h index dafc284ce3..73d17ac65a 100644 --- a/Code/Framework/AzCore/AzCore/Console/IConsole.h +++ b/Code/Framework/AzCore/AzCore/Console/IConsole.h @@ -31,7 +31,8 @@ namespace AZ using FunctorVisitor = AZStd::function; - inline static constexpr AZStd::string_view ConsoleRootCommandKey = "/Amazon/AzCore/Runtime/ConsoleCommands"; + inline static constexpr AZStd::string_view ConsoleRuntimeCommandKey = "/Amazon/AzCore/Runtime/ConsoleCommands"; + inline static constexpr AZStd::string_view ConsoleAutoexecCommandKey = "/O3DE/Autoexec/ConsoleCommands"; IConsole() = default; virtual ~IConsole() = default; From 7b1dd01d1d648b2e449dba283d051bcae2414483 Mon Sep 17 00:00:00 2001 From: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> Date: Fri, 8 Oct 2021 20:09:22 -0500 Subject: [PATCH 4/4] Implemented a deferred LoadLevel queue for the SpawnableLevelSystem (#4561) * Moved the SettingsRegistryTests.cpp and SettingsRegistryMergeUtilsTests.cpp to the Settings folder Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Implemented a deferred level load queue, that allows the SpawnableLevelSystem to re-run the last LoadLevel command that occured before it was constructed. Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Added SettingsRegistryVisitorUtils to reduce Array and Object visitor boilerplate. The VisitArray and VisitObject functions allows iteration over each element of array and object respectively via a callback. Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Removed the queuing logic for levels that attempt to load before the SpawnableLevelSystem is available Only the last level name that could not load is stored off and deferred until the SpawnableLevelsystem is created. Made the FieldVisitor AggregateTypes constructor protected and added a comment specifying the expected values. Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Bring in the SettingsRegistry::Visitor::Visit functions into scope to fix MSVC compilation errors. Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Changed the list of supported SettingsRegistry types to visit to an enum to constrain the values to Array and/or Object. Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> --- .../AzCore/AzCore/Settings/SettingsRegistry.h | 4 +- .../Settings/SettingsRegistryVisitorUtils.cpp | 136 ++++++++++++ .../Settings/SettingsRegistryVisitorUtils.h | 83 ++++++++ .../AzCore/AzCore/azcore_files.cmake | 2 + .../SettingsRegistryMergeUtilsTests.cpp | 0 .../{ => Settings}/SettingsRegistryTests.cpp | 0 .../SettingsRegistryVisitorUtilsTests.cpp | 196 ++++++++++++++++++ .../AzCore/Tests/azcoretests_files.cmake | 5 +- .../LevelSystem/SpawnableLevelSystem.cpp | 35 +++- Registry/setregbuilder.assetprocessor.setreg | 3 +- 10 files changed, 456 insertions(+), 8 deletions(-) create mode 100644 Code/Framework/AzCore/AzCore/Settings/SettingsRegistryVisitorUtils.cpp create mode 100644 Code/Framework/AzCore/AzCore/Settings/SettingsRegistryVisitorUtils.h rename Code/Framework/AzCore/Tests/{ => Settings}/SettingsRegistryMergeUtilsTests.cpp (100%) rename Code/Framework/AzCore/Tests/{ => Settings}/SettingsRegistryTests.cpp (100%) create mode 100644 Code/Framework/AzCore/Tests/Settings/SettingsRegistryVisitorUtilsTests.cpp diff --git a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistry.h b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistry.h index 3dc1be87c5..40022bdc04 100644 --- a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistry.h +++ b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistry.h @@ -204,14 +204,14 @@ namespace AZ [[nodiscard]] virtual PreMergeEventHandler RegisterPreMergeEvent(const PreMergeEventCallback& callback) = 0; //! Register a function that will be called before a file is merged. //! @callback The function to call before a file is merged. - [[nodiscard]] virtual PreMergeEventHandler RegisterPreMergeEvent (PreMergeEventCallback&& callback) = 0; + [[nodiscard]] virtual PreMergeEventHandler RegisterPreMergeEvent(PreMergeEventCallback&& callback) = 0; //! Register a function that will be called after a file is merged. //! @callback The function to call after a file is merged. [[nodiscard]] virtual PostMergeEventHandler RegisterPostMergeEvent(const PostMergeEventCallback& callback) = 0; //! Register a function that will be called after a file is merged. //! @callback The function to call after a file is merged. - [[nodiscard]] virtual PostMergeEventHandler RegisterPostMergeEvent (PostMergeEventCallback&& callback) = 0; + [[nodiscard]] virtual PostMergeEventHandler RegisterPostMergeEvent(PostMergeEventCallback&& callback) = 0; //! Gets the boolean value at the provided path. //! @param result The target to write the result to. diff --git a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryVisitorUtils.cpp b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryVisitorUtils.cpp new file mode 100644 index 0000000000..9456616024 --- /dev/null +++ b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryVisitorUtils.cpp @@ -0,0 +1,136 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include + + +namespace AZ::SettingsRegistryVisitorUtils +{ + // Field Visitor implementation + FieldVisitor::FieldVisitor() = default; + FieldVisitor::FieldVisitor(VisitFieldType visitFieldType) + : m_visitFieldType{ visitFieldType } + { + } + + auto FieldVisitor::Traverse(AZStd::string_view path, AZStd::string_view valueName, + VisitAction action, Type type) -> VisitResponse + { + // A default response skip prevents visiting grand children(depth 2 or lower) + VisitResponse visitResponse = VisitResponse::Skip; + if (action == VisitAction::Begin) + { + // Invoke FieldVisitor override if the root path has been set + if (m_rootPath.has_value()) + { + Visit(path, valueName, type); + } + // To make sure only the direct children are visited(depth 1) + // set the root path once and set the VisitReponsoe + // to Continue to recurse into is fields + if (!m_rootPath.has_value()) + { + bool visitableFieldType{}; + switch (m_visitFieldType) + { + case VisitFieldType::Array: + visitableFieldType = type == Type::Array; + break; + case VisitFieldType::Object: + visitableFieldType = type == Type::Object; + break; + case VisitFieldType::ArrayOrObject: + visitableFieldType = type == Type::Array || type ==Type::Object; + break; + default: + AZ_Error("FieldVisitor", false, "The field visitation type value is invalid"); + break; + } + + if (visitableFieldType) + { + m_rootPath = path; + visitResponse = VisitResponse::Continue; + } + } + } + else if (action == VisitAction::Value) + { + // Invoke FieldVisitor override if the root path has been set + if (m_rootPath.has_value()) + { + Visit(path, valueName, type); + } + } + else if (action == VisitAction::End) + { + // Reset m_rootPath back to null when the root path has finished being visited + if (m_rootPath.has_value() && *m_rootPath == path) + { + m_rootPath = AZStd::nullopt; + } + } + + + return visitResponse; + } + + // Array Visitor implementation + ArrayVisitor::ArrayVisitor() + : FieldVisitor(VisitFieldType::Array) + { + } + + // Object Visitor implementation + ObjectVisitor::ObjectVisitor() + : FieldVisitor(VisitFieldType::Object) + { + } + + // Generic VisitField Callback implemention + template + bool VisitFieldCallback(AZ::SettingsRegistryInterface& settingsRegistry, const VisitorCallback& visitCallback, AZStd::string_view path) + { + struct VisitFieldVisitor + : BaseVisitor + { + using BaseVisitor::Visit; + VisitFieldVisitor(const VisitorCallback& visitCallback) + : m_visitCallback{ visitCallback } + {} + + void Visit(AZStd::string_view path, AZStd::string_view fieldIndex, typename BaseVisitor::Type type) override + { + m_visitCallback(path, fieldIndex, type); + } + + const VisitorCallback& m_visitCallback; + }; + + VisitFieldVisitor visitor{ visitCallback }; + return settingsRegistry.Visit(visitor, path); + } + + // VisitField implementation + bool VisitField(AZ::SettingsRegistryInterface& settingsRegistry, const VisitorCallback& visitCallback, AZStd::string_view path) + { + return VisitFieldCallback(settingsRegistry, visitCallback, path); + } + + // VisitArray implementation + bool VisitArray(AZ::SettingsRegistryInterface& settingsRegistry, const VisitorCallback& visitCallback, AZStd::string_view path) + { + return VisitFieldCallback(settingsRegistry, visitCallback, path); + } + + // VisitObject implementation + bool VisitObject(AZ::SettingsRegistryInterface& settingsRegistry, const VisitorCallback& visitCallback, AZStd::string_view path) + { + return VisitFieldCallback(settingsRegistry, visitCallback, path); + } +} diff --git a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryVisitorUtils.h b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryVisitorUtils.h new file mode 100644 index 0000000000..a45712521f --- /dev/null +++ b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryVisitorUtils.h @@ -0,0 +1,83 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include + + +namespace AZ::SettingsRegistryVisitorUtils +{ + //! Interface for visiting the fields of an array or object + //! To access the values, use the SettingsRegistryInterface Get/GetObject methods + struct FieldVisitor + : public AZ::SettingsRegistryInterface::Visitor + { + using VisitResponse = AZ::SettingsRegistryInterface::VisitResponse; + using VisitAction = AZ::SettingsRegistryInterface::VisitAction; + using Type = AZ::SettingsRegistryInterface::Type; + + FieldVisitor(); + + // Bring the base class visitor functions into scope + using AZ::SettingsRegistryInterface::Visitor::Visit; + virtual void Visit(AZStd::string_view path, AZStd::string_view arrayIndex, Type type) = 0; + + protected: + // VisitFieldType is used for filtering the type of referenced by the root path + enum class VisitFieldType + { + Array, + Object, + ArrayOrObject + }; + FieldVisitor(const VisitFieldType visitFieldType); + private: + VisitResponse Traverse(AZStd::string_view path, AZStd::string_view valueName, + VisitAction action, Type type) override; + + VisitFieldType m_visitFieldType{ VisitFieldType::ArrayOrObject }; + AZStd::optional m_rootPath; + }; + + //! Interface for visiting the fields of an array + //! To access the values, use the SettingsRegistryInterface Get/GetObject methods + struct ArrayVisitor + : public FieldVisitor + { + ArrayVisitor(); + }; + + //! Interface for visiting the fields of an object + //! To access the values, use the SettingsRegistryInterface Get/GetObject methods + struct ObjectVisitor + : public FieldVisitor + { + ObjectVisitor(); + }; + + //! Signature of callback funcition invoked when visiting an element of an array or object + using VisitorCallback = AZStd::function; + + //! Invokes the visitor callback for each element of either the array or object at @path + //! If @path is not an array or object, then no elements are visited + //! This function will not recurse into children of elements + //! @visitCallback functor that is invoked for each array or object element found + bool VisitField(AZ::SettingsRegistryInterface& settingsRegistry, const VisitorCallback& visitCallback, AZStd::string_view path); + //! Invokes the visitor callback for each element of the array at @path + //! If @path is not an array, then no elements are visited + //! This function will not recurse into children of elements + //! @visitCallback functor that is invoked for each array element found + bool VisitArray(AZ::SettingsRegistryInterface& settingsRegistry, const VisitorCallback& visitCallback, AZStd::string_view path); + //! Invokes the visitor callback for each element of the object at @path + //! If @path is not an object, then no elements are visited + //! This function will not recurse into children of elements + //! @visitCallback functor that is invoked for each object element found + bool VisitObject(AZ::SettingsRegistryInterface& settingsRegistry, const VisitorCallback& visitCallback, AZStd::string_view path); +} diff --git a/Code/Framework/AzCore/AzCore/azcore_files.cmake b/Code/Framework/AzCore/AzCore/azcore_files.cmake index 010c748402..6675958247 100644 --- a/Code/Framework/AzCore/AzCore/azcore_files.cmake +++ b/Code/Framework/AzCore/AzCore/azcore_files.cmake @@ -566,6 +566,8 @@ set(FILES Settings/SettingsRegistryMergeUtils.h Settings/SettingsRegistryScriptUtils.cpp Settings/SettingsRegistryScriptUtils.h + Settings/SettingsRegistryVisitorUtils.cpp + Settings/SettingsRegistryVisitorUtils.h State/HSM.cpp State/HSM.h Statistics/NamedRunningStatistic.h diff --git a/Code/Framework/AzCore/Tests/SettingsRegistryMergeUtilsTests.cpp b/Code/Framework/AzCore/Tests/Settings/SettingsRegistryMergeUtilsTests.cpp similarity index 100% rename from Code/Framework/AzCore/Tests/SettingsRegistryMergeUtilsTests.cpp rename to Code/Framework/AzCore/Tests/Settings/SettingsRegistryMergeUtilsTests.cpp diff --git a/Code/Framework/AzCore/Tests/SettingsRegistryTests.cpp b/Code/Framework/AzCore/Tests/Settings/SettingsRegistryTests.cpp similarity index 100% rename from Code/Framework/AzCore/Tests/SettingsRegistryTests.cpp rename to Code/Framework/AzCore/Tests/Settings/SettingsRegistryTests.cpp diff --git a/Code/Framework/AzCore/Tests/Settings/SettingsRegistryVisitorUtilsTests.cpp b/Code/Framework/AzCore/Tests/Settings/SettingsRegistryVisitorUtilsTests.cpp new file mode 100644 index 0000000000..15f346ee93 --- /dev/null +++ b/Code/Framework/AzCore/Tests/Settings/SettingsRegistryVisitorUtilsTests.cpp @@ -0,0 +1,196 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + + +#include +#include +#include +#include +#include +#include + +namespace SettingsRegistryVisitorUtilsTests +{ + struct VisitCallbackParams + { + AZStd::string_view m_inputJsonDocument; + using VisitFieldFunction = bool(*)(AZ::SettingsRegistryInterface&, + const AZ::SettingsRegistryVisitorUtils::VisitorCallback&, + AZStd::string_view); + + static inline constexpr size_t MaxFieldCount = 10; + using ObjectFields = AZStd::fixed_vector, MaxFieldCount>; + using ArrayFields = AZStd::fixed_vector; + ObjectFields m_objectFields; + ArrayFields m_arrayFields; + }; + + template + class SettingsRegistryVisitorUtilsParamFixture + : public UnitTest::ScopedAllocatorSetupFixture + , public ::testing::WithParamInterface + { + public: + + void SetUp() override + { + m_registry = AZStd::make_unique(); + } + + void TearDown() override + { + m_registry.reset(); + } + + AZStd::unique_ptr m_registry; + }; + + using SettingsRegistryVisitCallbackFixture = SettingsRegistryVisitorUtilsParamFixture; + + TEST_P(SettingsRegistryVisitCallbackFixture, VisitFunction_VisitFieldsOfArrayType_ReturnsFields) + { + const VisitCallbackParams& visitParams = GetParam(); + + ASSERT_TRUE(m_registry->MergeSettings(visitParams.m_inputJsonDocument, AZ::SettingsRegistryInterface::Format::JsonMergePatch)); + + AZStd::fixed_vector testArrayFields; + auto visitorCallback = [this, &testArrayFields](AZStd::string_view path, AZStd::string_view, AZ::SettingsRegistryInterface::Type) + { + AZStd::string fieldValue; + EXPECT_TRUE(m_registry->Get(fieldValue, path)); + testArrayFields.emplace_back(AZStd::move(fieldValue)); + }; + + AZ::SettingsRegistryVisitorUtils::VisitField(*m_registry, visitorCallback, "/Test/Array"); + + const AZStd::fixed_vector expectedFields{ + visitParams.m_arrayFields.begin(), visitParams.m_arrayFields.end() }; + EXPECT_THAT(testArrayFields, ::testing::ContainerEq(expectedFields)); + } + + TEST_P(SettingsRegistryVisitCallbackFixture, VisitFunction_VisitFieldsOfObjectType_ReturnsFields) + { + const VisitCallbackParams& visitParams = GetParam(); + + ASSERT_TRUE(m_registry->MergeSettings(visitParams.m_inputJsonDocument, AZ::SettingsRegistryInterface::Format::JsonMergePatch)); + + AZStd::fixed_vector, VisitCallbackParams::MaxFieldCount> testObjectFields; + auto visitorCallback = [this, &testObjectFields](AZStd::string_view path, AZStd::string_view fieldName, AZ::SettingsRegistryInterface::Type) + { + AZStd::string fieldValue; + EXPECT_TRUE(m_registry->Get(fieldValue, path)); + testObjectFields.emplace_back(fieldName, AZStd::move(fieldValue)); + }; + + AZ::SettingsRegistryVisitorUtils::VisitField(*m_registry, visitorCallback, "/Test/Object"); + + const AZStd::fixed_vector, VisitCallbackParams::MaxFieldCount> expectedFields{ + visitParams.m_objectFields.begin(), visitParams.m_objectFields.end() }; + EXPECT_THAT(testObjectFields, ::testing::ContainerEq(expectedFields)); + } + + TEST_P(SettingsRegistryVisitCallbackFixture, VisitFunction_VisitArrayOfArrayType_ReturnsFields) + { + const VisitCallbackParams& visitParams = GetParam(); + + ASSERT_TRUE(m_registry->MergeSettings(visitParams.m_inputJsonDocument, AZ::SettingsRegistryInterface::Format::JsonMergePatch)); + + AZStd::fixed_vector testArrayFields; + auto visitorCallback = [this, &testArrayFields](AZStd::string_view path, AZStd::string_view, AZ::SettingsRegistryInterface::Type) + { + AZStd::string fieldValue; + EXPECT_TRUE(m_registry->Get(fieldValue, path)); + testArrayFields.emplace_back(AZStd::move(fieldValue)); + }; + + AZ::SettingsRegistryVisitorUtils::VisitArray(*m_registry, visitorCallback, "/Test/Array"); + + const AZStd::fixed_vector expectedArrayFields{ + visitParams.m_arrayFields.begin(), visitParams.m_arrayFields.end() }; + EXPECT_THAT(testArrayFields, ::testing::ContainerEq(expectedArrayFields)); + } + + TEST_P(SettingsRegistryVisitCallbackFixture, VisitFunction_VisitArrayOfObjectType_ReturnsEmpty) + { + const VisitCallbackParams& visitParams = GetParam(); + + ASSERT_TRUE(m_registry->MergeSettings(visitParams.m_inputJsonDocument, AZ::SettingsRegistryInterface::Format::JsonMergePatch)); + + AZStd::fixed_vector testArrayFields; + auto visitorCallback = [this, &testArrayFields](AZStd::string_view path, AZStd::string_view, AZ::SettingsRegistryInterface::Type) + { + AZStd::string fieldValue; + EXPECT_TRUE(m_registry->Get(fieldValue, path)); + testArrayFields.emplace_back(AZStd::move(fieldValue)); + }; + + AZ::SettingsRegistryVisitorUtils::VisitArray(*m_registry, visitorCallback, "/Test/Object"); + + EXPECT_TRUE(testArrayFields.empty()); + } + + TEST_P(SettingsRegistryVisitCallbackFixture, VisitFunction_VisitObjectOfArrayType_ReturnsEmpty) + { + const VisitCallbackParams& visitParams = GetParam(); + + ASSERT_TRUE(m_registry->MergeSettings(visitParams.m_inputJsonDocument, AZ::SettingsRegistryInterface::Format::JsonMergePatch)); + + AZStd::fixed_vector, VisitCallbackParams::MaxFieldCount> testObjectFields; + auto visitorCallback = [this, &testObjectFields](AZStd::string_view path, AZStd::string_view fieldName, AZ::SettingsRegistryInterface::Type) + { + AZStd::string fieldValue; + EXPECT_TRUE(m_registry->Get(fieldValue, path)); + testObjectFields.emplace_back(fieldName, AZStd::move(fieldValue)); + }; + + AZ::SettingsRegistryVisitorUtils::VisitObject(*m_registry, visitorCallback, "/Test/Array"); + + EXPECT_TRUE(testObjectFields.empty()); + } + + TEST_P(SettingsRegistryVisitCallbackFixture, VisitFunction_VisitObjectOfObjectType_ReturnsFields) + { + const VisitCallbackParams& visitParams = GetParam(); + + ASSERT_TRUE(m_registry->MergeSettings(visitParams.m_inputJsonDocument, AZ::SettingsRegistryInterface::Format::JsonMergePatch)); + + AZStd::fixed_vector, VisitCallbackParams::MaxFieldCount> testObjectFields; + auto visitorCallback = [this, &testObjectFields](AZStd::string_view path, AZStd::string_view fieldName, AZ::SettingsRegistryInterface::Type) + { + AZStd::string fieldValue; + EXPECT_TRUE(m_registry->Get(fieldValue, path)); + testObjectFields.emplace_back(fieldName, AZStd::move(fieldValue)); + }; + + AZ::SettingsRegistryVisitorUtils::VisitObject(*m_registry, visitorCallback, "/Test/Object"); + + const AZStd::fixed_vector, VisitCallbackParams::MaxFieldCount> expectedObjectFields{ + visitParams.m_objectFields.begin(), visitParams.m_objectFields.end() }; + EXPECT_THAT(testObjectFields, ::testing::ContainerEq(expectedObjectFields)); + } + + + INSTANTIATE_TEST_CASE_P( + VisitField, + SettingsRegistryVisitCallbackFixture, + ::testing::Values( + VisitCallbackParams + { + R"({)" "\n" + R"( "Test":)" "\n" + R"( {)" "\n" + R"( "Array": [ "Hello", "World" ],)" "\n" + R"( "Object": { "Foo": "Hello", "Bar": "World"})" "\n" + R"( })" "\n" + R"(})" "\n", + VisitCallbackParams::ObjectFields{{"Foo", "Hello"}, {"Bar", "World"}}, + VisitCallbackParams::ArrayFields{"Hello", "World"} + } + ) + ); +} diff --git a/Code/Framework/AzCore/Tests/azcoretests_files.cmake b/Code/Framework/AzCore/Tests/azcoretests_files.cmake index e155594aa0..d738711a4f 100644 --- a/Code/Framework/AzCore/Tests/azcoretests_files.cmake +++ b/Code/Framework/AzCore/Tests/azcoretests_files.cmake @@ -75,11 +75,12 @@ set(FILES Name/NameJsonSerializerTests.cpp Name/NameTests.cpp RTTI/TypeSafeIntegralTests.cpp - SettingsRegistryTests.cpp - SettingsRegistryMergeUtilsTests.cpp Settings/CommandLineTests.cpp + Settings/SettingsRegistryTests.cpp Settings/SettingsRegistryConsoleUtilsTests.cpp + Settings/SettingsRegistryMergeUtilsTests.cpp Settings/SettingsRegistryScriptUtilsTests.cpp + Settings/SettingsRegistryVisitorUtilsTests.cpp Streamer/BlockCacheTests.cpp Streamer/DedicatedCacheTests.cpp Streamer/FullDecompressorTests.cpp diff --git a/Code/Legacy/CrySystem/LevelSystem/SpawnableLevelSystem.cpp b/Code/Legacy/CrySystem/LevelSystem/SpawnableLevelSystem.cpp index a82c4a6914..b2b67c3b75 100644 --- a/Code/Legacy/CrySystem/LevelSystem/SpawnableLevelSystem.cpp +++ b/Code/Legacy/CrySystem/LevelSystem/SpawnableLevelSystem.cpp @@ -22,22 +22,33 @@ #include #include #include +#include #include #include namespace LegacyLevelSystem { + constexpr AZStd::string_view DeferredLoadLevelKey = "/O3DE/Runtime/SpawnableLevelSystem/DeferredLoadLevel"; //------------------------------------------------------------------------ static void LoadLevel(const AZ::ConsoleCommandContainer& arguments) { AZ_Error("SpawnableLevelSystem", !arguments.empty(), "LoadLevel requires a level file name to be provided."); AZ_Error("SpawnableLevelSystem", arguments.size() == 1, "LoadLevel requires a single level file name to be provided."); - if (!arguments.empty() && gEnv->pSystem && gEnv->pSystem->GetILevelSystem() && !gEnv->IsEditor()) + if (!arguments.empty() && gEnv && gEnv->pSystem && gEnv->pSystem->GetILevelSystem() && !gEnv->IsEditor()) { gEnv->pSystem->GetILevelSystem()->LoadLevel(arguments[0].data()); } + else if (!arguments.empty()) + { + // The SpawnableLevelSystem isn't available yet. + // Defer the level load until later by storing it in the SettingsRegistry + if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr) + { + settingsRegistry->Set(DeferredLoadLevelKey, arguments.front()); + } + } } //------------------------------------------------------------------------ @@ -45,7 +56,7 @@ namespace LegacyLevelSystem { AZ_Warning("SpawnableLevelSystem", !arguments.empty(), "UnloadLevel doesn't use any arguments."); - if (gEnv->pSystem && gEnv->pSystem->GetILevelSystem() && !gEnv->IsEditor()) + if (gEnv && gEnv->pSystem && gEnv->pSystem->GetILevelSystem() && !gEnv->IsEditor()) { gEnv->pSystem->GetILevelSystem()->UnloadLevel(); } @@ -73,6 +84,24 @@ namespace LegacyLevelSystem } AzFramework::RootSpawnableNotificationBus::Handler::BusConnect(); + + // If there were LoadLevel command invocations before the creation of the level system + // then those invocations were queued. + // load the last level in the queue, since only one level can be loaded at a time + if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr) + { + if (AZ::SettingsRegistryInterface::FixedValueString deferredLevelName; + settingsRegistry->Get(deferredLevelName, DeferredLoadLevelKey) && !deferredLevelName.empty()) + { + // since this is the constructor any derived classes vtables aren't setup yet + // call this class LoadLevel function + AZ_TracePrintf("SpawnableLevelSystem", "The Level System is now available." + " Loading level %s which could not be loaded earlier\n", deferredLevelName.c_str()); + SpawnableLevelSystem::LoadLevel(deferredLevelName.c_str()); + // Delete the key with the deferred level name + settingsRegistry->Remove(DeferredLoadLevelKey); + } + } } //------------------------------------------------------------------------ @@ -173,7 +202,7 @@ namespace LegacyLevelSystem } // Make sure a spawnable level exists that matches levelname - AZStd::string validLevelName = ""; + AZStd::string validLevelName; AZ::Data::AssetId rootSpawnableAssetId; AZ::Data::AssetCatalogRequestBus::BroadcastResult( rootSpawnableAssetId, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath, levelName, nullptr, false); diff --git a/Registry/setregbuilder.assetprocessor.setreg b/Registry/setregbuilder.assetprocessor.setreg index 25b51470bf..00e6e2f7f8 100644 --- a/Registry/setregbuilder.assetprocessor.setreg +++ b/Registry/setregbuilder.assetprocessor.setreg @@ -20,7 +20,8 @@ "Excludes": [ "/Amazon/AzCore/Runtime", - "/Amazon/AzCore/Bootstrap/project_path" + "/Amazon/AzCore/Bootstrap/project_path", + "/O3DE/Runtime", ] } }