diff --git a/AutomatedTesting/Gem/PythonTests/atom_renderer/atom_hydra_scripts/hydra_GPUTest_AtomFeatureIntegrationBenchmark.py b/AutomatedTesting/Gem/PythonTests/atom_renderer/atom_hydra_scripts/hydra_GPUTest_AtomFeatureIntegrationBenchmark.py new file mode 100644 index 0000000000..b899d7dcde --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/atom_renderer/atom_hydra_scripts/hydra_GPUTest_AtomFeatureIntegrationBenchmark.py @@ -0,0 +1,102 @@ +""" +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 + +Hydra script that is used to create a new level with a default rendering setup. +After the level is setup, screenshots are diffed against golden images are used to verify pass/fail results of the test. + +See the run() function for more in-depth test info. +""" + +import os +import sys + +import azlmbr.legacy.general as general + +sys.path.append(os.path.join(azlmbr.paths.devroot, "AutomatedTesting", "Gem", "PythonTests")) + +import editor_python_test_tools.hydra_editor_utils as hydra +from editor_python_test_tools.editor_test_helper import EditorTestHelper +from atom_renderer.atom_utils.benchmark_utils import BenchmarkHelper + +SCREEN_WIDTH = 1280 +SCREEN_HEIGHT = 720 +DEGREE_RADIAN_FACTOR = 0.0174533 + +helper = EditorTestHelper(log_prefix="Test_Atom_BasicLevelSetup") + + +def run(): + """ + 1. View -> Layouts -> Restore Default Layout, sets the viewport to ratio 16:9 @ 1280 x 720 + 2. Runs console command r_DisplayInfo = 0 + 3. Opens AtomFeatureIntegrationBenchmark level + 4. Initializes benchmark helper with benchmark name to capture benchmark metadata. + 5. Idles for 100 frames, then collects pass timings for 100 frames. + :return: None + """ + def initial_viewport_setup(screen_width, screen_height): + general.set_viewport_size(screen_width, screen_height) + general.update_viewport() + helper.wait_for_condition( + function=lambda: helper.isclose(a=general.get_viewport_size().x, b=SCREEN_WIDTH, rel_tol=0.1) + and helper.isclose(a=general.get_viewport_size().y, b=SCREEN_HEIGHT, rel_tol=0.1), + timeout_in_seconds=4.0 + ) + result = helper.isclose(a=general.get_viewport_size().x, b=SCREEN_WIDTH, rel_tol=0.1) and helper.isclose( + a=general.get_viewport_size().y, b=SCREEN_HEIGHT, rel_tol=0.1) + general.log(general.get_viewport_size().x) + general.log(general.get_viewport_size().y) + general.log(general.get_viewport_size().z) + general.log(f"Viewport is set to the expected size: {result}") + general.run_console("r_DisplayInfo = 0") + + def after_level_load(): + """Function to call after creating/opening a level to ensure it loads.""" + # Give everything a second to initialize. + general.idle_enable(True) + general.idle_wait(1.0) + general.update_viewport() + general.idle_wait(0.5) # half a second is more than enough for updating the viewport. + + # Close out problematic windows, FPS meters, and anti-aliasing. + if general.is_helpers_shown(): # Turn off the helper gizmos if visible + general.toggle_helpers() + general.idle_wait(1.0) + if general.is_pane_visible("Error Report"): # Close Error Report windows that block focus. + general.close_pane("Error Report") + if general.is_pane_visible("Error Log"): # Close Error Log windows that block focus. + general.close_pane("Error Log") + general.idle_wait(1.0) + general.run_console("r_displayInfo=0") + general.run_console("r_antialiasingmode=0") + general.idle_wait(1.0) + + return True + + # Wait for Editor idle loop before executing Python hydra scripts. + general.idle_enable(True) + + general.open_level_no_prompt("AtomFeatureIntegrationBenchmark") + + # Basic setup after opening level. + after_level_load() + initial_viewport_setup(SCREEN_WIDTH, SCREEN_HEIGHT) + + general.enter_game_mode() + general.idle_wait(1.0) + helper.wait_for_condition(function=lambda: general.is_in_game_mode(), timeout_in_seconds=2.0) + benchmarker = BenchmarkHelper("AtomFeatureIntegrationBenchmark") + benchmarker.capture_benchmark_metadata() + general.idle_wait_frames(100) + for i in range(1, 101): + benchmarker.capture_pass_timestamp(i) + general.exit_game_mode() + helper.wait_for_condition(function=lambda: not general.is_in_game_mode(), timeout_in_seconds=2.0) + general.log("Capturing complete.") + + +if __name__ == "__main__": + run() diff --git a/AutomatedTesting/Gem/PythonTests/atom_renderer/atom_utils/benchmark_utils.py b/AutomatedTesting/Gem/PythonTests/atom_renderer/atom_utils/benchmark_utils.py new file mode 100644 index 0000000000..21c7489ed3 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/atom_renderer/atom_utils/benchmark_utils.py @@ -0,0 +1,84 @@ +""" +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 +""" +import azlmbr.atom +import azlmbr.legacy.general as general + +FOLDER_PATH = '@user@/Scripts/PerformanceBenchmarks' +METADATA_FILE = 'benchmark_metadata.json' + +class BenchmarkHelper(object): + """ + A helper to capture benchmark data. + """ + def __init__(self, benchmark_name): + super().__init__() + self.benchmark_name = benchmark_name + self.output_path = f'{FOLDER_PATH}/{benchmark_name}' + self.done = False + self.capturedData = False + self.max_frames_to_wait = 200 + + def capture_benchmark_metadata(self): + """ + Capture benchmark metadata and block further execution until it has been written to the disk. + """ + self.handler = azlmbr.atom.ProfilingCaptureNotificationBusHandler() + self.handler.connect() + self.handler.add_callback('OnCaptureBenchmarkMetadataFinished', self.on_data_captured) + + self.done = False + self.capturedData = False + success = azlmbr.atom.ProfilingCaptureRequestBus( + azlmbr.bus.Broadcast, "CaptureBenchmarkMetadata", self.benchmark_name, f'{self.output_path}/{METADATA_FILE}' + ) + if success: + self.wait_until_data() + general.log('Benchmark metadata captured.') + else: + general.log('Failed to capture benchmark metadata.') + return self.capturedData + + def capture_pass_timestamp(self, frame_number): + """ + Capture pass timestamps and block further execution until it has been written to the disk. + """ + self.handler = azlmbr.atom.ProfilingCaptureNotificationBusHandler() + self.handler.connect() + self.handler.add_callback('OnCaptureQueryTimestampFinished', self.on_data_captured) + + self.done = False + self.capturedData = False + success = azlmbr.atom.ProfilingCaptureRequestBus( + azlmbr.bus.Broadcast, "CapturePassTimestamp", f'{self.output_path}/frame{frame_number}_timestamps.json') + if success: + self.wait_until_data() + general.log('Pass timestamps captured.') + else: + general.log('Failed to capture pass timestamps.') + return self.capturedData + + def on_data_captured(self, parameters): + # the parameters come in as a tuple + if parameters[0]: + general.log('Captured data successfully.') + self.capturedData = True + else: + general.log('Failed to capture data.') + self.done = True + self.handler.disconnect() + + def wait_until_data(self): + frames_waited = 0 + while self.done == False: + general.idle_wait_frames(1) + if frames_waited > self.max_frames_to_wait: + general.log('Timed out while waiting for the data to be captured') + self.handler.disconnect() + break + else: + frames_waited = frames_waited + 1 + general.log(f'(waited {frames_waited} frames)') diff --git a/AutomatedTesting/Gem/PythonTests/atom_renderer/test_Atom_GPUTests.py b/AutomatedTesting/Gem/PythonTests/atom_renderer/test_Atom_GPUTests.py index dad92d0932..ede140c075 100644 --- a/AutomatedTesting/Gem/PythonTests/atom_renderer/test_Atom_GPUTests.py +++ b/AutomatedTesting/Gem/PythonTests/atom_renderer/test_Atom_GPUTests.py @@ -14,6 +14,7 @@ import pytest import ly_test_tools.environment.file_system as file_system from ly_test_tools.image.screenshot_compare_qssim import qssim as compare_screenshots +from ly_test_tools.benchmark.data_aggregator import BenchmarkDataAggregator import editor_python_test_tools.hydra_test_utils as hydra logger = logging.getLogger(__name__) @@ -83,3 +84,43 @@ class TestAllComponentsIndepthTests(object): for test_screenshot, golden_screenshot in zip(test_screenshots, golden_images): compare_screenshots(test_screenshot, golden_screenshot) + +@pytest.mark.parametrize('rhi', ['dx12', 'vulkan']) +@pytest.mark.parametrize("project", ["AutomatedTesting"]) +@pytest.mark.parametrize("launcher_platform", ["windows_editor"]) +@pytest.mark.parametrize("level", ["AtomFeatureIntegrationBenchmark"]) +class TestPerformanceBenchmarkSuite(object): + def test_AtomFeatureIntegrationBenchmark( + self, request, editor, workspace, rhi, project, launcher_platform, level): + """ + Please review the hydra script run by this test for more specific test info. + Tests the performance of the Simple level. + """ + expected_lines = [ + "Benchmark metadata captured.", + "Pass timestamps captured.", + "Capturing complete.", + "Captured data successfully." + ] + + unexpected_lines = [ + "Failed to capture data.", + "Failed to capture pass timestamps.", + "Failed to capture benchmark metadata." + ] + + hydra.launch_and_validate_results( + request, + TEST_DIRECTORY, + editor, + "hydra_GPUTest_AtomFeatureIntegrationBenchmark.py", + timeout=EDITOR_TIMEOUT, + expected_lines=expected_lines, + unexpected_lines=unexpected_lines, + halt_on_unexpected=True, + cfg_args=[level], + null_renderer=False, + ) + + aggregator = BenchmarkDataAggregator(workspace, logger, 'periodic') + aggregator.upload_metrics(rhi) diff --git a/AutomatedTesting/Levels/AtomFeatureIntegrationBenchmark/AtomFeatureIntegrationBenchmark.ly b/AutomatedTesting/Levels/AtomFeatureIntegrationBenchmark/AtomFeatureIntegrationBenchmark.ly new file mode 100644 index 0000000000..4fc96f242b --- /dev/null +++ b/AutomatedTesting/Levels/AtomFeatureIntegrationBenchmark/AtomFeatureIntegrationBenchmark.ly @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5232563c3ff322669808ac4daeda3d822e4ef8c9c87db0fa245f0f9c9c34aada +size 23379 diff --git a/AutomatedTesting/Levels/AtomFeatureIntegrationBenchmark/filelist.xml b/AutomatedTesting/Levels/AtomFeatureIntegrationBenchmark/filelist.xml new file mode 100644 index 0000000000..15b79f5e4f --- /dev/null +++ b/AutomatedTesting/Levels/AtomFeatureIntegrationBenchmark/filelist.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/AutomatedTesting/Levels/AtomFeatureIntegrationBenchmark/level.pak b/AutomatedTesting/Levels/AtomFeatureIntegrationBenchmark/level.pak new file mode 100644 index 0000000000..fecbc0b394 --- /dev/null +++ b/AutomatedTesting/Levels/AtomFeatureIntegrationBenchmark/level.pak @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e075be2cb7cf5aa98e3503c1119b94c3098b35500c98c4db32d025c9e1afa52d +size 5450 diff --git a/AutomatedTesting/Levels/AtomFeatureIntegrationBenchmark/tags.txt b/AutomatedTesting/Levels/AtomFeatureIntegrationBenchmark/tags.txt new file mode 100644 index 0000000000..a5e0705349 --- /dev/null +++ b/AutomatedTesting/Levels/AtomFeatureIntegrationBenchmark/tags.txt @@ -0,0 +1,12 @@ +495.045,510.96,35.8437,-0.166,0,-1.82124 +4.79827,4.71364,64.7838,-1.41886,0,2.48964 +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/Code/Editor/CMakeLists.txt b/Code/Editor/CMakeLists.txt index fe694f165a..fca16a2093 100644 --- a/Code/Editor/CMakeLists.txt +++ b/Code/Editor/CMakeLists.txt @@ -163,6 +163,8 @@ ly_add_target( editor_files.cmake PLATFORM_INCLUDE_FILES Platform/${PAL_PLATFORM_NAME}/editor_${PAL_PLATFORM_NAME_LOWERCASE}.cmake + TARGET_PROPERTIES + LY_INSTALL_GENERATE_RUN_TARGET TRUE BUILD_DEPENDENCIES PRIVATE 3rdParty::Qt::Core diff --git a/Code/Editor/Core/QtEditorApplication.cpp b/Code/Editor/Core/QtEditorApplication.cpp index 46e789cd7c..5a4763d8e8 100644 --- a/Code/Editor/Core/QtEditorApplication.cpp +++ b/Code/Editor/Core/QtEditorApplication.cpp @@ -415,33 +415,37 @@ namespace Editor } // Ensure that the Windows WM_INPUT messages get passed through to the AzFramework input system. - // These events are now consumed both in and out of game mode. - if (msg->message == WM_INPUT) + // These events are only broadcast in game mode. In Editor mode, RenderViewportWidget creates synthetic + // keyboard and mouse events via Qt. + if (GetIEditor()->IsInGameMode()) { - UINT rawInputSize; - const UINT rawInputHeaderSize = sizeof(RAWINPUTHEADER); - GetRawInputData((HRAWINPUT)msg->lParam, RID_INPUT, NULL, &rawInputSize, rawInputHeaderSize); - - AZStd::array rawInputBytesArray; - LPBYTE rawInputBytes = rawInputBytesArray.data(); - - const UINT bytesCopied = GetRawInputData((HRAWINPUT)msg->lParam, RID_INPUT, rawInputBytes, &rawInputSize, rawInputHeaderSize); - CRY_ASSERT(bytesCopied == rawInputSize); - - RAWINPUT* rawInput = (RAWINPUT*)rawInputBytes; - CRY_ASSERT(rawInput); - - AzFramework::RawInputNotificationBusWindows::Broadcast(&AzFramework::RawInputNotificationsWindows::OnRawInputEvent, *rawInput); - - return false; - } - else if (msg->message == WM_DEVICECHANGE) - { - if (msg->wParam == 0x0007) // DBT_DEVNODES_CHANGED + if (msg->message == WM_INPUT) { - AzFramework::RawInputNotificationBusWindows::Broadcast(&AzFramework::RawInputNotificationsWindows::OnRawInputDeviceChangeEvent); + UINT rawInputSize; + const UINT rawInputHeaderSize = sizeof(RAWINPUTHEADER); + GetRawInputData((HRAWINPUT)msg->lParam, RID_INPUT, NULL, &rawInputSize, rawInputHeaderSize); + + AZStd::array rawInputBytesArray; + LPBYTE rawInputBytes = rawInputBytesArray.data(); + + const UINT bytesCopied = GetRawInputData((HRAWINPUT)msg->lParam, RID_INPUT, rawInputBytes, &rawInputSize, rawInputHeaderSize); + CRY_ASSERT(bytesCopied == rawInputSize); + + RAWINPUT* rawInput = (RAWINPUT*)rawInputBytes; + CRY_ASSERT(rawInput); + + AzFramework::RawInputNotificationBusWindows::Broadcast(&AzFramework::RawInputNotificationsWindows::OnRawInputEvent, *rawInput); + + return false; + } + else if (msg->message == WM_DEVICECHANGE) + { + if (msg->wParam == 0x0007) // DBT_DEVNODES_CHANGED + { + AzFramework::RawInputNotificationBusWindows::Broadcast(&AzFramework::RawInputNotificationsWindows::OnRawInputDeviceChangeEvent); + } + return true; } - return true; } return false; diff --git a/Code/Editor/Util/AffineParts.h b/Code/Editor/Util/AffineParts.h index cb2c609ecd..a2e3d2c79b 100644 --- a/Code/Editor/Util/AffineParts.h +++ b/Code/Editor/Util/AffineParts.h @@ -20,11 +20,11 @@ struct AffineParts Vec3 scale; //!< Stretch factors. float fDet; //!< Sign of determinant. - /** Decompose matrix to its affnie parts. + /** Decompose matrix to its affine parts. */ void Decompose(const Matrix34& mat); - /** Decompose matrix to its affnie parts. + /** Decompose matrix to its affine parts. Assume there`s no stretch rotation. */ void SpectralDecompose(const Matrix34& mat); diff --git a/Code/Framework/AzAutoGen/AzAutoGen.props b/Code/Framework/AzAutoGen/AzAutoGen.props deleted file mode 100644 index 2f809d03a6..0000000000 --- a/Code/Framework/AzAutoGen/AzAutoGen.props +++ /dev/null @@ -1,7 +0,0 @@ - - - - - True - - diff --git a/Code/Framework/AzCore/AzCore/EBus/Internal/CallstackEntry.h b/Code/Framework/AzCore/AzCore/EBus/Internal/CallstackEntry.h index 982040ee2f..bcda78aef8 100644 --- a/Code/Framework/AzCore/AzCore/EBus/Internal/CallstackEntry.h +++ b/Code/Framework/AzCore/AzCore/EBus/Internal/CallstackEntry.h @@ -84,7 +84,7 @@ namespace AZ else { AZ::Debug::Trace::Instance().Assert(__FILE__, __LINE__, AZ_FUNCTION_SIGNATURE, - "Bus has multiple threads in its callstack records. Configure MutexType on the bus, or don't send to it from multiple threads"); + "Bus %s has multiple threads in its callstack records. Configure MutexType on the bus, or don't send to it from multiple threads", BusType::GetName()); } } diff --git a/Code/Framework/AzCore/AzCore/EBus/Policies.h b/Code/Framework/AzCore/AzCore/EBus/Policies.h index 0217874416..db11043ef8 100644 --- a/Code/Framework/AzCore/AzCore/EBus/Policies.h +++ b/Code/Framework/AzCore/AzCore/EBus/Policies.h @@ -268,7 +268,7 @@ namespace AZ m_messages.pop(); if (numMessages == 1) { - m_messages.get_container().clear(); // If it was the last message, free all memory. + m_messages = {}; } } ////////////////////////////////////////////////////////////////////////// @@ -280,7 +280,7 @@ namespace AZ void Clear() { AZStd::lock_guard lock(m_messagesMutex); - m_messages.get_container().clear(); + m_messages = {}; } void SetActive(bool isActive) @@ -289,7 +289,7 @@ namespace AZ m_isActive = isActive; if (!m_isActive) { - m_messages.get_container().clear(); + m_messages = {}; } }; diff --git a/Code/Framework/AzCore/AzCore/Serialization/AZStdContainers.inl b/Code/Framework/AzCore/AzCore/Serialization/AZStdContainers.inl index 35a2d64c0d..9eb65dec76 100644 --- a/Code/Framework/AzCore/AzCore/Serialization/AZStdContainers.inl +++ b/Code/Framework/AzCore/AzCore/Serialization/AZStdContainers.inl @@ -42,8 +42,6 @@ namespace AZStd class unordered_multiset; template class bitset; - template*/ > - class stack; template class intrusive_ptr; diff --git a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.cpp b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.cpp index 74038c8aed..6b89b8c044 100644 --- a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.cpp +++ b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.cpp @@ -57,6 +57,7 @@ namespace AZ::Internal // and avoid all this logic. using namespace AZ::SettingsRegistryMergeUtils; + using FixedValueString = AZ::SettingsRegistryInterface::FixedValueString; AZ::IO::FixedMaxPath engineRoot; if (auto engineManifestPath = AZ::Utils::GetEngineManifestPath(); !engineManifestPath.empty()) @@ -72,45 +73,16 @@ namespace AZ::Internal struct EngineInfo { AZ::IO::FixedMaxPath m_path; - AZ::SettingsRegistryInterface::FixedValueString m_moniker; + FixedValueString m_moniker; }; struct EnginePathsVisitor : public AZ::SettingsRegistryInterface::Visitor { void Visit( - [[maybe_unused]] AZStd::string_view path, [[maybe_unused]] AZStd::string_view valueName, + [[maybe_unused]] AZStd::string_view path, AZStd::string_view valueName, [[maybe_unused]] AZ::SettingsRegistryInterface::Type type, AZStd::string_view value) override { - m_enginePaths.emplace_back(EngineInfo{AZ::IO::FixedMaxPath{value}.LexicallyNormal(), {}}); - } - - AZ::SettingsRegistryInterface::VisitResponse Traverse( - [[maybe_unused]] AZStd::string_view path, AZStd::string_view valueName, - AZ::SettingsRegistryInterface::VisitAction action, AZ::SettingsRegistryInterface::Type type) override - { - auto response = AZ::SettingsRegistryInterface::VisitResponse::Continue; - if (action == AZ::SettingsRegistryInterface::VisitAction::Begin) - { - if (type == AZ::SettingsRegistryInterface::Type::Array) - { - if (valueName.compare("engines") != 0) - { - response = AZ::SettingsRegistryInterface::VisitResponse::Skip; - } - } - } - else if (action == AZ::SettingsRegistryInterface::VisitAction::Value) - { - if (type == AZ::SettingsRegistryInterface::Type::String) - { - if (valueName.compare("path") != 0) - { - response = AZ::SettingsRegistryInterface::VisitResponse::Skip; - } - } - } - - return response; + m_enginePaths.emplace_back(EngineInfo{ AZ::IO::FixedMaxPath{value}.LexicallyNormal(), FixedValueString{valueName} }); } AZStd::vector m_enginePaths{}; @@ -119,11 +91,11 @@ namespace AZ::Internal EnginePathsVisitor pathVisitor; if (manifestLoaded) { - auto enginePathsKey = AZ::SettingsRegistryInterface::FixedValueString::format("%s/engines", EngineManifestRootKey); + auto enginePathsKey = FixedValueString::format("%s/engines_path", EngineManifestRootKey); settingsRegistry.Visit(pathVisitor, enginePathsKey); } - const auto engineMonikerKey = AZ::SettingsRegistryInterface::FixedValueString::format("%s/engine_name", EngineSettingsRootKey); + const auto engineMonikerKey = FixedValueString::format("%s/engine_name", EngineSettingsRootKey); AZStd::set projectPathsNotFound; @@ -135,7 +107,15 @@ namespace AZ::Internal if (settingsRegistry.MergeSettingsFile( engineSettingsPath.Native(), AZ::SettingsRegistryInterface::Format::JsonMergePatch, EngineSettingsRootKey)) { - settingsRegistry.Get(engineInfo.m_moniker, engineMonikerKey); + FixedValueString engineName; + settingsRegistry.Get(engineName, engineMonikerKey); + AZ_Warning("SettingsRegistryMergeUtils",engineInfo.m_moniker == engineName, + R"(The engine name key "%s" mapped to engine path "%s" within the global manifest of "%s")" + R"( does not match the "engine_name" field "%s" in the engine.json)" "\n" + "This engine should be re-registered.", + engineInfo.m_moniker.c_str(), engineInfo.m_path.c_str(), engineManifestPath.c_str(), + engineName.c_str()) + engineInfo.m_moniker = engineName; } } diff --git a/Code/Framework/AzCore/AzCore/std/containers/queue.h b/Code/Framework/AzCore/AzCore/std/containers/queue.h index f1df1dd787..8026ebe943 100644 --- a/Code/Framework/AzCore/AzCore/std/containers/queue.h +++ b/Code/Framework/AzCore/AzCore/std/containers/queue.h @@ -5,206 +5,17 @@ * SPDX-License-Identifier: Apache-2.0 OR MIT * */ -#ifndef AZSTD_QUEUE_H -#define AZSTD_QUEUE_H 1 +#pragma once #include #include #include +#include namespace AZStd { - /** - * FIFO queue complaint with \ref CStd (23.2.3.1) - * The only extension we have is that we allow access - * to the underlying container via: get_container function. - * Check the queue \ref AZStdExamples. - */ - template > - class queue - { - enum - { - CONTAINER_VERSION = 1 - }; - public: - typedef queue this_type; - typedef Container container_type; - typedef typename Container::value_type value_type; - typedef typename Container::size_type size_type; - typedef typename Container::reference reference; - typedef typename Container::const_reference const_reference; - - AZ_FORCE_INLINE queue() {} - - AZ_FORCE_INLINE explicit queue(const container_type& container) - : m_container(container) {} - AZ_FORCE_INLINE bool empty() const { return m_container.empty(); } - AZ_FORCE_INLINE size_type size() const { return m_container.size(); } - AZ_FORCE_INLINE reference front() { return m_container.front(); } - AZ_FORCE_INLINE const_reference front() const { return m_container.front(); } - AZ_FORCE_INLINE reference back() { return m_container.back(); } - AZ_FORCE_INLINE const_reference back() const { return m_container.back(); } - AZ_FORCE_INLINE void push(const value_type& value) { m_container.push_back(value); } - AZ_FORCE_INLINE void pop() { m_container.pop_front(); } - - AZ_FORCE_INLINE void push() { m_container.push_back(); } - - AZ_FORCE_INLINE queue(this_type&& rhs) - : m_container(AZStd::move(rhs.m_container)) {} - AZ_FORCE_INLINE explicit queue(Container&& container) - : m_container(AZStd::move(container)) {} - this_type& operator=(this_type&& rhs) - { - m_container = AZStd::move(rhs.m_container); - return (*this); - } - void push(value_type&& value) { m_container.push_back(AZStd::move(value)); } - template - void emplace(Args&&... args) { m_container.emplace_back(AZStd::forward(args)...); } - void swap(this_type& rhs) { AZStd::swap(m_container, rhs.m_container); } - - AZ_FORCE_INLINE Container& get_container() { return m_container; } - AZ_FORCE_INLINE const Container& get_container() const { return m_container; } - - protected: - Container m_container; - }; - - // queue TEMPLATE FUNCTIONS - template - AZ_FORCE_INLINE bool operator==(const AZStd::queue& left, const AZStd::queue& right) - { - return left.get_container() == right.get_container(); - } - - template - AZ_FORCE_INLINE bool operator!=(const AZStd::queue& left, const AZStd::queue& right) - { - return left.get_container() != right.get_container(); - } - - /* template - AZ_FORCE_INLINE bool operator<(const queue& left, const queue& right) - { - return left.get_container() < right.get_container(); - } - - template - AZ_FORCE_INLINE bool operator>(const queue& left, const queue& right) - { - return left.get_container() > right.get_container(); - } - - template - AZ_FORCE_INLINE operator<=(const queue& left, const queue& right) - { - return left.get_container() <= right.get_container(); - } - - template - AZ_FORCE_INLINE bool operator>=(const queue& left, const queue& right) - { - return left.get_container() >= right.get_container(); - }*/ - - /** - * Priority queue is complaint with \ref CStd (23.2.3.2) - * The only extension we have is that we allow access - * to the underlying container via: get_container function. - * Check the priority_queue \ref AZStdExamples. - */ - template, class Predicate = AZStd::less > - class priority_queue - { - enum - { - CONTAINER_VERSION = 1 - }; - public: - typedef priority_queue this_type; - typedef Container container_type; - typedef typename Container::value_type value_type; - typedef typename Container::size_type size_type; - typedef typename Container::reference reference; - typedef typename Container::const_reference const_reference; - - AZ_FORCE_INLINE priority_queue() {} - AZ_FORCE_INLINE explicit priority_queue(const Predicate& comp) - : m_comp(comp) {} - AZ_FORCE_INLINE priority_queue(const Predicate& comp, const container_type& container) - : m_container(container) - , m_comp(comp) - { - // construct by copying specified container, comparator - AZStd::make_heap(m_container.begin(), m_container.end(), comp); - } - template - AZ_FORCE_INLINE priority_queue(InputIterator first, InputIterator last) - : m_container(first, last) - , m_comp() - { - AZStd::make_heap(m_container.begin(), m_container.end(), m_comp); - } - - template - AZ_FORCE_INLINE priority_queue(InputIterator first, InputIterator last, const Predicate& comp) - : m_container(first, last) - , m_comp(comp) - { // construct by copying [_First, _Last), specified comparator - AZStd::make_heap(m_container.begin(), m_container.end(), m_comp); - } - - template - AZ_FORCE_INLINE priority_queue(InputIterator first, InputIterator last, const Predicate& comp, const container_type& container) - : m_container(container) - , m_comp(comp) - { // construct by copying [_First, _Last), container, and comparator - m_container.insert(m_container.end(), first, last); - AZStd::make_heap(m_container.begin(), m_container.end(), m_comp); - } - - AZ_FORCE_INLINE bool empty() const { return m_container.empty(); } - AZ_FORCE_INLINE size_type size() const { return m_container.size(); } - AZ_FORCE_INLINE const_reference top() const { return m_container.front(); } - AZ_FORCE_INLINE reference top() { return m_container.front(); } - AZ_FORCE_INLINE void push(const value_type& value) - { - m_container.push_back(value); - AZStd::push_heap(m_container.begin(), m_container.end(), m_comp); - } - - AZ_FORCE_INLINE void pop() - { - AZStd::pop_heap(m_container.begin(), m_container.end(), m_comp); - m_container.pop_back(); - } - - AZ_FORCE_INLINE priority_queue(this_type&& rhs) - : m_container(AZStd::move(rhs.m_container)) - , m_comp(AZStd::move(rhs.m_comp)) {} - AZ_FORCE_INLINE explicit priority_queue(const Predicate& pred, Container&& container) - : m_container(AZStd::move(container)) - , m_comp(pred) {} - this_type& operator=(this_type&& rhs) - { - m_container = AZStd::move(rhs.m_container); - m_comp = AZStd::move(rhs.m_comp); - return (*this); - } - void push(value_type&& value) { m_container.push_back(AZStd::move(value)); AZStd::push_heap(m_container.begin(), m_container.end(), m_comp); } - template - void emplace(Args&& args) { m_container.emplace_back(AZStd::forward(args)); AZStd::push_heap(m_container.begin(), m_container.end(), m_comp); } - void swap(this_type& rhs) { AZStd::swap(m_container, rhs.m_container); AZStd::swap(m_comp, rhs.m_comp); } - - AZ_FORCE_INLINE Container& get_container() { return m_container; } - AZ_FORCE_INLINE const Container& get_container() const { return m_container; } - - protected: - Container m_container; - Predicate m_comp; - }; + template> + using queue = std::queue; + template, class Compare = AZStd::less> + using priority_queue = std::priority_queue; } - -#endif // AZSTD_QUEUE_H -#pragma once diff --git a/Code/Framework/AzCore/AzCore/std/containers/stack.h b/Code/Framework/AzCore/AzCore/std/containers/stack.h index 715d46c933..aa0d62d105 100644 --- a/Code/Framework/AzCore/AzCore/std/containers/stack.h +++ b/Code/Framework/AzCore/AzCore/std/containers/stack.h @@ -5,103 +5,13 @@ * SPDX-License-Identifier: Apache-2.0 OR MIT * */ -#ifndef AZSTD_STACK_H -#define AZSTD_STACK_H 1 +#pragma once #include +#include namespace AZStd { - /** - * Stack container is complaint with \ref CStd (23.2.3.3) - * The only extension we have is that we allow access - * to the underlying container via: get_container function. - * Check the stack \ref AZStdExamples. - */ - template > - class stack - { - enum - { - CONTAINER_VERSION = 1 - }; - public: - typedef stack this_type; - typedef Container container_type; - typedef typename Container::value_type value_type; - typedef typename Container::size_type size_type; - typedef typename Container::reference reference; - typedef typename Container::const_reference const_reference; - - AZ_FORCE_INLINE stack() {} - AZ_FORCE_INLINE explicit stack(const container_type& container) - : m_container(container) {} - AZ_FORCE_INLINE bool empty() const { return m_container.empty(); } - AZ_FORCE_INLINE size_type size() const { return m_container.size(); } - AZ_FORCE_INLINE reference top() { return m_container.back(); } - AZ_FORCE_INLINE const_reference top() const { return m_container.back(); } - AZ_FORCE_INLINE reference back() { return m_container.back(); } - AZ_FORCE_INLINE const_reference back() const { return m_container.back(); } - AZ_FORCE_INLINE void push(const value_type& value) { m_container.push_back(value); } - AZ_FORCE_INLINE void pop() { m_container.pop_back(); } - AZ_FORCE_INLINE void push() { m_container.push_back(); } - - AZ_FORCE_INLINE stack(this_type&& rhs) - : m_container(AZStd::move(rhs.m_container)) {} - AZ_FORCE_INLINE explicit stack(Container&& container) - : m_container(AZStd::move(container)) {} - this_type& operator=(this_type&& rhs) { m_container = AZStd::move(rhs.m_container); return *this; } - void push(value_type&& value) { m_container.push_back(AZStd::move(value)); } - template - void emplace(Args&& args) { m_container.emplace_back(AZStd::forward(args)); } - void swap(this_type&& rhs) { m_container.swap(AZStd::move(rhs.m_container)); } - - void swap(this_type& rhs) { AZStd::swap(m_container, rhs.m_container); } - - AZ_FORCE_INLINE Container& get_container() { return m_container; } - AZ_FORCE_INLINE const Container& get_container() const { return m_container; } - - protected: - Container m_container; - }; - - // queue TEMPLATE FUNCTIONS - template - AZ_FORCE_INLINE bool operator==(const AZStd::stack& left, const AZStd::stack& right) - { - return left.get_container() == right.get_container(); - } - - template - AZ_FORCE_INLINE bool operator!=(const AZStd::stack& left, const AZStd::stack& right) - { - return left.get_container() != right.get_container(); - } - - /* template - AZ_FORCE_INLINE bool operator<(const queue& left, const queue& right) - { - return left.get_container() < right.get_container(); - } - - template - AZ_FORCE_INLINE bool operator>(const queue& left, const queue& right) - { - return left.get_container() > right.get_container(); - } - - template - AZ_FORCE_INLINE operator<=(const queue& left, const queue& right) - { - return left.get_container() <= right.get_container(); - } - - template - AZ_FORCE_INLINE bool operator>=(const queue& left, const queue& right) - { - return left.get_container() >= right.get_container(); - }*/ + template> + using stack = std::stack; } - -#endif // AZSTD_STACK_H -#pragma once diff --git a/Code/Framework/AzCore/Tests/AZStd/DequeAndSimilar.cpp b/Code/Framework/AzCore/Tests/AZStd/DequeAndSimilar.cpp index 587d900b90..33a5d29b4f 100644 --- a/Code/Framework/AzCore/Tests/AZStd/DequeAndSimilar.cpp +++ b/Code/Framework/AzCore/Tests/AZStd/DequeAndSimilar.cpp @@ -298,7 +298,7 @@ namespace UnitTest AZ_TEST_ASSERT(int_queue.empty()); AZ_TEST_ASSERT(int_queue.size() == 0); - // Queue uses deque as default container, so try to contruct to queue from a deque. + // Queue uses deque as default container, so try to construct to queue from a deque. deque container(40, 10); int_queue_type int_queue2(container); AZ_TEST_ASSERT(!int_queue2.empty()); @@ -324,7 +324,7 @@ namespace UnitTest AZ_TEST_ASSERT(int_queue2.size() == 40); AZ_TEST_ASSERT(int_queue2.back() == 20); - int_queue.push(); + int_queue.emplace(); AZ_TEST_ASSERT(!int_queue.empty()); AZ_TEST_ASSERT(int_queue.size() == 1); @@ -423,7 +423,7 @@ namespace UnitTest AZ_TEST_ASSERT(int_stack2.size() == 40); AZ_TEST_ASSERT(int_stack2.top() == 10); - int_stack.push(); + int_stack.emplace(); AZ_TEST_ASSERT(!int_stack.empty()); AZ_TEST_ASSERT(int_stack.size() == 1); // StackContainerTest-End @@ -669,4 +669,19 @@ namespace UnitTest ++iteration; } } + + using StackContainerTestFixture = ScopedAllocatorSetupFixture; + + TEST_F(StackContainerTestFixture, StackEmplaceOperator_SupportsZeroOrMoreArguments) + { + using TestPairType = AZStd::pair; + AZStd::stack testStack; + testStack.emplace(); + testStack.emplace(1); + testStack.emplace(2, 3); + + using ContainerType = typename AZStd::stack::container_type; + AZStd::stack expectedStack(ContainerType{ TestPairType{ 0, 0 }, TestPairType{ 1, 0 }, TestPairType{ 2, 3 } }); + EXPECT_EQ(expectedStack, testStack); + } } diff --git a/Code/Framework/AzFramework/AzFramework/Physics/Configuration/RigidBodyConfiguration.cpp b/Code/Framework/AzFramework/AzFramework/Physics/Configuration/RigidBodyConfiguration.cpp index a61f6a4845..cfea76f1d7 100644 --- a/Code/Framework/AzFramework/AzFramework/Physics/Configuration/RigidBodyConfiguration.cpp +++ b/Code/Framework/AzFramework/AzFramework/Physics/Configuration/RigidBodyConfiguration.cpp @@ -123,6 +123,12 @@ namespace AzPhysics ->Field("Kinematic", &RigidBodyConfiguration::m_kinematic) ->Field("CCD Enabled", &RigidBodyConfiguration::m_ccdEnabled) ->Field("Compute Mass", &RigidBodyConfiguration::m_computeMass) + ->Field("Lock Linear X", &RigidBodyConfiguration::m_lockLinearX) + ->Field("Lock Linear Y", &RigidBodyConfiguration::m_lockLinearY) + ->Field("Lock Linear Z", &RigidBodyConfiguration::m_lockLinearZ) + ->Field("Lock Angular X", &RigidBodyConfiguration::m_lockAngularX) + ->Field("Lock Angular Y", &RigidBodyConfiguration::m_lockAngularY) + ->Field("Lock Angular Z", &RigidBodyConfiguration::m_lockAngularZ) ->Field("Mass", &RigidBodyConfiguration::m_mass) ->Field("Compute COM", &RigidBodyConfiguration::m_computeCenterOfMass) ->Field("Centre of mass offset", &RigidBodyConfiguration::m_centerOfMassOffset) diff --git a/Code/Framework/AzFramework/AzFramework/Physics/Configuration/RigidBodyConfiguration.h b/Code/Framework/AzFramework/AzFramework/Physics/Configuration/RigidBodyConfiguration.h index 59829e740c..51dcce842d 100644 --- a/Code/Framework/AzFramework/AzFramework/Physics/Configuration/RigidBodyConfiguration.h +++ b/Code/Framework/AzFramework/AzFramework/Physics/Configuration/RigidBodyConfiguration.h @@ -62,6 +62,16 @@ namespace AzPhysics bool m_computeInertiaTensor = true; bool m_computeMass = true; + // Flags to restrict motion along specific world-space axes. + bool m_lockLinearX = false; + bool m_lockLinearY = false; + bool m_lockLinearZ = false; + + // Flags to restrict rotation around specific world-space axes. + bool m_lockAngularX = false; + bool m_lockAngularY = false; + bool m_lockAngularZ = false; + //! If set, non-simulated shapes will also be included in the mass properties calculation. bool m_includeAllShapesInMassCalculation = false; diff --git a/Code/Framework/AzFramework/AzFramework/UnitTest/TestDebugDisplayRequests.cpp b/Code/Framework/AzFramework/AzFramework/UnitTest/TestDebugDisplayRequests.cpp index cc663f36b8..ecbacd125b 100644 --- a/Code/Framework/AzFramework/AzFramework/UnitTest/TestDebugDisplayRequests.cpp +++ b/Code/Framework/AzFramework/AzFramework/UnitTest/TestDebugDisplayRequests.cpp @@ -32,7 +32,7 @@ namespace UnitTest void TestDebugDisplayRequests::DrawWireBox(const AZ::Vector3& min, const AZ::Vector3& max) { - const AZ::Transform& tm = m_transforms.back(); + const AZ::Transform& tm = m_transforms.top(); m_points.push_back(tm.TransformPoint(AZ::Vector3(min.GetX(), min.GetY(), min.GetZ()))); m_points.push_back(tm.TransformPoint(AZ::Vector3(min.GetX(), min.GetY(), max.GetZ()))); m_points.push_back(tm.TransformPoint(AZ::Vector3(min.GetX(), max.GetY(), min.GetZ()))); @@ -50,7 +50,7 @@ namespace UnitTest void TestDebugDisplayRequests::DrawWireQuad(float width, float height) { - const AZ::Transform& tm = m_transforms.back(); + const AZ::Transform& tm = m_transforms.top(); m_points.push_back(tm.TransformPoint(AZ::Vector3(-0.5f * width, 0.0f, -0.5f * height))); m_points.push_back(tm.TransformPoint(AZ::Vector3(-0.5f * width, 0.0f, 0.5f * height))); m_points.push_back(tm.TransformPoint(AZ::Vector3(0.5f * width, 0.0f, -0.5f * height))); @@ -64,7 +64,7 @@ namespace UnitTest void TestDebugDisplayRequests::DrawPoints(const AZStd::vector& points) { - const AZ::Transform& tm = m_transforms.back(); + const AZ::Transform& tm = m_transforms.top(); for (const auto& point : points) { m_points.push_back(tm.TransformPoint(point)); @@ -100,7 +100,7 @@ namespace UnitTest void TestDebugDisplayRequests::PushMatrix(const AZ::Transform& tm) { - m_transforms.push(m_transforms.back() * tm); + m_transforms.push(m_transforms.top() * tm); } void TestDebugDisplayRequests::PopMatrix() diff --git a/Code/Framework/AzFramework/AzFramework/Visibility/OctreeSystemComponent.cpp b/Code/Framework/AzFramework/AzFramework/Visibility/OctreeSystemComponent.cpp index 5ef1cda30e..b4cad8511f 100644 --- a/Code/Framework/AzFramework/AzFramework/Visibility/OctreeSystemComponent.cpp +++ b/Code/Framework/AzFramework/AzFramework/Visibility/OctreeSystemComponent.cpp @@ -481,7 +481,7 @@ namespace AzFramework if (!m_freeOctreeNodes.empty()) { // Take a free block of child nodes from our free list - ExtractPageAndOffsetFromIndex(m_freeOctreeNodes.back(), nextChildPage, nextChildOffset); + ExtractPageAndOffsetFromIndex(m_freeOctreeNodes.top(), nextChildPage, nextChildOffset); m_freeOctreeNodes.pop(); } else diff --git a/Code/Framework/AzFramework/Platform/Linux/AzFramework/API/ApplicationAPI_Linux.h b/Code/Framework/AzFramework/Platform/Linux/AzFramework/API/ApplicationAPI_Linux.h index 03c65ce0c3..9b57d1d49e 100644 --- a/Code/Framework/AzFramework/Platform/Linux/AzFramework/API/ApplicationAPI_Linux.h +++ b/Code/Framework/AzFramework/Platform/Linux/AzFramework/API/ApplicationAPI_Linux.h @@ -35,7 +35,7 @@ namespace AzFramework class LinuxXcbConnectionManager { public: - AZ_RTTI(LinuxXcbConnectionManager, "{649951316-3626-4C9D-9DCA-2E7ABF84C0A9}"); + AZ_RTTI(LinuxXcbConnectionManager, "{1F756E14-8D74-42FD-843C-4863307710DB}"); virtual ~LinuxXcbConnectionManager() = default; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceToTemplatePropagator.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceToTemplatePropagator.cpp index c7bf72ff7b..8f93ebb6df 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceToTemplatePropagator.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceToTemplatePropagator.cpp @@ -177,7 +177,7 @@ namespace AzToolsFramework PrefabDomUtils::ApplyPatches(templateDomReference, templateDomReference.GetAllocator(), providedPatch); //trigger propagation - if (result.GetOutcome() != AZ::JsonSerializationResult::Outcomes::Success) + if (result.GetProcessing() != AZ::JsonSerializationResult::Processing::Completed) { AZ_Error("Prefab", false, "Patch was not successfully applied."); return false; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp index 16db933192..a03e48062c 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp @@ -90,10 +90,11 @@ namespace AzToolsFramework AZStd::unordered_map nestedInstanceLinkPatchesMap; // Retrieve all entities affected and identify Instances - if (!RetrieveAndSortPrefabEntitiesAndInstances(inputEntityList, commonRootEntityOwningInstance->get(), entities, instances)) + PrefabOperationResult retrieveEntitiesAndInstancesOutcome = RetrieveAndSortPrefabEntitiesAndInstances( + inputEntityList, commonRootEntityOwningInstance->get(), entities, instances); + if (!retrieveEntitiesAndInstancesOutcome.IsSuccess()) { - return AZ::Failure( - AZStd::string("Could not create a new prefab out of the entities provided - invalid selection.")); + return retrieveEntitiesAndInstancesOutcome; } AZStd::unordered_map oldEntityAliases; @@ -646,7 +647,12 @@ namespace AzToolsFramework { // Retrieve all nested instances that are part of the subtree under the current entity. EntityList entities; - RetrieveAndSortPrefabEntitiesAndInstances({ entity }, beforeOwningInstance->get(), entities, instancesInvolved); + PrefabOperationResult retrieveEntitiesAndInstancesOutcome = RetrieveAndSortPrefabEntitiesAndInstances( + { entity }, beforeOwningInstance->get(), entities, instancesInvolved); + if (!retrieveEntitiesAndInstancesOutcome.IsSuccess()) + { + return retrieveEntitiesAndInstancesOutcome; + } } for (Instance* instance : instancesInvolved) @@ -748,7 +754,9 @@ namespace AzToolsFramework AZStd::vector instances; // Retrieve all descendant entities and instances of this entity that belonged to the same owning instance. - RetrieveAndSortPrefabEntitiesAndInstances({ entity }, beforeOwningInstance->get(), entities, instances); + PrefabOperationResult retrieveEntitiesAndInstancesOutcome = RetrieveAndSortPrefabEntitiesAndInstances( + { entity }, beforeOwningInstance->get(), entities, instances); + AZ_Error("Prefab", retrieveEntitiesAndInstancesOutcome.IsSuccess(), retrieveEntitiesAndInstancesOutcome.GetError().data()); AZStd::vector> instanceUniquePtrs; AZStd::vector> instancePatches; @@ -981,11 +989,12 @@ namespace AzToolsFramework AZStd::vector instances; EntityList inputEntityList = EntityIdSetToEntityList(duplicationSet); - bool success = RetrieveAndSortPrefabEntitiesAndInstances(inputEntityList, commonOwningInstance->get(), entities, instances); + PrefabOperationResult retrieveEntitiesAndInstancesOutcome = + RetrieveAndSortPrefabEntitiesAndInstances(inputEntityList, commonOwningInstance->get(), entities, instances); - if (!success) + if (!retrieveEntitiesAndInstancesOutcome.IsSuccess()) { - return AZ::Failure(AZStd::string("Failed to retrieve entities and instances from the given list of entity ids for duplication")); + return AZStd::move(retrieveEntitiesAndInstancesOutcome); } // Take a snapshot of the instance DOM before we manipulate it @@ -1128,11 +1137,12 @@ namespace AzToolsFramework AZStd::vector entities; AZStd::vector instances; - bool success = RetrieveAndSortPrefabEntitiesAndInstances(inputEntityList, commonOwningInstance->get(), entities, instances); + PrefabOperationResult retrieveEntitiesAndInstancesOutcome = + RetrieveAndSortPrefabEntitiesAndInstances(inputEntityList, commonOwningInstance->get(), entities, instances); - if (!success) + if (!retrieveEntitiesAndInstancesOutcome.IsSuccess()) { - return AZ::Failure(AZStd::string("DeleteEntitiesAndAllDescendantsInInstance")); + return AZStd::move(retrieveEntitiesAndInstancesOutcome); } for (AZ::Entity* entity : entities) @@ -1405,13 +1415,16 @@ namespace AzToolsFramework return nullptr; } - bool PrefabPublicHandler::RetrieveAndSortPrefabEntitiesAndInstances( - const EntityList& inputEntities, Instance& commonRootEntityOwningInstance, - EntityList& outEntities, AZStd::vector& outInstances) const + PrefabOperationResult PrefabPublicHandler::RetrieveAndSortPrefabEntitiesAndInstances( + const EntityList& inputEntities, + Instance& commonRootEntityOwningInstance, + EntityList& outEntities, + AZStd::vector& outInstances) const { if (inputEntities.size() == 0) { - return false; + return AZ::Failure( + AZStd::string("An empty list of input entities is provided to retrieve the prefab entities and instances.")); } AZStd::queue entityQueue; @@ -1438,8 +1451,8 @@ namespace AzToolsFramework AZ_Assert( owningInstance.has_value(), "An error occurred while retrieving entities and prefab instances : " - "Owning instance of entity with id '%llu' couldn't be found", - entity->GetId()); + "Owning instance of entity with name '%s' and id '%llu' couldn't be found", + entity->GetName().c_str(), static_cast(entity->GetId())); // Check if this entity is owned by the same instance owning the root. if (&owningInstance->get() == &commonRootEntityOwningInstance) @@ -1480,7 +1493,10 @@ namespace AzToolsFramework else { // This can only happen if one entity does not share the common root! - return false; + return AZ::Failure(AZStd::string::format( + "Entity with name '%s' and id '%llu' has an owning instance that doesn't belong to the instance " + "hierarchy of the selected entities.", + entity->GetName().c_str(), static_cast(entity->GetId()))); } } } @@ -1501,7 +1517,12 @@ namespace AzToolsFramework outInstances.push_back(instancePtr); } - return (outEntities.size() + outInstances.size()) > 0; + if ((outEntities.size() + outInstances.size()) == 0) + { + return AZ::Failure( + AZStd::string("An empty list of entities and prefab instances were retrieved from the selected entities")); + } + return AZ::Success(); } EntityIdList PrefabPublicHandler::GenerateEntityIdListWithoutLevelInstance( diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h index f0c88a7a79..0e24b0841d 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h @@ -64,8 +64,11 @@ namespace AzToolsFramework private: PrefabOperationResult DeleteFromInstance(const EntityIdList& entityIds, bool deleteDescendants); - bool RetrieveAndSortPrefabEntitiesAndInstances(const EntityList& inputEntities, Instance& commonRootEntityOwningInstance, - EntityList& outEntities, AZStd::vector& outInstances) const; + PrefabOperationResult RetrieveAndSortPrefabEntitiesAndInstances( + const EntityList& inputEntities, + Instance& commonRootEntityOwningInstance, + EntityList& outEntities, + AZStd::vector& outInstances) const; EntityIdList GenerateEntityIdListWithoutLevelInstance(const EntityIdList& entityIds) const; InstanceOptionalReference GetOwnerInstanceByEntityId(AZ::EntityId entityId) const; diff --git a/Code/Framework/GridMate/GridMate/Replica/ReplicaMgr.cpp b/Code/Framework/GridMate/GridMate/Replica/ReplicaMgr.cpp index 2bc8d3e9f0..b8eea00871 100644 --- a/Code/Framework/GridMate/GridMate/Replica/ReplicaMgr.cpp +++ b/Code/Framework/GridMate/GridMate/Replica/ReplicaMgr.cpp @@ -1688,12 +1688,12 @@ namespace GridMate return; //No connections to update } bool updateRate = false; - AZ::u32 minRateBytesPerSecond = m_connByCongestionState.top().m_rate; + AZ::u32 minRateBytesPerSecond = m_connByCongestionState.front().m_rate; //const AZ::u32 old = minRateBytesPerSecond; //For debugging - auto connIt = AZStd::find(m_connByCongestionState.get_container().begin(), m_connByCongestionState.get_container().end(), id); + auto connIt = AZStd::find(m_connByCongestionState.begin(), m_connByCongestionState.end(), id); - if ( connIt == m_connByCongestionState.get_container().end()) + if ( connIt == m_connByCongestionState.end()) { return; //Already disconnected } @@ -1708,11 +1708,11 @@ namespace GridMate //If new min or old min increased, rebuild the heap and send an update if (bytesPerSecond < minRateBytesPerSecond - || (id == m_connByCongestionState.top().m_connection && bytesPerSecond > minRateBytesPerSecond)) + || (id == m_connByCongestionState.front().m_connection && bytesPerSecond > minRateBytesPerSecond)) { updateRate = true; minRateBytesPerSecond = bytesPerSecond; - AZStd::make_heap(m_connByCongestionState.get_container().begin(), m_connByCongestionState.get_container().end()); + AZStd::make_heap(m_connByCongestionState.begin(), m_connByCongestionState.end()); } } diff --git a/Code/Framework/GridMate/GridMate/Replica/ReplicaMgr.h b/Code/Framework/GridMate/GridMate/Replica/ReplicaMgr.h index 93a46d8ad7..bd9f1a1ee9 100644 --- a/Code/Framework/GridMate/GridMate/Replica/ReplicaMgr.h +++ b/Code/Framework/GridMate/GridMate/Replica/ReplicaMgr.h @@ -459,7 +459,7 @@ namespace GridMate } }; static bool k_enableBackPressure; - AZStd::priority_queue m_connByCongestionState; ///< Connections priority queue sorted by congestion window + AZStd::vector m_connByCongestionState; ///< Connections priority queue sorted by congestion window /*** * Updates connection's rate in priority and updates send limit * @@ -479,7 +479,9 @@ namespace GridMate } AZ_Assert(carrier, "NULL carrier!"); - m_connByCongestionState.emplace(RateConnectionPair(AZ::u32(1500), id)); //default to 1500Bps (ex 1 Ethernet frame/second minimum) + m_connByCongestionState.emplace_back(AZ::u32(1500), id); //default to 1500Bps (ex 1 Ethernet frame/second minimum) + // Restore the heap property after pushing back another element + AZStd::push_heap(m_connByCongestionState.begin(), m_connByCongestionState.end()); } void OnDisconnect(Carrier* carrier, ConnectionID id, CarrierDisconnectReason reason) override { @@ -490,17 +492,17 @@ namespace GridMate } AZ_Assert(carrier, "NULL carrier!"); - auto connIt = AZStd::find(m_connByCongestionState.get_container().begin(), m_connByCongestionState.get_container().end(), id); - if (connIt != m_connByCongestionState.get_container().end()) + auto connIt = AZStd::find(m_connByCongestionState.begin(), m_connByCongestionState.end(), id); + if (connIt != m_connByCongestionState.end()) { //Since we are using a weakly sorted heap, we need to re-generate when the top is removed - bool remake = (connIt == m_connByCongestionState.get_container().begin()); + bool remake = (connIt == m_connByCongestionState.begin()); - m_connByCongestionState.get_container().erase(connIt); + m_connByCongestionState.erase(connIt); if (remake) { - AZStd::make_heap(m_connByCongestionState.get_container().begin(), m_connByCongestionState.get_container().end()); + AZStd::make_heap(m_connByCongestionState.begin(), m_connByCongestionState.end()); } } } diff --git a/Gems/Atom/Feature/Common/Assets/Scripts/material_find_overrides_demo.lua b/Gems/Atom/Feature/Common/Assets/Scripts/material_find_overrides_demo.lua new file mode 100644 index 0000000000..dda3974043 --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/Scripts/material_find_overrides_demo.lua @@ -0,0 +1,160 @@ +---------------------------------------------------------------------------------------------------- +-- +-- 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 +-- +-- +-- +---------------------------------------------------------------------------------------------------- + +local FindMaterialAssignmentTest = +{ + Properties = + { + Textures = + { + "materials/presets/macbeth/05_blue_flower_srgb.tif.streamingimage", + "materials/presets/macbeth/06_bluish_green_srgb.tif.streamingimage", + "materials/presets/macbeth/09_moderate_red_srgb.tif.streamingimage", + "materials/presets/macbeth/11_yellow_green_srgb.tif.streamingimage", + "materials/presets/macbeth/12_orange_yellow_srgb.tif.streamingimage", + "materials/presets/macbeth/17_magenta_srgb.tif.streamingimage" + }, + }, +} + +function randomColor() + return Color(math.random(), math.random(), math.random(), 1.0) +end + +function randomDir() + dir = {} + for i = 1, 3 do + lerpDir = math.random() + if lerpDir < 0.5 then + table.insert(dir, -1.0) + else + table.insert(dir, 1.0) + end + end + return dir +end + +function FindMaterialAssignmentTest:OnActivate() + self.timer = 0.0 + self.totalTime = 0.0 + self.totalTimeMax = 200.0 + self.timeUpdate = 2.0 + self.colors = {} + self.lerpDirs = {} + + self.assignmentIds = + { + MaterialComponentRequestBus.Event.FindMaterialAssignmentId(self.entityId, -1, "lambert"), + } + + for index = 1, #self.assignmentIds do + local id = self.assignmentIds[index] + if (id ~= nil) then + self.colors[index] = randomColor() + self.lerpDirs[index] = randomDir() + end + end + self.tickBusHandler = TickBus.Connect(self); +end + +function FindMaterialAssignmentTest:UpdateFactor(assignmentId) + local propertyName = Name("baseColor.factor") + local propertyValue = math.random() + MaterialComponentRequestBus.Event.SetPropertyOverride(self.entityId, assignmentId, propertyName, propertyValue); +end + +function FindMaterialAssignmentTest:UpdateColor(assignmentId, color) + local propertyName = Name("baseColor.color") + local propertyValue = color + MaterialComponentRequestBus.Event.SetPropertyOverride(self.entityId, assignmentId, propertyName, propertyValue); +end + +function FindMaterialAssignmentTest:UpdateTexture(assignmentId) + if (#self.Properties.Textures > 0) then + local propertyName = Name("baseColor.textureMap") + local textureName = self.Properties.Textures[ math.random( #self.Properties.Textures ) ] + Debug.Log(textureName) + local textureAssetId = AssetCatalogRequestBus.Broadcast.GetAssetIdByPath(textureName, Uuid(), false) + MaterialComponentRequestBus.Event.SetPropertyOverride(self.entityId, assignmentId, propertyName, textureAssetId); + end +end + +function FindMaterialAssignmentTest:UpdateProperties() + Debug.Log("Overriding properties...") + for index = 1, #self.assignmentIds do + local id = self.assignmentIds[index] + if (id ~= nil) then + self:UpdateFactor(id) + self:UpdateTexture(id) + end + end +end + +function FindMaterialAssignmentTest:ClearProperties() + Debug.Log("Clearing properties...") + MaterialComponentRequestBus.Event.ClearAllPropertyOverrides(self.entityId); +end + +function lerpColor(color, lerpDir, deltaTime) + local lerpSpeed = 0.5 + color.r = color.r + deltaTime * lerpDir[1] * lerpSpeed + if color.r > 1.0 then + color.r = 1.0 + lerpDir[1] = -1.0 + elseif color.r < 0 then + color.r = 0 + lerpDir[1] = 1.0 + end + + color.g = color.g + deltaTime * lerpDir[2] * lerpSpeed + if color.g > 1.0 then + color.g = 1.0 + lerpDir[2] = -1.0 + elseif color.g < 0 then + color.g = 0 + lerpDir[2] = 1.0 + end + + color.b = color.b + deltaTime * lerpDir[3] * lerpSpeed + if color.b > 1.0 then + color.b = 1.0 + lerpDir[3] = -1.0 + elseif color.b < 0 then + color.b = 0 + lerpDir[3] = 1.0 + end +end + +function FindMaterialAssignmentTest:lerpColors(deltaTime) + for index = 1, #self.assignmentIds do + local id = self.assignmentIds[index] + if (id ~= nil) then + lerpColor(self.colors[index], self.lerpDirs[index], deltaTime) + self:UpdateColor(id, self.colors[index]) + end + end +end + +function FindMaterialAssignmentTest:OnTick(deltaTime, timePoint) + self.timer = self.timer + deltaTime + self.totalTime = self.totalTime + deltaTime + self:lerpColors(deltaTime) + + if (self.timer > self.timeUpdate and self.totalTime < self.totalTimeMax) then + self.timer = self.timer - self.timeUpdate + self:UpdateProperties() + elseif self.totalTime > self.totalTimeMax then + self:ClearProperties() + self.tickBusHandler:Disconnect(self); + end +end + +return FindMaterialAssignmentTest \ No newline at end of file diff --git a/Gems/Atom/Feature/Common/Assets/Scripts/material_property_overrides_demo.lua b/Gems/Atom/Feature/Common/Assets/Scripts/material_property_overrides_demo.lua index c470748801..685fd8310b 100644 --- a/Gems/Atom/Feature/Common/Assets/Scripts/material_property_overrides_demo.lua +++ b/Gems/Atom/Feature/Common/Assets/Scripts/material_property_overrides_demo.lua @@ -53,10 +53,9 @@ function PropertyOverrideTest:OnActivate() self.originalAssignments = MaterialComponentRequestBus.Event.GetOriginalMaterialAssignments(self.entityId); self.assignmentIds = self.originalAssignments:GetKeys() - for index = 0, self.assignmentIds:Size() do - local idOutcome = self.assignmentIds:At(index) - if (idOutcome:IsSuccess()) then - local id = idOutcome:GetValue() + for index = 1, self.assignmentIds:GetSize() do + local id = self.assignmentIds[index] + if (id ~= nil) then self.colors[index] = randomColor() self.lerpDirs[index] = randomDir() end @@ -88,10 +87,9 @@ end function PropertyOverrideTest:UpdateProperties() Debug.Log("Overriding properties...") - for index = 0, self.assignmentIds:Size() do - local idOutcome = self.assignmentIds:At(index) - if (idOutcome:IsSuccess()) then - local id = idOutcome:GetValue() + for index = 1, self.assignmentIds:GetSize() do + local id = self.assignmentIds[index] + if (id ~= nil) then self:UpdateFactor(id) self:UpdateTexture(id) end @@ -134,10 +132,9 @@ function lerpColor(color, lerpDir, deltaTime) end function PropertyOverrideTest:lerpColors(deltaTime) - for index = 0, self.assignmentIds:Size() do - local idOutcome = self.assignmentIds:At(index) - if (idOutcome:IsSuccess()) then - local id = idOutcome:GetValue() + for index = 1, self.assignmentIds:GetSize() do + local id = self.assignmentIds[index] + if (id ~= nil) then lerpColor(self.colors[index], self.lerpDirs[index], deltaTime) self:UpdateColor(id, self.colors[index]) end diff --git a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Material/MaterialAssignment.h b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Material/MaterialAssignment.h index 12a9c0fccc..907b1a1740 100644 --- a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Material/MaterialAssignment.h +++ b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Material/MaterialAssignment.h @@ -5,6 +5,7 @@ * SPDX-License-Identifier: Apache-2.0 OR MIT * */ + #pragma once #include @@ -63,5 +64,8 @@ namespace AZ //! Utility function for generating a set of available material assignments in a model MaterialAssignmentMap GetMaterialAssignmentsFromModel(Data::Instance model); + //! Find an assignment id corresponding to the lod and label substring filters + MaterialAssignmentId FindMaterialAssignmentIdInModel( + const Data::Instance model, const MaterialAssignmentLodIndex lodFilter, const AZStd::string& labelFilter); } // namespace Render } // namespace AZ diff --git a/Gems/Atom/Feature/Common/Code/Source/DisplayMapper/DisplayMapperConfigurationDescriptor.cpp b/Gems/Atom/Feature/Common/Code/Source/DisplayMapper/DisplayMapperConfigurationDescriptor.cpp index 8b617cfe0f..a9ee46dd2c 100644 --- a/Gems/Atom/Feature/Common/Code/Source/DisplayMapper/DisplayMapperConfigurationDescriptor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/DisplayMapper/DisplayMapperConfigurationDescriptor.cpp @@ -124,6 +124,18 @@ namespace AZ ->Field("AcesParameterOverrides", &DisplayMapperConfigurationDescriptor::m_acesParameterOverrides) ; } + + if (auto* behaviorContext = azrtti_cast(context)) + { + behaviorContext->Class() + ->Enum<(uint32_t)DisplayMapperOperationType::Aces>("DisplayMapperOperationType_Aces") + ->Enum<(uint32_t)DisplayMapperOperationType::AcesLut>("DisplayMapperOperationType_AcesLut") + ->Enum<(uint32_t)DisplayMapperOperationType::Passthrough>("DisplayMapperOperationType_Passthrough") + ->Enum<(uint32_t)DisplayMapperOperationType::GammaSRGB>("DisplayMapperOperationType_GammaSRGB") + ->Enum<(uint32_t)DisplayMapperOperationType::Reinhard>("DisplayMapperOperationType_Reinhard") + ->Enum<(uint32_t)DisplayMapperOperationType::Invalid>("DisplayMapperOperationType_Invalid") + ; + } } void DisplayMapperPassData::Reflect(ReflectContext* context) diff --git a/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignment.cpp b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignment.cpp index ec48d57d1a..074d5c39e8 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignment.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignment.cpp @@ -166,5 +166,48 @@ namespace AZ return materials; } + + MaterialAssignmentId FindMaterialAssignmentIdInLod( + const Data::Instance& lod, const MaterialAssignmentLodIndex lodIndex, const AZStd::string& labelFilter) + { + for (const AZ::RPI::ModelLod::Mesh& mesh : lod->GetMeshes()) + { + if (mesh.m_material && mesh.m_material->GetAssetId().IsValid()) + { + AZ::Data::AssetInfo assetInfo; + AZ::Data::AssetCatalogRequestBus::BroadcastResult( + assetInfo, &AZ::Data::AssetCatalogRequests::GetAssetInfoById, mesh.m_material->GetAssetId()); + if (assetInfo.m_assetId.IsValid() && AZ::StringFunc::Contains(assetInfo.m_relativePath, labelFilter, true)) + { + return MaterialAssignmentId::CreateFromLodAndAsset(lodIndex, mesh.m_material->GetAssetId()); + } + } + } + return MaterialAssignmentId(); + } + + MaterialAssignmentId FindMaterialAssignmentIdInModel( + const Data::Instance model, const MaterialAssignmentLodIndex lodFilter, const AZStd::string& labelFilter) + { + if (model && !labelFilter.empty()) + { + if (lodFilter < model->GetLodCount()) + { + return FindMaterialAssignmentIdInLod(model->GetLods()[lodFilter], lodFilter, labelFilter); + } + + for (size_t lodIndex = 0; lodIndex < model->GetLodCount(); ++lodIndex) + { + const MaterialAssignmentId result = + FindMaterialAssignmentIdInLod(model->GetLods()[lodIndex], MaterialAssignmentId::NonLodIndex, labelFilter); + if (!result.IsDefault()) + { + return result; + } + } + } + + return MaterialAssignmentId(); + } } // namespace Render } // namespace AZ diff --git a/Gems/Atom/RHI/Code/Include/Atom/RHI/SwapChain.h b/Gems/Atom/RHI/Code/Include/Atom/RHI/SwapChain.h index 14e9968e42..c1fd4453d4 100644 --- a/Gems/Atom/RHI/Code/Include/Atom/RHI/SwapChain.h +++ b/Gems/Atom/RHI/Code/Include/Atom/RHI/SwapChain.h @@ -81,6 +81,15 @@ namespace AZ AZ_RTTI(SwapChain, "{888B64A5-D956-406F-9C33-CF6A54FC41B0}", Object); +#if defined(PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB) + // On Linux platforms that uses XCB, a resize may occur in the swap chain but the command queue may still + // reference the original surface. This flag is a temporary fix to make sure that all the swap chains + // have finished their resize events before presenting the command queue. + + // [GFX TODO][GHI - 2678] + AZStd::atomic_bool m_resized{ false }; +#endif // PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB + protected: SwapChain(); diff --git a/Gems/Atom/RHI/Code/Source/RHI/FrameGraph.cpp b/Gems/Atom/RHI/Code/Source/RHI/FrameGraph.cpp index b670dc1234..d2298cda3f 100644 --- a/Gems/Atom/RHI/Code/Source/RHI/FrameGraph.cpp +++ b/Gems/Atom/RHI/Code/Source/RHI/FrameGraph.cpp @@ -126,6 +126,7 @@ namespace AZ ResultCode FrameGraph::End() { + AZ_ATOM_PROFILE_FUNCTION("RHI", "FrameGraph: End"); ResultCode resultCode = ValidateEnd(); if (resultCode != ResultCode::Success) { diff --git a/Gems/Atom/RHI/Code/Source/RHI/FrameGraphExecuter.cpp b/Gems/Atom/RHI/Code/Source/RHI/FrameGraphExecuter.cpp index 3189acab49..342e537993 100644 --- a/Gems/Atom/RHI/Code/Source/RHI/FrameGraphExecuter.cpp +++ b/Gems/Atom/RHI/Code/Source/RHI/FrameGraphExecuter.cpp @@ -72,6 +72,7 @@ namespace AZ void FrameGraphExecuter::Begin(const FrameGraph& frameGraph) { AZ_TRACE_METHOD(); + AZ_ATOM_PROFILE_FUNCTION("RHI", "FrameGraphExecuter: Begin"); BeginInternal(frameGraph); } diff --git a/Gems/Atom/RHI/Code/Source/RHI/PipelineStateCache.cpp b/Gems/Atom/RHI/Code/Source/RHI/PipelineStateCache.cpp index 1170b82d78..0210d941dc 100644 --- a/Gems/Atom/RHI/Code/Source/RHI/PipelineStateCache.cpp +++ b/Gems/Atom/RHI/Code/Source/RHI/PipelineStateCache.cpp @@ -6,6 +6,7 @@ * */ +#include #include #include #include @@ -205,6 +206,7 @@ namespace AZ void PipelineStateCache::Compact() { + AZ_ATOM_PROFILE_FUNCTION("RHI", "PipelineStateCache: Compact"); AZStd::unique_lock lock(m_mutex); // Merge the pending cache into the read-only cache. diff --git a/Gems/Atom/RHI/Code/Source/RHI/RHISystem.cpp b/Gems/Atom/RHI/Code/Source/RHI/RHISystem.cpp index 083fb87b93..49244f776f 100644 --- a/Gems/Atom/RHI/Code/Source/RHI/RHISystem.cpp +++ b/Gems/Atom/RHI/Code/Source/RHI/RHISystem.cpp @@ -223,7 +223,7 @@ namespace AZ * own RHI scopes to the frame scheduler. This happens prior to the RPI pass graph registration. */ { - AZ_ATOM_PROFILE_TIME_GROUP_REGION("RHI", "RHISystem :FrameUpdate: OnFramePrepare"); + AZ_ATOM_PROFILE_TIME_GROUP_REGION("RHI", "RHISystem: FrameUpdate: OnFramePrepare"); RHISystemNotificationBus::Broadcast(&RHISystemNotificationBus::Events::OnFramePrepare, m_frameScheduler); } diff --git a/Gems/Atom/RHI/Code/Source/RHI/SwapChain.cpp b/Gems/Atom/RHI/Code/Source/RHI/SwapChain.cpp index b0501d937d..5fbb83fccf 100644 --- a/Gems/Atom/RHI/Code/Source/RHI/SwapChain.cpp +++ b/Gems/Atom/RHI/Code/Source/RHI/SwapChain.cpp @@ -164,6 +164,10 @@ namespace AZ m_currentImageIndex = 0; } +#if defined(PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB) + m_resized.store(true); +#endif // PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB + return resultCode; } diff --git a/Gems/Atom/RHI/DX12/Code/Source/RHI/FrameGraphCompiler.cpp b/Gems/Atom/RHI/DX12/Code/Source/RHI/FrameGraphCompiler.cpp index c98fd51fbb..ba4aa76a60 100644 --- a/Gems/Atom/RHI/DX12/Code/Source/RHI/FrameGraphCompiler.cpp +++ b/Gems/Atom/RHI/DX12/Code/Source/RHI/FrameGraphCompiler.cpp @@ -469,6 +469,8 @@ namespace AZ ResourceTransitionLoggerNull logger(imageFrameAttachment.GetId()); #endif + AZ_ATOM_PROFILE_FUNCTION("RHI", "FrameGraphCompiler: CompileImageBarriers (DX12)"); + Image& image = static_cast(*imageFrameAttachment.GetImage()); RHI::ImageScopeAttachment* scopeAttachment = imageFrameAttachment.GetFirstScopeAttachment(); diff --git a/Gems/Atom/RHI/Vulkan/Code/Source/RHI/CommandQueue.cpp b/Gems/Atom/RHI/Vulkan/Code/Source/RHI/CommandQueue.cpp index ca6c829cca..84b4964235 100644 --- a/Gems/Atom/RHI/Vulkan/Code/Source/RHI/CommandQueue.cpp +++ b/Gems/Atom/RHI/Vulkan/Code/Source/RHI/CommandQueue.cpp @@ -42,6 +42,15 @@ namespace AZ void CommandQueue::ExecuteWork(const RHI::ExecuteWorkRequest& rhiRequest) { +#if defined(PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB) + for (RHI::SwapChain* swapChain : rhiRequest.m_swapChainsToPresent) + { + if (!swapChain->m_resized) + { + return; + } + } +#endif // PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB const ExecuteWorkRequest& request = static_cast(rhiRequest); QueueCommand([=](void* queue) { diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Culling.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Culling.cpp index e192e71fc4..c630fe0e5d 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Culling.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Culling.cpp @@ -720,6 +720,7 @@ namespace AZ void CullingScene::BeginCulling(const AZStd::vector& views) { + AZ_ATOM_PROFILE_FUNCTION("RPI", "CullingScene: BeginCulling"); m_cullDataConcurrencyCheck.soft_lock(); m_debugCtx.ResetCullStats(); diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/PassSystem.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/PassSystem.cpp index f62dfa1d70..27e3612a4c 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/PassSystem.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Pass/PassSystem.cpp @@ -298,6 +298,7 @@ namespace AZ void PassSystem::ProcessQueuedChanges() { + AZ_ATOM_PROFILE_FUNCTION("RPI", "PassSystem: ProcessQueuedChanges"); RemovePasses(); BuildPasses(); InitializePasses(); @@ -313,7 +314,11 @@ namespace AZ m_state = PassSystemState::Rendering; Pass::FramePrepareParams params{ &frameGraphBuilder }; - m_rootPass->FrameBegin(params); + + { + AZ_ATOM_PROFILE_TIME_GROUP_REGION("RPI", "Pass: FrameBegin"); + m_rootPass->FrameBegin(params); + } } void PassSystem::FrameEnd() diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Scene.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Scene.cpp index bc700dd24f..071a07ecda 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Scene.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Scene.cpp @@ -408,6 +408,7 @@ namespace AZ { AZ_PROFILE_SCOPE(Debug::ProfileCategory::AzRender, "m_srgCallback"); + AZ_ATOM_PROFILE_TIME_GROUP_REGION("RPI", "ShaderResourceGroupCallback: SrgCallback"); // Set values for scene srg if (m_srg && m_srgCallback) { @@ -418,7 +419,7 @@ namespace AZ // Get active pipelines which need to be rendered and notify them frame started AZStd::vector activePipelines; { - AZ_ATOM_PROFILE_TIME_GROUP_REGION("RPI", "OnStartFrame"); + AZ_ATOM_PROFILE_TIME_GROUP_REGION("RPI", "Scene: OnStartFrame"); for (auto& pipeline : m_pipelines) { if (pipeline->NeedsRender()) @@ -483,6 +484,7 @@ namespace AZ { AZ_PROFILE_SCOPE(Debug::ProfileCategory::AzRender, "CollectDrawPackets"); + AZ_ATOM_PROFILE_TIME_GROUP_REGION("RPI", "CollectDrawPackets"); AZ::JobCompletion* collectDrawPacketsCompletion = aznew AZ::JobCompletion(); // Launch FeatureProcessor::Render() jobs diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Application/AtomToolsApplication.h b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Application/AtomToolsApplication.h new file mode 100644 index 0000000000..dc57179fcc --- /dev/null +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Application/AtomToolsApplication.h @@ -0,0 +1,115 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace AtomToolsFramework +{ + //! Base class for Atom tools to inherit from + class AtomToolsApplication + : public AzFramework::Application + , public AzQtComponents::AzQtApplication + , protected AzToolsFramework::AssetDatabase::AssetDatabaseRequestsBus::Handler + , protected AzFramework::AssetSystemStatusBus::Handler + , protected AzToolsFramework::EditorPythonConsoleNotificationBus::Handler + , protected AZ::UserSettingsOwnerRequestBus::Handler + { + public: + AZ_TYPE_INFO(AtomTools::AtomToolsApplication, "{A0DF25BA-6F74-4F11-9F85-0F99278D5986}"); + + using Base = AzFramework::Application; + + AtomToolsApplication(int* argc, char*** argv); + + ////////////////////////////////////////////////////////////////////////// + // AzFramework::Application + void CreateReflectionManager() override; + void Reflect(AZ::ReflectContext* context) override; + void RegisterCoreComponents() override; + AZ::ComponentTypeList GetRequiredSystemComponents() const override; + void CreateStaticModules(AZStd::vector& outModules) override; + const char* GetCurrentConfigurationName() const override; + void StartCommon(AZ::Entity* systemEntity) override; + void Tick(float deltaOverride = -1.f) override; + void Stop() override; + + protected: + ////////////////////////////////////////////////////////////////////////// + // AssetDatabaseRequestsBus::Handler overrides... + bool GetAssetDatabaseLocation(AZStd::string& result) override; + ////////////////////////////////////////////////////////////////////////// + + ////////////////////////////////////////////////////////////////////////// + // AzFramework::Application overrides... + void Destroy() override; + ////////////////////////////////////////////////////////////////////////// + + ////////////////////////////////////////////////////////////////////////// + // AzFramework::AssetSystemStatusBus::Handler overrides... + void AssetSystemAvailable() override; + ////////////////////////////////////////////////////////////////////////// + + ////////////////////////////////////////////////////////////////////////// + // AZ::ComponentApplication overrides... + void QueryApplicationType(AZ::ApplicationTypeQuery& appType) const override; + ////////////////////////////////////////////////////////////////////////// + + ////////////////////////////////////////////////////////////////////////// + // AZ::UserSettingsOwnerRequestBus::Handler overrides... + void SaveSettings() override; + ////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////// + // EditorPythonConsoleNotificationBus::Handler overrides... + void OnTraceMessage(AZStd::string_view message) override; + void OnErrorMessage(AZStd::string_view message) override; + void OnExceptionMessage(AZStd::string_view message) override; + //////////////////////////////////////////////////////////////////////// + + //! Executable target name generally used as a prefix for logging and other saved files + virtual AZStd::string GetBuildTargetName() const; + + //! List of filters for assets that need to be pre-built to run the application + virtual AZStd::vector GetCriticalAssetFilters() const; + + virtual void LoadSettings(); + virtual void UnloadSettings(); + virtual void CompileCriticalAssets(); + virtual void ProcessCommandLine(const AZ::CommandLine& commandLine); + virtual bool LaunchDiscoveryService(); + virtual void StartInternal(); + + static void PyIdleWaitFrames(uint32_t frames); + + AzToolsFramework::TraceLogger m_traceLogger; + + //! Local user settings are used to store material browser tree expansion state + AZ::UserSettingsProvider m_localUserSettings; + + //! Are local settings loaded + bool m_activatedLocalUserSettings = false; + + QTimer m_timer; + + AtomToolsFramework::LocalSocket m_socket; + AtomToolsFramework::LocalServer m_server; + }; +} // namespace AtomToolsFramework diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Application/AtomToolsApplication.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Application/AtomToolsApplication.cpp new file mode 100644 index 0000000000..3a542db1a4 --- /dev/null +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Application/AtomToolsApplication.cpp @@ -0,0 +1,512 @@ +/* + * 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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") // disable warnings spawned by QT +#include +#include +AZ_POP_DISABLE_WARNING + +namespace AtomToolsFramework +{ + AZStd::string AtomToolsApplication::GetBuildTargetName() const + { + return AZStd::string("AtomTools"); + } + + const char* AtomToolsApplication::GetCurrentConfigurationName() const + { +#if defined(_RELEASE) + return "ReleaseAtomTools"; +#elif defined(_DEBUG) + return "DebugAtomTools"; +#else + return "ProfileAtomTools"; +#endif + } + + AtomToolsApplication::AtomToolsApplication(int* argc, char*** argv) + : Application(argc, argv) + , AzQtApplication(*argc, *argv) + { + connect(&m_timer, &QTimer::timeout, this, [&]() + { + this->PumpSystemEventLoopUntilEmpty(); + this->Tick(); + }); + } + + void AtomToolsApplication::CreateReflectionManager() + { + Base::CreateReflectionManager(); + GetSerializeContext()->CreateEditContext(); + } + + void AtomToolsApplication::Reflect(AZ::ReflectContext* context) + { + Base::Reflect(context); + + AzToolsFramework::AssetBrowser::AssetBrowserEntry::Reflect(context); + AzToolsFramework::AssetBrowser::RootAssetBrowserEntry::Reflect(context); + AzToolsFramework::AssetBrowser::FolderAssetBrowserEntry::Reflect(context); + AzToolsFramework::AssetBrowser::SourceAssetBrowserEntry::Reflect(context); + AzToolsFramework::AssetBrowser::ProductAssetBrowserEntry::Reflect(context); + + AzToolsFramework::QTreeViewWithStateSaving::Reflect(context); + AzToolsFramework::QWidgetSavedState::Reflect(context); + + if (auto behaviorContext = azrtti_cast(context)) + { + auto targetName = GetBuildTargetName(); + + // this will put these methods into the 'azlmbr.AtomTools.general' module + auto addGeneral = [targetName](AZ::BehaviorContext::GlobalMethodBuilder methodBuilder) + { + methodBuilder->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation) + ->Attribute(AZ::Script::Attributes::Category, "Editor") + ->Attribute(AZ::Script::Attributes::Module, targetName); + }; + // The reflection here is based on patterns in CryEditPythonHandler::Reflect + addGeneral(behaviorContext->Method( + "idle_wait_frames", &AtomToolsApplication::PyIdleWaitFrames, nullptr, + "Waits idling for a frames. Primarily used for auto-testing.")); + } + } + + void AtomToolsApplication::RegisterCoreComponents() + { + Base::RegisterCoreComponents(); + RegisterComponentDescriptor(AzToolsFramework::AssetBrowser::AssetBrowserComponent::CreateDescriptor()); + RegisterComponentDescriptor(AzToolsFramework::Thumbnailer::ThumbnailerComponent::CreateDescriptor()); + RegisterComponentDescriptor(AzToolsFramework::Components::PropertyManagerComponent::CreateDescriptor()); + RegisterComponentDescriptor(AzToolsFramework::AssetSystem::AssetSystemComponent::CreateDescriptor()); + RegisterComponentDescriptor(AzToolsFramework::PerforceComponent::CreateDescriptor()); + } + + AZ::ComponentTypeList AtomToolsApplication::GetRequiredSystemComponents() const + { + AZ::ComponentTypeList components = Base::GetRequiredSystemComponents(); + + components.insert( + components.end(), + { + azrtti_typeid(), + azrtti_typeid(), + azrtti_typeid(), + azrtti_typeid(), + }); + + return components; + } + + void AtomToolsApplication::CreateStaticModules(AZStd::vector& outModules) + { + Base::CreateStaticModules(outModules); + outModules.push_back(aznew AzToolsFramework::AzToolsFrameworkModule); + } + + void AtomToolsApplication::StartCommon(AZ::Entity* systemEntity) + { + AzFramework::AssetSystemStatusBus::Handler::BusConnect(); + AzToolsFramework::EditorPythonConsoleNotificationBus::Handler::BusConnect(); + + Base::StartCommon(systemEntity); + + StartInternal(); + + m_timer.start(); + } + + void AtomToolsApplication::Destroy() + { + AzToolsFramework::EditorPythonConsoleNotificationBus::Handler::BusDisconnect(); + AzToolsFramework::AssetDatabase::AssetDatabaseRequestsBus::Handler::BusDisconnect(); + + AzFramework::AssetSystemRequestBus::Broadcast(&AzFramework::AssetSystem::AssetSystemRequests::StartDisconnectingAssetProcessor); + Base::Destroy(); + } + + AZStd::vector AtomToolsApplication::GetCriticalAssetFilters() const + { + return AZStd::vector({}); + } + + void AtomToolsApplication::AssetSystemAvailable() + { + bool connectedToAssetProcessor = false; + + // When the AssetProcessor is already launched it should take less than a second to perform a connection + // but when the AssetProcessor needs to be launch it could take up to 15 seconds to have the AssetProcessor initialize + // and able to negotiate a connection when running a debug build + // and to negotiate a connection + + auto targetName = GetBuildTargetName(); + + AzFramework::AssetSystem::ConnectionSettings connectionSettings; + AzFramework::AssetSystem::ReadConnectionSettingsFromSettingsRegistry(connectionSettings); + connectionSettings.m_connectionDirection = + AzFramework::AssetSystem::ConnectionSettings::ConnectionDirection::ConnectToAssetProcessor; + connectionSettings.m_connectionIdentifier = GetBuildTargetName(); + connectionSettings.m_loggingCallback = [targetName]([[maybe_unused]] AZStd::string_view logData) + { + AZ_TracePrintf(targetName.c_str(), "%.*s", aznumeric_cast(logData.size()), logData.data()); + }; + AzFramework::AssetSystemRequestBus::BroadcastResult( + connectedToAssetProcessor, &AzFramework::AssetSystemRequestBus::Events::EstablishAssetProcessorConnection, connectionSettings); + + if (connectedToAssetProcessor) + { + CompileCriticalAssets(); + } + + AzFramework::AssetSystemStatusBus::Handler::BusDisconnect(); + } + + void AtomToolsApplication::CompileCriticalAssets() + { + AZ_TracePrintf(GetBuildTargetName().c_str(), "Compiling critical assets.\n"); + + QStringList failedAssets; + + // Forced asset processor to synchronously process all critical assets + // Note: with AssetManager's current implementation, a compiled asset won't be added in asset registry until next system tick. + // So the asset id won't be found right after CompileAssetSync call. + for (const AZStd::string& assetFilters : GetCriticalAssetFilters()) + { + AZ_TracePrintf(GetBuildTargetName().c_str(), "Compiling critical asset matching: %s.\n", assetFilters.c_str()); + + // Wait for the asset be compiled + AzFramework::AssetSystem::AssetStatus status = AzFramework::AssetSystem::AssetStatus_Unknown; + AzFramework::AssetSystemRequestBus::BroadcastResult( + status, &AzFramework::AssetSystemRequestBus::Events::CompileAssetSync, assetFilters); + if (status != AzFramework::AssetSystem::AssetStatus_Compiled) + { + failedAssets.append(assetFilters.c_str()); + } + } + + if (!failedAssets.empty()) + { + QMessageBox::critical( + activeWindow(), QString("Failed to compile critical assets"), + QString("Failed to compile the following critical assets:\n%1\n%2") + .arg(failedAssets.join(",\n")) + .arg("Make sure this is an Atom project.")); + ExitMainLoop(); + } + } + + void AtomToolsApplication::SaveSettings() + { + if (m_activatedLocalUserSettings) + { + AZ::SerializeContext* context = nullptr; + AZ::ComponentApplicationBus::BroadcastResult(context, &AZ::ComponentApplicationRequests::GetSerializeContext); + AZ_Assert(context, "No serialize context"); + + char resolvedPath[AZ_MAX_PATH_LEN] = ""; + AZStd::string fileName = "@user@/" + GetBuildTargetName() + "UserSettings.xml"; + + AZ::IO::FileIOBase::GetInstance()->ResolvePath( + fileName.c_str(), resolvedPath, AZ_ARRAY_SIZE(resolvedPath)); + m_localUserSettings.Save(resolvedPath, context); + } + } + + void AtomToolsApplication::LoadSettings() + { + AZ::SerializeContext* context = nullptr; + AZ::ComponentApplicationBus::BroadcastResult(context, &AZ::ComponentApplicationRequests::GetSerializeContext); + AZ_Assert(context, "No serialize context"); + + char resolvedPath[AZ_MAX_PATH_LEN] = ""; + AZStd::string fileName = "@user@/" + GetBuildTargetName() + "UserSettings.xml"; + + AZ::IO::FileIOBase::GetInstance()->ResolvePath(fileName.c_str(), resolvedPath, AZ_MAX_PATH_LEN); + + m_localUserSettings.Load(resolvedPath, context); + m_localUserSettings.Activate(AZ::UserSettings::CT_LOCAL); + AZ::UserSettingsOwnerRequestBus::Handler::BusConnect(AZ::UserSettings::CT_LOCAL); + m_activatedLocalUserSettings = true; + } + + void AtomToolsApplication::UnloadSettings() + { + if (m_activatedLocalUserSettings) + { + SaveSettings(); + m_localUserSettings.Deactivate(); + AZ::UserSettingsOwnerRequestBus::Handler::BusDisconnect(); + m_activatedLocalUserSettings = false; + } + } + + void AtomToolsApplication::ProcessCommandLine(const AZ::CommandLine& commandLine) + { + const AZStd::string timeoputSwitchName = "timeout"; + if (commandLine.HasSwitch(timeoputSwitchName)) + { + const AZStd::string& timeoutValue = commandLine.GetSwitchValue(timeoputSwitchName, 0); + const uint32_t timeoutInMs = atoi(timeoutValue.c_str()); + AZ_Printf(GetBuildTargetName().c_str(), "Timeout scheduled, shutting down in %u ms", timeoutInMs); + QTimer::singleShot( + timeoutInMs, + [this] + { + AZ_Printf(GetBuildTargetName().c_str(), "Timeout reached, shutting down"); + ExitMainLoop(); + }); + } + + // Process command line options for running one or more python scripts on startup + const AZStd::string runPythonScriptSwitchName = "runpython"; + size_t runPythonScriptCount = commandLine.GetNumSwitchValues(runPythonScriptSwitchName); + for (size_t runPythonScriptIndex = 0; runPythonScriptIndex < runPythonScriptCount; ++runPythonScriptIndex) + { + const AZStd::string runPythonScriptPath = commandLine.GetSwitchValue(runPythonScriptSwitchName, runPythonScriptIndex); + AZStd::vector runPythonArgs; + + AZ_Printf(GetBuildTargetName().c_str(), "Launching script: %s", runPythonScriptPath.c_str()); + AzToolsFramework::EditorPythonRunnerRequestBus::Broadcast( + &AzToolsFramework::EditorPythonRunnerRequestBus::Events::ExecuteByFilenameWithArgs, runPythonScriptPath, runPythonArgs); + } + + const AZStd::string exitAfterCommandsSwitchName = "exitaftercommands"; + if (commandLine.HasSwitch(exitAfterCommandsSwitchName)) + { + ExitMainLoop(); + } + } + + bool AtomToolsApplication::LaunchDiscoveryService() + { + // Determine if this is the first launch of the tool by attempting to connect to a running server + if (m_socket.Connect(QApplication::applicationName())) + { + // If the server was located, the application is already running. + // Forward commandline options to other application instance. + QByteArray buffer; + buffer.append("ProcessCommandLine:"); + + // Add the command line options from this process to the message, skipping the executable path + for (int argi = 1; argi < m_argC; ++argi) + { + buffer.append(QString(m_argV[argi]).append("\n").toUtf8()); + } + + // Inject command line option to always bring the main window to the foreground + buffer.append("--activatewindow\n"); + + m_socket.Send(buffer); + m_socket.Disconnect(); + return false; + } + + // Setup server to handle basic commands + m_server.SetReadHandler( + [this](const QByteArray& buffer) + { + // Handle commmand line params from connected socket + if (buffer.startsWith("ProcessCommandLine:")) + { + // Remove header and parse commands + AZStd::string params(buffer.data(), buffer.size()); + params = params.substr(strlen("ProcessCommandLine:")); + + AZStd::vector tokens; + AZ::StringFunc::Tokenize(params, tokens, "\n"); + + if (!tokens.empty()) + { + AZ::CommandLine commandLine; + commandLine.Parse(tokens); + ProcessCommandLine(commandLine); + } + } + }); + + // Launch local server + if (!m_server.Connect(QApplication::applicationName())) + { + return false; + } + + return true; + } + + void AtomToolsApplication::StartInternal() + { + if (WasExitMainLoopRequested()) + { + return; + } + + AZStd::string fileName = GetBuildTargetName() + ".log"; + + m_traceLogger.WriteStartupLog(fileName.c_str()); + + if (!LaunchDiscoveryService()) + { + ExitMainLoop(); + return; + } + + AzToolsFramework::AssetDatabase::AssetDatabaseRequestsBus::Handler::BusConnect(); + AzToolsFramework::AssetBrowser::AssetDatabaseLocationNotificationBus::Broadcast( + &AzToolsFramework::AssetBrowser::AssetDatabaseLocationNotifications::OnDatabaseInitialized); + + AZ::Data::AssetCatalogRequestBus::Broadcast(&AZ::Data::AssetCatalogRequestBus::Events::LoadCatalog, "@assets@/assetcatalog.xml"); + + AZ::RPI::RPISystemInterface::Get()->InitializeSystemAssets(); + + LoadSettings(); + + auto editorPythonEventsInterface = AZ::Interface::Get(); + if (editorPythonEventsInterface) + { + // The PythonSystemComponent does not call StartPython to allow for lazy python initialization, so start it here + // The PythonSystemComponent will call StopPython when it deactivates, so we do not need our own corresponding call to + // StopPython + editorPythonEventsInterface->StartPython(); + } + + // Delay execution of commands and scripts post initialization + QTimer::singleShot( + 0, + [this]() + { + ProcessCommandLine(m_commandLine); + }); + } + + bool AtomToolsApplication::GetAssetDatabaseLocation(AZStd::string& result) + { + AZ::SettingsRegistryInterface* settingsRegistry = AZ::SettingsRegistry::Get(); + AZ::IO::FixedMaxPath assetDatabaseSqlitePath; + if (settingsRegistry && + settingsRegistry->Get(assetDatabaseSqlitePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_CacheProjectRootFolder)) + { + assetDatabaseSqlitePath /= "assetdb.sqlite"; + result = AZStd::string_view(assetDatabaseSqlitePath.Native()); + return true; + } + + return false; + } + + void AtomToolsApplication::Tick(float deltaOverride) + { + TickSystem(); + Base::Tick(deltaOverride); + + if (WasExitMainLoopRequested()) + { + m_timer.disconnect(); + quit(); + } + } + + void AtomToolsApplication::Stop() + { + UnloadSettings(); + Base::Stop(); + } + + void AtomToolsApplication::QueryApplicationType(AZ::ApplicationTypeQuery& appType) const + { + appType.m_maskValue = AZ::ApplicationTypeQuery::Masks::Game; + } + + void AtomToolsApplication::OnTraceMessage([[maybe_unused]] AZStd::string_view message) + { +#if defined(AZ_ENABLE_TRACING) + AZStd::vector lines; + AzFramework::StringFunc::Tokenize( + message, lines, "\n", + false, // Keep empty strings + false // Keep space strings + ); + + for (auto& line : lines) + { + AZ_TracePrintf(GetBuildTargetName().c_str(), "Python: %s\n", line.c_str()); + } +#endif + } + + void AtomToolsApplication::OnErrorMessage(AZStd::string_view message) + { + // Use AZ_TracePrintf instead of AZ_Error or AZ_Warning to avoid all the metadata noise + OnTraceMessage(message); + } + + void AtomToolsApplication::OnExceptionMessage([[maybe_unused]] AZStd::string_view message) + { + AZ_Error(GetBuildTargetName().c_str(), false, "Python: " AZ_STRING_FORMAT, AZ_STRING_ARG(message)); + } + + // Copied from PyIdleWaitFrames in CryEdit.cpp + void AtomToolsApplication::PyIdleWaitFrames(uint32_t frames) + { + struct Ticker : public AZ::TickBus::Handler + { + Ticker(QEventLoop* loop, uint32_t targetFrames) + : m_loop(loop) + , m_targetFrames(targetFrames) + { + AZ::TickBus::Handler::BusConnect(); + } + ~Ticker() + { + AZ::TickBus::Handler::BusDisconnect(); + } + + void OnTick(float deltaTime, AZ::ScriptTimePoint time) override + { + AZ_UNUSED(deltaTime); + AZ_UNUSED(time); + if (++m_elapsedFrames == m_targetFrames) + { + m_loop->quit(); + } + } + QEventLoop* m_loop = nullptr; + uint32_t m_elapsedFrames = 0; + uint32_t m_targetFrames = 0; + }; + + QEventLoop loop; + Ticker ticker(&loop, frames); + loop.exec(); + } +} // namespace AtomToolsFramework diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/atomtoolsframework_files.cmake b/Gems/Atom/Tools/AtomToolsFramework/Code/atomtoolsframework_files.cmake index dfe222bfc5..86fc3f5f5e 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/atomtoolsframework_files.cmake +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/atomtoolsframework_files.cmake @@ -7,6 +7,7 @@ # set(FILES + Include/AtomToolsFramework/Application/AtomToolsApplication.h Include/AtomToolsFramework/Communication/LocalServer.h Include/AtomToolsFramework/Communication/LocalSocket.h Include/AtomToolsFramework/Debug/TraceRecorder.h @@ -23,6 +24,7 @@ set(FILES Include/AtomToolsFramework/Viewport/RenderViewportWidget.h Include/AtomToolsFramework/Viewport/ModularViewportCameraController.h Include/AtomToolsFramework/Viewport/ModularViewportCameraControllerRequestBus.h + Source/Application/AtomToolsApplication.cpp Source/Communication/LocalServer.cpp Source/Communication/LocalSocket.cpp Source/Debug/TraceRecorder.cpp diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/MaterialEditorApplication.cpp b/Gems/Atom/Tools/MaterialEditor/Code/Source/MaterialEditorApplication.cpp index 10ad1df5f3..0977694b90 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/MaterialEditorApplication.cpp +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/MaterialEditorApplication.cpp @@ -8,56 +8,53 @@ #include #include -#include +#include +#include #include #include -#include - -#include +#include #include #include -#include -#include -#include -#include #include #include +#include +#include +#include +#include #include #include -#include +#include +#include #include +#include #include #include -#include +#include +#include +#include +#include +#include + +#include #include -#include -#include - -#include - -#include -#include -#include -#include - AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") // disable warnings spawned by QT -#include #include +#include AZ_POP_DISABLE_WARNING namespace MaterialEditor { //! This function returns the build system target name of "MaterialEditor - AZStd::string_view GetBuildTargetName() + AZStd::string MaterialEditorApplication::GetBuildTargetName() const { -#if !defined (LY_CMAKE_TARGET) +#if !defined(LY_CMAKE_TARGET) #error "LY_CMAKE_TARGET must be defined in order to add this source file to a CMake executable target" #endif - return AZStd::string_view{ LY_CMAKE_TARGET }; + return AZStd::string{ LY_CMAKE_TARGET }; } const char* MaterialEditorApplication::GetCurrentConfigurationName() const @@ -72,19 +69,12 @@ namespace MaterialEditor } MaterialEditorApplication::MaterialEditorApplication(int* argc, char*** argv) - : Application(argc, argv) - , AzQtApplication(*argc, *argv) + : AtomToolsApplication(argc, argv) { QApplication::setApplicationName("O3DE Material Editor"); AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddBuildSystemTargetSpecialization( *AZ::SettingsRegistry::Get(), GetBuildTargetName()); - - connect(&m_timer, &QTimer::timeout, this, [&]() - { - this->PumpSystemEventLoopUntilEmpty(); - this->Tick(); - }); } MaterialEditorApplication::~MaterialEditorApplication() @@ -94,88 +84,14 @@ namespace MaterialEditor AzToolsFramework::EditorPythonConsoleNotificationBus::Handler::BusDisconnect(); } - void MaterialEditorApplication::CreateReflectionManager() - { - Application::CreateReflectionManager(); - GetSerializeContext()->CreateEditContext(); - } - - void MaterialEditorApplication::Reflect(AZ::ReflectContext* context) - { - Application::Reflect(context); - - AzToolsFramework::AssetBrowser::AssetBrowserEntry::Reflect(context); - AzToolsFramework::AssetBrowser::RootAssetBrowserEntry::Reflect(context); - AzToolsFramework::AssetBrowser::FolderAssetBrowserEntry::Reflect(context); - AzToolsFramework::AssetBrowser::SourceAssetBrowserEntry::Reflect(context); - AzToolsFramework::AssetBrowser::ProductAssetBrowserEntry::Reflect(context); - - AzToolsFramework::QTreeViewWithStateSaving::Reflect(context); - AzToolsFramework::QWidgetSavedState::Reflect(context); - - if (auto behaviorContext = azrtti_cast(context)) - { - // this will put these methods into the 'azlmbr.materialeditor.general' module - auto addGeneral = [](AZ::BehaviorContext::GlobalMethodBuilder methodBuilder) - { - methodBuilder->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation) - ->Attribute(AZ::Script::Attributes::Category, "Editor") - ->Attribute(AZ::Script::Attributes::Module, "materialeditor.general"); - }; - // The reflection here is based on patterns in CryEditPythonHandler::Reflect - addGeneral(behaviorContext->Method("idle_wait_frames", &MaterialEditorApplication::PyIdleWaitFrames, nullptr, "Waits idling for a frames. Primarily used for auto-testing.")); - } - } - - void MaterialEditorApplication::RegisterCoreComponents() - { - Application::RegisterCoreComponents(); - RegisterComponentDescriptor(AzToolsFramework::AssetBrowser::AssetBrowserComponent::CreateDescriptor()); - RegisterComponentDescriptor(AzToolsFramework::Thumbnailer::ThumbnailerComponent::CreateDescriptor()); - RegisterComponentDescriptor(AzToolsFramework::Components::PropertyManagerComponent::CreateDescriptor()); - RegisterComponentDescriptor(AzToolsFramework::AssetSystem::AssetSystemComponent::CreateDescriptor()); - RegisterComponentDescriptor(AzToolsFramework::PerforceComponent::CreateDescriptor()); - } - - AZ::ComponentTypeList MaterialEditorApplication::GetRequiredSystemComponents() const - { - AZ::ComponentTypeList components = Application::GetRequiredSystemComponents(); - - components.insert(components.end(), { - azrtti_typeid(), - azrtti_typeid(), - azrtti_typeid(), - azrtti_typeid(), - }); - - return components; - } - void MaterialEditorApplication::CreateStaticModules(AZStd::vector& outModules) { - Application::CreateStaticModules(outModules); - outModules.push_back(aznew AzToolsFramework::AzToolsFrameworkModule); + Base::CreateStaticModules(outModules); outModules.push_back(aznew MaterialDocumentModule); outModules.push_back(aznew MaterialViewportModule); outModules.push_back(aznew MaterialEditorWindowModule); } - void MaterialEditorApplication::StartCommon(AZ::Entity* systemEntity) - { - { - //[GFX TODO][ATOM-408] This needs to be updated in some way to support the MaterialViewport render widget - } - - AzFramework::AssetSystemStatusBus::Handler::BusConnect(); - AzToolsFramework::EditorPythonConsoleNotificationBus::Handler::BusConnect(); - - AzFramework::Application::StartCommon(systemEntity); - - StartInternal(); - - m_timer.start(); - } - void MaterialEditorApplication::OnMaterialEditorWindowClosing() { ExitMainLoop(); @@ -187,120 +103,14 @@ namespace MaterialEditor MaterialEditor::MaterialEditorWindowFactoryRequestBus::Broadcast( &MaterialEditor::MaterialEditorWindowFactoryRequestBus::Handler::DestroyMaterialEditorWindow); - AzToolsFramework::EditorPythonConsoleNotificationBus::Handler::BusDisconnect(); - AzToolsFramework::AssetDatabase::AssetDatabaseRequestsBus::Handler::BusDisconnect(); MaterialEditorWindowNotificationBus::Handler::BusDisconnect(); - AZ::Debug::TraceMessageBus::Handler::BusDisconnect(); - m_logFile = {}; - m_startupLogSink = {}; - - AzFramework::AssetSystemRequestBus::Broadcast(&AzFramework::AssetSystem::AssetSystemRequests::StartDisconnectingAssetProcessor); - Application::Destroy(); + Base::Destroy(); } - void MaterialEditorApplication::AssetSystemAvailable() + AZStd::vector MaterialEditorApplication::GetCriticalAssetFilters() const { - bool connectedToAssetProcessor = false; - - // When the AssetProcessor is already launched it should take less than a second to perform a connection - // but when the AssetProcessor needs to be launch it could take up to 15 seconds to have the AssetProcessor initialize - // and able to negotiate a connection when running a debug build - // and to negotiate a connection - - AzFramework::AssetSystem::ConnectionSettings connectionSettings; - AzFramework::AssetSystem::ReadConnectionSettingsFromSettingsRegistry(connectionSettings); - connectionSettings.m_connectionDirection = AzFramework::AssetSystem::ConnectionSettings::ConnectionDirection::ConnectToAssetProcessor; - connectionSettings.m_connectionIdentifier = "MaterialEditor"; - connectionSettings.m_loggingCallback = []([[maybe_unused]] AZStd::string_view logData) - { - AZ_TracePrintf("Material Editor", "%.*s", aznumeric_cast(logData.size()), logData.data()); - }; - AzFramework::AssetSystemRequestBus::BroadcastResult(connectedToAssetProcessor, - &AzFramework::AssetSystemRequestBus::Events::EstablishAssetProcessorConnection, connectionSettings); - if (connectedToAssetProcessor) - { - CompileCriticalAssets(); - } - - AzFramework::AssetSystemStatusBus::Handler::BusDisconnect(); - } - - - void MaterialEditorApplication::CompileCriticalAssets() - { - AZ_TracePrintf("MaterialEditor", "Compiling critical assets.\n"); - - // List of common asset filters for things that need to be compiled to run the material editor - // Some of these things will not be necessary once we have proper support for queued asset loading and reloading - const AZStd::string assetFiltersArray[] = - { - "passes/", - "config/", - "MaterialEditor/", - }; - - QStringList failedAssets; - - // Forced asset processor to synchronously process all critical assets - // Note: with AssetManager's current implementation, a compiled asset won't be added in asset registry until next system tick. - // So the asset id won't be found right after CompileAssetSync call. - for (const AZStd::string& assetFilters : assetFiltersArray) - { - AZ_TracePrintf("MaterialEditor", "Compiling critical asset matching: %s.\n", assetFilters.c_str()); - - // Wait for the asset be compiled - AzFramework::AssetSystem::AssetStatus status = AzFramework::AssetSystem::AssetStatus_Unknown; - AzFramework::AssetSystemRequestBus::BroadcastResult( - status, &AzFramework::AssetSystemRequestBus::Events::CompileAssetSync, assetFilters); - if (status != AzFramework::AssetSystem::AssetStatus_Compiled) - { - failedAssets.append(assetFilters.c_str()); - } - } - - if (!failedAssets.empty()) - { - QMessageBox::critical(activeWindow(), - QString("Failed to compile critical assets"), - QString("Failed to compile the following critical assets:\n%1\n%2") - .arg(failedAssets.join(",\n")) - .arg("Make sure this is an Atom project.")); - ExitMainLoop(); - } - } - - void MaterialEditorApplication::SaveSettings() - { - if (m_activatedLocalUserSettings) - { - AZ::SerializeContext* context = nullptr; - AZ::ComponentApplicationBus::BroadcastResult(context, &AZ::ComponentApplicationRequests::GetSerializeContext); - AZ_Assert(context, "No serialize context"); - - char resolvedPath[AZ_MAX_PATH_LEN] = ""; - AZ::IO::FileIOBase::GetInstance()->ResolvePath("@user@/MaterialEditorUserSettings.xml", resolvedPath, AZ_ARRAY_SIZE(resolvedPath)); - m_localUserSettings.Save(resolvedPath, context); - } - } - - bool MaterialEditorApplication::OnOutput(const char* window, const char* message) - { - // Suppress spam from the Source Control system - if (0 == strncmp(window, AzToolsFramework::SCC_WINDOW, AZ_ARRAY_SIZE(AzToolsFramework::SCC_WINDOW))) - { - return true; - } - - if (m_logFile) - { - m_logFile->AppendLog(AzFramework::LogFile::SEV_NORMAL, window, message); - } - else - { - m_startupLogSink.push_back({ window, message }); - } - return false; + return AZStd::vector({ "passes/", "config/", "MaterialEditor" }); } void MaterialEditorApplication::ProcessCommandLine(const AZ::CommandLine& commandLine) @@ -312,195 +122,27 @@ namespace MaterialEditor &MaterialEditor::MaterialEditorWindowRequestBus::Handler::ActivateWindow); } - const AZStd::string timeoputSwitchName = "timeout"; - if (commandLine.HasSwitch(timeoputSwitchName)) - { - const AZStd::string& timeoutValue = commandLine.GetSwitchValue(timeoputSwitchName, 0); - const uint32_t timeoutInMs = atoi(timeoutValue.c_str()); - AZ_Printf("MaterialEditor", "Timeout scheduled, shutting down in %u ms", timeoutInMs); - QTimer::singleShot(timeoutInMs, [this] { - AZ_Printf("MaterialEditor", "Timeout reached, shutting down"); - ExitMainLoop(); - }); - } - - // Process command line options for running one or more python scripts on startup - const AZStd::string runPythonScriptSwitchName = "runpython"; - size_t runPythonScriptCount = commandLine.GetNumSwitchValues(runPythonScriptSwitchName); - for (size_t runPythonScriptIndex = 0; runPythonScriptIndex < runPythonScriptCount; ++runPythonScriptIndex) - { - const AZStd::string runPythonScriptPath = commandLine.GetSwitchValue(runPythonScriptSwitchName, runPythonScriptIndex); - AZStd::vector runPythonArgs; - - AZ_Printf("MaterialEditor", "Launching script: %s", runPythonScriptPath.c_str()); - AzToolsFramework::EditorPythonRunnerRequestBus::Broadcast( - &AzToolsFramework::EditorPythonRunnerRequestBus::Events::ExecuteByFilenameWithArgs, - runPythonScriptPath, - runPythonArgs); - } - // Process command line options for opening one or more material documents on startup size_t openDocumentCount = commandLine.GetNumMiscValues(); for (size_t openDocumentIndex = 0; openDocumentIndex < openDocumentCount; ++openDocumentIndex) { const AZStd::string openDocumentPath = commandLine.GetMiscValue(openDocumentIndex); - AZ_Printf("MaterialEditor", "Opening document: %s", openDocumentPath.c_str()); + AZ_Printf(GetBuildTargetName().c_str(), "Opening document: %s", openDocumentPath.c_str()); MaterialDocumentSystemRequestBus::Broadcast(&MaterialDocumentSystemRequestBus::Events::OpenDocument, openDocumentPath); } - const AZStd::string exitAfterCommandsSwitchName = "exitaftercommands"; - if (commandLine.HasSwitch(exitAfterCommandsSwitchName)) - { - ExitMainLoop(); - } - } - - void MaterialEditorApplication::LoadSettings() - { - AZ::SerializeContext* context = nullptr; - AZ::ComponentApplicationBus::BroadcastResult(context, &AZ::ComponentApplicationRequests::GetSerializeContext); - AZ_Assert(context, "No serialize context"); - - char resolvedPath[AZ_MAX_PATH_LEN] = ""; - AZ::IO::FileIOBase::GetInstance()->ResolvePath("@user@/EditorUserSettings.xml", resolvedPath, AZ_MAX_PATH_LEN); - - m_localUserSettings.Load(resolvedPath, context); - m_localUserSettings.Activate(AZ::UserSettings::CT_LOCAL); - AZ::UserSettingsOwnerRequestBus::Handler::BusConnect(AZ::UserSettings::CT_LOCAL); - m_activatedLocalUserSettings = true; - } - - void MaterialEditorApplication::UnloadSettings() - { - if (m_activatedLocalUserSettings) - { - SaveSettings(); - m_localUserSettings.Deactivate(); - AZ::UserSettingsOwnerRequestBus::Handler::BusDisconnect(); - m_activatedLocalUserSettings = false; - } - } - - bool MaterialEditorApplication::LaunchDiscoveryService() - { - // Determine if this is the first launch of the tool by attempting to connect to a running server - if (m_socket.Connect(QApplication::applicationName())) - { - // If the server was located, the application is already running. - // Forward commandline options to other application instance. - QByteArray buffer; - buffer.append("ProcessCommandLine:"); - - // Add the command line options from this process to the message, skipping the executable path - for (int argi = 1; argi < m_argC; ++argi) - { - buffer.append(QString(m_argV[argi]).append("\n").toUtf8()); - } - - // Inject command line option to always bring the main window to the foreground - buffer.append("--activatewindow\n"); - - m_socket.Send(buffer); - m_socket.Disconnect(); - return false; - } - - // Setup server to handle basic commands - m_server.SetReadHandler([this](const QByteArray& buffer) { - // Handle commmand line params from connected socket - if (buffer.startsWith("ProcessCommandLine:")) - { - // Remove header and parse commands - AZStd::string params(buffer.data(), buffer.size()); - params = params.substr(strlen("ProcessCommandLine:")); - - AZStd::vector tokens; - AZ::StringFunc::Tokenize(params, tokens, "\n"); - - if (!tokens.empty()) - { - AZ::CommandLine commandLine; - commandLine.Parse(tokens); - ProcessCommandLine(commandLine); - } - } - }); - - // Launch local server - if (!m_server.Connect(QApplication::applicationName())) - { - return false; - } - - return true; + Base::ProcessCommandLine(commandLine); } void MaterialEditorApplication::StartInternal() { - if (WasExitMainLoopRequested()) - { - return; - } - - m_traceLogger.WriteStartupLog("MaterialEditor.log"); - - if (!LaunchDiscoveryService()) - { - ExitMainLoop(); - return; - } - - AzToolsFramework::AssetDatabase::AssetDatabaseRequestsBus::Handler::BusConnect(); - AzToolsFramework::AssetBrowser::AssetDatabaseLocationNotificationBus::Broadcast(&AzToolsFramework::AssetBrowser::AssetDatabaseLocationNotifications::OnDatabaseInitialized); - - AZ::Data::AssetCatalogRequestBus::Broadcast(&AZ::Data::AssetCatalogRequestBus::Events::LoadCatalog, "@assets@/assetcatalog.xml"); - - AZ::RPI::RPISystemInterface::Get()->InitializeSystemAssets(); - - LoadSettings(); + Base::StartInternal(); MaterialEditorWindowNotificationBus::Handler::BusConnect(); MaterialEditor::MaterialEditorWindowFactoryRequestBus::Broadcast( &MaterialEditor::MaterialEditorWindowFactoryRequestBus::Handler::CreateMaterialEditorWindow); - - auto editorPythonEventsInterface = AZ::Interface::Get(); - if (editorPythonEventsInterface) - { - // The PythonSystemComponent does not call StartPython to allow for lazy python initialization, so start it here - // The PythonSystemComponent will call StopPython when it deactivates, so we do not need our own corresponding call to StopPython - editorPythonEventsInterface->StartPython(); - } - - // Delay execution of commands and scripts post initialization - QTimer::singleShot(0, [this]() { ProcessCommandLine(m_commandLine); }); - } - - bool MaterialEditorApplication::GetAssetDatabaseLocation(AZStd::string& result) - { - AZ::SettingsRegistryInterface* settingsRegistry = AZ::SettingsRegistry::Get(); - AZ::IO::FixedMaxPath assetDatabaseSqlitePath; - if (settingsRegistry && settingsRegistry->Get(assetDatabaseSqlitePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_CacheProjectRootFolder)) - { - assetDatabaseSqlitePath /= "assetdb.sqlite"; - result = AZStd::string_view(assetDatabaseSqlitePath.Native()); - return true; - } - - return false; - } - - void MaterialEditorApplication::Tick(float deltaOverride) - { - TickSystem(); - Application::Tick(deltaOverride); - - if (WasExitMainLoopRequested()) - { - m_timer.disconnect(); - quit(); - } } void MaterialEditorApplication::Stop() @@ -508,76 +150,6 @@ namespace MaterialEditor MaterialEditor::MaterialEditorWindowFactoryRequestBus::Broadcast( &MaterialEditor::MaterialEditorWindowFactoryRequestBus::Handler::DestroyMaterialEditorWindow); - UnloadSettings(); - AzFramework::Application::Stop(); + Base::Stop(); } - - void MaterialEditorApplication::QueryApplicationType(AZ::ApplicationTypeQuery& appType) const - { - appType.m_maskValue = AZ::ApplicationTypeQuery::Masks::Game; - } - - void MaterialEditorApplication::OnTraceMessage([[maybe_unused]] AZStd::string_view message) - { -#if defined(AZ_ENABLE_TRACING) - AZStd::vector lines; - AzFramework::StringFunc::Tokenize( - message, - lines, - "\n", - false, // Keep empty strings - false // Keep space strings - ); - - for (auto& line : lines) - { - AZ_TracePrintf("MaterialEditor", "Python: %s\n", line.c_str()); - } -#endif - } - - void MaterialEditorApplication::OnErrorMessage(AZStd::string_view message) - { - // Use AZ_TracePrintf instead of AZ_Error or AZ_Warning to avoid all the metadata noise - OnTraceMessage(message); - } - - void MaterialEditorApplication::OnExceptionMessage([[maybe_unused]] AZStd::string_view message) - { - AZ_Error("MaterialEditor", false, "Python: " AZ_STRING_FORMAT, AZ_STRING_ARG(message)); - } - - // Copied from PyIdleWaitFrames in CryEdit.cpp - void MaterialEditorApplication::PyIdleWaitFrames(uint32_t frames) - { - struct Ticker : public AZ::TickBus::Handler - { - Ticker(QEventLoop* loop, uint32_t targetFrames) : m_loop(loop), m_targetFrames(targetFrames) - { - AZ::TickBus::Handler::BusConnect(); - } - ~Ticker() - { - AZ::TickBus::Handler::BusDisconnect(); - } - - void OnTick(float deltaTime, AZ::ScriptTimePoint time) override - { - AZ_UNUSED(deltaTime); - AZ_UNUSED(time); - if (++m_elapsedFrames == m_targetFrames) - { - m_loop->quit(); - } - } - QEventLoop* m_loop = nullptr; - uint32_t m_elapsedFrames = 0; - uint32_t m_targetFrames = 0; - }; - - QEventLoop loop; - Ticker ticker(&loop, frames); - loop.exec(); - } - } // namespace MaterialEditor diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/MaterialEditorApplication.h b/Gems/Atom/Tools/MaterialEditor/Code/Source/MaterialEditorApplication.h index e6fe9401f6..ec2a48288c 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/MaterialEditorApplication.h +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/MaterialEditorApplication.h @@ -10,19 +10,7 @@ #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include #include @@ -31,41 +19,24 @@ namespace MaterialEditor class MaterialThumbnailRenderer; class MaterialEditorApplication - : public AzFramework::Application - , public AzQtComponents::AzQtApplication - , private AzToolsFramework::AssetDatabase::AssetDatabaseRequestsBus::Handler + : public AtomToolsFramework::AtomToolsApplication , private MaterialEditorWindowNotificationBus::Handler - , private AzFramework::AssetSystemStatusBus::Handler - , private AZ::UserSettingsOwnerRequestBus::Handler - , private AZ::Debug::TraceMessageBus::Handler - , private AzToolsFramework::EditorPythonConsoleNotificationBus::Handler { public: AZ_TYPE_INFO(MaterialEditor::MaterialEditorApplication, "{30F90CA5-1253-49B5-8143-19CEE37E22BB}"); - using Base = AzFramework::Application; + using Base = AtomToolsFramework::AtomToolsApplication; MaterialEditorApplication(int* argc, char*** argv); virtual ~MaterialEditorApplication(); ////////////////////////////////////////////////////////////////////////// // AzFramework::Application - void CreateReflectionManager() override; - void Reflect(AZ::ReflectContext* context) override; - void RegisterCoreComponents() override; - AZ::ComponentTypeList GetRequiredSystemComponents() const override; void CreateStaticModules(AZStd::vector& outModules) override; const char* GetCurrentConfigurationName() const override; - void StartCommon(AZ::Entity* systemEntity) override; - void Tick(float deltaOverride = -1.f) override; void Stop() override; private: - ////////////////////////////////////////////////////////////////////////// - // AssetDatabaseRequestsBus::Handler overrides... - bool GetAssetDatabaseLocation(AZStd::string& result) override; - ////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////// // MaterialEditorWindowNotificationBus::Handler overrides... void OnMaterialEditorWindowClosing() override; @@ -76,66 +47,9 @@ namespace MaterialEditor void Destroy() override; ////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////// - // AZ::ComponentApplication overrides... - void QueryApplicationType(AZ::ApplicationTypeQuery& appType) const override; - ////////////////////////////////////////////////////////////////////////// - - //////////////////////////////////////////////////////////////////////// - // EditorPythonConsoleNotificationBus::Handler overrides... - void OnTraceMessage(AZStd::string_view message) override; - void OnErrorMessage(AZStd::string_view message) override; - void OnExceptionMessage(AZStd::string_view message) override; - //////////////////////////////////////////////////////////////////////// - - ////////////////////////////////////////////////////////////////////////// - // AzFramework::AssetSystemStatusBus::Handler overrides... - void AssetSystemAvailable() override; - ////////////////////////////////////////////////////////////////////////// - - ////////////////////////////////////////////////////////////////////////// - // AZ::UserSettingsOwnerRequestBus::Handler overrides... - void SaveSettings() override; - ////////////////////////////////////////////////////////////////////////// - - ////////////////////////////////////////////////////////////////////////// - // AZ::Debug::TraceMessageBus::Handler overrides... - bool OnOutput(const char* window, const char* message) override; - ////////////////////////////////////////////////////////////////////////// - - void CompileCriticalAssets(); - - void ProcessCommandLine(const AZ::CommandLine& commandLine); - - void LoadSettings(); - void UnloadSettings(); - - bool LaunchDiscoveryService(); - - void StartInternal(); - - static void PyIdleWaitFrames(uint32_t frames); - - struct LogMessage - { - AZStd::string window; - AZStd::string message; - }; - - AZStd::vector m_startupLogSink; - AZStd::unique_ptr m_logFile; - - AzToolsFramework::TraceLogger m_traceLogger; - - //! Local user settings are used to store material browser tree expansion state - AZ::UserSettingsProvider m_localUserSettings; - - //! Are local settings loaded - bool m_activatedLocalUserSettings = false; - - QTimer m_timer; - - AtomToolsFramework::LocalSocket m_socket; - AtomToolsFramework::LocalServer m_server; - }; + void ProcessCommandLine(const AZ::CommandLine& commandLine) override; + void StartInternal() override; + AZStd::string GetBuildTargetName() const override; + AZStd::vector GetCriticalAssetFilters() const override; + }; } // namespace MaterialEditor diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/MaterialViewportRenderer.cpp b/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/MaterialViewportRenderer.cpp index 461beffd06..23e37b2f7c 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/MaterialViewportRenderer.cpp +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Viewport/MaterialViewportRenderer.cpp @@ -324,7 +324,7 @@ namespace MaterialEditor { if (!preset) { - AZ_Error("MaterialViewportRenderer", false, "Attempting to set invalid lighting preset."); + AZ_Warning("MaterialViewportRenderer", false, "Attempting to set invalid lighting preset."); return; } @@ -365,13 +365,13 @@ namespace MaterialEditor { if (!preset) { - AZ_Error("MaterialViewportRenderer", false, "Attempting to set invalid model preset."); + AZ_Warning("MaterialViewportRenderer", false, "Attempting to set invalid model preset."); return; } if (!preset->m_modelAsset.GetId().IsValid()) { - AZ_Error("MaterialViewportRenderer", false, "Attempting to set invalid model for preset: '%s'\n.", preset->m_displayName.c_str()); + AZ_Warning("MaterialViewportRenderer", false, "Attempting to set invalid model for preset: '%s'\n.", preset->m_displayName.c_str()); return; } diff --git a/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/ShaderManagementConsoleApplication.cpp b/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/ShaderManagementConsoleApplication.cpp index a39480637b..7e09f88f4f 100644 --- a/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/ShaderManagementConsoleApplication.cpp +++ b/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/ShaderManagementConsoleApplication.cpp @@ -6,51 +6,48 @@ * */ -#include +#include +#include +#include #include #include - -#include +#include #include #include -#include -#include -#include -#include #include #include +#include +#include +#include +#include #include #include -#include - -#include +#include +#include #include #include +#include -#include +#include #include #include #include - #include #include -#include -#include -#include AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") // disable warnings spawned by QT #include -#include #include +#include AZ_POP_DISABLE_WARNING namespace ShaderManagementConsole { - AZStd::string_view GetBuildTargetName() + AZStd::string ShaderManagementConsoleApplication::GetBuildTargetName() const { -#if !defined (LY_CMAKE_TARGET) +#if !defined(LY_CMAKE_TARGET) #error "LY_CMAKE_TARGET must be defined in order to add this source file to a CMake executable target" #endif return AZStd::string_view{ LY_CMAKE_TARGET }; @@ -68,99 +65,22 @@ namespace ShaderManagementConsole } ShaderManagementConsoleApplication::ShaderManagementConsoleApplication(int* argc, char*** argv) - : Application(argc, argv) - , AzQtApplication(*argc, *argv) + : AtomToolsApplication(argc, argv) { QApplication::setApplicationName("O3DE Shader Management Console"); // The settings registry has been created at this point, so add the CMake target AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddBuildSystemTargetSpecialization( *AZ::SettingsRegistry::Get(), GetBuildTargetName()); - - connect(&m_timer, &QTimer::timeout, this, [&]() - { - this->PumpSystemEventLoopUntilEmpty(); - this->Tick(); - }); - } - - void ShaderManagementConsoleApplication::CreateReflectionManager() - { - Application::CreateReflectionManager(); - GetSerializeContext()->CreateEditContext(); - } - - void ShaderManagementConsoleApplication::Reflect(AZ::ReflectContext* context) - { - Application::Reflect(context); - - AzToolsFramework::AssetBrowser::AssetBrowserEntry::Reflect(context); - AzToolsFramework::AssetBrowser::RootAssetBrowserEntry::Reflect(context); - AzToolsFramework::AssetBrowser::FolderAssetBrowserEntry::Reflect(context); - AzToolsFramework::AssetBrowser::SourceAssetBrowserEntry::Reflect(context); - AzToolsFramework::AssetBrowser::ProductAssetBrowserEntry::Reflect(context); - - AzToolsFramework::QTreeViewWithStateSaving::Reflect(context); - AzToolsFramework::QWidgetSavedState::Reflect(context); - - if (auto behaviorContext = azrtti_cast(context)) - { - // this will put these methods into the 'azlmbr.shadermanagementconsole.general' module - auto addGeneral = [](AZ::BehaviorContext::GlobalMethodBuilder methodBuilder) - { - methodBuilder->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation) - ->Attribute(AZ::Script::Attributes::Category, "Editor") - ->Attribute(AZ::Script::Attributes::Module, "shadermanagementconsole.general"); - }; - // The reflection here is based on patterns in CryEditPythonHandler::Reflect - addGeneral(behaviorContext->Method("idle_wait_frames", &ShaderManagementConsoleApplication::PyIdleWaitFrames, nullptr, "Waits idling for a frames. Primarily used for auto-testing.")); - } - } - - void ShaderManagementConsoleApplication::RegisterCoreComponents() - { - Application::RegisterCoreComponents(); - RegisterComponentDescriptor(AzToolsFramework::AssetBrowser::AssetBrowserComponent::CreateDescriptor()); - RegisterComponentDescriptor(AzToolsFramework::Thumbnailer::ThumbnailerComponent::CreateDescriptor()); - RegisterComponentDescriptor(AzToolsFramework::Components::PropertyManagerComponent::CreateDescriptor()); - RegisterComponentDescriptor(AzToolsFramework::AssetSystem::AssetSystemComponent::CreateDescriptor()); - RegisterComponentDescriptor(AzToolsFramework::PerforceComponent::CreateDescriptor()); - } - - AZ::ComponentTypeList ShaderManagementConsoleApplication::GetRequiredSystemComponents() const - { - AZ::ComponentTypeList components = Application::GetRequiredSystemComponents(); - - components.insert(components.end(), { - azrtti_typeid(), - azrtti_typeid(), - azrtti_typeid(), - azrtti_typeid(), - }); - - return components; } void ShaderManagementConsoleApplication::CreateStaticModules(AZStd::vector& outModules) { - Application::CreateStaticModules(outModules); - outModules.push_back(aznew AzToolsFramework::AzToolsFrameworkModule); + Base::CreateStaticModules(outModules); outModules.push_back(aznew ShaderManagementConsoleDocumentModule); outModules.push_back(aznew ShaderManagementConsoleWindowModule); } - void ShaderManagementConsoleApplication::StartCommon(AZ::Entity* systemEntity) - { - AzFramework::AssetSystemStatusBus::Handler::BusConnect(); - AzToolsFramework::EditorPythonConsoleNotificationBus::Handler::BusConnect(); - - AzFramework::Application::StartCommon(systemEntity); - - StartInternal(); - - m_timer.start(); - } - void ShaderManagementConsoleApplication::OnShaderManagementConsoleWindowClosing() { ExitMainLoop(); @@ -171,113 +91,17 @@ namespace ShaderManagementConsole void ShaderManagementConsoleApplication::Destroy() { // before modules are unloaded, destroy UI to free up any assets it cached - ShaderManagementConsole::ShaderManagementConsoleWindowRequestBus::Broadcast(&ShaderManagementConsole::ShaderManagementConsoleWindowRequestBus::Handler::DestroyShaderManagementConsoleWindow); + ShaderManagementConsole::ShaderManagementConsoleWindowRequestBus::Broadcast( + &ShaderManagementConsole::ShaderManagementConsoleWindowRequestBus::Handler::DestroyShaderManagementConsoleWindow); ShaderManagementConsoleWindowNotificationBus::Handler::BusDisconnect(); - AzToolsFramework::AssetDatabase::AssetDatabaseRequestsBus::Handler::BusDisconnect(); - AzFramework::AssetSystemRequestBus::Broadcast(&AzFramework::AssetSystem::AssetSystemRequests::StartDisconnectingAssetProcessor); - - Application::Destroy(); + Base::Destroy(); } - void ShaderManagementConsoleApplication::AssetSystemAvailable() + AZStd::vector ShaderManagementConsoleApplication::GetCriticalAssetFilters() const { - // Try connect to AP first before try to launch it manually. - bool connected = false; - auto ConnectToAssetProcessorWithIdentifier = [&connected](AzFramework::AssetSystem::AssetSystemRequests* assetSystemRequests) - { - // When the AssetProcessor is already launched it should take less than a second to perform a connection - // but when the AssetProcessor needs to be launch it could take up to 15 seconds to have the AssetProcessor initialize - // and able to negotiate a connection when running a debug build - // and to negotiate a connection - - AzFramework::AssetSystem::ConnectionSettings connectionSettings; - AzFramework::AssetSystem::ReadConnectionSettingsFromSettingsRegistry(connectionSettings); - connectionSettings.m_connectionDirection = AzFramework::AssetSystem::ConnectionSettings::ConnectionDirection::ConnectToAssetProcessor; - connectionSettings.m_connectionIdentifier = "Shader Management Console"; - connectionSettings.m_loggingCallback = []([[maybe_unused]] AZStd::string_view logData) - { - AZ_TracePrintf("Shader Management Console", "%.*s", aznumeric_cast(logData.size()), logData.data()); - }; - - connected = assetSystemRequests->EstablishAssetProcessorConnection(connectionSettings); - }; - AzFramework::AssetSystemRequestBus::Broadcast(ConnectToAssetProcessorWithIdentifier); - - if (connected) - { - CompileCriticalAssets(); - } - - AzFramework::AssetSystemStatusBus::Handler::BusDisconnect(); - } - - void ShaderManagementConsoleApplication::CompileCriticalAssets() - { - AZ_TracePrintf("Shader Management Console", "Compiling critical assets.\n"); - - // List of common asset filters for things that need to be compiled to run - // Some of these things will not be necessary once we have proper support for queued asset loading and reloading - const AZStd::string assetFilterss[] = - { - "passes/", - "config/", - }; - - QStringList failedAssets; - - // Forced asset processor to synchronously process all critical assets - // Note: with AssetManager's current implementation, a compiled asset won't be added in asset registry until next system tick. - // So the asset id won't be found right after CompileAssetSync call. - for (const AZStd::string& assetFilters : assetFilterss) - { - AZ_TracePrintf("Shader Management Console", "Compiling critical asset matching: %s.\n", assetFilters.c_str()); - - // Wait for the asset be compiled - AzFramework::AssetSystem::AssetStatus status = AzFramework::AssetSystem::AssetStatus_Unknown; - AzFramework::AssetSystemRequestBus::BroadcastResult( - status, &AzFramework::AssetSystemRequestBus::Events::CompileAssetSync, assetFilters); - if (status != AzFramework::AssetSystem::AssetStatus_Compiled) - { - failedAssets.append(assetFilters.c_str()); - } - } - - if (!failedAssets.empty()) - { - QMessageBox::critical(activeWindow(), - QString("Failed to compile critical assets"), - QString("Failed to compile the following critical assets:\n%1\n%2") - .arg(failedAssets.join(",\n")) - .arg("Make sure this is an Atom project.")); - m_closing = true; - } - } - - void ShaderManagementConsoleApplication::SaveSettings() - { - if (m_activatedLocalUserSettings) - { - AZ::SerializeContext* context = nullptr; - AZ::ComponentApplicationBus::BroadcastResult(context, &AZ::ComponentApplicationRequests::GetSerializeContext); - AZ_Assert(context, "No serialize context"); - - char resolvedPath[AZ_MAX_PATH_LEN] = ""; - AZ::IO::FileIOBase::GetInstance()->ResolvePath("@user@/EditorUserSettings.xml", resolvedPath, AZ_ARRAY_SIZE(resolvedPath)); - m_localUserSettings.Save(resolvedPath, context); - } - } - - bool ShaderManagementConsoleApplication::OnPrintf(const char* window, const char* /*message*/) - { - // Suppress spam from the Source Control system - if (0 == strncmp(window, AzToolsFramework::SCC_WINDOW, AZ_ARRAY_SIZE(AzToolsFramework::SCC_WINDOW))) - { - return true; - } - - return false; + return AZStd::vector({ "passes/", "config/" }); } void ShaderManagementConsoleApplication::ProcessCommandLine() @@ -290,9 +114,7 @@ namespace ShaderManagementConsole const AZStd::string runPythonScriptPath = m_commandLine.GetSwitchValue(runPythonScriptSwitchName, runPythonScriptIndex); AZStd::vector runPythonArgs; AzToolsFramework::EditorPythonRunnerRequestBus::Broadcast( - &AzToolsFramework::EditorPythonRunnerRequestBus::Events::ExecuteByFilenameWithArgs, - runPythonScriptPath, - runPythonArgs); + &AzToolsFramework::EditorPythonRunnerRequestBus::Events::ExecuteByFilenameWithArgs, runPythonScriptPath, runPythonArgs); } // Process command line options for opening one or more documents on startup @@ -300,177 +122,18 @@ namespace ShaderManagementConsole for (size_t openDocumentIndex = 0; openDocumentIndex < openDocumentCount; ++openDocumentIndex) { const AZStd::string openDocumentPath = m_commandLine.GetMiscValue(openDocumentIndex); - ShaderManagementConsoleDocumentSystemRequestBus::Broadcast(&ShaderManagementConsoleDocumentSystemRequestBus::Events::OpenDocument, openDocumentPath); + ShaderManagementConsoleDocumentSystemRequestBus::Broadcast( + &ShaderManagementConsoleDocumentSystemRequestBus::Events::OpenDocument, openDocumentPath); } } - void ShaderManagementConsoleApplication::LoadSettings() - { - AZ::SerializeContext* context = nullptr; - AZ::ComponentApplicationBus::BroadcastResult(context, &AZ::ComponentApplicationRequests::GetSerializeContext); - AZ_Assert(context, "No serialize context"); - - char resolvedPath[AZ_MAX_PATH_LEN] = ""; - AZ::IO::FileIOBase::GetInstance()->ResolvePath("@user@/EditorUserSettings.xml", resolvedPath, AZ_MAX_PATH_LEN); - - m_localUserSettings.Load(resolvedPath, context); - m_localUserSettings.Activate(AZ::UserSettings::CT_LOCAL); - AZ::UserSettingsOwnerRequestBus::Handler::BusConnect(AZ::UserSettings::CT_LOCAL); - m_activatedLocalUserSettings = true; - } - - void ShaderManagementConsoleApplication::UnloadSettings() - { - if (m_activatedLocalUserSettings) - { - SaveSettings(); - m_localUserSettings.Deactivate(); - AZ::UserSettingsOwnerRequestBus::Handler::BusDisconnect(); - m_activatedLocalUserSettings = false; - } - } - - bool ShaderManagementConsoleApplication::LaunchDiscoveryService() - { - const QStringList arguments = { "-fail_silently" }; - - return AtomToolsFramework::LaunchTool("GridHub", AZ_TRAIT_SHADER_MANAGEMENT_CONSOLE_EXT, arguments); - } - void ShaderManagementConsoleApplication::StartInternal() { - if (m_closing) - { - return; - } - - m_traceLogger.WriteStartupLog("ShaderManagementConsole.log"); - - //[GFX TODO][ATOM-415] Try to factor out some of this stuff with AtomSampleViewerApplication - AzToolsFramework::AssetDatabase::AssetDatabaseRequestsBus::Handler::BusConnect(); - AzToolsFramework::AssetBrowser::AssetDatabaseLocationNotificationBus::Broadcast(&AzToolsFramework::AssetBrowser::AssetDatabaseLocationNotifications::OnDatabaseInitialized); - - AZ::Data::AssetCatalogRequestBus::Broadcast(&AZ::Data::AssetCatalogRequestBus::Events::LoadCatalog, "@assets@/assetcatalog.xml"); - - AZ::RPI::RPISystemInterface::Get()->InitializeSystemAssets(); - - LoadSettings(); - - LaunchDiscoveryService(); + Base::StartInternal(); ShaderManagementConsoleWindowNotificationBus::Handler::BusConnect(); - ShaderManagementConsole::ShaderManagementConsoleWindowRequestBus::Broadcast(&ShaderManagementConsole::ShaderManagementConsoleWindowRequestBus::Handler::CreateShaderManagementConsoleWindow); - - auto editorPythonEventsInterface = AZ::Interface::Get(); - if (editorPythonEventsInterface) - { - // The PythonSystemComponent does not call StartPython to allow for lazy python initialization, so start it here - // The PythonSystemComponent will call StopPython when it deactivates, so we do not need our own corresponding call to StopPython - editorPythonEventsInterface->StartPython(); - } - - ProcessCommandLine(); + ShaderManagementConsole::ShaderManagementConsoleWindowRequestBus::Broadcast( + &ShaderManagementConsole::ShaderManagementConsoleWindowRequestBus::Handler::CreateShaderManagementConsoleWindow); } - - bool ShaderManagementConsoleApplication::GetAssetDatabaseLocation(AZStd::string& result) - { - AZ::SettingsRegistryInterface* settingsRegistry = AZ::SettingsRegistry::Get(); - AZ::IO::FixedMaxPath assetDatabaseSqlitePath; - if (settingsRegistry && settingsRegistry->Get(assetDatabaseSqlitePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_CacheProjectRootFolder)) - { - assetDatabaseSqlitePath /= "assetdb.sqlite"; - result = AZStd::string_view(assetDatabaseSqlitePath.Native()); - return true; - } - - return false; - } - - void ShaderManagementConsoleApplication::Tick(float deltaOverride) - { - TickSystem(); - Application::Tick(deltaOverride); - - if (m_closing) - { - m_timer.disconnect(); - quit(); - } - } - - void ShaderManagementConsoleApplication::Stop() - { - UnloadSettings(); - AzFramework::Application::Stop(); - } - - void ShaderManagementConsoleApplication::QueryApplicationType(AZ::ApplicationTypeQuery& appType) const - { - appType.m_maskValue = AZ::ApplicationTypeQuery::Masks::Game; - } - - void ShaderManagementConsoleApplication::OnTraceMessage([[maybe_unused]] AZStd::string_view message) - { -#if defined(AZ_ENABLE_TRACING) - AZStd::vector lines; - AzFramework::StringFunc::Tokenize( - message, - lines, - "\n", - false, // Keep empty strings - false // Keep space strings - ); - - for (auto& line : lines) - { - AZ_TracePrintf("Shader Management Console", "Python: %s\n", line.c_str()); - } -#endif - } - - void ShaderManagementConsoleApplication::OnErrorMessage(AZStd::string_view message) - { - // Use AZ_TracePrintf instead of AZ_Error or AZ_Warning to avoid all the metadata noise - OnTraceMessage(message); - } - - void ShaderManagementConsoleApplication::OnExceptionMessage([[maybe_unused]] AZStd::string_view message) - { - AZ_Error("Shader Management Console", false, "Python: " AZ_STRING_FORMAT, AZ_STRING_ARG(message)); - } - - // Copied from PyIdleWaitFrames in CryEdit.cpp - void ShaderManagementConsoleApplication::PyIdleWaitFrames(uint32_t frames) - { - struct Ticker : public AZ::TickBus::Handler - { - Ticker(QEventLoop* loop, uint32_t targetFrames) : m_loop(loop), m_targetFrames(targetFrames) - { - AZ::TickBus::Handler::BusConnect(); - } - ~Ticker() - { - AZ::TickBus::Handler::BusDisconnect(); - } - - void OnTick(float deltaTime, AZ::ScriptTimePoint time) override - { - AZ_UNUSED(deltaTime); - AZ_UNUSED(time); - if (++m_elapsedFrames == m_targetFrames) - { - m_loop->quit(); - } - } - QEventLoop* m_loop = nullptr; - uint32_t m_elapsedFrames = 0; - uint32_t m_targetFrames = 0; - }; - - QEventLoop loop; - Ticker ticker(&loop, frames); - loop.exec(); - } - } // namespace ShaderManagementConsole diff --git a/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/ShaderManagementConsoleApplication.h b/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/ShaderManagementConsoleApplication.h index a137e3a646..5d3696fee3 100644 --- a/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/ShaderManagementConsoleApplication.h +++ b/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/ShaderManagementConsoleApplication.h @@ -8,63 +8,32 @@ #pragma once -#include -#include -#include -#include - -#include -#include - -#include -#include -#include - #include #include - -#include +#include #include namespace ShaderManagementConsole { class ShaderManagementConsoleApplication - : public AzFramework::Application - , public AzQtComponents::AzQtApplication - , private AzToolsFramework::AssetDatabase::AssetDatabaseRequestsBus::Handler + : public AtomToolsFramework::AtomToolsApplication , private ShaderManagementConsoleWindowNotificationBus::Handler - , private AzFramework::AssetSystemStatusBus::Handler - , private AZ::UserSettingsOwnerRequestBus::Handler - , private AZ::Debug::TraceMessageBus::Handler - , private AzToolsFramework::EditorPythonConsoleNotificationBus::Handler { public: - AZ_TYPE_INFO(ShaderManagementConsole::ShaderManagementConsoleApplication, "{30F90CA5-1253-49B5-8143-19CEE37E22BB}"); + AZ_TYPE_INFO(ShaderManagementConsole::ShaderManagementConsoleApplication, "{A31B1AEB-4DA3-49CD-884A-CC998FF7546F}"); - using Base = AzFramework::Application; + using Base = AtomToolsFramework::AtomToolsApplication; ShaderManagementConsoleApplication(int* argc, char*** argv); virtual ~ShaderManagementConsoleApplication() = default; ////////////////////////////////////////////////////////////////////////// // AzFramework::Application - void CreateReflectionManager() override; - void Reflect(AZ::ReflectContext* context) override; - void RegisterCoreComponents() override; - AZ::ComponentTypeList GetRequiredSystemComponents() const override; void CreateStaticModules(AZStd::vector& outModules) override; const char* GetCurrentConfigurationName() const override; - void StartCommon(AZ::Entity* systemEntity) override; - void Tick(float deltaOverride = -1.f) override; - void Stop() override; private: - ////////////////////////////////////////////////////////////////////////// - // AssetDatabaseRequestsBus::Handler overrides... - bool GetAssetDatabaseLocation(AZStd::string& result) override; - ////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////// // ShaderManagementConsoleWindowNotificationBus::Handler overrides... void OnShaderManagementConsoleWindowClosing() override; @@ -75,57 +44,9 @@ namespace ShaderManagementConsole void Destroy() override; ////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////// - // AzFramework::ApplicationRequests::Bus overrides... - void QueryApplicationType(AZ::ApplicationTypeQuery& appType) const override; - ////////////////////////////////////////////////////////////////////////// - - //////////////////////////////////////////////////////////////////////// - // EditorPythonConsoleNotificationBus::Handler overrides... - void OnTraceMessage(AZStd::string_view message) override; - void OnErrorMessage(AZStd::string_view message) override; - void OnExceptionMessage(AZStd::string_view message) override; - //////////////////////////////////////////////////////////////////////// - - ////////////////////////////////////////////////////////////////////////// - // AzFramework::AssetSystemStatusBus::Handler overrides... - void AssetSystemAvailable() override; - ////////////////////////////////////////////////////////////////////////// - - ////////////////////////////////////////////////////////////////////////// - // AZ::UserSettingsOwnerRequestBus::Handler overrides... - void SaveSettings() override; - ////////////////////////////////////////////////////////////////////////// - - ////////////////////////////////////////////////////////////////////////// - // AZ::Debug::TraceMessageBus::Handler overrides... - bool OnPrintf(const char* window, const char* message) override; - ////////////////////////////////////////////////////////////////////////// - - void CompileCriticalAssets(); - void ProcessCommandLine(); - - void LoadSettings(); - void UnloadSettings(); - - bool LaunchDiscoveryService(); - - void StartInternal(); - - static void PyIdleWaitFrames(uint32_t frames); - - AzToolsFramework::TraceLogger m_traceLogger; - - //! Local user settings are used to store asset browser tree expansion state - AZ::UserSettingsProvider m_localUserSettings; - - //! Are local settings loaded - bool m_activatedLocalUserSettings = false; - - QTimer m_timer; - - bool m_started = false; - bool m_closing = false; + void StartInternal() override; + AZStd::string GetBuildTargetName() const override; + AZStd::vector GetCriticalAssetFilters() const override; }; } // namespace ShaderManagementConsole diff --git a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.h b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.h index c0ee4f8e02..75b7ec9fdd 100644 --- a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.h +++ b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.h @@ -24,37 +24,64 @@ namespace AZ namespace Render { - struct ThreadRegionEntry + //! Stores all the data associated with a row in the table. + struct TableRow { - AZStd::thread_id m_threadId; - AZStd::sys_time_t m_startTick = 0; - AZStd::sys_time_t m_endTick = 0; + template + struct TableRowCompareFunctor + { + TableRowCompareFunctor(T memberPointer, bool isAscending) : m_memberPointer(memberPointer), m_ascending(isAscending){}; + + bool operator()(const TableRow* lhs, const TableRow* rhs) + { + return m_ascending ? lhs->*m_memberPointer < rhs->*m_memberPointer : lhs->*m_memberPointer > rhs->*m_memberPointer; + } + + T m_memberPointer; + bool m_ascending; + }; + + // Update running statistics with new region data + void RecordRegion(const AZ::RHI::CachedTimeRegion& region, AZStd::thread_id threadId); + + void ResetPerFrameStatistics(); + + // Get a string of all threads that this region executed in during the last frame + AZStd::string GetExecutingThreadsLabel() const; + + AZStd::string m_groupName; + AZStd::string m_regionName; + + // --- Per frame statistics --- + + u64 m_invocationsLastFrame = 0; + + // NOTE: set over unordered_set so the threads can be shown in increasing order in tooltip. + AZStd::set m_executingThreads; + + AZStd::sys_time_t m_lastFrameTotalTicks = 0; + + // Maximum execution time of a region in the last frame. + AZStd::sys_time_t m_maxTicks = 0; + + // --- Aggregate statistics --- + + u64 m_invocationsTotal = 0; + + // Running average of Mean Time Per Call + AZStd::sys_time_t m_runningAverageTicks = 0; }; - // Stores data about a region that is agreggated from all collected frames - // Data collection can be toggled on and off through m_record. - struct RegionStatistics - { - float CalcAverageTimeMs() const; - void RecordRegion(const AZ::RHI::CachedTimeRegion& region); - - bool m_draw = false; - bool m_record = true; - u64 m_invocations = 0; - AZStd::sys_time_t m_totalTicks = 0; - }; - - //! Visual profiler for Cpu statistics. - //! It uses ImGui as the library for displaying the Attachments and Heaps. - //! It shows all heaps that are being used by the RHI and how the - //! resources are allocated in each heap. + //! ImGui widget for examining Atom CPU Profiling instrumentation. + //! Offers both a statistical view (with sorting and searching capability) and a visualizer + //! similar to RAD and other profiling tools. class ImGuiCpuProfiler : SystemTickBus::Handler { - // Region Name -> Array of ThreadRegion entries - using RegionEntryMap = AZStd::map>; - // Group Name -> RegionEntryMap - using GroupRegionMap = AZStd::map; + // Region Name -> statistical view row data + using RegionRowMap = AZStd::map; + // Group Name -> RegionRowMap + using GroupRegionMap = AZStd::map; using TimeRegion = AZ::RHI::CachedTimeRegion; using GroupRegionName = AZ::RHI::CachedTimeRegion::GroupRegionName; @@ -66,42 +93,26 @@ namespace AZ //! Draws the overall CPU profiling window, defaults to the statistical view void Draw(bool& keepDrawing, const AZ::RHI::CpuTimingStatistics& cpuTimingStatistics); - //! Draws the statistical view of the CPU profiling data - void DrawStatisticsView(); - - //! Draws the CPU profiling visualizer in a new window. - void DrawVisualizer(); - private: static constexpr float RowHeight = 50.0; static constexpr int DefaultFramesToCollect = 50; + static constexpr float MediumFrameTimeLimit = 16.6; // 60 fps + static constexpr float HighFrameTimeLimit = 33.3; // 30 fps - // Draw the shared header between the two windows + //! Draws the statistical view of the CPU profiling data. + void DrawStatisticsView(); + + //! Draws the CPU profiling visualizer. + void DrawVisualizer(); + + // Draw the shared header between the two windows. void DrawCommonHeader(); - // ImGui filter used to filter TimedRegions. - ImGuiTextFilter m_timedRegionFilter; + // Draw the region statistics table in the order specified by the pointers in m_tableData. + void DrawTable(); - // Saves statistical view data organized by group name -> region name -> regions - GroupRegionMap m_groupRegionMap; - - // Pause cpu profiling. The profiler will show the statistics of the last frame before pause - bool m_paused = false; - - // Export the profiling data from a single frame to a local file - bool m_captureToFile = false; - - // Toggle between the normal statistical view and the visual profiling view - bool m_enableVisualizer = false; - - // Total frames need to be saved - int m_captureFrameCount = 1; - - AZ::RHI::CpuTimingStatistics m_cpuTimingStatisticsWhenPause; - - AZStd::string m_lastCapturedFilePath; - - // Visualizer methods + // Sort the table by a given column, rearranges the pointers in m_tableData. + void SortTable(ImGuiTableSortSpecs* sortSpecs); // Get the profiling data from the last frame, only called when the profiler is not paused. void CollectFrameData(); @@ -109,7 +120,7 @@ namespace AZ // Cull old data from internal storage, only called when profiler is not paused. void CullFrameData(const AZ::RHI::CpuTimingStatistics& currentCpuTimingStatistics); - // Draws a single block onto the timeline + // Draws a single block onto the timeline into the specified row void DrawBlock(const TimeRegion& block, u64 targetRow); // Draw horizontal lines between threads in the timeline @@ -118,28 +129,28 @@ namespace AZ // Draw the "Thread XXXXX" label onto the viewport void DrawThreadLabel(u64 baseRow, AZStd::thread_id threadId); - // Draws all active function statistics windows - void DrawRegionStatistics(); - // Draw the vertical lines separating frames in the timeline void DrawFrameBoundaries(); // Draw the ruler with frame time labels void DrawRuler(); + // Draw the frame time histogram + void DrawFrameTimeHistogram(); + // Converts raw ticks to a pixel value suitable to give to ImDrawList, handles window scrolling - float ConvertTickToPixelSpace(AZStd::sys_time_t tick) const; + float ConvertTickToPixelSpace(AZStd::sys_time_t tick, AZStd::sys_time_t leftBound, AZStd::sys_time_t rightBound) const; AZStd::sys_time_t GetViewportTickWidth() const; - // Gets the color for a block using the GroupRegionName as a key into the cache - // Generates a random ImU32 if the block does not yet have a color + // Gets the color for a block using the GroupRegionName as a key into the cache. + // Generates a random ImU32 if the block does not yet have a color. ImU32 GetBlockColor(const TimeRegion& block); // System tick bus overrides virtual void OnSystemTick() override; - // Visualizer state + // --- Visualizer Members --- int m_framesToCollect = DefaultFramesToCollect; @@ -159,10 +170,34 @@ namespace AZ // Tracks the frame boundaries AZStd::vector m_frameEndTicks = { INT64_MIN }; - // Main data structure for storing function statistics to be shown in the popup windows. - // For now we default allocate for all regions on the first render frame and then use RegionStatistics.m_draw to determine - // if we should draw the window or not. FIXME(ATOM-15948) this should be changed once RegionStatistics gets heavier. - AZStd::unordered_map m_regionStatisticsMap; + // Filter for highlighting regions on the visualizer + ImGuiTextFilter m_visualizerHighlightFilter; + + // --- Tabular view members --- + + // ImGui filter used to filter TimedRegions. + ImGuiTextFilter m_timedRegionFilter; + + // Saves statistical view data organized by group name -> region name -> row data + GroupRegionMap m_groupRegionMap; + + // Saves pointers to objects in m_groupRegionMap, order reflects table ordering. + // Non-owning, will be cleared when m_groupRegionMap is cleared. + AZStd::vector m_tableData; + + // Pause cpu profiling. The profiler will show the statistics of the last frame before pause. + bool m_paused = false; + + // Export the profiling data from a single frame to a local file. + bool m_captureToFile = false; + + // Toggle between the normal statistical view and the visual profiling view. + bool m_enableVisualizer = false; + + // Last captured CPU timing statistics + AZ::RHI::CpuTimingStatistics m_cpuTimingStatisticsWhenPause; + + AZStd::string m_lastCapturedFilePath; }; } // namespace Render } // namespace AZ diff --git a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.inl b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.inl index b59747421e..ffd7af2f20 100644 --- a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.inl +++ b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.inl @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -36,6 +37,7 @@ namespace AZ { return AZStd::string::format("Thread: %zu", static_cast(threadId)); } + inline float TicksToMs(AZStd::sys_time_t ticks) { // Note: converting to microseconds integer before converting to milliseconds float @@ -134,6 +136,99 @@ namespace AZ } } + inline void ImGuiCpuProfiler::DrawTable() + { + const auto flags = + ImGuiTableFlags_Borders | ImGuiTableFlags_Sortable | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable; + if (ImGui::BeginTable("FunctionStatisticsTable", 6, flags)) + { + // Table header setup + ImGui::TableSetupColumn("Group"); + ImGui::TableSetupColumn("Region"); + ImGui::TableSetupColumn("MTPC (ms)"); + ImGui::TableSetupColumn("Max (ms)"); + ImGui::TableSetupColumn("Invocations"); + ImGui::TableSetupColumn("Total (ms)"); + ImGui::TableHeadersRow(); + ImGui::TableNextColumn(); + + ImGuiTableSortSpecs* sortSpecs = ImGui::TableGetSortSpecs(); + if (sortSpecs && sortSpecs->SpecsDirty) + { + SortTable(sortSpecs); + } + + // Draw all of the rows held in the GroupRegionMap + for (const auto* statistics : m_tableData) + { + if (!m_timedRegionFilter.PassFilter(statistics->m_groupName.c_str()) + && !m_timedRegionFilter.PassFilter(statistics->m_regionName.c_str())) + { + continue; + } + + ImGui::Text(statistics->m_groupName.c_str()); + const ImVec2 topLeftBound = ImGui::GetItemRectMin(); + ImGui::TableNextColumn(); + + ImGui::Text(statistics->m_regionName.c_str()); + ImGui::TableNextColumn(); + + ImGui::Text("%.2f", CpuProfilerImGuiHelper::TicksToMs(statistics->m_runningAverageTicks)); + ImGui::TableNextColumn(); + + ImGui::Text("%.2f", CpuProfilerImGuiHelper::TicksToMs(statistics->m_maxTicks)); + ImGui::TableNextColumn(); + + ImGui::Text("%llu", statistics->m_invocationsLastFrame); + ImGui::TableNextColumn(); + + ImGui::Text("%.2f", CpuProfilerImGuiHelper::TicksToMs(statistics->m_lastFrameTotalTicks)); + const ImVec2 botRightBound = ImGui::GetItemRectMax(); + ImGui::TableNextColumn(); + + // NOTE: we are manually checking the bounds rather than using ImGui::IsItemHovered + Begin/EndGroup because + // ImGui reports incorrect bounds when using Begin/End group in the Tables API. + if (ImGui::IsWindowHovered() && ImGui::IsMouseHoveringRect(topLeftBound, botRightBound, false)) + { + ImGui::BeginTooltip(); + ImGui::Text(statistics->GetExecutingThreadsLabel().c_str()); + ImGui::EndTooltip(); + } + } + } + ImGui::EndTable(); + } + + inline void ImGuiCpuProfiler::SortTable(ImGuiTableSortSpecs* sortSpecs) + { + const bool ascending = sortSpecs->Specs->SortDirection == ImGuiSortDirection_Ascending; + const ImS16 columnToSort = sortSpecs->Specs->ColumnIndex; + + switch (columnToSort) + { + case (0): // Sort by group name + AZStd::sort(m_tableData.begin(), m_tableData.end(), TableRow::TableRowCompareFunctor(&TableRow::m_groupName, ascending)); + break; + case (1): // Sort by region name + AZStd::sort(m_tableData.begin(), m_tableData.end(), TableRow::TableRowCompareFunctor(&TableRow::m_regionName, ascending)); + break; + case (2): // Sort by average time + AZStd::sort(m_tableData.begin(), m_tableData.end(), TableRow::TableRowCompareFunctor(&TableRow::m_runningAverageTicks, ascending)); + break; + case (3): // Sort by max time + AZStd::sort(m_tableData.begin(), m_tableData.end(), TableRow::TableRowCompareFunctor(&TableRow::m_maxTicks, ascending)); + break; + case (4): // Sort by invocations + AZStd::sort(m_tableData.begin(), m_tableData.end(), TableRow::TableRowCompareFunctor(&TableRow::m_invocationsLastFrame, ascending)); + break; + case (5): // Sort by total time + AZStd::sort(m_tableData.begin(), m_tableData.end(), TableRow::TableRowCompareFunctor(&TableRow::m_lastFrameTotalTicks, ascending)); + break; + } + sortSpecs->SpecsDirty = false; + } + inline void ImGuiCpuProfiler::DrawStatisticsView() { DrawCommonHeader(); @@ -156,62 +251,6 @@ namespace AZ ImGui::NextColumn(); }; - const auto DrawRegionHoverMarker = [this, &ShowTimeInMs](AZStd::vector& entries) - { - if (ImGui::IsItemHovered()) - { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(ImGui::GetFontSize() * 60.0f); - - for (ThreadRegionEntry& entry : entries) - { - ImGui::Text(CpuProfilerImGuiHelper::TextThreadId(entry.m_threadId.m_id).c_str()); - - const AZStd::sys_time_t elapsed = entry.m_endTick - entry.m_startTick; - ShowTimeInMs(elapsed); - ImGui::Separator(); - } - - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } - }; - - const auto ShowRegionRow = - [ticksPerSecond, &DrawRegionHoverMarker, - &ShowTimeInMs](const char* regionLabel, AZStd::vector regions, AZStd::sys_time_t duration) - { - // Draw the region label - ImGui::Text(regionLabel); - ImGui::NextColumn(); - - // Draw the thread count label - AZStd::sys_time_t totalTime = 0; - AZStd::set threads; - for (ThreadRegionEntry& entry : regions) // Find the thread count and total execution time for all threads - { - threads.insert(entry.m_threadId); - totalTime += entry.m_endTick - entry.m_startTick; - } - const AZStd::string threadLabel = AZStd::string::format("Threads: %u", static_cast(threads.size())); - ImGui::Text(threadLabel.c_str()); - DrawRegionHoverMarker(regions); - ImGui::NextColumn(); - - // Draw the overall invocation count - const AZStd::string invocationLabel = AZStd::string::format("Total calls: %u", static_cast(regions.size())); - ImGui::Text(invocationLabel.c_str()); - DrawRegionHoverMarker(regions); - ImGui::NextColumn(); - - // Draw the time labels (max and then total) - const AZStd::string timeLabel = AZStd::string::format( - "%.2f ms max, %.2f ms total", CpuProfilerImGuiHelper::TicksToMs(duration), - CpuProfilerImGuiHelper::TicksToMs(totalTime)); - ImGui::Text(timeLabel.c_str()); - ImGui::NextColumn(); - }; - if (ImGui::BeginChild("Statistics View", { 0, 0 }, true)) { // Set column settings. @@ -229,44 +268,20 @@ namespace AZ ImGui::Separator(); ImGui::Columns(1, "view", false); - m_timedRegionFilter.Draw("TimedRegion Filter"); - - // Draw the timed regions - if (ImGui::BeginChild("TimedRegions")) + m_timedRegionFilter.Draw("Filter"); + ImGui::SameLine(); + if (ImGui::Button("Clear Filter")) { - for (auto& timeRegionMapEntry : m_groupRegionMap) - { - // Draw the regions - if (ImGui::TreeNodeEx(timeRegionMapEntry.first.c_str(), ImGuiTreeNodeFlags_DefaultOpen)) - { - ImGui::Columns(4, "view", false); - ImGui::SetColumnWidth(0, 400.0f); - ImGui::SetColumnWidth(1, 100.0f); - ImGui::SetColumnWidth(2, 150.0f); - ImGui::SetColumnWidth(3, 240.0f); - - for (auto& region : timeRegionMapEntry.second) - { - // Calculate the region with the longest execution time - AZStd::sys_time_t threadExecutionElapsed = 0; - for (ThreadRegionEntry& entry : region.second) - { - const AZStd::sys_time_t elapsed = entry.m_endTick - entry.m_startTick; - threadExecutionElapsed = AZStd::max(threadExecutionElapsed, elapsed); - } - - // Only draw the TimedRegion rows when it passes the filter - if (m_timedRegionFilter.PassFilter(region.first.c_str())) - { - ShowRegionRow(region.first.c_str(), region.second, threadExecutionElapsed); - } - } - ImGui::Columns(1, "view", false); - ImGui::TreePop(); - } - } - ImGui::EndChild(); + m_timedRegionFilter.Clear(); } + ImGui::SameLine(); + if (ImGui::Button("Reset Table")) + { + m_tableData.clear(); + m_groupRegionMap.clear(); + } + + DrawTable(); } } @@ -279,8 +294,8 @@ namespace AZ if (ImGui::BeginChild("Options and Statistics", { 0, 0 }, true)) { ImGui::Columns(3, "Options", true); - ImGui::Text("Frames To Collect:"); - ImGui::SliderInt("", &m_framesToCollect, 10, 100, "%d", ImGuiSliderFlags_AlwaysClamp | ImGuiSliderFlags_Logarithmic); + ImGui::SliderInt("Saved Frames", &m_framesToCollect, 10, 10000, "%d", ImGuiSliderFlags_AlwaysClamp | ImGuiSliderFlags_Logarithmic); + m_visualizerHighlightFilter.Draw("Find Region"); ImGui::NextColumn(); @@ -295,6 +310,13 @@ namespace AZ "Hold the right mouse button to move around. Zoom by scrolling the mouse wheel while holding ."); } + ImGui::Columns(1, "FrameTimeColumn", true); + + if (ImGui::BeginChild("FrameTimeHistogram", { 0, 50 }, true, ImGuiWindowFlags_NoScrollbar)) + { + DrawFrameTimeHistogram(); + } + ImGui::EndChild(); ImGui::Columns(1, "RulerColumn", true); @@ -365,7 +387,6 @@ namespace AZ baseRow += maxDepth + 1; // Next draw loop should start one row down } - DrawRegionStatistics(); DrawFrameBoundaries(); // Draw an invisible button to capture inputs @@ -425,14 +446,11 @@ namespace AZ // view is only holding data from the last frame, the memory overhead is minimal and gives us a faster redraw // compared to if we needed to transform the visualizer's data into the statistical format every frame. - // Clear the statistical view's cached entries - m_groupRegionMap.clear(); - // Get the latest TimeRegionMap const RHI::CpuProfiler::TimeRegionMap& timeRegionMap = RHI::CpuProfiler::Get()->GetTimeRegionMap(); - m_viewportStartTick = INT64_MAX; - m_viewportEndTick = INT64_MIN; + m_viewportStartTick = AZStd::numeric_limits::max(); + m_viewportEndTick = AZStd::numeric_limits::lowest(); // Iterate through the entire TimeRegionMap and copy the data since it will get deleted on the next frame for (const auto& [threadId, singleThreadRegionMap] : timeRegionMap) @@ -454,14 +472,15 @@ namespace AZ // Also update the statistical view's data const AZStd::string& groupName = region.m_groupRegionName->m_groupName; - m_groupRegionMap[groupName][regionName].push_back( - { threadId, region.m_startTick, region.m_endTick }); - // Update running statistics if we want to record this region's data - if (m_regionStatisticsMap[region.m_groupRegionName].m_record) + if (!m_groupRegionMap[groupName].contains(regionName)) { - m_regionStatisticsMap[region.m_groupRegionName].RecordRegion(region); + m_groupRegionMap[groupName][regionName].m_groupName = groupName; + m_groupRegionMap[groupName][regionName].m_regionName = regionName; + m_tableData.push_back(&m_groupRegionMap[groupName][regionName]); } + + m_groupRegionMap[groupName][regionName].RecordRegion(region, threadId); } } @@ -515,12 +534,18 @@ namespace AZ inline void ImGuiCpuProfiler::DrawBlock(const TimeRegion& block, u64 targetRow) { + // Don't draw anything if the user is searching for regions and this block doesn't pass the filter + if (!m_visualizerHighlightFilter.PassFilter(block.m_groupRegionName->m_regionName)) + { + return; + } + float wy = ImGui::GetWindowPos().y - ImGui::GetScrollY(); ImDrawList* drawList = ImGui::GetWindowDrawList(); - const float startPixel = ConvertTickToPixelSpace(block.m_startTick); - const float endPixel = ConvertTickToPixelSpace(block.m_endTick); + const float startPixel = ConvertTickToPixelSpace(block.m_startTick, m_viewportStartTick, m_viewportEndTick); + const float endPixel = ConvertTickToPixelSpace(block.m_endTick, m_viewportStartTick, m_viewportEndTick); const ImVec2 startPoint = { startPixel, wy + targetRow * RowHeight }; const ImVec2 endPoint = { endPixel, wy + targetRow * RowHeight + 40 }; @@ -560,13 +585,14 @@ namespace AZ // Tooltip and block highlighting if (ImGui::IsMouseHoveringRect(startPoint, endPoint) && ImGui::IsWindowHovered()) { - // Open function statistics map on click + // Go to the statistics view when a region is clicked if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) { - const GroupRegionName* key = block.m_groupRegionName; - m_regionStatisticsMap[key].m_draw = true; + m_enableVisualizer = false; + const auto newFilter = AZStd::string(block.m_groupRegionName->m_regionName); + m_timedRegionFilter = ImGuiTextFilter(newFilter.c_str()); + m_timedRegionFilter.Build(); } - // Hovering outline drawList->AddRect(startPoint, endPoint, ImGui::GetColorU32({ 1, 1, 1, 1 }), 0.0, 0, 1.5); @@ -618,31 +644,6 @@ namespace AZ ImGui::GetWindowDrawList()->AddText({ wx + 10, wy + baseRow * RowHeight + 5 }, IM_COL32_WHITE, threadIdText.c_str()); } - inline void ImGuiCpuProfiler::DrawRegionStatistics() - { - for (auto& [groupRegionName, stat] : m_regionStatisticsMap) - { - if (stat.m_draw) - { - ImGui::SetNextWindowSize({300, 340}, ImGuiCond_FirstUseEver); - ImGui::Begin(groupRegionName->m_regionName, &stat.m_draw, 0); - - if (ImGui::Button(stat.m_record ? "Pause" : "Resume")) - { - stat.m_record = !stat.m_record; - } - - ImGui::Text("Invocations: %llu", stat.m_invocations); - ImGui::Text("Average time: %.3f ms", stat.CalcAverageTimeMs()); - - ImGui::Separator(); - - ImGui::ColorPicker4("Region color", &m_regionColorMap[groupRegionName].x); - ImGui::End(); - } - } - } - inline void ImGuiCpuProfiler::DrawFrameBoundaries() { ImDrawList* drawList = ImGui::GetWindowDrawList(); @@ -656,7 +657,7 @@ namespace AZ while (endTickItr != m_frameEndTicks.end() && *endTickItr < m_viewportEndTick) { - const float horizontalPixel = ConvertTickToPixelSpace(*endTickItr); + const float horizontalPixel = ConvertTickToPixelSpace(*endTickItr, m_viewportStartTick, m_viewportEndTick); drawList->AddLine({ horizontalPixel, wy }, { horizontalPixel, wy + windowHeight }, red); ++endTickItr; } @@ -684,8 +685,8 @@ namespace AZ break; } - const float lastFrameBoundaryPixel = ConvertTickToPixelSpace(lastFrameBoundaryTick); - const float nextFrameBoundaryPixel = ConvertTickToPixelSpace(nextFrameBoundaryTick); + const float lastFrameBoundaryPixel = ConvertTickToPixelSpace(lastFrameBoundaryTick, m_viewportStartTick, m_viewportEndTick); + const float nextFrameBoundaryPixel = ConvertTickToPixelSpace(nextFrameBoundaryTick, m_viewportStartTick, m_viewportEndTick); const AZStd::string label = AZStd::string::format("%.2f ms", CpuProfilerImGuiHelper::TicksToMs(nextFrameBoundaryTick - lastFrameBoundaryTick)); @@ -738,16 +739,98 @@ namespace AZ } } + inline void ImGuiCpuProfiler::DrawFrameTimeHistogram() + { + ImDrawList* drawList = ImGui::GetWindowDrawList(); + const auto [wx, wy] = ImGui::GetWindowPos(); + const ImU32 orange = ImGui::GetColorU32({ 1, .7, 0, 1 }); + const ImU32 red = ImGui::GetColorU32({ 1, 0, 0, 1 }); + + const AZStd::sys_time_t ticksPerSecond = AZStd::GetTimeTicksPerSecond(); + const AZStd::sys_time_t viewportCenter = m_viewportEndTick - (m_viewportEndTick - m_viewportStartTick) / 2; + const AZStd::sys_time_t leftHistogramBound = viewportCenter - ticksPerSecond; + const AZStd::sys_time_t rightHistogramBound = viewportCenter + ticksPerSecond; + + // Draw frame limit lines + drawList->AddLine( + { wx, wy + ImGui::GetWindowHeight() - MediumFrameTimeLimit }, + { wx + ImGui::GetWindowWidth(), wy + ImGui::GetWindowHeight() - MediumFrameTimeLimit }, + orange); + + drawList->AddLine( + { wx, wy + ImGui::GetWindowHeight() - HighFrameTimeLimit }, + { wx + ImGui::GetWindowWidth(), wy + ImGui::GetWindowHeight() - HighFrameTimeLimit }, + red); + + + // Draw viewport bound rectangle + const float leftViewportPixel = ConvertTickToPixelSpace(m_viewportStartTick, leftHistogramBound, rightHistogramBound); + const float rightViewportPixel = ConvertTickToPixelSpace(m_viewportEndTick, leftHistogramBound, rightHistogramBound); + const ImVec2 topLeftPos = { leftViewportPixel, wy }; + const ImVec2 botRightPos = { rightViewportPixel, wy + ImGui::GetWindowHeight() }; + const ImU32 gray = ImGui::GetColorU32({ 1, 1, 1, .3 }); + drawList->AddRectFilled(topLeftPos, botRightPos, gray); + + // Find the first onscreen frame execution time + auto frameEndTickItr = AZStd::lower_bound(m_frameEndTicks.begin(), m_frameEndTicks.end(), leftHistogramBound); + if (frameEndTickItr != m_frameEndTicks.begin()) + { + --frameEndTickItr; + } + + // Since we only store the frame end ticks, we must calculate the execution times on the fly by comparing pairs of elements. + AZStd::sys_time_t lastFrameEndTick = *frameEndTickItr; + while (*frameEndTickItr < rightHistogramBound && ++frameEndTickItr != m_frameEndTicks.end()) + { + const AZStd::sys_time_t frameEndTick = *frameEndTickItr; + + const float framePixelPos = ConvertTickToPixelSpace(frameEndTick, leftHistogramBound, rightHistogramBound); + const float frameTimeMs = CpuProfilerImGuiHelper::TicksToMs(frameEndTick - lastFrameEndTick); + + const ImVec2 lineBottom = { framePixelPos, ImGui::GetWindowHeight() + wy }; + const ImVec2 lineTop = { framePixelPos, ImGui::GetWindowHeight() + wy - frameTimeMs }; + + ImU32 lineColor = ImGui::GetColorU32({ .3, .3, .3, 1 }); // Gray + if (frameTimeMs > HighFrameTimeLimit) + { + lineColor = ImGui::GetColorU32({1, 0, 0, 1}); // Red + } + else if (frameTimeMs > MediumFrameTimeLimit) + { + lineColor = ImGui::GetColorU32({1, .7, 0, 1}); // Orange + } + + drawList->AddLine(lineBottom, lineTop, lineColor, 3.0); + + lastFrameEndTick = frameEndTick; + } + + // Handle input + ImGui::InvisibleButton("HistogramInputCapture", { ImGui::GetWindowWidth(), ImGui::GetWindowHeight() }); + ImGuiIO& io = ImGui::GetIO(); + if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) + { + const float mousePixelX = io.MousePos.x; + const float percentWindow = (mousePixelX - wx) / ImGui::GetWindowWidth(); + const AZStd::sys_time_t newViewportCenterTick = leftHistogramBound + + aznumeric_cast((rightHistogramBound - leftHistogramBound) * percentWindow); + + const AZStd::sys_time_t viewportWidth = GetViewportTickWidth(); + m_viewportEndTick = newViewportCenterTick + viewportWidth / 2; + m_viewportStartTick = newViewportCenterTick - viewportWidth / 2; + } + } + inline AZStd::sys_time_t ImGuiCpuProfiler::GetViewportTickWidth() const { return m_viewportEndTick - m_viewportStartTick; } - inline float ImGuiCpuProfiler::ConvertTickToPixelSpace(AZStd::sys_time_t tick) const + inline float ImGuiCpuProfiler::ConvertTickToPixelSpace(AZStd::sys_time_t tick, AZStd::sys_time_t leftBound, AZStd::sys_time_t rightBound) const { const float wx = ImGui::GetWindowPos().x; - const float tickSpaceShifted = aznumeric_cast(tick - m_viewportStartTick); // This will be close to zero, so FP inaccuracy should not be too bad - const float tickSpaceNormalized = tickSpaceShifted / GetViewportTickWidth(); + const float tickSpaceShifted = aznumeric_cast(tick - leftBound); // This will be close to zero, so FP inaccuracy should not be too bad + const float tickSpaceNormalized = tickSpaceShifted / (rightBound - leftBound); const float pixelSpace = tickSpaceNormalized * ImGui::GetWindowWidth() + wx; return pixelSpace; } @@ -762,25 +845,51 @@ namespace AZ else { m_frameEndTicks.push_back(AZStd::GetTimeNowTicks()); + + for (auto& [groupName, regionMap] : m_groupRegionMap) + { + for (auto& [regionName, row] : regionMap) + { + row.ResetPerFrameStatistics(); + } + } } } - // ----- RegionStatistics implementation ----- - - inline float RegionStatistics::CalcAverageTimeMs() const + // ---- TableRow impl ---- + + inline void TableRow::RecordRegion(const AZ::RHI::CachedTimeRegion& region, AZStd::thread_id threadId) { - if (m_invocations == 0) + const AZStd::sys_time_t deltaTime = region.m_endTick - region.m_startTick; + + // Update per frame statistics + ++m_invocationsLastFrame; + m_executingThreads.insert(threadId); + m_lastFrameTotalTicks += deltaTime; + m_maxTicks = AZStd::max(m_maxTicks, deltaTime); + + // Update aggregate statistics + m_runningAverageTicks = + aznumeric_cast((1.0 * (deltaTime + m_invocationsTotal * m_runningAverageTicks)) / (m_invocationsTotal + 1)); + ++m_invocationsTotal; + } + + inline void TableRow::ResetPerFrameStatistics() + { + m_invocationsLastFrame = 0; + m_executingThreads.clear(); + m_lastFrameTotalTicks = 0; + m_maxTicks = 0; + } + + inline AZStd::string TableRow::GetExecutingThreadsLabel() const + { + auto threadString = AZStd::string::format("Executed in %zu threads\n", m_executingThreads.size()); + for (const auto& threadId : m_executingThreads) { - return 0.0; + threadString.append(CpuProfilerImGuiHelper::TextThreadId(threadId.m_id) + "\n"); } - const double averageTicks = aznumeric_cast(m_totalTicks) / m_invocations; - return CpuProfilerImGuiHelper::TicksToMs(aznumeric_cast(averageTicks)); - } - - inline void RegionStatistics::RecordRegion(const AZ::RHI::CachedTimeRegion& region) - { - m_invocations++; - m_totalTicks += region.m_endTick - region.m_startTick; + return threadString; } } // namespace Render } // namespace AZ diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/Material/MaterialComponentBus.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/Material/MaterialComponentBus.h index 134bf6db47..6b637a67c5 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/Material/MaterialComponentBus.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/Material/MaterialComponentBus.h @@ -21,6 +21,8 @@ namespace AZ public: //! Get all material assignments that can be overridden virtual MaterialAssignmentMap GetOriginalMaterialAssignments() const = 0; + //! Get material assignment id matching lod and label substring + virtual MaterialAssignmentId FindMaterialAssignmentId(const MaterialAssignmentLodIndex lod, const AZStd::string& label) const = 0; //! Set material overrides virtual void SetMaterialOverrides(const MaterialAssignmentMap& materials) = 0; //! Get material overrides @@ -69,6 +71,9 @@ namespace AZ : public ComponentBus { public: + //! Get material assignment id matching lod and label substring + virtual MaterialAssignmentId FindMaterialAssignmentId( + const MaterialAssignmentLodIndex lod, const AZStd::string& label) const = 0; virtual MaterialAssignmentMap GetMaterialAssignments() const = 0; virtual AZStd::unordered_set GetModelUvNames() const = 0; }; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/MaterialComponentController.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/MaterialComponentController.cpp index 5d9df24854..e39f5cfad5 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/MaterialComponentController.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/MaterialComponentController.cpp @@ -33,6 +33,7 @@ namespace AZ ->Attribute(AZ::Script::Attributes::Category, "render") ->Attribute(AZ::Script::Attributes::Module, "render") ->Event("GetOriginalMaterialAssignments", &MaterialComponentRequestBus::Events::GetOriginalMaterialAssignments) + ->Event("FindMaterialAssignmentId", &MaterialComponentRequestBus::Events::FindMaterialAssignmentId) ->Event("SetMaterialOverrides", &MaterialComponentRequestBus::Events::SetMaterialOverrides) ->Event("GetMaterialOverrides", &MaterialComponentRequestBus::Events::GetMaterialOverrides) ->Event("ClearAllMaterialOverrides", &MaterialComponentRequestBus::Events::ClearAllMaterialOverrides) @@ -249,10 +250,20 @@ namespace AZ MaterialAssignmentMap MaterialComponentController::GetOriginalMaterialAssignments() const { MaterialAssignmentMap materialAssignmentMap; - MaterialReceiverRequestBus::EventResult(materialAssignmentMap, m_entityId, &MaterialReceiverRequestBus::Events::GetMaterialAssignments); + MaterialReceiverRequestBus::EventResult( + materialAssignmentMap, m_entityId, &MaterialReceiverRequestBus::Events::GetMaterialAssignments); return materialAssignmentMap; } + MaterialAssignmentId MaterialComponentController::FindMaterialAssignmentId( + const MaterialAssignmentLodIndex lod, const AZStd::string& label) const + { + MaterialAssignmentId materialAssignmentId; + MaterialReceiverRequestBus::EventResult( + materialAssignmentId, m_entityId, &MaterialReceiverRequestBus::Events::FindMaterialAssignmentId, lod, label); + return materialAssignmentId; + } + void MaterialComponentController::SetMaterialOverrides(const MaterialAssignmentMap& materials) { // this function is called twice once material asset is changed, a temp variable is diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/MaterialComponentController.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/MaterialComponentController.h index eb1d7465c0..de7f991d60 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/MaterialComponentController.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/MaterialComponentController.h @@ -46,6 +46,7 @@ namespace AZ //! MaterialComponentRequestBus overrides... MaterialAssignmentMap GetOriginalMaterialAssignments() const override; + MaterialAssignmentId FindMaterialAssignmentId(const MaterialAssignmentLodIndex lod, const AZStd::string& label) const override; void SetMaterialOverrides(const MaterialAssignmentMap& materials) override; const MaterialAssignmentMap& GetMaterialOverrides() const override; void ClearAllMaterialOverrides() override; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.cpp index 29bd9a839b..fa4586daba 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.cpp @@ -252,6 +252,12 @@ namespace AZ } } + MaterialAssignmentId MeshComponentController::FindMaterialAssignmentId( + const MaterialAssignmentLodIndex lod, const AZStd::string& label) const + { + return FindMaterialAssignmentIdInModel(GetModel(), lod, label); + } + MaterialAssignmentMap MeshComponentController::GetMaterialAssignments() const { return GetMaterialAssignmentsFromModel(GetModel()); diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.h index 80b483452f..d99d5000eb 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.h @@ -111,6 +111,8 @@ namespace AZ void OnTransformChanged(const AZ::Transform& local, const AZ::Transform& world) override; // MaterialReceiverRequestBus::Handler overrides ... + virtual MaterialAssignmentId FindMaterialAssignmentId( + const MaterialAssignmentLodIndex lod, const AZStd::string& label) const override; MaterialAssignmentMap GetMaterialAssignments() const override; AZStd::unordered_set GetModelUvNames() const override; diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.cpp b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.cpp index 4c6045d7ce..706ed27a4d 100644 --- a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.cpp +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.cpp @@ -308,6 +308,17 @@ namespace AZ m_skinnedMeshFeatureProcessor = nullptr; } + MaterialAssignmentId AtomActorInstance::FindMaterialAssignmentId( + const MaterialAssignmentLodIndex lod, const AZStd::string& label) const + { + if (m_skinnedMeshInstance && m_skinnedMeshInstance->m_model) + { + return FindMaterialAssignmentIdInModel(m_skinnedMeshInstance->m_model, lod, label); + } + + return MaterialAssignmentId(); + } + MaterialAssignmentMap AtomActorInstance::GetMaterialAssignments() const { if (m_skinnedMeshInstance && m_skinnedMeshInstance->m_model) diff --git a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.h b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.h index c854fed3c1..1686b52d1a 100644 --- a/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.h +++ b/Gems/AtomLyIntegration/EMotionFXAtom/Code/Source/AtomActorInstance.h @@ -120,6 +120,8 @@ namespace AZ ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// // MaterialReceiverRequestBus::Handler overrides... + virtual MaterialAssignmentId FindMaterialAssignmentId( + const MaterialAssignmentLodIndex lod, const AZStd::string& label) const override; MaterialAssignmentMap GetMaterialAssignments() const override; AZStd::unordered_set GetModelUvNames() const override; diff --git a/Gems/AudioSystem/Code/Source/Editor/AudioControlsEditorPlugin.cpp b/Gems/AudioSystem/Code/Source/Editor/AudioControlsEditorPlugin.cpp index 973cb836a0..b318cea128 100644 --- a/Gems/AudioSystem/Code/Source/Editor/AudioControlsEditorPlugin.cpp +++ b/Gems/AudioSystem/Code/Source/Editor/AudioControlsEditorPlugin.cpp @@ -14,7 +14,7 @@ #include #include -#include +#include #include #include @@ -39,7 +39,7 @@ CAudioControlsEditorPlugin::CAudioControlsEditorPlugin(IEditor* editor) QtViewOptions options; options.canHaveMultipleInstances = true; RegisterQtViewPane(editor, LyViewPane::AudioControlsEditor, LyViewPane::CategoryOther, options); - RegisterModuleResourceSelectors(GetIEditor()->GetResourceSelectorHost()); + RegisterAudioControlsResourceSelectors(); Audio::AudioSystemRequestBus::BroadcastResult(ms_pIAudioProxy, &Audio::AudioSystemRequestBus::Events::GetFreeAudioProxy); diff --git a/Gems/AudioSystem/Code/Source/Editor/AudioResourceSelectors.cpp b/Gems/AudioSystem/Code/Source/Editor/AudioResourceSelectors.cpp index f0c9ebe3d1..74233be2e9 100644 --- a/Gems/AudioSystem/Code/Source/Editor/AudioResourceSelectors.cpp +++ b/Gems/AudioSystem/Code/Source/Editor/AudioResourceSelectors.cpp @@ -7,14 +7,13 @@ */ +#include #include #include #include #include #include -using namespace AudioControls; - namespace AudioControls { //-------------------------------------------------------------------------------------------// @@ -67,10 +66,32 @@ namespace AudioControls } //-------------------------------------------------------------------------------------------// - REGISTER_RESOURCE_SELECTOR("AudioTrigger", AudioTriggerSelector, ":/AudioControlsEditor/Icons/Trigger_Icon.png"); - REGISTER_RESOURCE_SELECTOR("AudioSwitch", AudioSwitchSelector, ":/AudioControlsEditor/Icons/Switch_Icon.png"); - REGISTER_RESOURCE_SELECTOR("AudioSwitchState", AudioSwitchStateSelector, ":/AudioControlsEditor/Icons/State_Icon.png"); - REGISTER_RESOURCE_SELECTOR("AudioRTPC", AudioRTPCSelector, ":/AudioControlsEditor/Icons/RTPC_Icon.png"); - REGISTER_RESOURCE_SELECTOR("AudioEnvironment", AudioEnvironmentSelector, ":/AudioControlsEditor/Icons/Environment_Icon.png"); - REGISTER_RESOURCE_SELECTOR("AudioPreloadRequest", AudioPreloadRequestSelector, ":/AudioControlsEditor/Icons/Bank_Icon.png"); + static SStaticResourceSelectorEntry audioTriggerSelector( + "AudioTrigger", AudioTriggerSelector, ":/Icons/Trigger_Icon.svg"); + static SStaticResourceSelectorEntry audioSwitchSelector( + "AudioSwitch", AudioSwitchSelector, ":/Icons/Switch_Icon.svg"); + static SStaticResourceSelectorEntry audioStateSelector( + "AudioSwitchState", AudioSwitchStateSelector, ":/Icons/Property_Icon.png"); + static SStaticResourceSelectorEntry audioRtpcSelector( + "AudioRTPC", AudioRTPCSelector, ":/Icons/RTPC_Icon.svg"); + static SStaticResourceSelectorEntry audioEnvironmentSelector( + "AudioEnvironment", AudioEnvironmentSelector, ":/Icons/Environment_Icon.svg"); + static SStaticResourceSelectorEntry audioPreloadSelector( + "AudioPreloadRequest", AudioPreloadRequestSelector, ":/Icons/Bank_Icon.png"); + + //-------------------------------------------------------------------------------------------// + void RegisterAudioControlsResourceSelectors() + { + if (IResourceSelectorHost* host = GetIEditor()->GetResourceSelectorHost(); + host != nullptr) + { + host->RegisterResourceSelector(&audioTriggerSelector); + host->RegisterResourceSelector(&audioSwitchSelector); + host->RegisterResourceSelector(&audioStateSelector); + host->RegisterResourceSelector(&audioRtpcSelector); + host->RegisterResourceSelector(&audioEnvironmentSelector); + host->RegisterResourceSelector(&audioPreloadSelector); + } + } + } // namespace AudioControls diff --git a/Gems/AudioSystem/Code/Source/Editor/AudioResourceSelectors.h b/Gems/AudioSystem/Code/Source/Editor/AudioResourceSelectors.h new file mode 100644 index 0000000000..d2bff7c799 --- /dev/null +++ b/Gems/AudioSystem/Code/Source/Editor/AudioResourceSelectors.h @@ -0,0 +1,14 @@ +/* + * 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 + +namespace AudioControls +{ + void RegisterAudioControlsResourceSelectors(); +} diff --git a/Gems/AudioSystem/Code/audiosystem_editor_files.cmake b/Gems/AudioSystem/Code/audiosystem_editor_files.cmake index 9333cea7e3..25f78121d6 100644 --- a/Gems/AudioSystem/Code/audiosystem_editor_files.cmake +++ b/Gems/AudioSystem/Code/audiosystem_editor_files.cmake @@ -54,6 +54,7 @@ set(FILES Source/Editor/AudioControlsEditorWindow.h Source/Editor/AudioControlsLoader.h Source/Editor/AudioControlsWriter.h + Source/Editor/AudioResourceSelectors.h Source/Editor/AudioSystemPanel.h Source/Editor/ImplementationManager.h Source/Editor/InspectorPanel.h diff --git a/Gems/EMotionFX/Code/EMotionFX/Rendering/Common/OrthographicCamera.cpp b/Gems/EMotionFX/Code/EMotionFX/Rendering/Common/OrthographicCamera.cpp index 39b382a8d3..722f43c113 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Rendering/Common/OrthographicCamera.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Rendering/Common/OrthographicCamera.cpp @@ -7,6 +7,7 @@ */ #include "OrthographicCamera.h" +#include #include #include #include diff --git a/Gems/EMotionFX/Code/EMotionFX/Rendering/Common/RotateManipulator.cpp b/Gems/EMotionFX/Code/EMotionFX/Rendering/Common/RotateManipulator.cpp index a10f5fe80b..123ef00333 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Rendering/Common/RotateManipulator.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Rendering/Common/RotateManipulator.cpp @@ -8,7 +8,7 @@ #include "RotateManipulator.h" #include - +#include namespace MCommon { diff --git a/Gems/EMotionFX/Code/EMotionFX/Rendering/Common/ScaleManipulator.cpp b/Gems/EMotionFX/Code/EMotionFX/Rendering/Common/ScaleManipulator.cpp index e1dd7e0563..65915aec65 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Rendering/Common/ScaleManipulator.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Rendering/Common/ScaleManipulator.cpp @@ -7,7 +7,7 @@ */ #include "ScaleManipulator.h" - +#include namespace MCommon { diff --git a/Gems/EMotionFX/Code/EMotionFX/Rendering/Common/TranslateManipulator.cpp b/Gems/EMotionFX/Code/EMotionFX/Rendering/Common/TranslateManipulator.cpp index b7c6b87fbf..e20ae5a77e 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Rendering/Common/TranslateManipulator.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Rendering/Common/TranslateManipulator.cpp @@ -7,7 +7,7 @@ */ #include "TranslateManipulator.h" - +#include namespace MCommon { diff --git a/Gems/EMotionFX/Code/EMotionFX/Rendering/OpenGL2/Source/GLSLShader.cpp b/Gems/EMotionFX/Code/EMotionFX/Rendering/OpenGL2/Source/GLSLShader.cpp index 6bd8ab7b2f..02aad3aa04 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Rendering/OpenGL2/Source/GLSLShader.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Rendering/OpenGL2/Source/GLSLShader.cpp @@ -7,6 +7,7 @@ */ #include +#include #include "GLSLShader.h" #include "GraphicsManager.h" #include diff --git a/Gems/EMotionFX/Code/EMotionFX/Source/Actor.cpp b/Gems/EMotionFX/Code/EMotionFX/Source/Actor.cpp index 56d08760c3..973d8eb460 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Source/Actor.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Source/Actor.cpp @@ -41,6 +41,7 @@ #include #include +#include #include #include diff --git a/Gems/EMotionFX/Code/EMotionFX/Source/BlendTreeParameterNode.cpp b/Gems/EMotionFX/Code/EMotionFX/Source/BlendTreeParameterNode.cpp index 682657e5bb..8197cae9d0 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Source/BlendTreeParameterNode.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Source/BlendTreeParameterNode.cpp @@ -395,7 +395,7 @@ namespace EMotionFX } // If new parameter matches the last deleted parameter, we add it back to the parameter mask. - if (!m_deletedParameterNames.empty() && newParameterName == m_deletedParameterNames.back()) + if (!m_deletedParameterNames.empty() && newParameterName == m_deletedParameterNames.top()) { m_parameterNames.push_back(newParameterName); SortAndRemoveDuplicates(GetAnimGraph(), m_parameterNames); // make sure the mask is sorted correctly. diff --git a/Gems/EMotionFX/Code/EMotionFX/Source/Importer/ChunkProcessors.cpp b/Gems/EMotionFX/Code/EMotionFX/Source/Importer/ChunkProcessors.cpp index e1a3e0bbfa..7e7a9ae1c2 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Source/Importer/ChunkProcessors.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Source/Importer/ChunkProcessors.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include diff --git a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/LogWindow/LogWindowPlugin.h b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/LogWindow/LogWindowPlugin.h index 022a6d5bd7..1b40b3fa2a 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/LogWindow/LogWindowPlugin.h +++ b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/LogWindow/LogWindowPlugin.h @@ -10,6 +10,7 @@ #define __EMSTUDIO_LOGWINDOWPLUGIN_H #if !defined(Q_MOC_RUN) +#include #include "../StandardPluginsConfig.h" #include "../../../../EMStudioSDK/Source/DockWidgetPlugin.h" #endif diff --git a/Gems/EMotionFX/Code/MCore/Source/AzCoreConversions.h b/Gems/EMotionFX/Code/MCore/Source/AzCoreConversions.h index 79e3ef9608..d185a804b8 100644 --- a/Gems/EMotionFX/Code/MCore/Source/AzCoreConversions.h +++ b/Gems/EMotionFX/Code/MCore/Source/AzCoreConversions.h @@ -17,7 +17,7 @@ #include #include #include -#include +#include #include // This file is "glue" code to convert math back-forward between MCore and AZ. It also has functions that MCore used to @@ -37,18 +37,6 @@ namespace MCore return RGBAColor(static_cast(azColor.GetR()), static_cast(azColor.GetG()), static_cast(azColor.GetB()), static_cast(azColor.GetA())); } - // Deprecated - AZ_FORCE_INLINE AZ::Quaternion EmfxQuatToAzQuat(const MCore::Quaternion& emfxQuat) - { - return AZ::Quaternion(emfxQuat.x, emfxQuat.y, emfxQuat.z, emfxQuat.w); - } - - // Deprecated - AZ_FORCE_INLINE MCore::Quaternion AzQuatToEmfxQuat(const AZ::Quaternion& azQuat) - { - return MCore::Quaternion(azQuat.GetX(), azQuat.GetY(), azQuat.GetZ(), azQuat.GetW()); - } - AZ_FORCE_INLINE AZ::Transform EmfxTransformToAzTransform(const EMotionFX::Transform& emfxTransform) { AZ::Transform transform = AZ::Transform::CreateFromQuaternionAndTranslation(emfxTransform.mRotation, emfxTransform.mPosition); @@ -530,91 +518,4 @@ namespace MCore AZ::Vector3ToVector4(m33.GetRow(2), translation.GetZ()), mat.GetRow(3)); } - - // Deprecated. Please use AZ::Transform instead of MCore::Matrix. - MCORE_INLINE AZ::Quaternion MCoreMatrixToQuaternion(const MCore::Matrix& m) - { - const float trace = MMAT(m, 0, 0) + MMAT(m, 1, 1) + MMAT(m, 2, 2); - if (trace > 0.0f /*Math::epsilon*/) - { - const float s = 0.5f / Math::Sqrt(trace + 1.0f); - return AZ::Quaternion((MMAT(m, 1, 2) - MMAT(m, 2, 1)) * s, - (MMAT(m, 2, 0) - MMAT(m, 0, 2)) * s, - (MMAT(m, 0, 1) - MMAT(m, 1, 0)) * s, - 0.25f / s); - } - else - { - if (MMAT(m, 0, 0) > MMAT(m, 1, 1) && MMAT(m, 0, 0) > MMAT(m, 2, 2)) - { - const float s = 2.0f * Math::Sqrt(1.0f + MMAT(m, 0, 0) - MMAT(m, 1, 1) - MMAT(m, 2, 2)); - const float oneOverS = 1.0f / s; - return AZ::Quaternion(0.25f * s, - (MMAT(m, 1, 0) + MMAT(m, 0, 1)) * oneOverS, - (MMAT(m, 2, 0) + MMAT(m, 0, 2)) * oneOverS, - (MMAT(m, 1, 2) - MMAT(m, 2, 1)) * oneOverS); - } - else if (MMAT(m, 1, 1) > MMAT(m, 2, 2)) - { - const float s = 2.0f * Math::Sqrt(1.0f + MMAT(m, 1, 1) - MMAT(m, 0, 0) - MMAT(m, 2, 2)); - const float oneOverS = 1.0f / s; - return AZ::Quaternion((MMAT(m, 1, 0) + MMAT(m, 0, 1)) * oneOverS, - 0.25f * s, - (MMAT(m, 2, 1) + MMAT(m, 1, 2)) * oneOverS, - (MMAT(m, 2, 0) - MMAT(m, 0, 2)) * oneOverS); - } - else - { - const float s = 2.0f * Math::Sqrt(1.0f + MMAT(m, 2, 2) - MMAT(m, 0, 0) - MMAT(m, 1, 1)); - const float oneOverS = 1.0f / s; - return AZ::Quaternion((MMAT(m, 2, 0) + MMAT(m, 0, 2)) * oneOverS, - (MMAT(m, 2, 1) + MMAT(m, 1, 2)) * oneOverS, - 0.25f * s, - (MMAT(m, 0, 1) - MMAT(m, 1, 0)) * oneOverS); - } - } - - /* - const float trace = MMAT(m,0,0) + MMAT(m,1,1) + MMAT(m,2,2) + 1.0f; - if (trace > Math::epsilon) - { - const float s = 0.5f / Math::Sqrt(trace); - result.w = 0.25f / s; - result.x = ( MMAT(m,1,2) - MMAT(m,2,1) ) * s; - result.y = ( MMAT(m,2,0) - MMAT(m,0,2) ) * s; - result.z = ( MMAT(m,0,1) - MMAT(m,1,0) ) * s; - } - else - { - if (MMAT(m,0,0) > MMAT(m,1,1) && MMAT(m,0,0) > MMAT(m,2,2)) - { - const float s = 2.0f * Math::Sqrt( 1.0f + MMAT(m,0,0) - MMAT(m,1,1) - MMAT(m,2,2)); - const float oneOverS = 1.0f / s; - result.x = 0.25f * s; - result.y = (MMAT(m,1,0) + MMAT(m,0,1) ) * oneOverS; - result.z = (MMAT(m,2,0) + MMAT(m,0,2) ) * oneOverS; - result.w = (MMAT(m,2,1) - MMAT(m,1,2) ) * oneOverS; - } - else - if (MMAT(m,1,1) > MMAT(m,2,2)) - { - const float s = 2.0f * Math::Sqrt( 1.0f + MMAT(m,1,1) - MMAT(m,0,0) - MMAT(m,2,2)); - const float oneOverS = 1.0f / s; - result.x = (MMAT(m,1,0) + MMAT(m,0,1) ) * oneOverS; - result.y = 0.25f * s; - result.z = (MMAT(m,2,1) + MMAT(m,1,2) ) * oneOverS; - result.w = (MMAT(m,2,0) - MMAT(m,0,2) ) * oneOverS; - } - else - { - const float s = 2.0f * Math::Sqrt( 1.0f + MMAT(m,2,2) - MMAT(m,0,0) - MMAT(m,1,1) ); - const float oneOverS = 1.0f / s; - result.x = (MMAT(m,2,0) + MMAT(m,0,2) ) * oneOverS; - result.y = (MMAT(m,2,1) + MMAT(m,1,2) ) * oneOverS; - result.z = 0.25f * s; - result.w = (MMAT(m,1,0) - MMAT(m,0,1) ) * oneOverS; - } - } - */ - } } // namespace MCore diff --git a/Gems/EMotionFX/Code/MCore/Source/Matrix4.cpp b/Gems/EMotionFX/Code/MCore/Source/Matrix4.cpp index 7314e9fe9c..229f67ec0c 100644 --- a/Gems/EMotionFX/Code/MCore/Source/Matrix4.cpp +++ b/Gems/EMotionFX/Code/MCore/Source/Matrix4.cpp @@ -2248,27 +2248,6 @@ namespace MCore } - - // simple decompose a matrix into translation and rotation - void Matrix::Decompose(AZ::Vector3* outTranslation, AZ::Quaternion* outRotation) const - { - // make a copy of the matrix - Matrix mat(*this); - - // normalize the basis vectors - mat.SetRight(SafeNormalize(mat.GetRight())); - mat.SetUp(SafeNormalize(mat.GetUp())); - mat.SetForward(SafeNormalize(mat.GetForward())); - - // extract the translation from the matrix - *outTranslation = mat.GetTranslation(); - - // convert the normalized 3x3 rotation part into a AZ::Quaternion - *outRotation = MCore::MCoreMatrixToQuaternion(*this); - } - - - // calculate a rotation matrix from two vectors void Matrix::SetRotationMatrixTwoVectors(const AZ::Vector3& from, const AZ::Vector3& to) { @@ -2365,30 +2344,6 @@ namespace MCore } - // - void Matrix::DecomposeQRGramSchmidt(AZ::Vector3& translation, AZ::Quaternion& rot, AZ::Vector3& scale, AZ::Vector3& shear) const - { - Matrix rotMatrix; - DecomposeQRGramSchmidt(translation, rotMatrix, scale, shear); - rot = MCore::MCoreMatrixToQuaternion(*this); - } - - // - void Matrix::DecomposeQRGramSchmidt(AZ::Vector3& translation, AZ::Quaternion& rot, AZ::Vector3& scale) const - { - Matrix rotMatrix; - DecomposeQRGramSchmidt(translation, rotMatrix, scale); - rot = MCore::MCoreMatrixToQuaternion(rotMatrix); - } - - - // - void Matrix::DecomposeQRGramSchmidt(AZ::Vector3& translation, AZ::Quaternion& rot) const - { - Matrix rotMatrix; - DecomposeQRGramSchmidt(translation, rotMatrix); - rot = MCore::MCoreMatrixToQuaternion(rotMatrix); - } // diff --git a/Gems/EMotionFX/Code/MCore/Source/Matrix4.h b/Gems/EMotionFX/Code/MCore/Source/Matrix4.h index 8cb09aa769..4be819bcc1 100644 --- a/Gems/EMotionFX/Code/MCore/Source/Matrix4.h +++ b/Gems/EMotionFX/Code/MCore/Source/Matrix4.h @@ -685,26 +685,11 @@ namespace MCore */ void Frustum(float left, float right, float top, float bottom, float znear, float zfar); - /** - * Decompose a transformation matrix into translation and rotation components. - * The translation part is just the translation part of the matrix. - * The rotation AZ::Quaternion is calculated by normalizing the basis vectors and converting the - * 3x3 rotation part of the matrix to a AZ::Quaternion. - * It is allowed for the matrix to contain scaling. - * The matrix where you call Decompose on remains unchanged. - * @param outTranslation A pointer to a vector where the translation will be written to. - * @param outRotation A pointer to a AZ::Quaternion where the rotation will be written to. - * @note Please keep in mind that nullptr values for the parameters are NOT allowed. - */ - void Decompose(AZ::Vector3* outTranslation, AZ::Quaternion* outRotation) const; // QR Gram-Schmidt decomposition - void DecomposeQRGramSchmidt(AZ::Vector3& translation, AZ::Quaternion& rot) const; void DecomposeQRGramSchmidt(AZ::Vector3& translation, Matrix& rot) const; void DecomposeQRGramSchmidt(AZ::Vector3& translation, Matrix& rot, AZ::Vector3& scale) const; void DecomposeQRGramSchmidt(AZ::Vector3& translation, Matrix& rot, AZ::Vector3& scale, AZ::Vector3& shear) const; - void DecomposeQRGramSchmidt(AZ::Vector3& translation, AZ::Quaternion& rot, AZ::Vector3& scale, AZ::Vector3& shear) const; - void DecomposeQRGramSchmidt(AZ::Vector3& translation, AZ::Quaternion& rot, AZ::Vector3& scale) const; static Matrix OuterProduct(const AZ::Vector4& column, const AZ::Vector4& row); diff --git a/Gems/EMotionFX/Code/MCore/Source/Quaternion.cpp b/Gems/EMotionFX/Code/MCore/Source/Quaternion.cpp deleted file mode 100644 index 2f7db53d2c..0000000000 --- a/Gems/EMotionFX/Code/MCore/Source/Quaternion.cpp +++ /dev/null @@ -1,604 +0,0 @@ -/* - * 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 required headers -#include "Quaternion.h" -#include - -namespace MCore -{ - // spherical quadratic interpolation - Quaternion Quaternion::Squad(const Quaternion& p, const Quaternion& a, const Quaternion& b, const Quaternion& q, float t) - { - Quaternion q0(p.Slerp(q, t)); - Quaternion q1(a.Slerp(b, t)); - return q0.Slerp(q1, 2.0f * t * (1.0f - t)); - } - - - // returns the approximately normalized linear interpolated result [t must be between 0..1] - Quaternion Quaternion::NLerp(const Quaternion& to, float t) const - { - AZ_Assert(t > -MCore::Math::epsilon && t < (1 + MCore::Math::epsilon), "Expected t to be between 0..1"); - static const float weightCloseToOne = 1.0f - MCore::Math::epsilon; - - // Early out for boundaries (common cases) - if (t < MCore::Math::epsilon) - { - return *this; - } - else if (t > weightCloseToOne) - { - return to; - } - - #if AZ_TRAIT_USE_PLATFORM_SIMD_SSE - __m128 num1, num2, num3, num4, fromVec, toVec; - const float omt = 1.0f - t; - float dot; - - // perform dot product between this quat and the 'to' quat - num4 = _mm_setzero_ps(); // sets sum to zero - fromVec = _mm_loadu_ps(&x); // - toVec = _mm_loadu_ps(&to.x); // - num3 = _mm_mul_ps(fromVec, toVec); // performs multiplication num3 = a[3]*b[3] a[2]*b[2] a[1]*b[1] a[0]*b[0] - num3 = _mm_hadd_ps(num3, num3); // performs horizontal addition - num3= a[3]*b[3]+ a[2]*b[2] a[1]*b[1]+a[0]*b[0] a[3]*b[3]+ a[2]*b[2] a[1]*b[1]+a[0]*b[0] - num4 = _mm_add_ps(num4, num3); // performs vertical addition - num4 = _mm_hadd_ps(num4, num4); - _mm_store_ss(&dot, num4); // store the dot result - - if (dot < 0.0f) - { - t = -t; - } - - // calculate interpolated value - num2 = _mm_load_ps1(&omt); - num3 = _mm_load_ps1(&t); - num4 = _mm_mul_ps(fromVec, num2); // omt * xyzw - num1 = _mm_mul_ps(toVec, num3); // t * to.xyzw - num2 = _mm_add_ps(num1, num4); // interpolated value - - // calculate the square length - num4 = _mm_setzero_ps(); - num3 = _mm_mul_ps(num2, num2); // square length - num1 = _mm_hadd_ps(num3, num3); - num4 = _mm_add_ps(num4, num1); - num3 = _mm_hadd_ps(num4, num4); - //num4 = _mm_rsqrt_ps( num3 ); // length (argh, too inaccurate on some models) - - AZStd::aligned_storage::type numFloatStorage; - float* numFloat = reinterpret_cast(&numFloatStorage); - - _mm_store_ps(numFloat, num3); - const float invLen = Math::InvSqrt(numFloat[0]); - num4 = _mm_load_ps1(&invLen); - - // calc inverse length, which normalizes everything - num1 = _mm_mul_ps(num2, num4); - - _mm_store_ps(numFloat, num1); - return Quaternion(numFloat[0], numFloat[1], numFloat[2], numFloat[3]); - #else - const float omt = 1.0f - t; - const float dot = x * to.x + y * to.y + z * to.z + w * to.w; - if (dot < 0.0f) - { - t = -t; - } - - // calculate the interpolated values - const float newX = (omt * x + t * to.x); - const float newY = (omt * y + t * to.y); - const float newZ = (omt * z + t * to.z); - const float newW = (omt * w + t * to.w); - - // calculate the inverse length - // const float invLen = 1.0f / Math::FastSqrt( newX*newX + newY*newY + newZ*newZ + newW*newW ); - // const float invLen = Math::FastInvSqrt( newX*newX + newY*newY + newZ*newZ + newW*newW ); - const float invLen = Math::InvSqrt(newX * newX + newY * newY + newZ * newZ + newW * newW); - - // return the normalized linear interpolation - return Quaternion(newX * invLen, - newY * invLen, - newZ * invLen, - newW * invLen); - #endif - } - - - - // returns the linear interpolated result [t must be between 0..1] - Quaternion Quaternion::Lerp(const Quaternion& to, float t) const - { - const float omt = 1.0f - t; - const float cosom = x * to.x + y * to.y + z * to.z + w * to.w; - if (cosom < 0.0f) - { - t = -t; - } - - // return the linear interpolation - return Quaternion(omt * x + t * to.x, - omt * y + t * to.y, - omt * z + t * to.z, - omt * w + t * to.w); - } - - - - // quaternion from an axis and angle - Quaternion::Quaternion(const AZ::Vector3& axis, float angle) - { - const float squaredLength = axis.GetLengthSq(); - if (squaredLength > 0.0f) - { - const float halfAngle = angle * 0.5f; - const float sinScale = Math::Sin(halfAngle) / Math::Sqrt(squaredLength); - x = axis.GetX() * sinScale; - y = axis.GetY() * sinScale; - z = axis.GetZ() * sinScale; - w = Math::Cos(halfAngle); - } - else - { - x = y = z = 0.0f; - w = 1.0f; - } - } - - - - // quaternion from a spherical rotation - Quaternion::Quaternion(const AZ::Vector2& spherical, float angle) - { - const float latitude = spherical.GetX(); - const float longitude = spherical.GetY(); - - const float s = Math::Sin(angle / 2.0f); - const float c = Math::Cos(angle / 2.0f); - - const float sin_lat = Math::Sin(latitude); - const float cos_lat = Math::Cos(latitude); - - const float sin_lon = Math::Sin(longitude); - const float cos_lon = Math::Cos(longitude); - - x = s * cos_lat * sin_lon; - y = s * sin_lat; - z = s * sin_lat * cos_lon; - w = c; - } - - - // convert to an axis and angle - void Quaternion::ToAxisAngle(AZ::Vector3* axis, float* angle) const - { - *angle = 2.0f * Math::ACos(w); - - const float sinHalfAngle = Math::Sin(*angle * 0.5f); - if (sinHalfAngle > 0.0f) - { - const float invS = 1.0f / sinHalfAngle; - axis->Set(x * invS, y * invS, z * invS); - } - else - { - axis->Set(0.0f, 1.0f, 0.0f); - *angle = 0.0f; - } - } - - - // converts from unit quaternion to spherical rotation angles - void Quaternion::ToSpherical(AZ::Vector2* spherical, float* angle) const - { - AZ::Vector3 axis; - ToAxisAngle(&axis, angle); - - float longitude; - if (axis.GetX() * axis.GetX() + axis.GetZ() * axis.GetZ() < 0.0001f) - { - longitude = 0.0f; - } - else - { - longitude = Math::ATan2(axis.GetX(), axis.GetZ()); - if (longitude < 0.0f) - { - longitude += Math::twoPi; - } - } - - spherical->SetX(-Math::ASin(axis.GetY())); - spherical->SetY(longitude); - } - - - - // setup the quaternion from a roll, pitch and yaw - Quaternion& Quaternion::SetEuler(float pitch, float yaw, float roll) - { - // METHOD #1: - const float halfYaw = yaw * 0.5f; - const float halfPitch = pitch * 0.5f; - const float halfRoll = roll * 0.5f; - - const float cY = Math::Cos(halfYaw); - const float sY = Math::Sin(halfYaw); - const float cP = Math::Cos(halfPitch); - const float sP = Math::Sin(halfPitch); - const float cR = Math::Cos(halfRoll); - const float sR = Math::Sin(halfRoll); - - x = cY * sP * cR - sY * cP * sR; - y = cY * sP * sR + sY * cP * cR; - z = cY * cP * sR - sY * sP * cR; - w = cY * cP * cR + sY * sP * sR; - - // Normalize(); // we might be able to leave the normalize away, but better safe than not, this is more robust :) - - return *this; - - /* - - // METHOD #2: - Quaternion Qx(Vector3(sP, 0, 0), cP); - Quaternion Qy(Vector3(0, sY, 0), cY); - Quaternion Qz(Vector3(0, 0, sR), cR); - - Quaternion result = Qx * Qy * Qz; - - x = result.x; - y = result.y; - z = result.z; - w = result.w; - - return *this; - */ - } - - - - // convert the quaternion to a matrix - Matrix Quaternion::ToMatrix() const - { - Matrix m; - - const float xx = x * x; - const float xy = x * y, yy = y * y; - const float xz = x * z, yz = y * z, zz = z * z; - const float xw = x * w, yw = y * w, zw = z * w, ww = w * w; - - MMAT(m, 0, 0) = +xx - yy - zz + ww; - MMAT(m, 0, 1) = +xy + zw + xy + zw; - MMAT(m, 0, 2) = +xz - yw + xz - yw; - MMAT(m, 0, 3) = 0.0f; - MMAT(m, 1, 0) = +xy - zw + xy - zw; - MMAT(m, 1, 1) = -xx + yy - zz + ww; - MMAT(m, 1, 2) = +yz + xw + yz + xw; - MMAT(m, 1, 3) = 0.0f; - MMAT(m, 2, 0) = +xz + yw + xz + yw; - MMAT(m, 2, 1) = +yz - xw + yz - xw; - MMAT(m, 2, 2) = -xx - yy + zz + ww; - MMAT(m, 2, 3) = 0.0f; - MMAT(m, 3, 0) = 0.0f; - MMAT(m, 3, 1) = 0.0f; - MMAT(m, 3, 2) = 0.0f; - MMAT(m, 3, 3) = 1.0f; - - return m; - } - - - - // construct the quaternion from a given rotation matrix - Quaternion Quaternion::ConvertFromMatrix(const Matrix& m) - { - Quaternion result; - - const float trace = MMAT(m, 0, 0) + MMAT(m, 1, 1) + MMAT(m, 2, 2); - if (trace > 0.0f /*Math::epsilon*/) - { - const float s = 0.5f / Math::Sqrt(trace + 1.0f); - result.w = 0.25f / s; - result.x = (MMAT(m, 1, 2) - MMAT(m, 2, 1)) * s; - result.y = (MMAT(m, 2, 0) - MMAT(m, 0, 2)) * s; - result.z = (MMAT(m, 0, 1) - MMAT(m, 1, 0)) * s; - } - else - { - if (MMAT(m, 0, 0) > MMAT(m, 1, 1) && MMAT(m, 0, 0) > MMAT(m, 2, 2)) - { - const float s = 2.0f * Math::Sqrt(1.0f + MMAT(m, 0, 0) - MMAT(m, 1, 1) - MMAT(m, 2, 2)); - const float oneOverS = 1.0f / s; - result.x = 0.25f * s; - result.y = (MMAT(m, 1, 0) + MMAT(m, 0, 1)) * oneOverS; - result.z = (MMAT(m, 2, 0) + MMAT(m, 0, 2)) * oneOverS; - result.w = (MMAT(m, 1, 2) - MMAT(m, 2, 1)) * oneOverS; - } - else - if (MMAT(m, 1, 1) > MMAT(m, 2, 2)) - { - const float s = 2.0f * Math::Sqrt(1.0f + MMAT(m, 1, 1) - MMAT(m, 0, 0) - MMAT(m, 2, 2)); - const float oneOverS = 1.0f / s; - result.x = (MMAT(m, 1, 0) + MMAT(m, 0, 1)) * oneOverS; - result.y = 0.25f * s; - result.z = (MMAT(m, 2, 1) + MMAT(m, 1, 2)) * oneOverS; - result.w = (MMAT(m, 2, 0) - MMAT(m, 0, 2)) * oneOverS; - } - else - { - const float s = 2.0f * Math::Sqrt(1.0f + MMAT(m, 2, 2) - MMAT(m, 0, 0) - MMAT(m, 1, 1)); - const float oneOverS = 1.0f / s; - result.x = (MMAT(m, 2, 0) + MMAT(m, 0, 2)) * oneOverS; - result.y = (MMAT(m, 2, 1) + MMAT(m, 1, 2)) * oneOverS; - result.z = 0.25f * s; - result.w = (MMAT(m, 0, 1) - MMAT(m, 1, 0)) * oneOverS; - } - } - - /* - const float trace = MMAT(m,0,0) + MMAT(m,1,1) + MMAT(m,2,2) + 1.0f; - if (trace > Math::epsilon) - { - const float s = 0.5f / Math::Sqrt(trace); - result.w = 0.25f / s; - result.x = ( MMAT(m,1,2) - MMAT(m,2,1) ) * s; - result.y = ( MMAT(m,2,0) - MMAT(m,0,2) ) * s; - result.z = ( MMAT(m,0,1) - MMAT(m,1,0) ) * s; - } - else - { - if (MMAT(m,0,0) > MMAT(m,1,1) && MMAT(m,0,0) > MMAT(m,2,2)) - { - const float s = 2.0f * Math::Sqrt( 1.0f + MMAT(m,0,0) - MMAT(m,1,1) - MMAT(m,2,2)); - const float oneOverS = 1.0f / s; - result.x = 0.25f * s; - result.y = (MMAT(m,1,0) + MMAT(m,0,1) ) * oneOverS; - result.z = (MMAT(m,2,0) + MMAT(m,0,2) ) * oneOverS; - result.w = (MMAT(m,2,1) - MMAT(m,1,2) ) * oneOverS; - } - else - if (MMAT(m,1,1) > MMAT(m,2,2)) - { - const float s = 2.0f * Math::Sqrt( 1.0f + MMAT(m,1,1) - MMAT(m,0,0) - MMAT(m,2,2)); - const float oneOverS = 1.0f / s; - result.x = (MMAT(m,1,0) + MMAT(m,0,1) ) * oneOverS; - result.y = 0.25f * s; - result.z = (MMAT(m,2,1) + MMAT(m,1,2) ) * oneOverS; - result.w = (MMAT(m,2,0) - MMAT(m,0,2) ) * oneOverS; - } - else - { - const float s = 2.0f * Math::Sqrt( 1.0f + MMAT(m,2,2) - MMAT(m,0,0) - MMAT(m,1,1) ); - const float oneOverS = 1.0f / s; - result.x = (MMAT(m,2,0) + MMAT(m,0,2) ) * oneOverS; - result.y = (MMAT(m,2,1) + MMAT(m,1,2) ) * oneOverS; - result.z = 0.25f * s; - result.w = (MMAT(m,1,0) - MMAT(m,0,1) ) * oneOverS; - } - } - */ - return result; - } - - - // convert a quaternion to euler angles (in degrees) - AZ::Vector3 Quaternion::ToEuler() const - { - /* - // METHOD #1: - - Vector3 euler; - - float matrix[3][3]; - float cx,sx; - float cy,sy,yr; - float cz,sz; - - matrix[0][0] = 1.0 - (2.0 * y * y) - (2.0 * z * z); - matrix[1][0] = (2.0 * x * y) + (2.0 * w * z); - matrix[2][0] = (2.0 * x * z) - (2.0 * w * y); - matrix[2][1] = (2.0 * y * z) + (2.0 * w * x); - matrix[2][2] = 1.0 - (2.0 * x * x) - (2.0 * y * y); - - sy = -matrix[2][0]; - cy = Math::Sqrt(1 - (sy * sy)); - yr = Math::ATan2(sy,cy); - euler.y = yr; - - // avoid divide by zero only where y ~90 or ~270 - if (sy != 1.0 && sy != -1.0) - { - cx = matrix[2][2] / cy; - sx = matrix[2][1] / cy; - euler.x = Math::ATan2(sx,cx); - - cz = matrix[0][0] / cy; - sz = matrix[1][0] / cy; - euler.z = Math::ATan2(sz,cz); - } - else - { - matrix[1][1] = 1.0 - (2.0 * x * x) - (2.0 * z * z); - matrix[1][2] = (2.0 * y * z) - (2.0 * w * x); - cx = matrix[1][1]; - sx = -matrix[1][2]; - euler.x = Math::ATan2(sx,cx); - - cz = 1.0; - sz = 0.0; - euler.z = Math::ATan2(sz,cz); - } - - return euler; - */ - - /* - // METHOD #2: - Matrix mat = ToMatrix(); - - // - float cy = Math::Sqrt(mat.m44[0][0]*mat.m44[0][0] + mat.m44[0][1]*mat.m44[0][1]); - if (cy > 16.0*Math::epsilon) - { - result.x = -atan2(mat.m44[1][2], mat.m44[2][2]); - result.y = -atan2(-mat.m44[0][2], cy); - result.z = -atan2(mat.m44[0][1], mat.m44[0][0]); - } - else - { - result.x = -atan2(-mat.m44[2][1], mat.m44[1][1]); - result.y = -atan2(-mat.m44[0][2], cy); - result.z = 0.0; - } - - return result; - */ - - // METHOD #3 (without conversion to matrix first): - // TODO: safety checks? - float m00 = 1.0f - (2.0f * ((y * y) + z * z)); - float m01 = 2.0f * (x * y + w * z); - - AZ::Vector3 result( - Math::ATan2(2.0f * (y * z + w * x), 1.0f - (2.0f * ((x * x) + (y * y)))), - Math::ATan2(-2.0f * (x * z - w * y), Math::Sqrt((m00 * m00) + (m01 * m01))), - Math::ATan2(m01, m00) - ); - - return result; - } - - float Quaternion::GetEulerZ() const - { - float m00 = 1.0f - (2.0f * ((y * y) + z * z)); - float m01 = 2.0f * (x * y + w * z); - return Math::ATan2(m01, m00); - } - - // returns the spherical interpolated result [t must be between 0..1] - Quaternion Quaternion::Slerp(const Quaternion& to, float t) const - { - float cosom = (x * to.x) + (y * to.y) + (z * to.z) + (w * to.w); - float scale0, scale1, scale1sign = 1.0f; - - if (cosom < 0.0f) - { - scale1sign = -1.0f; - cosom *= -1.0f; - } - - if ((1.0 - cosom) > Math::epsilon) - { - const float omega = Math::ACos(cosom); - const float sinOmega = Math::Sin(omega); - const float oosinom = 1.0f / sinOmega; - scale0 = Math::Sin((1.0f - t) * omega) * oosinom; - scale1 = Math::Sin(t * omega) * oosinom; - } - else - { - scale0 = 1.0f - t; - scale1 = t; - } - - scale1 *= scale1sign; - - return Quaternion(scale0 * x + scale1 * to.x, - scale0 * y + scale1 * to.y, - scale0 * z + scale1 * to.z, - scale0 * w + scale1 * to.w); - } - - - // set as delta rotation - Quaternion Quaternion::CreateDeltaRotation(const AZ::Vector3& fromVector, const AZ::Vector3& toVector) - { - Quaternion q; - q.SetAsDeltaRotation(fromVector, toVector); - return q; - } - - - // set as delta rotation but limited - Quaternion Quaternion::CreateDeltaRotation(const AZ::Vector3& fromVector, const AZ::Vector3& toVector, float maxAngleRadians) - { - Quaternion q; - q.SetAsDeltaRotation(fromVector, toVector, maxAngleRadians); - return q; - } - - - // set as delta rotation - void Quaternion::SetAsDeltaRotation(const AZ::Vector3& fromVector, const AZ::Vector3& toVector) - { - // check if we are in parallel or not - const float dot = fromVector.Dot(toVector); - if (dot < 0.99999f) // we have rotated compared to the forward direction - { - const float angleRadians = Math::ACos(dot); - const AZ::Vector3 rotAxis = fromVector.Cross(toVector); - *this = Quaternion(rotAxis, angleRadians); - } - else - { - Identity(); - } - } - - - // set as delta rotation, but limited - void Quaternion::SetAsDeltaRotation(const AZ::Vector3& fromVector, const AZ::Vector3& toVector, float maxAngleRadians) - { - // check if we are in parallel or not - const float dot = fromVector.Dot(toVector); - if (dot < 0.99999f) // we have rotated compared to the forward direction - { - const float angleRadians = Math::ACos(dot); - const float rotAngle = Min(angleRadians, maxAngleRadians); - const AZ::Vector3 rotAxis = fromVector.Cross(toVector); - *this = Quaternion(rotAxis, rotAngle); - } - else - { - Identity(); - } - } - - - /* - Decompose the rotation on to 2 parts. - 1. Twist - rotation around the "direction" vector - 2. Swing - rotation around axis that is perpendicular to "direction" vector - The rotation can be composed back by - rotation = swing * twist - - has singularity in case of swing_rotation close to 180 degrees rotation. - if the input quaternion is of non-unit length, the outputs are non-unit as well - otherwise, outputs are both unit - */ - void Quaternion::DecomposeSwingTwist(const AZ::Vector3& direction, Quaternion* outSwing, Quaternion* outTwist) const - { - AZ::Vector3 rotAxis(x, y, z); - AZ::Vector3 p = Projected(rotAxis, direction); // return projection v1 on to v2 (parallel component) - outTwist->Set(p.GetX(), p.GetY(), p.GetZ(), w); - outTwist->Normalize(); - *outSwing = *this * outTwist->Conjugated(); - } - - - // rotate the current quaternion and renormalize it - void Quaternion::RotateFromTo(const AZ::Vector3& fromVector, const AZ::Vector3& toVector) - { - *this = CreateDeltaRotation(fromVector, toVector) * *this; - Normalize(); - } -} // namespace MCore - diff --git a/Gems/EMotionFX/Code/MCore/Source/Quaternion.h b/Gems/EMotionFX/Code/MCore/Source/Quaternion.h deleted file mode 100644 index bbee1d8265..0000000000 --- a/Gems/EMotionFX/Code/MCore/Source/Quaternion.h +++ /dev/null @@ -1,386 +0,0 @@ -/* - * 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 required headers -#include -#include -#include "StandardHeaders.h" -#include "FastMath.h" -#include "Vector.h" -#include "Matrix4.h" -#include "Algorithms.h" - - -namespace MCore -{ - /** - * Depracated. Please use AZ::Quaternion instead. - * The quaternion class in MCore. - * Quaternions are mostly used to represent rotations in 3D applications. - * The advantages of quaternions over matrices are that they take up less space and that interpolation between - * two quaternions is easier to perform. Instead of a 3x3 rotation matrix, which is 9 floats or doubles, a quaternion - * only uses 4 floats or doubles. This template/class provides you with methods to perform all kind of operations on - * these quaternions, from interpolation to conversion to matrices and other rotation representations. - */ - class MCORE_API Quaternion - { - public: - AZ_TYPE_INFO(MCore::Quaternion, "{1807CD22-EBB5-45E8-8113-3B1DABB53F12}") - - /** - * Default constructor. Sets x, y and z to 0 and w to 1. - */ - MCORE_INLINE Quaternion() - : x(0.0f) - , y(0.0f) - , z(0.0f) - , w(1.0f) {} - - /** - * Constructor which sets the x, y, z and w. - * @param xVal The value of x. - * @param yVal The value of y. - * @param zVal The value of z. - * @param wVal The value of w. - */ - MCORE_INLINE Quaternion(float xVal, float yVal, float zVal, float wVal) - : x(xVal) - , y(yVal) - , z(zVal) - , w(wVal) {} - - /** - * Copy constructor. Copies the x, y, z, w values from the other quaternion. - * @param other The quaternion to copy the attributes from. - */ - MCORE_INLINE Quaternion(const Quaternion& other) - : x(other.x) - , y(other.y) - , z(other.z) - , w(other.w) {} - - /** - * Constructor which creates a quaternion from a pitch, yaw and roll. - * @param pitch Rotation around the x-axis, in radians. - * @param yaw Rotation around the y-axis, in radians. - * @param roll Rotation around the z-axis, in radians. - */ - MCORE_INLINE Quaternion(float pitch, float yaw, float roll) { SetEuler(pitch, yaw, roll); } - - /** - * Constructor which takes a matrix as input parameter. - * This converts the rotation of the specified matrix into a quaternion. Please keep in mind that the matrix may NOT contain - * any scaling, so if it does, please normalize your matrix first! - * @param matrix The matrix to initialize the quaternion from. - */ - MCORE_INLINE Quaternion(const Matrix& matrix) { FromMatrix(matrix); } - - /** - * Constructor which creates a quaternion from a spherical rotation. - * @param spherical The spherical coordinates in radians, which creates an axis to rotate around. - * @param angle The angle to rotate around this axis. - */ - Quaternion(const AZ::Vector2& spherical, float angle); - - /** - * Constructor which creates a quaternion from an axis and angle. - * @param axis The axis to rotate around. - * @param angle The angle in radians to rotate around the given axis. - */ - Quaternion(const AZ::Vector3& axis, float angle); - - /** - * Set the quaternion x/y/z/w component values. - * @param vx The value of x. - * @param vy The value of y. - * @param vz The value of z. - * @param vw The value of w. - */ - MCORE_INLINE void Set(float vx, float vy, float vz, float vw) { x = vx; y = vy; z = vz; w = vw; } - - /** - * Calculates the square length of the quaternion. - * @result The square length (length*length). - */ - MCORE_INLINE float SquareLength() const { return (x * x + y * y + z * z + w * w); } - - /** - * Calculates the length of the quaternion. - * It's safe, since it prevents a division by 0. - * @result The length of the quaternion. - */ - MCORE_INLINE float Length() const; - - /** - * Performs a dot product on the quaternions. - * @param q The quaternion to multiply (dot product) this quaternion with. - * @result The quaternion which is the result of the dot product. - */ - MCORE_INLINE float Dot(const Quaternion& q) const { return (x * q.x + y * q.y + z * q.z + w * q.w); } - - /** - * Normalize the quaternion. - * @result The normalized quaternion. It modifies itself, so no new quaternion is returned. - */ - MCORE_INLINE Quaternion& Normalize(); - - /** - * Sets the quaternion to identity. Where x, y and z are set to 0 and w is set to 1. - * @result The quaternion, now set to identity. - */ - MCORE_INLINE Quaternion& Identity() { x = 0.0f; y = 0.0f; z = 0.0f; w = 1.0f; return *this; } - - /** - * Calculate the inversed version of this quaternion. - * @result The inversed version of this quaternion. - */ - MCORE_INLINE Quaternion& Inverse() { const float len = 1.0f / SquareLength(); x = -x * len; y = -y * len; z = -z * len; w = w * len; return *this; } - - /** - * Conjugate this quaternion. - * @result Returns itself Conjugated. - */ - MCORE_INLINE Quaternion& Conjugate() { x = -x; y = -y; z = -z; return *this; } - - /** - * Calculate the inversed version of this quaternion. - * @result The inversed version of this quaternion. - */ - MCORE_INLINE Quaternion Inversed() const { const float len = 1.0f / SquareLength(); return Quaternion(-x * len, -y * len, -z * len, w * len); } - - /** - * Returns the normalized version of this quaternion. - * @result The normalized version of this quaternion. - */ - MCORE_INLINE Quaternion Normalized() const { Quaternion result(*this); result.Normalize(); return result; } - - /** - * Return the conjugated version of this quaternion. - * @result The conjugated version of this quaternion. - */ - MCORE_INLINE Quaternion Conjugated() const { return Quaternion(-x, -y, -z, w); } - - /** - * Calculate the exponent of this quaternion. - * @result The resulting quaternion of the exp. - */ - MCORE_INLINE Quaternion Exp() const { const float r = Math::Sqrt(x * x + y * y + z * z); const float expW = Math::Exp(w); const float s = (r >= 0.00001f) ? expW* Math::Sin(r) / r : 0.0f; return Quaternion(s * x, s * y, s * z, expW * Math::Cos(r)); } - - /** - * Calculate the log of the quaternion. - * @result The resulting quaternion of the log. - */ - MCORE_INLINE Quaternion LogN() const { const float r = Math::Sqrt(x * x + y * y + z * z); float t = (r > 0.00001f) ? Math::ATan2(r, w) / r : 0.0f; return Quaternion(t * x, t * y, t * z, 0.5f * Math::Log(SquareLength())); } - - /** - * Calculate and get the right basis vector. - * @result The basis vector pointing to the right. This assumes x+ points to the right. - */ - MCORE_INLINE AZ::Vector3 CalcRightAxis() const; - - /** - * Calculate and get the up basis vector. - * @result The basis vector pointing upwards. This assumes z+ points up. - */ - MCORE_INLINE AZ::Vector3 CalcUpAxis() const; - - /** - * Calculate and get the forward basis vector. - * @result The basis vector pointing forward. This assumes y+ points forward, into the depth. - */ - MCORE_INLINE AZ::Vector3 CalcForwardAxis() const; - - /** - * Initialize the current quaternion from a specified matrix. - * Please note that the matrix may not contain any scaling! - * So make sure the matrix has been normalized before, if it contains any scale. - * @param m The matrix to initialize the quaternion from. - */ - MCORE_INLINE void FromMatrix(const Matrix& m) { *this = Quaternion::ConvertFromMatrix(m); } - - /** - * Setup the quaternion from a pitch, yaw and roll. - * @param pitch The rotation around the x-axis, in radians. - * @param yaw The rotation around the y-axis, in radians. - * @param roll The rotation around the z-axis in radians. - * @result The quaternion, now initialized with the given pitch, yaw, roll rotation. - */ - Quaternion& SetEuler(float pitch, float yaw, float roll); - - /** - * Convert the quaternion to an axis and angle. Which represents a rotation of the resulting angle around the resulting axis. - * @param axis Pointer to the vector to store the axis in. - * @param angle Pointer to the variable to store the angle in (will be in radians). - */ - void ToAxisAngle(AZ::Vector3* axis, float* angle) const; - - /** - * Convert the quaternion to a spherical rotation. - * @param spherical A pointer to the 2D vector to store the spherical coordinates in radians, which build the axis. - * @param angle The pointer to the variable to store the angle around this axis in radians. - */ - void ToSpherical(AZ::Vector2* spherical, float* angle) const; - - /** - * Extract the euler angles in radians. - * The x component of the resulting vector represents the rotation around the x-axis (pitch). - * The y component results the rotation around the y-axis (yaw) and the z component represents - * the rotation around the z-axis (roll). - * @result The 3D vector containing the euler angles in radians, around each axis. - */ - AZ::Vector3 ToEuler() const; - - /** - * Returns the angle of rotation about the z axis. This is same as - * the z component of the vector returned by the ToEuler method. It - * is just more efficient to call this when one is interested only in rotation about the z axis. - * @result The angle of rotation about z axis in radians. - */ - float GetEulerZ() const; - - /** - * Convert this quaternion into a matrix. - * @result The matrix representing the rotation of this quaternion. - */ - Matrix ToMatrix() const; - - /** - * Convert a matrix into a quaternion. - * Please keep in mind that the specified matrix may NOT contain any scaling! - * So make sure the matrix has been normalized before, if it contains any scale. - * @param m The matrix to extract the rotation from. - * @result The quaternion, now containing the rotation of the given matrix, in quaternion form. - */ - static Quaternion ConvertFromMatrix(const Matrix& m); - - /** - * Create a delta rotation that rotates one vector onto another vector. - * @param fromVector The normalized vector to start from. This must be normalized! - * @param toVector The normalized vector to rotate towards. This must be normalized as well! - * @result The delta rotation quaternion. - */ - static Quaternion CreateDeltaRotation(const AZ::Vector3& fromVector, const AZ::Vector3& toVector); - - /** - * Create a delta rotation that rotates one vector onto another vector. - * If the angle is bigger than the max allowed angle that is specified it will rotate with an angle of the maximum specified angle. - * So if the angle between the vectors is 40 degrees and you maxAngleRadians equals 10 degrees (in radians) it will only rotate 10 degrees. - * @param fromVector The normalized vector to start from. This must be normalized! - * @param toVector The normalized vector to rotate towards. This must be normalized as well! - * @param maxAngleRadians The maximum rotation angle on the plane defined by the two vectors. This cannot be more than Math::pi (180 degrees). - * @result The delta rotation quaternion. - */ - static Quaternion CreateDeltaRotation(const AZ::Vector3& fromVector, const AZ::Vector3& toVector, float maxAngleRadians); - - /** - * Init this quaternion as a delta rotation that rotates one vector onto another vector. - * @param fromVector The normalized vector to start from. This must be normalized! - * @param toVector The normalized vector to rotate towards. This must be normalized as well! - */ - void SetAsDeltaRotation(const AZ::Vector3& fromVector, const AZ::Vector3& toVector); - - /** - * Init this quaternion as a delta rotation that rotates one vector onto another vector. - * If the angle is bigger than the max allowed angle that is specified it will rotate with an angle of the maximum specified angle. - * So if the angle between the vectors is 40 degrees and you maxAngleRadians equals 10 degrees (in radians) it will only rotate 10 degrees. - * @param fromVector The normalized vector to start from. This must be normalized! - * @param toVector The normalized vector to rotate towards. This must be normalized as well! - * @param maxAngleRadians The maximum rotation angle on the plane defined by the two vectors. This cannot be more than Math::pi (180 degrees). - */ - void SetAsDeltaRotation(const AZ::Vector3& fromVector, const AZ::Vector3& toVector, float maxAngleRadians); - - /** - * Rotate this current quaternion using a given delta that is calculated from two vectors. - * The rotation axis used is the cross product between the from and to vector. The rotation angle is the angle between these two vectors. - * @param fromVector The current direction vector, must be normalized. - * @param toVector The desired new direction vector, must be normalized. - */ - void RotateFromTo(const AZ::Vector3& fromVector, const AZ::Vector3& toVector); - - /** - * Decompose into swing and twist. - * The original rotation quat can be reassembled by doing swing * twist. - * @param direction The direction vector to get the twist from. - * @param outSwing This will contain the swing quaternion. - * @param outTwist This will contain the twist quaternion. - */ - void DecomposeSwingTwist(const AZ::Vector3& direction, Quaternion* outSwing, Quaternion* outTwist) const; - - /** - * Linear interpolate between this and another quaternion. - * @param to The quaternion to interpolate towards. - * @param t The time value, between 0 and 1. - * @result The quaternion at the given time in the interpolation process. - */ - Quaternion Lerp(const Quaternion& to, float t) const; - - /** - * Linear interpolate between this and another quaternion, and normalize afterwards. - * @param to The quaternion to interpolate towards. - * @param t The time value, between 0 and 1. - * @result The normalized quaternion at the given time in the interpolation process. - */ - Quaternion NLerp(const Quaternion& to, float t) const; - - /** - * Spherical Linear interpolate between this and another quaternion. - * @param to The quaternion to interpolate towards. - * @param t The time value, between 0 and 1. - * @result The quaternion at the given time in the interpolation process. - */ - Quaternion Slerp(const Quaternion& to, float t) const; - - /** - * Spherical cubic interpolate. - * @param p The first quaternion. - * @param a The second quaternion. - * @param b The third quaternion. - * @param q The fourth quaternion. - * @param t The time value, between 0 and 1. - * @result The quaternion at the given time in the interpolation process. - */ - static Quaternion Squad(const Quaternion& p, const Quaternion& a, const Quaternion& b, const Quaternion& q, float t); - - // operators - MCORE_INLINE const Quaternion& operator=(const Matrix& m) { FromMatrix(m); return *this; } - MCORE_INLINE const Quaternion& operator=(const Quaternion& other) { x = other.x; y = other.y; z = other.z; w = other.w; return *this; } - MCORE_INLINE Quaternion operator-() const { return Quaternion(-x, -y, -z, -w); } - MCORE_INLINE const Quaternion& operator+=(const Quaternion& q) { x += q.x; y += q.y; z += q.z; w += q.w; return *this; } - MCORE_INLINE const Quaternion& operator-=(const Quaternion& q) { x -= q.x; y -= q.y; z -= q.z; w -= q.w; return *this; } - MCORE_INLINE const Quaternion& operator*=(const Quaternion& q); - MCORE_INLINE const Quaternion& operator*=(float f) { x *= f; y *= f; z *= f; w *= f; return *this; } - //MCORE_INLINE const Quaternion& operator*=(double f) { x*=f; y*=f; z*=f; w*=f; return *this; } - MCORE_INLINE bool operator==(const Quaternion& q) const { return ((q.x == x) && (q.y == y) && (q.z == z) && (q.w == w)); } - MCORE_INLINE bool operator!=(const Quaternion& q) const { return ((q.x != x) || (q.y != y) || (q.z != z) || (q.w != w)); } - - //MCORE_INLINE float& operator[](int32 row) { return ((float*)&x)[row]; } - MCORE_INLINE operator float*() { return (float*)&x; } - MCORE_INLINE operator const float*() const { return (const float*)&x; } - - MCORE_INLINE AZ::Vector3 operator*(const AZ::Vector3& p) const; // multiply a vector by a quaternion - MCORE_INLINE Quaternion operator/(const Quaternion& q) const; // returns the ratio of two quaternions - - // attributes - float x, y, z, w; - }; - - - // operators - MCORE_INLINE Quaternion operator*(const Quaternion& a, float f) { return Quaternion(a.x * f, a.y * f, a.z * f, a.w * f); } - MCORE_INLINE Quaternion operator*(float f, const Quaternion& b) { return Quaternion(f * b.x, f * b.y, f * b.z, f * b.w); } - //MCORE_INLINE Quaternion operator*(const Quaternion& a, double f) { return Quaternion(a.x*f, a.y*f, a.z*f, a.w*f); } - //MCORE_INLINE Quaternion operator*(double f, const Quaternion& b) { return Quaternion(f*b.x, f*b.y, f*b.z, f*b.w); } - MCORE_INLINE Quaternion operator+(const Quaternion& a, const Quaternion& b) { return Quaternion(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w); } - MCORE_INLINE Quaternion operator-(const Quaternion& a, const Quaternion& b) { return Quaternion(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w); } - MCORE_INLINE Quaternion operator*(const Quaternion& a, const Quaternion& b) { return Quaternion(a.w * b.x + a.x * b.w + a.y * b.z - a.z * b.y, a.w * b.y + a.y * b.w + a.z * b.x - a.x * b.z, a.w * b.z + a.z * b.w + a.x * b.y - a.y * b.x, a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z); } - - // include the inline code -#include "Quaternion.inl" -} // namespace MCore diff --git a/Gems/EMotionFX/Code/MCore/Source/Quaternion.inl b/Gems/EMotionFX/Code/MCore/Source/Quaternion.inl deleted file mode 100644 index c89f497a1e..0000000000 --- a/Gems/EMotionFX/Code/MCore/Source/Quaternion.inl +++ /dev/null @@ -1,96 +0,0 @@ -/* - * 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 - * - */ - -// multiply a vector by a quaternion -MCORE_INLINE AZ::Vector3 Quaternion::operator * (const AZ::Vector3& p) const -{ - Quaternion v(p.GetX(), p.GetY(), p.GetZ(), 0.0f); - v = *this* v* this->Conjugated(); - return AZ::Vector3(v.x, v.y, v.z); -} - - - -// returns the ratio of two quaternions -MCORE_INLINE Quaternion Quaternion::operator / (const Quaternion& q) const -{ - Quaternion t((*this) * -q); - Quaternion s((-q) * (-q)); - t *= (1.0f / s.w); - return t; -} - - - -// calculates the length of the quaternion -MCORE_INLINE float Quaternion::Length() const -{ - const float sqLen = SquareLength(); - return Math::SafeSqrt(sqLen); -} - - -// normalizes the quaternion using approximation -MCORE_INLINE Quaternion& Quaternion::Normalize() -{ - // calculate 1.0 / length - // const float ooLen = 1.0f / Math::FastSqrt(x*x + y*y + z*z + w*w); - // const float ooLen = Math::FastInvSqrt(x*x + y*y + z*z + w*w); - const float squareValue = x * x + y * y + z * z + w * w; - const float ooLen = Math::InvSqrt(squareValue); - - x *= ooLen; - y *= ooLen; - z *= ooLen; - w *= ooLen; - - return *this; -} - - -// get the right axis -MCORE_INLINE AZ::Vector3 Quaternion::CalcRightAxis() const -{ - return AZ::Vector3(1.0f - 2.0f * y * y - 2.0f * z * z, - 2.0f * x * y + 2.0f * z * w, - 2.0f * x * z - 2.0f * y * w); -} - - -// get the forward axis -MCORE_INLINE AZ::Vector3 Quaternion::CalcForwardAxis() const -{ - return AZ::Vector3(2.0f * x * y - 2.0f * z * w, - 1.0f - 2.0f * x * x - 2.0f * z * z, - 2.0f * y * z + 2.0f * x * w); -} - - -// get the up axis -MCORE_INLINE AZ::Vector3 Quaternion::CalcUpAxis() const -{ - return AZ::Vector3(2.0f * x * z + 2.0f * y * w, - 2.0f * y * z - 2.0f * x * w, - 1.0f - 2.0f * x * x - 2.0f * y * y); -} - - -// multiply by a quaternion -MCORE_INLINE const Quaternion& Quaternion::operator*=(const Quaternion& q) -{ - const float vx = w * q.x + x * q.w + y * q.z - z * q.y; - const float vy = w * q.y + y * q.w + z * q.x - x * q.z; - const float vz = w * q.z + z * q.w + x * q.y - y * q.x; - const float vw = w * q.w - x * q.x - y * q.y - z * q.z; - x = vx; - y = vy; - z = vz; - w = vw; - return *this; -} - diff --git a/Gems/EMotionFX/Code/MCore/mcore_files.cmake b/Gems/EMotionFX/Code/MCore/mcore_files.cmake index b63c9e1bae..b0d8a67ccd 100644 --- a/Gems/EMotionFX/Code/MCore/mcore_files.cmake +++ b/Gems/EMotionFX/Code/MCore/mcore_files.cmake @@ -107,9 +107,6 @@ set(FILES Source/PlaneEq.cpp Source/PlaneEq.h Source/PlaneEq.inl - Source/Quaternion.cpp - Source/Quaternion.h - Source/Quaternion.inl Source/Random.cpp Source/Random.h Source/Ray.cpp diff --git a/Gems/EMotionFX/Code/Tests/EmotionFXMathLibTests.cpp b/Gems/EMotionFX/Code/Tests/EmotionFXMathLibTests.cpp index 43422dd654..041cc9e862 100644 --- a/Gems/EMotionFX/Code/Tests/EmotionFXMathLibTests.cpp +++ b/Gems/EMotionFX/Code/Tests/EmotionFXMathLibTests.cpp @@ -12,7 +12,6 @@ #include #include -#include #include #include @@ -25,7 +24,6 @@ protected: { m_azNormalizedVector3_a = AZ::Vector3(s_x1, s_y1, s_z1); m_azNormalizedVector3_a.Normalize(); - m_emQuaternion_a = MCore::Quaternion(m_azNormalizedVector3_a, s_angle_a); m_azQuaternion_a = AZ::Quaternion::CreateFromAxisAngle(m_azNormalizedVector3_a, s_angle_a); } @@ -55,26 +53,6 @@ protected: return true; } - bool EmfxQuaternionCompareExact(MCore::Quaternion& quaternion, float x, float y, float z, float w) - { - if (quaternion.x != x) - { - return false; - } - if (quaternion.y != y) - { - return false; - } - if (quaternion.z != z) - { - return false; - } - if (quaternion.w != w) - { - return false; - } - return true; - } bool AZQuaternionCompareClose(AZ::Quaternion& quaternion, float x, float y, float z, float w, float tolerance) { @@ -131,26 +109,6 @@ protected: return true; } - bool AZEMQuaternionsAreEqual(AZ::Quaternion& azQuaternion, const MCore::Quaternion& emQuaternion) - { - if (AZQuaternionCompareExact(azQuaternion, emQuaternion.x, emQuaternion.y, - emQuaternion.z, emQuaternion.w)) - { - return true; - } - return false; - } - - bool AZEMQuaternionsAreClose(AZ::Quaternion& azQuaternion, const MCore::Quaternion& emQuaternion, const float tolerance) - { - if (AZQuaternionCompareClose(azQuaternion, emQuaternion.x, emQuaternion.y, - emQuaternion.z, emQuaternion.w, tolerance)) - { - return true; - } - return false; - } - static const float s_toleranceHigh; static const float s_toleranceMedium; static const float s_toleranceLow; @@ -161,7 +119,6 @@ protected: static const float s_angle_a; AZ::Vector3 m_azNormalizedVector3_a; AZ::Quaternion m_azQuaternion_a; - MCore::Quaternion m_emQuaternion_a; }; const float EmotionFXMathLibTests::s_toleranceHigh = 0.00001f; @@ -174,18 +131,6 @@ const float EmotionFXMathLibTests::s_y1 = 0.3f; const float EmotionFXMathLibTests::s_z1 = 0.4f; const float EmotionFXMathLibTests::s_angle_a = 0.5f; - -/////////////////////////////////////////////////////////////////////////////// - - -// MCore::Quaternion: Test identity values -TEST_F(EmotionFXMathLibTests, QuaternionIdentity_Identity_Success) -{ - MCore::Quaternion test(0.1f, 0.2f, 0.3f, 0.4f); - test.Identity(); - ASSERT_TRUE(test == MCore::Quaternion(0.0f, 0.0f, 0.0f, 1.0f)); -} - ////////////////////////////////////////////////////////////////// //Getting and setting of Quaternions ////////////////////////////////////////////////////////////////// @@ -196,52 +141,6 @@ TEST_F(EmotionFXMathLibTests, AZQuaternionGet_Elements_Success) ASSERT_TRUE(AZQuaternionCompareExact(test, 0.1f, 0.2f, 0.3f, 0.4f)); } -// Compare equivalent normalized quaternions between systems -TEST_F(EmotionFXMathLibTests, AZEMQuaternionNormalizeEquivalent_Success) -{ - AZ::Quaternion azTest(0.1f, 0.2f, 0.3f, 0.4f); - MCore::Quaternion emTest(0.1f, 0.2f, 0.3f, 0.4f); - azTest.Normalize(); - emTest.Normalize(); - - ASSERT_TRUE(AZQuaternionCompareClose(azTest, emTest.x, emTest.y, emTest.z, emTest.w, s_toleranceMedium)); -} - -/////////////////////////////////////////////////////////////////////////////// -// Axis Angle -/////////////////////////////////////////////////////////////////////////////// - -// Compare setting a quaternion using axis and angle -TEST_F(EmotionFXMathLibTests, AZEMQuaternionConversion_SetToAxisAngleEquivalent_Success) -{ - MCore::Quaternion emQuaternion(m_azNormalizedVector3_a, s_angle_a); - AZ::Quaternion azQuaternion = AZ::Quaternion::CreateFromAxisAngle(m_azNormalizedVector3_a, s_angle_a); - - ASSERT_TRUE(AZQuaternionCompareClose(azQuaternion, emQuaternion.x, emQuaternion.y, emQuaternion.z, emQuaternion.w, s_toleranceLow)); -} - -// Compare equivalent conversions quaternions -> (axis, angle) between systems -TEST_F(EmotionFXMathLibTests, AZEMQuaternionConversion_ToAxisAngleEquivalent_Success) -{ - //populate Quaternions with same data - MCore::Quaternion emTest = m_emQuaternion_a; - AZ::Quaternion azTest(emTest.x, emTest.y, emTest.z, emTest.w); - - AZ::Vector3 emAxis; - float emAngle; - emTest.ToAxisAngle(&emAxis, &emAngle); - - AZ::Vector3 azAxis; - float azAngle; - AZ::ConvertQuaternionToAxisAngle(azTest, azAxis, azAngle); - - bool same = AZ::IsClose(azAngle, emAngle, s_toleranceLow) && - AZVector3CompareClose(azAxis, emAxis, s_toleranceLow); - - ASSERT_TRUE(same); -} - - /////////////////////////////////////////////////////////////////////////////// //Basic rotations /////////////////////////////////////////////////////////////////////////////// @@ -420,18 +319,6 @@ TEST_F(EmotionFXMathLibTests, AZQuaternion_EulerGetSet3ComponentAxisCompareTrans ASSERT_TRUE(same); } - -// EM Quaternion to Euler test -TEST_F(EmotionFXMathLibTests, EMQuaternionConversion_ToEulerEquivalent_Success) -{ - AZ::Vector3 eulerIn(0.1f, 0.2f, 0.3f); - MCore::Quaternion test; - test.SetEuler(eulerIn.GetX(), eulerIn.GetY(), eulerIn.GetZ()); - AZ::Vector3 eulerOut = test.ToEuler(); - - ASSERT_TRUE(AZVector3CompareClose(eulerOut, 0.1f, 0.2f, 0.3f, s_toleranceHigh)); -} - // AZ Quaternion to Euler test //only way to test Quaternions sameness is to apply it to a vector and measure result TEST_F(EmotionFXMathLibTests, AZQuaternionConversion_ToEulerEquivalent_Success) @@ -456,41 +343,6 @@ TEST_F(EmotionFXMathLibTests, AZQuaternionConversion_ToEulerEquivalent_Success) ASSERT_TRUE(AZVector3CompareClose(eulerOut1, eulerOut2, s_toleranceReallyLow)); } -/////////////////////////////////////////////////////////////////////////////// -//Quaternion order test -//determines that ordering is same between systems. -/////////////////////////////////////////////////////////////////////////////// -TEST_F(EmotionFXMathLibTests, AZEMQuaternion_OrderTest_Success) -{ - AZ::Vector3 axis = AZ::Vector3(1.0f, 0.7f, 0.3f); - axis.Normalize(); - AZ::Quaternion azQuaternion1 = AZ::Quaternion::CreateFromAxisAngle(axis, AZ::Constants::HalfPi); - - AZ::Vector3 axis2 = AZ::Vector3(0.2f, 0.5f, 0.9f); - axis2.Normalize(); - AZ::Quaternion azQuaternion2 = AZ::Quaternion::CreateFromAxisAngle(axis2, AZ::Constants::HalfPi); - - MCore::Quaternion emQuaternion1(azQuaternion1.GetX(), azQuaternion1.GetY(), azQuaternion1.GetZ(), azQuaternion1.GetW()); - MCore::Quaternion emQuaternion2(azQuaternion2.GetX(), azQuaternion2.GetY(), azQuaternion2.GetZ(), azQuaternion2.GetW()); - - AZ::Quaternion azQuaterionOut = azQuaternion1 * azQuaternion2; - AZ::Quaternion azQuaterionOut2 = azQuaternion2 * azQuaternion1; - MCore::Quaternion emQuaterionOut = emQuaternion1 * emQuaternion2; - - AZ::Vector3 azVertexIn(0.1f, 0.2f, 0.3f); - - AZ::Vector3 azVertexOut, azVertexOut2; - AZ::Vector3 emVertexOut; - - azVertexOut = azQuaterionOut.TransformVector(azVertexIn); - azVertexOut2 = azQuaterionOut2.TransformVector(azVertexIn); - emVertexOut = emQuaterionOut * azVertexIn; - - bool same = AZVector3CompareClose(emVertexOut, azVertexOut.GetX(), azVertexOut.GetY(), azVertexOut.GetZ(), s_toleranceMedium); - ASSERT_TRUE(same); -} - - /////////////////////////////////////////////////////////////////////////////// // Quaternion Matrix /////////////////////////////////////////////////////////////////////////////// @@ -616,225 +468,6 @@ TEST_F(EmotionFXMathLibTests, AZQuaternionConversion_ToMatrix_Success) ASSERT_TRUE(AZ::IsClose(azMatrix.GetElement(3, 3), 1.0f, s_toleranceReallyLow)); } -/////////////////////////////////////////////////////////////////////////////// -// AZEMQuaternion Compare Output tests -// Determines the AZ and MCore quaternion outputs are same/close after same math operations. -/////////////////////////////////////////////////////////////////////////////// -TEST_F(EmotionFXMathLibTests, AZEMQuaternion_CompareOperatorAddEquivalent_Success) -{ - // Quaternion test: operator '+' and operator '+=' - AZ::Quaternion azQuaternion = AZ::Quaternion(0.1f, 0.2f, 0.3f, 1.0f); - AZ::Quaternion azQuaternion2 = AZ::Quaternion(0.8f, 0.7f, 0.6f, 1.0f); - azQuaternion.Normalize(); - azQuaternion2.Normalize(); - azQuaternion = azQuaternion + azQuaternion2; - azQuaternion2 += azQuaternion; - - MCore::Quaternion emQuaternion = MCore::Quaternion(0.1f, 0.2f, 0.3f, 1.0f); - MCore::Quaternion emQuaternion2 = MCore::Quaternion(0.8f, 0.7f, 0.6f, 1.0f); - emQuaternion.Normalize(); - emQuaternion2.Normalize(); - emQuaternion = emQuaternion + emQuaternion2; - emQuaternion2 += emQuaternion; - - EXPECT_TRUE(AZEMQuaternionsAreClose(azQuaternion, emQuaternion, s_toleranceLow)) << "AZ/MCore Quaternions should have similar output with operator '+'"; - EXPECT_TRUE(AZEMQuaternionsAreClose(azQuaternion2, emQuaternion2, s_toleranceLow)) << "AZ/MCore Quaternions should have similar output with operator '+='"; -} - -TEST_F(EmotionFXMathLibTests, AZEMQuaternion_CompareOperatorSubtractEquivalent_Success) -{ - // Quaternion test: operator '-' and operator '-=' - AZ::Quaternion azQuaternion = AZ::Quaternion(0.1f, 0.2f, 0.3f, 1.0f); - AZ::Quaternion azQuaternion2 = AZ::Quaternion(0.8f, 0.7f, 0.6f, 1.0f); - azQuaternion.Normalize(); - azQuaternion2.Normalize(); - azQuaternion = azQuaternion - azQuaternion2; - azQuaternion2 -= azQuaternion; - - MCore::Quaternion emQuaternion = MCore::Quaternion(0.1f, 0.2f, 0.3f, 1.0f); - MCore::Quaternion emQuaternion2 = MCore::Quaternion(0.8f, 0.7f, 0.6f, 1.0f); - emQuaternion.Normalize(); - emQuaternion2.Normalize(); - emQuaternion = emQuaternion - emQuaternion2; - emQuaternion2 -= emQuaternion; - - EXPECT_TRUE(AZEMQuaternionsAreClose(azQuaternion, emQuaternion, s_toleranceLow)) << "AZ/MCore Quaternions should have similar output with operator '-'"; - EXPECT_TRUE(AZEMQuaternionsAreClose(azQuaternion2, emQuaternion2, s_toleranceLow)) << "AZ/MCore Quaternions should have similar output with operator '-='"; -} - -TEST_F(EmotionFXMathLibTests, AZEMQuaternion_CompareOperatorMultiplyHasSimilarOutput_Success) -{ - // Quaternion test: operator '*' and operator '*=' with another quaternion, vector3 and float - AZ::Quaternion azQuaternion = AZ::Quaternion(0.1f, 0.2f, 0.3f, 1.0f); - AZ::Quaternion azQuaternion2 = AZ::Quaternion(0.8f, 0.7f, 0.6f, 1.0f); - AZ::Quaternion azQuaternion3 = AZ::Quaternion(0.1f, 0.2f, 0.3f, 1.0f); - azQuaternion.Normalize(); - azQuaternion2.Normalize(); - azQuaternion3.Normalize(); - azQuaternion = azQuaternion * azQuaternion2; - azQuaternion2 *= azQuaternion; - azQuaternion3 *= 0.5f; - AZ::Vector3 aztestVec3 = azQuaternion2.TransformVector(m_azNormalizedVector3_a); - - MCore::Quaternion emQuaternion = MCore::Quaternion(0.1f, 0.2f, 0.3f, 1.0f); - MCore::Quaternion emQuaternion2 = MCore::Quaternion(0.8f, 0.7f, 0.6f, 1.0f); - MCore::Quaternion emQuaternion3 = MCore::Quaternion(0.1f, 0.2f, 0.3f, 1.0f); - emQuaternion.Normalize(); - emQuaternion2.Normalize(); - emQuaternion3.Normalize(); - emQuaternion = emQuaternion * emQuaternion2; - emQuaternion2 *= emQuaternion; - emQuaternion3 *= 0.5f; - AZ::Vector3 emtestVec3 = emQuaternion2 * m_azNormalizedVector3_a; - - EXPECT_TRUE(AZEMQuaternionsAreClose(azQuaternion, emQuaternion, s_toleranceLow)) << "AZ/MCore Quaternions should have similar output with operator '*' with another quaternion"; - EXPECT_TRUE(AZEMQuaternionsAreClose(azQuaternion2, emQuaternion2, s_toleranceLow)) << "AZ/MCore Quaternions should have similar output with operator '*=' with another quaternion"; - EXPECT_TRUE(AZEMQuaternionsAreClose(azQuaternion3, emQuaternion3, s_toleranceLow)) << "AZ/MCore Quaternions should have similar output with operator '*=' with a float value"; - EXPECT_TRUE(AZVector3CompareClose(aztestVec3, emtestVec3, s_toleranceLow)) << "AZ/MCore Quaternions should have similar output with operator '*' with a vector3"; -} - -TEST_F(EmotionFXMathLibTests, AZEMQuaternion_EquivalentOperatorsHasSameOutput_Success) -{ - // Testing Quaternion == Quaternion and operator!= - bool azCheck = AZ::Quaternion(0.1f, 0.2f, 0.3f, 1.0f).GetNormalized() == AZ::Quaternion(0.1f, 0.2f, 0.3f, 1.0f).GetNormalized(); - bool azCheck2 = AZ::Quaternion(0.1f, 0.2f, 0.3f, 1.0f).GetNormalized() == AZ::Quaternion(0.1000001f, 0.2000001f, 0.3000001f, 1.0f).GetNormalized(); - bool azCheck3 = AZ::Quaternion(0.1f, 0.2f, 0.3f, 1.0f).GetNormalized() != AZ::Quaternion(0.1f, 0.2f, 0.3f, 1.0f).GetNormalized(); - bool azCheck4 = AZ::Quaternion(0.1f, 0.2f, 0.3f, 1.0f).GetNormalized() != AZ::Quaternion(0.1000001f, 0.2000001f, 0.3000001f, 1.0f).GetNormalized(); - - bool emCheck = MCore::Quaternion(0.1f, 0.2f, 0.3f, 1.0f).Normalized() == MCore::Quaternion(0.1f, 0.2f, 0.3f, 1.0f).Normalized(); - bool emCheck2 = MCore::Quaternion(0.1f, 0.2f, 0.3f, 1.0f).Normalized() == MCore::Quaternion(0.1000001f, 0.2000001f, 0.3000001f, 1.0f).Normalized(); - bool emCheck3 = MCore::Quaternion(0.1f, 0.2f, 0.3f, 1.0f).Normalized() != MCore::Quaternion(0.1f, 0.2f, 0.3f, 1.0f).Normalized(); - bool emCheck4 = MCore::Quaternion(0.1f, 0.2f, 0.3f, 1.0f).Normalized() != MCore::Quaternion(0.1000001f, 0.2000001f, 0.3000001f, 1.0f).Normalized(); - - EXPECT_TRUE(azCheck == emCheck) << "AZ/MCore Quaternions should have same output of 'true' with operator '=='"; - EXPECT_TRUE(azCheck2 == emCheck2) << "AZ/MCore Quaternions should have same output of 'false' with operator '=='"; - EXPECT_TRUE(azCheck3 == emCheck3) << "AZ/MCore Quaternions should have same output of 'false' with operator '!='"; - EXPECT_TRUE(azCheck4 == emCheck4) << "AZ/MCore Quaternions should have same output of 'true' with operator '!='"; -} - -TEST_F(EmotionFXMathLibTests, AZEMQuaternion_InverseHasSimilarOutput_Success) -{ - // Test quaternions inverse method - AZ::Quaternion azQuaternion = AZ::Quaternion(0.1f, 0.2f, 0.3f, 1.0f).GetNormalized().GetInverseFull(); - AZ::Quaternion azQuaternion2 = AZ::Quaternion(0.0f, 0.0f, 0.0f, 1.0f).GetNormalized().GetInverseFull(); - - MCore::Quaternion emQuaternion = MCore::Quaternion(0.1f, 0.2f, 0.3f, 1.0f).Normalized().Inverse(); - MCore::Quaternion emQuaternion2 = MCore::Quaternion(0.0f, 0.0f, 0.0f, 1.0f).Normalized().Inverse(); - - EXPECT_TRUE(AZEMQuaternionsAreClose(azQuaternion, emQuaternion, s_toleranceLow)) << "AZ/MCore Quaternions should have similar Inverse output"; - EXPECT_TRUE(AZEMQuaternionsAreClose(azQuaternion2, emQuaternion2, s_toleranceLow)) << "AZ/MCore Quaternion(0.0f, 0.0f, 0.0f, 1.0f) should have similar Inverse output"; -} - -TEST_F(EmotionFXMathLibTests, AZEMQuaternion_ConjugateHasSimilarOutput_Success) -{ - // Test quaternion conjugate method - AZ::Quaternion azQuaternion = AZ::Quaternion(0.1f, 0.2f, 0.3f, 1.0f).GetNormalized().GetConjugate(); - AZ::Quaternion azQuaternion2 = AZ::Quaternion(0.0f, 0.0f, 0.0f, 1.0f).GetNormalized().GetConjugate(); - - MCore::Quaternion emQuaternion = MCore::Quaternion(0.1f, 0.2f, 0.3f, 1.0f).Normalized().Conjugate(); - MCore::Quaternion emQuaternion2 = MCore::Quaternion(0.0f, 0.0f, 0.0f, 1.0f).Normalized().Conjugate(); - - EXPECT_TRUE(AZEMQuaternionsAreClose(azQuaternion, emQuaternion, s_toleranceLow)) << "AZ/MCore Quaternions should have similar Conjugate output"; - EXPECT_TRUE(AZEMQuaternionsAreClose(azQuaternion2, emQuaternion2, s_toleranceLow)) << "AZ/MCore Quaternion(0.0f, 0.0f, 0.0f, 1.0f) should have similar Conjugate output"; -} - -TEST_F(EmotionFXMathLibTests, AZEMQuaternion_HasSameSquareLengthOutput_Success) -{ - // Test AZ and MCore quaternions to have similar square length - float azTest = AZ::Quaternion(0.1f, 0.2f, 0.3f, 1.0f).GetNormalized().GetLengthSq(); - float azTest2 = AZ::Quaternion(0.0f, 0.0f, 0.0f, 1.0f).GetNormalized().GetLengthSq(); - - float emTest = MCore::Quaternion(0.1f, 0.2f, 0.3f, 1.0f).Normalized().SquareLength(); - float emTest2 = MCore::Quaternion(0.0f, 0.0f, 0.0f, 1.0f).Normalized().SquareLength(); - - EXPECT_TRUE(AZ::GetAbs(azTest - emTest) < s_toleranceLow) << "AZ/MCore Quaternions should have similar square length output"; - EXPECT_TRUE(AZ::GetAbs(azTest2 - emTest2) < s_toleranceLow) << "AZ/MCore Quaternion(0.0f, 0.0f, 0.0f, 1.0f) should have similar square length output"; -} - -TEST_F(EmotionFXMathLibTests, AZEMQuaternion_HasSameLengthOutput_Success) -{ - // Test AZ and MCore quaternions to have similar length - // AZ GetLength, GetLengthApprox, GetLength all returns sqrtf(Dot(*this)) - float azTest = AZ::Quaternion(0.1f, 0.2f, 0.3f, 1.0f).GetNormalized().GetLength(); - float azTest2 = AZ::Quaternion(0.0f, 0.0f, 0.0f, 1.0f).GetNormalized().GetLength(); - - float emTest = MCore::Quaternion(0.1f, 0.2f, 0.3f, 1.0f).Normalized().Length(); - float emTest2 = MCore::Quaternion(0.0f, 0.0f, 0.0f, 1.0f).Normalized().Length(); - - EXPECT_TRUE(AZ::GetAbs(azTest - emTest) < s_toleranceLow) << "AZ/MCore Quaternions should have similar length output"; - EXPECT_TRUE(AZ::GetAbs(azTest2 - emTest2) < s_toleranceLow) << "AZ/MCore Quaternion(0.0f, 0.0f, 0.0f, 1.0f) should have similar length output"; -} - -TEST_F(EmotionFXMathLibTests, AZEMQuaternion_HasSameDotProductOutput_Success) -{ - // Test AZ and MCore quaternions to have similar dot product - float azDotTest = AZ::Quaternion(0.1f, 0.2f, 0.3f, 1.0f).GetNormalized().Dot(AZ::Quaternion(0.8f, 0.7f, 0.6f, 1.0f)); - float azDotTest2 = AZ::Quaternion(0.1f, 0.2f, 0.3f, 1.0f).GetNormalized().Dot(AZ::Quaternion(0.0f, 0.0f, 0.0f, 1.0f)); - float azDotTest3 = AZ::Quaternion(0.0f, 0.0f, 0.0f, 1.0f).GetNormalized().Dot(AZ::Quaternion(0.0f, 0.0f, 0.0f, 1.0f)); - - float emDotTest = MCore::Quaternion(0.1f, 0.2f, 0.3f, 1.0f).Normalized().Dot(MCore::Quaternion(0.8f, 0.7f, 0.6f, 1.0f)); - float emDotTest2 = MCore::Quaternion(0.1f, 0.2f, 0.3f, 1.0f).Normalized().Dot(MCore::Quaternion(0.0f, 0.0f, 0.0f, 1.0f)); - float emDotTest3 = MCore::Quaternion(0.0f, 0.0f, 0.0f, 1.0f).Normalized().Dot(MCore::Quaternion(0.0f, 0.0f, 0.0f, 1.0f)); - - EXPECT_TRUE(AZ::GetAbs(azDotTest - emDotTest) < s_toleranceLow) << "AZ/MCore Quaternions should have similar dot product output"; - EXPECT_TRUE(AZ::GetAbs(azDotTest2 - emDotTest2) < s_toleranceLow) << "AZ/MCore Quaternions should have similar dot product output"; - EXPECT_TRUE(AZ::GetAbs(azDotTest3 - emDotTest3) < s_toleranceLow) << "AZ/MCore Quaternion(0.0f, 0.0f, 0.0f, 1.0f) should have similar dot product output"; -} - -TEST_F(EmotionFXMathLibTests, AZEMQuaternion_HasSimilarLerpOutput_Success) -{ - // Test AZ and MCore quaternions to have similar Linear Interpolated quaternions - float testCases[6] = { 0.0f, 0.1f, 0.25f, 0.5f, 0.8f, 1.0f }; - for (float testVal : testCases) - { - AZ::Quaternion azQuaternionA = AZ::Quaternion(0.1f, 0.2f, 0.3f, 1.0f).GetNormalized(); - AZ::Quaternion azQuaternionB = AZ::Quaternion(0.8f, 0.7f, 0.6f, 1.0f).GetNormalized(); - AZ::Quaternion azQuaternionC = azQuaternionA.Lerp(azQuaternionB, testVal); - - MCore::Quaternion emQuaternionA = MCore::Quaternion(0.1f, 0.2f, 0.3f, 1.0f).Normalized(); - MCore::Quaternion emQuaternionB = MCore::Quaternion(0.8f, 0.7f, 0.6f, 1.0f).Normalized(); - MCore::Quaternion emQuaternionC = emQuaternionA.Lerp(emQuaternionB, testVal); - - EXPECT_TRUE(AZEMQuaternionsAreClose(azQuaternionA, emQuaternionA, s_toleranceLow)) << "AZ/MCore Quaternions should have similar Lerp output with given float: " << testVal; - } -} - -TEST_F(EmotionFXMathLibTests, AZEMQuaternion_HasSimilarNLerpOutput_Success) -{ - // Test AZ and MCore quaternions to have similar Linear Interpolated and then normalized quaternions - float testCases[6] = {0.0f, 0.1f, 0.25f, 0.5f, 0.8f, 1.0f}; - for (float testVal : testCases) - { - AZ::Quaternion azQuaternionA = AZ::Quaternion(0.1f, 0.2f, 0.3f, 1.0f).GetNormalized(); - AZ::Quaternion azQuaternionB = AZ::Quaternion(0.8f, 0.7f, 0.6f, 1.0f).GetNormalized(); - AZ::Quaternion azQuaternionC = azQuaternionA.NLerp(azQuaternionB, testVal); - - MCore::Quaternion emQuaternionA = MCore::Quaternion(0.1f, 0.2f, 0.3f, 1.0f).Normalized(); - MCore::Quaternion emQuaternionB = MCore::Quaternion(0.8f, 0.7f, 0.6f, 1.0f).Normalized(); - MCore::Quaternion emQuaternionC = emQuaternionA.NLerp(emQuaternionB, testVal); - - EXPECT_TRUE(AZEMQuaternionsAreClose(azQuaternionA, emQuaternionA, s_toleranceLow)) << "AZ/MCore Quaternions should have similar NLerp output with given float: " << testVal; - } -} - -TEST_F(EmotionFXMathLibTests, AZEMQuaternion_HasSimilarSLerpOutput_Success) -{ - // Test AZ and MCore quaternions to have similar spherical Linear Interpolated quaternions - float testCases[6] = { 0.0f, 0.1f, 0.25f, 0.5f, 0.8f, 1.0f }; - for (float testVal : testCases) - { - AZ::Quaternion azQuaternionA = AZ::Quaternion(0.1f, 0.2f, 0.3f, 1.0f).GetNormalized(); - AZ::Quaternion azQuaternionB = AZ::Quaternion(0.8f, 0.7f, 0.6f, 1.0f).GetNormalized(); - AZ::Quaternion azQuaternionC = azQuaternionA.Slerp(azQuaternionB, testVal); - - MCore::Quaternion emQuaternionA = MCore::Quaternion(0.1f, 0.2f, 0.3f, 1.0f).Normalized(); - MCore::Quaternion emQuaternionB = MCore::Quaternion(0.8f, 0.7f, 0.6f, 1.0f).Normalized(); - MCore::Quaternion emQuaternionC = emQuaternionA.Slerp(emQuaternionB, testVal); - - EXPECT_TRUE(AZEMQuaternionsAreClose(azQuaternionA, emQuaternionA, s_toleranceLow)) << "AZ/MCore Quaternions should have similar Slerp output with given float: " << testVal; - } -} - ////////////////////////////////////////////////////////////////// // Skinning ////////////////////////////////////////////////////////////////// diff --git a/Gems/EMotionFX/Code/Tests/Matchers.h b/Gems/EMotionFX/Code/Tests/Matchers.h index 64200e84f2..ad3c204f72 100644 --- a/Gems/EMotionFX/Code/Tests/Matchers.h +++ b/Gems/EMotionFX/Code/Tests/Matchers.h @@ -13,8 +13,8 @@ #include #include #include -#include #include +#include #include #include @@ -76,37 +76,6 @@ inline bool IsCloseMatcherP::gmock_Impl:: return false; } -template<> -template<> -inline bool IsCloseMatcherP::gmock_Impl::MatchAndExplain(const MCore::Quaternion& arg, ::testing::MatchResultListener* result_listener) const -{ - const MCore::Quaternion compareQuat = (expected.Dot(arg) < 0.0f) ? -arg : arg; - const AZ::Vector4 compareVec4(compareQuat.x, compareQuat.y, compareQuat.z, compareQuat.w); - - if (::testing::ExplainMatchResult(IsClose(AZ::Vector4(expected.x, expected.y, expected.z, expected.w)), compareVec4, result_listener)) - { - return true; - } - - AZ::Vector3 gotAxis; - AZ::Vector3 expectedAxis; - float gotAngle; - float expectedAngle; - - // convert to an axis and angle representation - expected.ToAxisAngle(&expectedAxis, &expectedAngle); - compareQuat.ToAxisAngle(&gotAxis, &gotAngle); - - *result_listener << "\n Got Axis: "; - PrintTo(gotAxis, result_listener->stream()); - *result_listener << ", Got Angle: " << gotAngle << "\n"; - *result_listener << "Expected Axis: "; - PrintTo(expectedAxis, result_listener->stream()); - *result_listener << ", Expected Angle: " << expectedAngle; - - return false; -} - template<> template<> inline bool IsCloseMatcherP::gmock_Impl::MatchAndExplain(const EMotionFX::Transform& arg, ::testing::MatchResultListener* result_listener) const diff --git a/Gems/EMotionFX/Code/Tests/Printers.cpp b/Gems/EMotionFX/Code/Tests/Printers.cpp index 8fd196fda0..ebf5167508 100644 --- a/Gems/EMotionFX/Code/Tests/Printers.cpp +++ b/Gems/EMotionFX/Code/Tests/Printers.cpp @@ -34,18 +34,6 @@ namespace AZStd } } // namespace AZStd -namespace MCore -{ - void PrintTo(const Quaternion& quaternion, ::std::ostream* os) - { - *os << "(x: " << quaternion.x - << ", y: " << quaternion.y - << ", z: " << quaternion.z - << ", w: " << quaternion.w - << ")"; - } -} // namespace MCore - namespace EMotionFX { void PrintTo(const Transform& transform, ::std::ostream* os) diff --git a/Gems/EMotionFX/Code/Tests/Printers.h b/Gems/EMotionFX/Code/Tests/Printers.h index c262cb2bed..afa8ea4b7b 100644 --- a/Gems/EMotionFX/Code/Tests/Printers.h +++ b/Gems/EMotionFX/Code/Tests/Printers.h @@ -11,7 +11,6 @@ #include #include #include -#include #include namespace AZ @@ -25,11 +24,6 @@ namespace AZStd void PrintTo(const string& string, ::std::ostream* os); } // namespace AZStd -namespace MCore -{ - void PrintTo(const Quaternion& quaternion, ::std::ostream* os); -} // namespace MCore - namespace EMotionFX { void PrintTo(const Transform& transform, ::std::ostream* os); diff --git a/Gems/GraphCanvas/Code/Source/GraphCanvas.cpp b/Gems/GraphCanvas/Code/Source/GraphCanvas.cpp index 33cc99e2d5..c5333152a0 100644 --- a/Gems/GraphCanvas/Code/Source/GraphCanvas.cpp +++ b/Gems/GraphCanvas/Code/Source/GraphCanvas.cpp @@ -191,7 +191,6 @@ namespace GraphCanvas void GraphCanvasSystemComponent::Activate() { - RegisterAssetHandler(); RegisterTranslationBuilder(); AzFramework::AssetCatalogEventBus::Handler::BusConnect(); @@ -386,34 +385,6 @@ namespace GraphCanvas AZ::Data::AssetCatalogRequestBus::Broadcast(&AZ::Data::AssetCatalogRequestBus::Events::EnumerateAssets, nullptr, collectAssetsCb, postEnumerateCb); } - void GraphCanvasSystemComponent::RegisterAssetHandler() - { - AZ::Data::AssetType assetType(azrtti_typeid()); - if (AZ::Data::AssetManager::Instance().GetHandler(assetType)) - { - return; // Asset Type already handled - } - - auto* catalogBus = AZ::Data::AssetCatalogRequestBus::FindFirstHandler(); - if (catalogBus) - { - // Register asset types the asset DB should query our catalog for. - catalogBus->AddAssetType(assetType); - - // Build the catalog (scan). - catalogBus->AddExtension(".names"); - } - - m_assetHandler = AZStd::make_unique(); - AZ::Data::AssetManager::Instance().RegisterHandler(m_assetHandler.get(), assetType); - - // Use AssetCatalog service to register ScriptEvent asset type and extension - AZ::Data::AssetCatalogRequestBus::Broadcast(&AZ::Data::AssetCatalogRequests::AddAssetType, assetType); - AZ::Data::AssetCatalogRequestBus::Broadcast(&AZ::Data::AssetCatalogRequests::EnableCatalogForAsset, assetType); - AZ::Data::AssetCatalogRequestBus::Broadcast(&AZ::Data::AssetCatalogRequests::AddExtension, TranslationAsset::GetFileFilter()); - - } - void GraphCanvasSystemComponent::UnregisterAssetHandler() { if (m_assetHandler) diff --git a/Gems/GraphCanvas/Code/Source/GraphCanvas.h b/Gems/GraphCanvas/Code/Source/GraphCanvas.h index a68052d5e1..7a4d9677ab 100644 --- a/Gems/GraphCanvas/Code/Source/GraphCanvas.h +++ b/Gems/GraphCanvas/Code/Source/GraphCanvas.h @@ -82,8 +82,6 @@ namespace GraphCanvas AZStd::unique_ptr m_assetHandler; void RegisterTranslationBuilder(); - - void RegisterAssetHandler(); void UnregisterAssetHandler(); TranslationAssetWorker m_translationAssetWorker; AZStd::vector m_translationAssets; diff --git a/Gems/ImGui/Code/Source/ImGuiManager.cpp b/Gems/ImGui/Code/Source/ImGuiManager.cpp index cd8486b711..ac3247b6a4 100644 --- a/Gems/ImGui/Code/Source/ImGuiManager.cpp +++ b/Gems/ImGui/Code/Source/ImGuiManager.cpp @@ -452,7 +452,7 @@ bool ImGuiManager::OnInputChannelEventFiltered(const InputChannel& inputChannel) const InputDeviceId& inputDeviceId = inputChannel.GetInputDevice().GetInputDeviceId(); // Handle Keyboard Hotkeys - if (inputDeviceId == InputDeviceKeyboard::Id && inputChannel.IsStateBegan()) + if (InputDeviceKeyboard::IsKeyboardDevice(inputDeviceId) && inputChannel.IsStateBegan()) { // Cycle through ImGui Menu Bar States on Home button press if (inputChannelId == InputDeviceKeyboard::Key::NavigationHome) @@ -477,7 +477,7 @@ bool ImGuiManager::OnInputChannelEventFiltered(const InputChannel& inputChannel) } // Handle Keyboard Modifier Keys - if (inputDeviceId == InputDeviceKeyboard::Id) + if (InputDeviceKeyboard::IsKeyboardDevice(inputDeviceId)) { if (inputChannelId == InputDeviceKeyboard::Key::ModifierShiftL || inputChannelId == InputDeviceKeyboard::Key::ModifierShiftR) @@ -506,14 +506,10 @@ bool ImGuiManager::OnInputChannelEventFiltered(const InputChannel& inputChannel) // Handle Controller Inputs int inputControllerIndex = -1; bool controllerInput = false; - for (int i = 0; i < MaxControllerNumber; ++i) + if (InputDeviceGamepad::IsGamepadDevice(inputDeviceId)) { - //Allow only one controller navigating ImGui at the same time. After menu bar dismissed, other controllers could take over - if (inputDeviceId == InputDeviceGamepad::IdForIndexN(i)) - { - inputControllerIndex = i; - controllerInput = true; - } + inputControllerIndex = inputDeviceId.GetIndex(); + controllerInput = true; } @@ -570,7 +566,7 @@ bool ImGuiManager::OnInputChannelEventFiltered(const InputChannel& inputChannel) } // Handle Mouse Inputs - if (inputDeviceId == InputDeviceMouse::Id) + if (InputDeviceMouse::IsMouseDevice(inputDeviceId)) { const int mouseButtonIndex = GetAzMouseButtonIndex(inputChannelId); if (0 <= mouseButtonIndex && mouseButtonIndex < AZ_ARRAY_SIZE(io.MouseDown)) @@ -584,7 +580,7 @@ bool ImGuiManager::OnInputChannelEventFiltered(const InputChannel& inputChannel) } // Handle Touch Inputs - if (inputDeviceId == InputDeviceTouch::Id) + if (InputDeviceTouch::IsTouchDevice(inputDeviceId)) { const int touchIndex = GetAzTouchIndex(inputChannelId); if (0 <= touchIndex && touchIndex < AZ_ARRAY_SIZE(io.MouseDown)) @@ -605,7 +601,7 @@ bool ImGuiManager::OnInputChannelEventFiltered(const InputChannel& inputChannel) } // Handle Virtual Keyboard Inputs - if (inputDeviceId == InputDeviceVirtualKeyboard::Id) + if (InputDeviceVirtualKeyboard::IsVirtualKeyboardDevice(inputDeviceId)) { if (inputChannelId == AzFramework::InputDeviceVirtualKeyboard::Command::EditEnter) { diff --git a/Gems/LmbrCentral/Code/Source/Shape/PolygonPrismShapeComponent.cpp b/Gems/LmbrCentral/Code/Source/Shape/PolygonPrismShapeComponent.cpp index dcaf4c1577..ef03ad8056 100644 --- a/Gems/LmbrCentral/Code/Source/Shape/PolygonPrismShapeComponent.cpp +++ b/Gems/LmbrCentral/Code/Source/Shape/PolygonPrismShapeComponent.cpp @@ -98,7 +98,7 @@ namespace LmbrCentral if (AZ::BehaviorContext* behaviorContext = azrtti_cast(context)) { behaviorContext->EBus("PolygonPrismShapeComponentRequestBus") - ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation) + ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common) ->Attribute(AZ::Edit::Attributes::Category, "Shape") ->Attribute(AZ::Script::Attributes::Module, "shape") ->Event("GetPolygonPrism", &PolygonPrismShapeComponentRequestBus::Events::GetPolygonPrism) diff --git a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Common.jinja b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Common.jinja index 05403a00ef..72583f9062 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Common.jinja +++ b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Common.jinja @@ -323,10 +323,12 @@ namespace {{ Component.attrib['Namespace'] }} /// Place in your .cpp #include <{{ Component.attrib['OverrideInclude'] }}> +#include + namespace {{ Component.attrib['Namespace'] }} { {% if ComponentDerived %} - void {{ ComponentName }}::{{ ComponentName }}::Reflect(AZ::ReflectContext* context) + void {{ ComponentName }}::Reflect(AZ::ReflectContext* context) { AZ::SerializeContext* serializeContext = azrtti_cast(context); if (serializeContext) diff --git a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja index c0d4fe0dac..d2be2f682a 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja +++ b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja @@ -970,7 +970,7 @@ enum class NetworkProperties {% macro DefineComponentServiceProxyGrabs(Component, ClassType, ComponentType) %} {% for Service in Component.iter('ComponentRelation') %} {% if Service.attrib['Constraint'] != 'Incompatible' %} -m_{{ LowerFirst(Service.attrib['Name']) }} = FindComponent<{{ UpperFirst(Service.attrib['Namespace']) }}::{{ UpperFirst(Service.attrib['Name']) }}>(); +m_{{ LowerFirst(Service.attrib['Name']) }} = FindComponent<{{ Service.attrib['Namespace'] }}::{{ UpperFirst(Service.attrib['Name']) }}>(); {% endif %} {% endfor %} {% endmacro %} @@ -1709,12 +1709,12 @@ namespace {{ Component.attrib['Namespace'] }} {% for Service in Component.iter('ComponentRelation') %} {% if Service.attrib['Constraint'] != 'Incompatible' %} - const {{ UpperFirst(Service.attrib['Namespace']) }}::{{ UpperFirst(Service.attrib['Name']) }}* {{ ComponentBaseName }}::Get{{ UpperFirst(Service.attrib['Name']) }}() const + const {{ Service.attrib['Namespace'] }}::{{ UpperFirst(Service.attrib['Name']) }}* {{ ComponentBaseName }}::Get{{ UpperFirst(Service.attrib['Name']) }}() const { return m_{{ LowerFirst(Service.attrib['Name']) }}; } - {{ UpperFirst(Service.attrib['Namespace']) }}::{{ UpperFirst(Service.attrib['Name']) }}* {{ ComponentBaseName }}::Get{{ UpperFirst(Service.attrib['Name']) }}() + {{ Service.attrib['Namespace'] }}::{{ UpperFirst(Service.attrib['Name']) }}* {{ ComponentBaseName }}::Get{{ UpperFirst(Service.attrib['Name']) }}() { return m_{{ LowerFirst(Service.attrib['Name']) }}; } diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp index 9a46bb10de..21c7ce80d6 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp @@ -69,19 +69,24 @@ namespace Multiplayer { using namespace AzNetworking; - AZ_CVAR(uint16_t, cl_clientport, 0, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The port to bind to for game traffic when connecting to a remote host, a value of 0 will select any available port"); - AZ_CVAR(AZ::CVarFixedString, cl_serveraddr, AZ::CVarFixedString(LocalHost), nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The address of the remote server or host to connect to"); - AZ_CVAR(AZ::CVarFixedString, cl_serverpassword, "", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Optional server password"); + AZ_CVAR(uint16_t, cl_clientport, 0, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, + "The port to bind to for game traffic when connecting to a remote host, a value of 0 will select any available port"); + AZ_CVAR(AZ::CVarFixedString, cl_serveraddr, AZ::CVarFixedString(LocalHost), nullptr, AZ::ConsoleFunctorFlags::DontReplicate, + "The address of the remote server or host to connect to"); AZ_CVAR(uint16_t, cl_serverport, DefaultServerPort, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The port of the remote host to connect to for game traffic"); AZ_CVAR(uint16_t, sv_port, DefaultServerPort, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The port that this multiplayer gem will bind to for game traffic"); AZ_CVAR(AZ::CVarFixedString, sv_map, "nolevel", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The map the server should load"); - AZ_CVAR(AZ::CVarFixedString, sv_gamerules, "norules", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "GameRules server works with"); AZ_CVAR(ProtocolType, sv_protocol, ProtocolType::Udp, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "This flag controls whether we use TCP or UDP for game networking"); AZ_CVAR(bool, sv_isDedicated, true, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Whether the host command creates an independent or client hosted server"); AZ_CVAR(bool, sv_isTransient, true, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Whether a dedicated server shuts down if all existing connections disconnect."); - AZ_CVAR(AZ::TimeMs, cl_defaultNetworkEntityActivationTimeSliceMs, AZ::TimeMs{ 0 }, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Max Ms to use to activate entities coming from the network, 0 means instantiate everything"); + AZ_CVAR(AZ::TimeMs, cl_defaultNetworkEntityActivationTimeSliceMs, AZ::TimeMs{ 0 }, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, + "Max Ms to use to activate entities coming from the network, 0 means instantiate everything"); AZ_CVAR(AZ::TimeMs, sv_serverSendRateMs, AZ::TimeMs{ 50 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Minimum number of milliseconds between each network update"); - AZ_CVAR(AZ::CVarFixedString, sv_defaultPlayerSpawnAsset, "prefabs/player.network.spawnable", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The default spawnable to use when a new player connects"); + AZ_CVAR(AZ::CVarFixedString, sv_defaultPlayerSpawnAsset, "prefabs/player.network.spawnable", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, + "The default spawnable to use when a new player connects"); + AZ_CVAR(float, cl_renderTickBlendBase, 0.15f, nullptr, AZ::ConsoleFunctorFlags::Null, + "The base used for blending between network updates, 0.1 will be quite linear, 0.2 or 0.3 will " + "slow down quicker and may be better suited to connections with highly variable latency"); void MultiplayerSystemComponent::Reflect(AZ::ReflectContext* context) { @@ -546,7 +551,7 @@ namespace Multiplayer if ((GetAgentType() == MultiplayerAgentType::Client) && (packet.GetHostFrameId() > m_lastReplicatedHostFrameId)) { // Update client to latest server time - m_renderBlendFactor = 0.0f; + m_tickFactor = 0.0f; m_lastReplicatedHostTimeMs = packet.GetHostTimeMs(); m_lastReplicatedHostFrameId = packet.GetHostFrameId(); m_networkTime.AlterTime(m_lastReplicatedHostFrameId, m_lastReplicatedHostTimeMs, AzNetworking::InvalidConnectionId); @@ -849,10 +854,18 @@ namespace Multiplayer void MultiplayerSystemComponent::TickVisibleNetworkEntities(float deltaTime, float serverRateSeconds) { + m_tickFactor += deltaTime / serverRateSeconds; // Linear close to the origin, but asymptote at y = 1 - const float targetAdjustBlend = AZStd::clamp(deltaTime / serverRateSeconds, 0.0f, 1.0f); - m_renderBlendFactor = 1.0f - (std::pow(0.2f, m_renderBlendFactor + targetAdjustBlend)); - AZLOG(NET_Blending, "Computed blend factor of %0.2f using a frametime of %0.2f and a serverTickRate of %0.2f", m_renderBlendFactor, deltaTime, serverRateSeconds); + const float renderBlendFactor = AZStd::clamp(1.0f - (std::pow(cl_renderTickBlendBase, m_tickFactor)), 0.0f, 1.0f); + AZLOG + ( + NET_Blending, + "Computed blend factor of %0.3f using a tick factor of %0.3f, a frametime of %0.3f and a serverTickRate of %0.3f", + renderBlendFactor, + m_tickFactor, + deltaTime, + serverRateSeconds + ); if (Camera::ActiveCameraRequestBus::HasHandlers()) { @@ -895,7 +908,7 @@ namespace Multiplayer for (NetBindComponent* netBindComponent : gatheredEntities) { - netBindComponent->NotifyPreRender(deltaTime, m_renderBlendFactor); + netBindComponent->NotifyPreRender(deltaTime, renderBlendFactor); } } else @@ -907,7 +920,7 @@ namespace Multiplayer NetBindComponent* netBindComponent = entity->FindComponent(); if (netBindComponent != nullptr) { - netBindComponent->NotifyPreRender(deltaTime, m_renderBlendFactor); + netBindComponent->NotifyPreRender(deltaTime, renderBlendFactor); } } } diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h index 37e8138a71..ab37958962 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h @@ -155,7 +155,7 @@ namespace Multiplayer HostFrameId m_lastReplicatedHostFrameId = HostFrameId(0); double m_serverSendAccumulator = 0.0; - float m_renderBlendFactor = 0.0f; + float m_tickFactor = 0.0f; #if !defined(AZ_RELEASE_BUILD) MultiplayerEditorConnection m_editorConnectionListener; diff --git a/Gems/Multiplayer/Code/Source/ReplicationWindows/ServerToClientReplicationWindow.cpp b/Gems/Multiplayer/Code/Source/ReplicationWindows/ServerToClientReplicationWindow.cpp index 740f9abea2..ba9740f8db 100644 --- a/Gems/Multiplayer/Code/Source/ReplicationWindows/ServerToClientReplicationWindow.cpp +++ b/Gems/Multiplayer/Code/Source/ReplicationWindows/ServerToClientReplicationWindow.cpp @@ -107,8 +107,10 @@ namespace Multiplayer void ServerToClientReplicationWindow::UpdateWindow() { // clear the candidate queue, we're going to rebuild it - ReplicationCandidateQueue clearQueue; - clearQueue.get_container().reserve(sv_MaxEntitiesToTrackReplication); + ReplicationCandidateQueue::container_type clearQueueContainer; + clearQueueContainer.reserve(sv_MaxEntitiesToTrackReplication); + // Move the clearQueueContainer into the ReplicationCandidateQueue to maintain the reserved memory + ReplicationCandidateQueue clearQueue(ReplicationCandidateQueue::value_compare{}, AZStd::move(clearQueueContainer)); m_candidateQueue.swap(clearQueue); m_replicationSet.clear(); diff --git a/Gems/NvCloth/Code/Source/Components/ClothComponentMesh/ClothComponentMesh.cpp b/Gems/NvCloth/Code/Source/Components/ClothComponentMesh/ClothComponentMesh.cpp index 9381d98620..bcce080752 100644 --- a/Gems/NvCloth/Code/Source/Components/ClothComponentMesh/ClothComponentMesh.cpp +++ b/Gems/NvCloth/Code/Source/Components/ClothComponentMesh/ClothComponentMesh.cpp @@ -578,7 +578,7 @@ namespace NvCloth const AZ::Vector3& renderTangent = renderTangents[renderVertexIndex]; destTangentsBuffer[index].Set( renderTangent, - 1.0f); + -1.0f); // Shader function ConstructTBN inverts w to change bitangent sign, but the bitangents passed are already corrected, so passing -1.0 to counteract. } if (destBitangentsBuffer) diff --git a/Gems/NvCloth/Code/Source/System/TangentSpaceHelper.cpp b/Gems/NvCloth/Code/Source/System/TangentSpaceHelper.cpp index d7ebc8b371..3c47ad46fd 100644 --- a/Gems/NvCloth/Code/Source/System/TangentSpaceHelper.cpp +++ b/Gems/NvCloth/Code/Source/System/TangentSpaceHelper.cpp @@ -12,7 +12,7 @@ namespace NvCloth { namespace { - const float Tolerance = 0.0001f; + const float Tolerance = 1e-7f; } bool TangentSpaceHelper::CalculateNormals( @@ -34,7 +34,8 @@ namespace NvCloth const size_t vertexCount = vertices.size(); // Reset results - outNormals.resize(vertexCount, AZ::Vector3::CreateZero()); + outNormals.resize(vertexCount); + AZStd::fill(outNormals.begin(), outNormals.end(), AZ::Vector3::CreateZero()); // calculate the normals per triangle for (size_t i = 0; i < triangleCount; ++i) @@ -115,8 +116,10 @@ namespace NvCloth const size_t vertexCount = vertices.size(); // Reset results - outTangents.resize(vertexCount, AZ::Vector3::CreateZero()); - outBitangents.resize(vertexCount, AZ::Vector3::CreateZero()); + outTangents.resize(vertexCount); + outBitangents.resize(vertexCount); + AZStd::fill(outTangents.begin(), outTangents.end(), AZ::Vector3::CreateZero()); + AZStd::fill(outBitangents.begin(), outBitangents.end(), AZ::Vector3::CreateZero()); // calculate the base vectors per triangle for (size_t i = 0; i < triangleCount; ++i) @@ -193,9 +196,12 @@ namespace NvCloth const size_t vertexCount = vertices.size(); // Reset results - outTangents.resize(vertexCount, AZ::Vector3::CreateZero()); - outBitangents.resize(vertexCount, AZ::Vector3::CreateZero()); - outNormals.resize(vertexCount, AZ::Vector3::CreateZero()); + outTangents.resize(vertexCount); + outBitangents.resize(vertexCount); + outNormals.resize(vertexCount); + AZStd::fill(outTangents.begin(), outTangents.end(), AZ::Vector3::CreateZero()); + AZStd::fill(outBitangents.begin(), outBitangents.end(), AZ::Vector3::CreateZero()); + AZStd::fill(outNormals.begin(), outNormals.end(), AZ::Vector3::CreateZero()); // calculate the base vectors per triangle for (size_t i = 0; i < triangleCount; ++i) diff --git a/Gems/NvCloth/Code/Tests/Components/ClothComponentMesh/ClothConstraintsTest.cpp b/Gems/NvCloth/Code/Tests/Components/ClothComponentMesh/ClothConstraintsTest.cpp index 4beb03502a..c44d200fe3 100644 --- a/Gems/NvCloth/Code/Tests/Components/ClothComponentMesh/ClothConstraintsTest.cpp +++ b/Gems/NvCloth/Code/Tests/Components/ClothComponentMesh/ClothConstraintsTest.cpp @@ -125,6 +125,9 @@ namespace UnitTest const AZStd::vector& motionConstraints = clothConstraints->GetMotionConstraints(); EXPECT_TRUE(motionConstraints.size() == SimulationParticles.size()); + EXPECT_THAT(motionConstraints[0].GetAsVector3(), IsCloseTolerance(SimulationParticles[0].GetAsVector3(), Tolerance)); + EXPECT_THAT(motionConstraints[1].GetAsVector3(), IsCloseTolerance(SimulationParticles[1].GetAsVector3(), Tolerance)); + EXPECT_THAT(motionConstraints[2].GetAsVector3(), IsCloseTolerance(SimulationParticles[2].GetAsVector3(), Tolerance)); EXPECT_NEAR(motionConstraints[0].GetW(), 6.0f, Tolerance); EXPECT_NEAR(motionConstraints[1].GetW(), 0.0f, Tolerance); EXPECT_NEAR(motionConstraints[2].GetW(), 0.0f, Tolerance); @@ -278,6 +281,9 @@ namespace UnitTest const AZStd::vector& separationConstraints = clothConstraints->GetSeparationConstraints(); EXPECT_TRUE(motionConstraints.size() == newParticles.size()); + EXPECT_THAT(motionConstraints[0].GetAsVector3(), IsCloseTolerance(newParticles[0].GetAsVector3(), Tolerance)); + EXPECT_THAT(motionConstraints[1].GetAsVector3(), IsCloseTolerance(newParticles[1].GetAsVector3(), Tolerance)); + EXPECT_THAT(motionConstraints[2].GetAsVector3(), IsCloseTolerance(newParticles[2].GetAsVector3(), Tolerance)); EXPECT_NEAR(motionConstraints[0].GetW(), 3.0f, Tolerance); EXPECT_NEAR(motionConstraints[1].GetW(), 1.5f, Tolerance); EXPECT_NEAR(motionConstraints[2].GetW(), 0.0f, Tolerance); @@ -286,8 +292,8 @@ namespace UnitTest EXPECT_NEAR(separationConstraints[0].GetW(), 3.0f, Tolerance); EXPECT_NEAR(separationConstraints[1].GetW(), 1.5f, Tolerance); EXPECT_NEAR(separationConstraints[2].GetW(), 0.3f, Tolerance); - EXPECT_THAT(separationConstraints[0].GetAsVector3(), IsCloseTolerance(AZ::Vector3(-3.03902f, 2.80752f, 3.80752f), Tolerance)); - EXPECT_THAT(separationConstraints[1].GetAsVector3(), IsCloseTolerance(AZ::Vector3(-1.41659f, 0.651243f, -0.348757f), Tolerance)); - EXPECT_THAT(separationConstraints[2].GetAsVector3(), IsCloseTolerance(AZ::Vector3(6.15313f, -0.876132f, 0.123868f), Tolerance)); + EXPECT_THAT(separationConstraints[0].GetAsVector3(), IsCloseTolerance(AZ::Vector3(0.0f, 3.53553f, 4.53553f), Tolerance)); + EXPECT_THAT(separationConstraints[1].GetAsVector3(), IsCloseTolerance(AZ::Vector3(0.0f, 2.06066f, 1.06066f), Tolerance)); + EXPECT_THAT(separationConstraints[2].GetAsVector3(), IsCloseTolerance(AZ::Vector3(1.0f, -3.74767f, -2.74767f), Tolerance)); } } // namespace UnitTest diff --git a/Gems/PhysX/Code/Source/EditorRigidBodyComponent.cpp b/Gems/PhysX/Code/Source/EditorRigidBodyComponent.cpp index d27874f030..bd7c49a0a7 100644 --- a/Gems/PhysX/Code/Source/EditorRigidBodyComponent.cpp +++ b/Gems/PhysX/Code/Source/EditorRigidBodyComponent.cpp @@ -156,6 +156,33 @@ namespace PhysX ->DataElement(AZ::Edit::UIHandlers::Default, &AzPhysics::RigidBodyConfiguration::m_kinematic, "Kinematic", "Rigid body is kinematic") ->Attribute(AZ::Edit::Attributes::Visibility, &AzPhysics::RigidBodyConfiguration::GetKinematicVisibility) + + // Linear axis locking properties + ->ClassElement(AZ::Edit::ClassElements::Group, "Linear Axis Locking") + ->Attribute(AZ::Edit::Attributes::AutoExpand, false) + ->DataElement( + AZ::Edit::UIHandlers::Default, &AzPhysics::RigidBodyConfiguration::m_lockLinearX, "Lock X", + "Lock motion along X direction") + ->DataElement( + AZ::Edit::UIHandlers::Default, &AzPhysics::RigidBodyConfiguration::m_lockLinearY, "Lock Y", + "Lock motion along Y direction") + ->DataElement( + AZ::Edit::UIHandlers::Default, &AzPhysics::RigidBodyConfiguration::m_lockLinearZ, "Lock Z", + "Lock motion along Z direction") + + // Angular axis locking properties + ->ClassElement(AZ::Edit::ClassElements::Group, "Angular Axis Locking") + ->Attribute(AZ::Edit::Attributes::AutoExpand, false) + ->DataElement( + AZ::Edit::UIHandlers::Default, &AzPhysics::RigidBodyConfiguration::m_lockAngularX, "Lock X", + "Lock rotation around X direction") + ->DataElement( + AZ::Edit::UIHandlers::Default, &AzPhysics::RigidBodyConfiguration::m_lockAngularY, "Lock Y", + "Lock rotation around Y direction") + ->DataElement( + AZ::Edit::UIHandlers::Default, &AzPhysics::RigidBodyConfiguration::m_lockAngularZ, "Lock Z", + "Lock rotation around Z direction") + ->ClassElement(AZ::Edit::ClassElements::Group, "Continuous Collision Detection") ->Attribute(AZ::Edit::Attributes::AutoExpand, true) ->Attribute(AZ::Edit::Attributes::Visibility, &AzPhysics::RigidBodyConfiguration::GetCCDVisibility) diff --git a/Gems/PhysX/Code/Source/SystemComponent.cpp b/Gems/PhysX/Code/Source/SystemComponent.cpp index af38927f10..d8c4b47a2a 100644 --- a/Gems/PhysX/Code/Source/SystemComponent.cpp +++ b/Gems/PhysX/Code/Source/SystemComponent.cpp @@ -95,6 +95,7 @@ namespace PhysX { serialize->Class() ->Version(1) + ->Attribute(AZ::Edit::Attributes::SystemComponentTags, AZStd::vector({ AZ_CRC_CE("AssetBuilder") })) ->Field("Enabled", &SystemComponent::m_enabled) ; @@ -122,13 +123,14 @@ namespace PhysX incompatible.push_back(AZ_CRC("PhysXService", 0x75beae2d)); } - void SystemComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required) + void SystemComponent::GetRequiredServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& required) { - required.push_back(AZ_CRC("AssetDatabaseService", 0x3abf5601)); } - void SystemComponent::GetDependentServices([[maybe_unused]]AZ::ComponentDescriptor::DependencyArrayType& dependent) + void SystemComponent::GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent) { + dependent.push_back(AZ_CRC_CE("AssetDatabaseService")); + dependent.push_back(AZ_CRC_CE("AssetCatalogService")); } SystemComponent::SystemComponent() diff --git a/Gems/PhysX/Code/Source/Utils.cpp b/Gems/PhysX/Code/Source/Utils.cpp index 4e5695e3c6..0b7ab9436b 100644 --- a/Gems/PhysX/Code/Source/Utils.cpp +++ b/Gems/PhysX/Code/Source/Utils.cpp @@ -1466,6 +1466,14 @@ namespace PhysX rigidDynamic->setRigidBodyFlag(physx::PxRigidBodyFlag::eKINEMATIC, configuration.m_kinematic); rigidDynamic->setMaxAngularVelocity(configuration.m_maxAngularVelocity); + // Set axis locks. + rigidDynamic->setRigidDynamicLockFlag(physx::PxRigidDynamicLockFlag::eLOCK_LINEAR_X, configuration.m_lockLinearX); + rigidDynamic->setRigidDynamicLockFlag(physx::PxRigidDynamicLockFlag::eLOCK_LINEAR_Y, configuration.m_lockLinearY); + rigidDynamic->setRigidDynamicLockFlag(physx::PxRigidDynamicLockFlag::eLOCK_LINEAR_Z, configuration.m_lockLinearZ); + rigidDynamic->setRigidDynamicLockFlag(physx::PxRigidDynamicLockFlag::eLOCK_ANGULAR_X, configuration.m_lockAngularX); + rigidDynamic->setRigidDynamicLockFlag(physx::PxRigidDynamicLockFlag::eLOCK_ANGULAR_Y, configuration.m_lockAngularY); + rigidDynamic->setRigidDynamicLockFlag(physx::PxRigidDynamicLockFlag::eLOCK_ANGULAR_Z, configuration.m_lockAngularZ); + return rigidDynamic; } diff --git a/Gems/PhysX/Code/Tests/PhysXSpecificTest.cpp b/Gems/PhysX/Code/Tests/PhysXSpecificTest.cpp index 770e47d477..6e96db4db2 100644 --- a/Gems/PhysX/Code/Tests/PhysXSpecificTest.cpp +++ b/Gems/PhysX/Code/Tests/PhysXSpecificTest.cpp @@ -1102,6 +1102,62 @@ namespace PhysX SanityCheckValidFrustumParams(points.value(), validHeight, validBottomRadius, validTopRadius, validSubdivisions); } + TEST_F(PhysXSpecificTest, RigidBody_RigidBodyWithAxisLockFlagsCreated_InternalPhysXFlagsSetAccordingly) + { + // Helper function wrapping creation logic + auto CreateRigidBody = [this](bool linearX, bool linearY, bool linearZ, bool angularX, bool angularY, bool angularZ) -> AzPhysics::RigidBody* + { + AzPhysics::RigidBodyConfiguration rigidBodyConfig; + + rigidBodyConfig.m_lockLinearX = linearX; + rigidBodyConfig.m_lockLinearY = linearY; + rigidBodyConfig.m_lockLinearZ = linearZ; + + rigidBodyConfig.m_lockAngularX = angularX; + rigidBodyConfig.m_lockAngularY = angularY; + rigidBodyConfig.m_lockAngularZ = angularZ; + + if (auto* sceneInterface = AZ::Interface::Get()) + { + AzPhysics::SimulatedBodyHandle simBodyHandle = sceneInterface->AddSimulatedBody(m_testSceneHandle, &rigidBodyConfig); + return azdynamic_cast(sceneInterface->GetSimulatedBodyFromHandle(m_testSceneHandle, simBodyHandle)); + } + + return nullptr; + }; + + auto RemoveRigidBody = [this](AzPhysics::RigidBody*& rigidBody) + { + auto* sceneInterface = AZ::Interface::Get(); + if (rigidBody && sceneInterface) + { + sceneInterface->RemoveSimulatedBody(rigidBody->m_sceneOwner, rigidBody->m_bodyHandle); + } + rigidBody = nullptr; + }; + + auto TestLockFlags = [&CreateRigidBody, &RemoveRigidBody](bool linearX, bool linearY, bool linearZ, + bool angularX, bool angularY, bool angularZ, + physx::PxRigidDynamicLockFlags expectedFlags) + { + auto* rigidBody = CreateRigidBody(linearX, linearY, linearZ, angularX, angularY, angularZ); + ASSERT_TRUE(rigidBody != nullptr); + + physx::PxRigidDynamic* pxRigidBody = static_cast(rigidBody->GetNativePointer()); + + // These values need to be cast to integral types to prevent a compilation error on somme platforms. + EXPECT_EQ(static_cast(pxRigidBody->getRigidDynamicLockFlags()), static_cast((expectedFlags))); + + RemoveRigidBody(rigidBody); + }; + + TestLockFlags(false, false, false, false, false, false, physx::PxRigidDynamicLockFlags(0)); + TestLockFlags(true, false, false, false, false, false, physx::PxRigidDynamicLockFlags(physx::PxRigidDynamicLockFlag::eLOCK_LINEAR_X)); + TestLockFlags(false, false, false, false, true, false, physx::PxRigidDynamicLockFlags(physx::PxRigidDynamicLockFlag::eLOCK_ANGULAR_Y)); + TestLockFlags(false, true, false, false, false, true, + physx::PxRigidDynamicLockFlags(physx::PxRigidDynamicLockFlag::eLOCK_LINEAR_Y | physx::PxRigidDynamicLockFlag::eLOCK_ANGULAR_Z)); + } + TEST_F(PhysXSpecificTest, RigidBody_RigidBodyWithSimulatedFlagsHitsPlane_OnlySimulatedShapeCollidesWithPlane) { // Helper function wrapping creation logic diff --git a/Gems/ScriptCanvas/Code/Builder/ScriptCanvasBuilder.cpp b/Gems/ScriptCanvas/Code/Builder/ScriptCanvasBuilder.cpp index 6a5344ed9b..9c3cdca3e8 100644 --- a/Gems/ScriptCanvas/Code/Builder/ScriptCanvasBuilder.cpp +++ b/Gems/ScriptCanvas/Code/Builder/ScriptCanvasBuilder.cpp @@ -30,29 +30,43 @@ namespace ScriptCanvasBuilder { m_source.Reset(); m_variables.clear(); + m_overrides.clear(); + m_overridesUnused.clear(); m_entityIds.clear(); m_dependencies.clear(); } void BuildVariableOverrides::CopyPreviousOverriddenValues(const BuildVariableOverrides& source) { - for (auto& overriddenValue : m_overrides) + auto copyPreviousIfFound = [](ScriptCanvas::GraphVariable& overriddenValue, const AZStd::vector& source) { - auto iter = AZStd::find_if(source.m_overrides.begin(), source.m_overrides.end(), [&overriddenValue](const auto& candidate) { return candidate.GetVariableId() == overriddenValue.GetVariableId(); }); - - if (iter != source.m_overrides.end()) + if (auto iter = AZStd::find_if(source.begin(), source.end(), [&overriddenValue](const auto& candidate) { return candidate.GetVariableId() == overriddenValue.GetVariableId(); }); + iter != source.end()) { overriddenValue.DeepCopy(*iter); overriddenValue.SetScriptInputControlVisibility(AZ::Edit::PropertyVisibility::Hide); overriddenValue.SetAllowSignalOnChange(false); - // check that a name update is not necessary anymore + return true; + } + else + { + return false; + } + }; + + for (auto& overriddenValue : m_overrides) + { + if (!copyPreviousIfFound(overriddenValue, source.m_overrides)) + { + // the variable in question may have been previously unused, and is now used, so copy the previous value over + copyPreviousIfFound(overriddenValue, source.m_overridesUnused); } } ////////////////////////////////////////////////////////////////////////// // #functions2 provide an identifier for the node/variable in the source that caused the dependency. the root will not have one. // the above will provide the data to handle the cases where only certain dependency nodes were removed - // until then we do a sanity check, if any part of the depenecies were altered, assume no overrides are valid. + // until then we do a sanity check, if any part of the dependencies were altered, assume no overrides are valid. if (m_dependencies.size() != source.m_dependencies.size()) { return; @@ -85,31 +99,41 @@ namespace ScriptCanvasBuilder if (auto serializeContext = azrtti_cast(reflectContext)) { serializeContext->Class() - ->Version(0) + ->Version(1) ->Field("source", &BuildVariableOverrides::m_source) ->Field("variables", &BuildVariableOverrides::m_variables) ->Field("entityId", &BuildVariableOverrides::m_entityIds) ->Field("overrides", &BuildVariableOverrides::m_overrides) + ->Field("overridesUnused", &BuildVariableOverrides::m_overridesUnused) ->Field("dependencies", &BuildVariableOverrides::m_dependencies) ; if (auto editContext = serializeContext->GetEditContext()) { - editContext->Class< BuildVariableOverrides>("Variables", "Variables exposed by the attached Script Canvas Graph") - ->ClassElement(AZ::Edit::ClassElements::Group, "Variable Fields") - ->Attribute(AZ::Edit::Attributes::AutoExpand, true) + editContext->Class("Variables", "Variables exposed by the attached Script Canvas Graph") ->DataElement(AZ::Edit::UIHandlers::Default, &BuildVariableOverrides::m_overrides, "Variables", "Array of Variables within Script Canvas Graph") - ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) + ->Attribute(AZ::Edit::Attributes::AutoExpand, true) + ->Attribute(AZ::Edit::Attributes::ContainerCanBeModified, false) + ->DataElement(AZ::Edit::UIHandlers::Default, &BuildVariableOverrides::m_overridesUnused, "Unused Variables", "Unused variables within Script Canvas Graph, when used they keep the values set here") + ->Attribute(AZ::Edit::Attributes::ContainerCanBeModified, false) ->DataElement(AZ::Edit::UIHandlers::Default, &BuildVariableOverrides::m_dependencies, "Dependencies", "Variables in Dependencies of the Script Canvas Graph") - ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) + ->Attribute(AZ::Edit::Attributes::ContainerCanBeModified, false) ; } } } // use this to initialize the new data, and make sure they have a editor graph variable for proper editor display - void BuildVariableOverrides::PopulateFromParsedResults(const ScriptCanvas::Grammar::ParsedRuntimeInputs& inputs, const ScriptCanvas::VariableData& variables) + void BuildVariableOverrides::PopulateFromParsedResults(ScriptCanvas::Grammar::AbstractCodeModelConstPtr abstractCodeModel, const ScriptCanvas::VariableData& variables) { + if (!abstractCodeModel) + { + AZ_Error("ScriptCanvasBuider", false, "null abstract code model"); + return; + } + + const ScriptCanvas::Grammar::ParsedRuntimeInputs& inputs = abstractCodeModel->GetRuntimeInputs(); + for (auto& variable : inputs.m_variables) { auto graphVariable = variables.FindVariable(variable.first); @@ -147,6 +171,23 @@ namespace ScriptCanvasBuilder } } } + + for (auto& variable : abstractCodeModel->GetVariablesUnused()) + { + auto graphVariable = variables.FindVariable(variable->m_sourceVariableId); + if (!graphVariable) + { + AZ_Error("ScriptCanvasBuilder", false, "Missing Variable from graph data that was just parsed"); + continue; + } + + // copy to override unused list for editor display + m_overridesUnused.push_back(*graphVariable); + auto& overrideValue = m_overridesUnused.back(); + overrideValue.DeepCopy(*graphVariable); + overrideValue.SetScriptInputControlVisibility(AZ::Edit::PropertyVisibility::Hide); + overrideValue.SetAllowSignalOnChange(false); + } } EditorAssetTree* EditorAssetTree::ModRoot() @@ -345,7 +386,7 @@ namespace ScriptCanvasBuilder BuildVariableOverrides result; result.m_source = editorAssetTree.m_asset; - result.PopulateFromParsedResults(parseOutcome.GetValue()->GetRuntimeInputs(), *variableData); + result.PopulateFromParsedResults(parseOutcome.GetValue(), *variableData); // recurse... for (auto& dependentAsset : editorAssetTree.m_dependencies) @@ -355,7 +396,7 @@ namespace ScriptCanvasBuilder if (!parseDependentOutcome.IsSuccess()) { return AZ::Failure(AZStd::string::format - ("ParseEditorAssetTree failed to parse dependent graph from %s-%s: %s" + ( "ParseEditorAssetTree failed to parse dependent graph from %s-%s: %s" , dependentAsset.m_asset.GetId().ToString().c_str() , dependentAsset.m_asset.GetHint().c_str() , parseDependentOutcome.GetError().c_str())); diff --git a/Gems/ScriptCanvas/Code/Builder/ScriptCanvasBuilder.h b/Gems/ScriptCanvas/Code/Builder/ScriptCanvasBuilder.h index c5478b51f4..f03e78bc3e 100644 --- a/Gems/ScriptCanvas/Code/Builder/ScriptCanvasBuilder.h +++ b/Gems/ScriptCanvas/Code/Builder/ScriptCanvasBuilder.h @@ -10,16 +10,9 @@ #include #include +#include #include -namespace ScriptCanvas -{ - namespace Grammar - { - struct ParsedRuntimeInputs; - } -} - namespace ScriptCanvasEditor { class ScriptCanvasAsset; @@ -43,7 +36,7 @@ namespace ScriptCanvasBuilder bool IsEmpty() const; // use this to initialize the new data, and make sure they have a editor graph variable for proper editor display - void PopulateFromParsedResults(const ScriptCanvas::Grammar::ParsedRuntimeInputs& inputs, const ScriptCanvas::VariableData& variables); + void PopulateFromParsedResults(ScriptCanvas::Grammar::AbstractCodeModelConstPtr abstractCodeModel, const ScriptCanvas::VariableData& variables); // #functions2 provide an identifier for the node/variable in the source that caused the dependency. the root will not have one. AZ::Data::Asset m_source; @@ -52,8 +45,9 @@ namespace ScriptCanvasBuilder AZStd::vector m_variables; // the values here may or may not be overrides AZStd::vector> m_entityIds; - // this is all that gets exposed to the edit context + // these two variable lists are all that gets exposed to the edit context AZStd::vector m_overrides; + AZStd::vector m_overridesUnused; // AZStd::vector m_entityIdRuntimeInputIndices; since all of the entity ids need to go in, they may not need indices AZStd::vector m_dependencies; }; diff --git a/Gems/ScriptCanvas/Code/Builder/ScriptCanvasBuilderWorker.cpp b/Gems/ScriptCanvas/Code/Builder/ScriptCanvasBuilderWorker.cpp index 2bc7019d01..536a678577 100644 --- a/Gems/ScriptCanvas/Code/Builder/ScriptCanvasBuilderWorker.cpp +++ b/Gems/ScriptCanvas/Code/Builder/ScriptCanvasBuilderWorker.cpp @@ -82,23 +82,35 @@ namespace ScriptCanvasBuilder m_processEditorAssetDependencies.clear(); - auto assetFilter = [this, &response](const AZ::Data::AssetFilterInfo& filterInfo) + AZStd::unordered_multimap jobDependenciesByKey; + + auto assetFilter = [this, &jobDependenciesByKey](const AZ::Data::AssetFilterInfo& filterInfo) { // force load these before processing if (filterInfo.m_assetType == azrtti_typeid() - || filterInfo.m_assetType == azrtti_typeid()) + || filterInfo.m_assetType == azrtti_typeid()) { this->m_processEditorAssetDependencies.push_back(filterInfo); } // these trigger re-processing - if (filterInfo.m_assetType == azrtti_typeid() - || filterInfo.m_assetType == azrtti_typeid() - || filterInfo.m_assetType == azrtti_typeid()) + if (filterInfo.m_assetType == azrtti_typeid()) + { + AZ_Error("ScriptCanvas", false, "ScriptAsset Reference in a graph detected"); + } + + if (filterInfo.m_assetType == azrtti_typeid()) { AssetBuilderSDK::SourceFileDependency dependency; dependency.m_sourceFileDependencyUUID = filterInfo.m_assetId.m_guid; - response.m_sourceFileDependencyList.push_back(dependency); + jobDependenciesByKey.insert({ ScriptEvents::k_builderJobKey, dependency }); + } + + if (filterInfo.m_assetType == azrtti_typeid()) + { + AssetBuilderSDK::SourceFileDependency dependency; + dependency.m_sourceFileDependencyUUID = filterInfo.m_assetId.m_guid; + jobDependenciesByKey.insert({ s_scriptCanvasProcessJobKey, dependency }); } // Asset filter always returns false to prevent parsing dependencies, but makes note of the script canvas dependencies @@ -163,9 +175,10 @@ namespace ScriptCanvasBuilder jobDescriptor.m_additionalFingerprintInfo = AZStd::string(GetFingerprintString()).append("|").append(AZStd::to_string(static_cast(fingerprint))); // Graph process job needs to wait until its dependency asset job finished - for (const auto& processingDependency : response.m_sourceFileDependencyList) + for (const auto& processingDependency : jobDependenciesByKey) { - jobDescriptor.m_jobDependencyList.emplace_back(s_scriptCanvasProcessJobKey, info.m_identifier.c_str(), AssetBuilderSDK::JobDependencyType::Order, processingDependency); + response.m_sourceFileDependencyList.push_back(processingDependency.second); + jobDescriptor.m_jobDependencyList.emplace_back(processingDependency.first, info.m_identifier.c_str(), AssetBuilderSDK::JobDependencyType::Order, processingDependency.second); } response.m_createJobOutputs.push_back(jobDescriptor); diff --git a/Gems/ScriptCanvas/Code/Builder/ScriptCanvasBuilderWorker.h b/Gems/ScriptCanvas/Code/Builder/ScriptCanvasBuilderWorker.h index 6bd5caf1a6..668e1a0bcd 100644 --- a/Gems/ScriptCanvas/Code/Builder/ScriptCanvasBuilderWorker.h +++ b/Gems/ScriptCanvas/Code/Builder/ScriptCanvasBuilderWorker.h @@ -58,6 +58,7 @@ namespace ScriptCanvasBuilder AddAssetDependencySearch, PrefabIntegration, CorrectGraphVariableVersion, + ReflectEntityIdNodes, // add new entries above Current, }; diff --git a/Gems/ScriptCanvas/Code/Builder/ScriptCanvasBuilderWorkerUtility.cpp b/Gems/ScriptCanvas/Code/Builder/ScriptCanvasBuilderWorkerUtility.cpp index f300436534..af5e49bdeb 100644 --- a/Gems/ScriptCanvas/Code/Builder/ScriptCanvasBuilderWorkerUtility.cpp +++ b/Gems/ScriptCanvas/Code/Builder/ScriptCanvasBuilderWorkerUtility.cpp @@ -97,7 +97,7 @@ namespace ScriptCanvasBuilder bool pathFound = false; AZStd::string relativePath; AzToolsFramework::AssetSystemRequestBus::BroadcastResult - (pathFound + ( pathFound , &AzToolsFramework::AssetSystem::AssetSystemRequest::GetRelativeProductPathFromFullSourceOrProductPath , fullPath.c_str(), relativePath); diff --git a/Gems/ScriptCanvas/Code/Editor/Components/EditorScriptCanvasComponent.cpp b/Gems/ScriptCanvas/Code/Editor/Components/EditorScriptCanvasComponent.cpp index 9135f348b3..dbc525e8e1 100644 --- a/Gems/ScriptCanvas/Code/Editor/Components/EditorScriptCanvasComponent.cpp +++ b/Gems/ScriptCanvas/Code/Editor/Components/EditorScriptCanvasComponent.cpp @@ -474,6 +474,8 @@ namespace ScriptCanvasEditor OnScriptCanvasAssetReady(memoryAsset); } } + + AzToolsFramework::ToolsApplicationNotificationBus::Broadcast(&AzToolsFramework::ToolsApplicationEvents::InvalidatePropertyDisplay, AzToolsFramework::Refresh_EntireTree_NewContent); } void EditorScriptCanvasComponent::OnStartPlayInEditor() diff --git a/Gems/ScriptCanvas/Code/Editor/Framework/ScriptCanvasGraphUtilities.inl b/Gems/ScriptCanvas/Code/Editor/Framework/ScriptCanvasGraphUtilities.inl index a6d278be2a..4b22864b6e 100644 --- a/Gems/ScriptCanvas/Code/Editor/Framework/ScriptCanvasGraphUtilities.inl +++ b/Gems/ScriptCanvas/Code/Editor/Framework/ScriptCanvasGraphUtilities.inl @@ -21,6 +21,7 @@ #include #include #include +#include namespace ScriptCanvasEditor { @@ -217,11 +218,27 @@ namespace ScriptCanvasEditor if (!reporter.IsProcessOnly()) { - dependencies = LoadInterpretedDepencies(luaAssetResult.m_dependencies.source.userSubgraphs); - RuntimeDataOverrides runtimeDataOverrides; runtimeDataOverrides.m_runtimeAsset = loadResult.m_runtimeAsset; +#if defined(LINUX) ////////////////////////////////////////////////////////////////////////// + // Temporarily disable testing on the Linux build until the file name casing discrepancy + // is sorted out through the SC build and testing pipeline. + if (!luaAssetResult.m_dependencies.source.userSubgraphs.empty()) + { + auto graphEntityId = AZ::Entity::MakeId(); + reporter.SetGraph(graphEntityId); + loadResult.m_entity->Activate(); + ScriptCanvas::UnitTesting::EventSender::MarkComplete(graphEntityId, ""); + loadResult.m_entity->Deactivate(); + reporter.FinishReport(); + ScriptCanvas::SystemRequestBus::Broadcast(&ScriptCanvas::SystemRequests::MarkScriptUnitTestEnd); + return; + } +#else /////////////////////////////////////////////////////////////////////////////////////// + + dependencies = LoadInterpretedDepencies(luaAssetResult.m_dependencies.source.userSubgraphs); + if (!dependencies.empty()) { // #functions2_recursive_unit_tests eventually, this will need to be recursive, or the full asset handling system will need to be integrated into the testing framework @@ -258,6 +275,7 @@ namespace ScriptCanvasEditor Execution::InitializeInterpretedStatics(dependencyData); } } +#endif ////////////////////////////////////////////////////////////////////////////////////// loadResult.m_scriptAsset = luaAssetResult.m_scriptAsset; loadResult.m_runtimeAsset.Get()->GetData().m_script = loadResult.m_scriptAsset; diff --git a/Gems/ScriptCanvas/Code/Editor/View/Widgets/VariablePanel/VariableDockWidget.cpp b/Gems/ScriptCanvas/Code/Editor/View/Widgets/VariablePanel/VariableDockWidget.cpp index 65333059cc..60bc7ba0d2 100644 --- a/Gems/ScriptCanvas/Code/Editor/View/Widgets/VariablePanel/VariableDockWidget.cpp +++ b/Gems/ScriptCanvas/Code/Editor/View/Widgets/VariablePanel/VariableDockWidget.cpp @@ -120,7 +120,6 @@ namespace ScriptCanvasEditor m_variableName = m_variable->GetVariableName(); const AZStd::string variableTypeName = TranslationHelper::GetSafeTypeName(m_variable->GetDatum()->GetType()); - m_variable->SetDisplayName(variableTypeName); m_componentTitle = AZStd::string::format("%s Variable", variableTypeName.data()); diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/Datum.cpp b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/Datum.cpp index 47f74329e0..e4569af820 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/Datum.cpp +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/Datum.cpp @@ -2083,7 +2083,6 @@ namespace ScriptCanvas editContext->Class("Datum", "Datum") ->ClassElement(AZ::Edit::ClassElements::EditorData, "Datum") ->Attribute(AZ::Edit::Attributes::Visibility, &Datum::GetVisibility) - ->Attribute(AZ::Edit::Attributes::ChildNameLabelOverride, &Datum::GetLabel) ->DataElement(AZ::Edit::UIHandlers::Default, &Datum::m_storage, "Datum", "") ->Attribute(AZ::Edit::Attributes::Visibility, &Datum::GetDatumVisibility) ->Attribute(AZ::Edit::Attributes::AutoExpand, true) diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/SubgraphInterface.cpp b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/SubgraphInterface.cpp index 31272bfebb..81d6445556 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/SubgraphInterface.cpp +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/SubgraphInterface.cpp @@ -803,12 +803,6 @@ namespace ScriptCanvas m_namespacePath = namespacePath; } - void SubgraphInterface::TakeNamespacePath(NamespacePath&& namespacePath) - { - m_namespacePath = AZStd::move(namespacePath); - } - - AZStd::string SubgraphInterface::ToExecutionString() const { AZStd::string result; diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/SubgraphInterface.h b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/SubgraphInterface.h index 572f729f67..4a512082ea 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/SubgraphInterface.h +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/SubgraphInterface.h @@ -236,8 +236,6 @@ namespace ScriptCanvas void SetNamespacePath(const NamespacePath& namespacePath); - void TakeNamespacePath(NamespacePath&& namespacePath); - AZStd::string ToExecutionString() const; private: diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Data/BehaviorContextObject.h b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Data/BehaviorContextObject.h index 52c0c704e9..ca4500eccc 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Data/BehaviorContextObject.h +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Data/BehaviorContextObject.h @@ -37,8 +37,7 @@ namespace ScriptCanvas static void Reflect(AZ::ReflectContext* reflection); static BehaviorContextObjectPtr Create(const AZ::BehaviorClass& behaviorClass, const void* value = nullptr); - static BehaviorContextObjectPtr CreateDeepCopy(const AZ::BehaviorClass& behaviorClass, const BehaviorContextObject* value = nullptr); - + template AZ_INLINE static BehaviorContextObjectPtr Create(const t_Value& value, const AZ::BehaviorClass& behaviorClass); @@ -116,6 +115,7 @@ namespace ScriptCanvas AZ_FORCE_INLINE BehaviorContextObject() = default; BehaviorContextObject& operator=(const BehaviorContextObject&) = delete; + BehaviorContextObject(const BehaviorContextObject&) = delete; // copy ctor diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/AbstractCodeModel.cpp b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/AbstractCodeModel.cpp index 883e15cd29..4509e7e907 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/AbstractCodeModel.cpp +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/AbstractCodeModel.cpp @@ -1358,17 +1358,23 @@ namespace ScriptCanvas { if (variable->m_isMember) { - return !this->m_variableUse.memberVariables.contains(variable); + if (!this->m_variableUse.memberVariables.contains(variable)) + { + m_variablesUnused.push_back(variable); + return true; + } } else { - return !this->m_variableUse.localVariables.contains(variable); + if (!this->m_variableUse.localVariables.contains(variable)) + { + m_variablesUnused.push_back(variable); + return true; + } } } - else - { - return false; - } + + return false; }); } @@ -2068,6 +2074,11 @@ namespace ScriptCanvas return m_variables; } + const AZStd::vector& AbstractCodeModel::GetVariablesUnused() const + { + return m_variablesUnused; + } + bool AbstractCodeModel::IsActiveGraph() const { if (!m_nodeablesByNode.empty()) @@ -3230,7 +3241,7 @@ namespace ScriptCanvas auto valueSlot = forEachNodeSC->GetSlot(forEachNodeSC->GetValueSlotId()); AZ_Assert(valueSlot, "no value slot in for each node"); - lastExecution->AddChild({}); + lastExecution->AddChild({ &loopSlot, {}, nullptr }); auto outputValue = CreateOutputData(lastExecution, lastExecution->ModChild(0), *valueSlot); lastExecution->ModChild(0).m_output.push_back({ valueSlot, outputValue }); diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/AbstractCodeModel.h b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/AbstractCodeModel.h index cdbdf611e5..7c5340bd18 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/AbstractCodeModel.h +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/AbstractCodeModel.h @@ -138,6 +138,8 @@ namespace ScriptCanvas const AZStd::vector& GetVariables() const; + const AZStd::vector& GetVariablesUnused() const; + bool IsErrorFree() const; // has modified data or handlers @@ -166,8 +168,6 @@ namespace ScriptCanvas void AddAllVariablesPreParse(); - void AddAllVariablesPreParse_LegacyFunctions(); - void AddDebugInformation(); void AddDebugInformation(ExecutionChild& execution); @@ -519,6 +519,7 @@ namespace ScriptCanvas AZStd::unordered_map m_dependencyByVariable; AZStd::vector m_variables; + AZStd::vector m_variablesUnused; AZStd::vector m_possibleExecutionRoots; // true iff there are no internal errors and no error validation events diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/Primitives.cpp b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/Primitives.cpp index 7f446d29ef..ee7c1ee2d2 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/Primitives.cpp +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/Primitives.cpp @@ -235,7 +235,7 @@ namespace ScriptCanvas const VariableData Source::k_emptyVardata{}; Source::Source - (const Graph& graph + ( const Graph& graph , const AZ::Data::AssetId& id , const GraphData& graphData , const VariableData& variableData @@ -277,7 +277,7 @@ namespace ScriptCanvas AzFramework::StringFunc::Path::StripExtension(namespacePath); return AZ::Success(Source - (*request.graph + (*request.graph , request.scriptAssetId , *graphData , *sourceVariableData diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/PrimitivesDeclarations.h b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/PrimitivesDeclarations.h index b4fe4bdf52..16befe79b0 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/PrimitivesDeclarations.h +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/PrimitivesDeclarations.h @@ -290,7 +290,7 @@ namespace ScriptCanvas Source() = default; Source - (const Graph& graph + ( const Graph& graph , const AZ::Data::AssetId& id , const GraphData& graphData , const VariableData& variableData diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Entity/Entity.cpp b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Entity/Entity.cpp index 1cce8cb3d3..b2bfd25031 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Entity/Entity.cpp +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Entity/Entity.cpp @@ -23,10 +23,10 @@ namespace ScriptCanvas // The DataElementNode is being copied purposefully in this statement to clone the data AZ::SerializeContext::DataElementNode baseNodeElement = rootNodeElement.GetSubElement(nodeElementIndex); - if (!rootNodeElement.Convert(context, azrtti_typeid())) + if (!rootNodeElement.Convert(context, azrtti_typeid())) { AZ_Error("Script Canvas", false, "Unable to convert old Entity::IsValid function node(%s) to new EntityId::IsValid function node(%s)", - rootNodeElement.GetId().ToString().data(), azrtti_typeid().ToString().data()); + rootNodeElement.GetId().ToString().data(), azrtti_typeid().ToString().data()); return false; } @@ -79,14 +79,12 @@ namespace ScriptCanvas void Entity::InitNodeRegistry(NodeRegistry& nodeRegistry) { - EntityIDNodes::Registrar::AddToRegistry(nodeRegistry); EntityNodes::Registrar::AddToRegistry(nodeRegistry); } AZStd::vector Entity::GetComponentDescriptors() { AZStd::vector descriptors; - EntityIDNodes::Registrar::AddDescriptors(descriptors); EntityNodes::Registrar::AddDescriptors(descriptors); return descriptors; } diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Entity/Entity.h b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Entity/Entity.h index dcfae2db9f..f8171a3fe0 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Entity/Entity.h +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Entity/Entity.h @@ -12,5 +12,4 @@ // shared code #include "RotateMethod.h" -#include "EntityIDNodes.h" #include "EntityNodes.h" diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Entity/EntityIDNodes.h b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Entity/EntityIDNodes.h deleted file mode 100644 index fa6cd9981e..0000000000 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Entity/EntityIDNodes.h +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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 -#include -#include - -namespace ScriptCanvas -{ - namespace EntityIDNodes - { - using namespace Data; - static const char* k_categoryName = "Entity/Entity"; - - AZ_INLINE BooleanType IsValid(const EntityIDType& source) - { - return source.IsValid(); - } - SCRIPT_CANVAS_GENERIC_FUNCTION_NODE(IsValid, k_categoryName, "{0ED8A583-A397-4657-98B1-433673323F21}", "returns true if Source is valid, else false", "Source"); - - AZ_INLINE StringType ToString(const EntityIDType& source) - { - return source.ToString(); - } - SCRIPT_CANVAS_GENERIC_FUNCTION_NODE(ToString, k_categoryName, "{B094DCAE-15D5-42A3-8D8C-5BD68FE6E356}", "returns a string representation of Source", "Source"); - - AZ_INLINE BooleanType IsActive(const EntityIDType& entityId) - { - AZ::Entity* entity = nullptr; - AZ::ComponentApplicationBus::BroadcastResult(entity, &AZ::ComponentApplicationRequests::FindEntity, entityId); - return (entity && entity->GetState() == AZ::Entity::State::Active); - } - SCRIPT_CANVAS_GENERIC_FUNCTION_NODE(IsActive, k_categoryName, "{DF5240FD-6510-4C24-8382-9515C4B0C7B4}", "returns true if entity with the provided Id is valid and active.", "Entity Id"); - - using Registrar = RegistrarGeneric< - IsValidNode, - ToStringNode, - IsActiveNode - >; - - } -} - diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Entity/EntityNodes.h b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Entity/EntityNodes.h index dcad06d2c7..701c3a0869 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Entity/EntityNodes.h +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Libraries/Entity/EntityNodes.h @@ -21,7 +21,7 @@ namespace ScriptCanvas namespace EntityNodes { using namespace Data; - static const char* k_categoryName = "Entity/Transform"; + static const char* k_categoryName = "Entity/Entity"; template AZ_INLINE void DefaultScale(Node& node) { SetDefaultValuesByIndex::_(node, Data::One()); } @@ -59,10 +59,33 @@ namespace ScriptCanvas } SCRIPT_CANVAS_GENERIC_FUNCTION_NODE_WITH_DEFAULTS(GetEntityUp, DefaultScale<1>, k_categoryName, "{96B86F3F-F022-4611-9AEA-175EA952C562}", "returns the up direction vector from the specified entity's world transform, scaled by a given value (Lumberyard uses Z up, right handed)", "EntityId", "Scale"); + AZ_INLINE BooleanType IsActive(const EntityIDType& entityId) + { + AZ::Entity* entity = nullptr; + AZ::ComponentApplicationBus::BroadcastResult(entity, &AZ::ComponentApplicationRequests::FindEntity, entityId); + return (entity && entity->GetState() == AZ::Entity::State::Active); + } + SCRIPT_CANVAS_GENERIC_FUNCTION_NODE(IsActive, k_categoryName, "{DF5240FD-6510-4C24-8382-9515C4B0C7B4}", "returns true if entity with the provided Id is valid and active.", "Entity Id"); + + AZ_INLINE BooleanType IsValid(const EntityIDType& source) + { + return source.IsValid(); + } + SCRIPT_CANVAS_GENERIC_FUNCTION_NODE(IsValid, k_categoryName, "{0ED8A583-A397-4657-98B1-433673323F21}", "returns true if Source is valid, else false", "Source"); + + AZ_INLINE StringType ToString(const EntityIDType& source) + { + return source.ToString(); + } + SCRIPT_CANVAS_GENERIC_FUNCTION_NODE(ToString, k_categoryName, "{B094DCAE-15D5-42A3-8D8C-5BD68FE6E356}", "returns a string representation of Source", "Source"); + using Registrar = RegistrarGeneric< GetEntityRightNode, GetEntityForwardNode, - GetEntityUpNode + GetEntityUpNode, + IsActiveNode, + IsValidNode, + ToStringNode >; } } diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Variable/GraphVariable.cpp b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Variable/GraphVariable.cpp index cc9e60e765..3d76883adc 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Variable/GraphVariable.cpp +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Variable/GraphVariable.cpp @@ -348,7 +348,6 @@ namespace ScriptCanvas void GraphVariable::SetVariableName(AZStd::string_view variableName) { m_variableName = variableName; - SetDisplayName(variableName); } AZStd::string_view GraphVariable::GetVariableName() const @@ -356,16 +355,6 @@ namespace ScriptCanvas return m_variableName; } - void GraphVariable::SetDisplayName(const AZStd::string& displayName) - { - m_datum.SetLabel(displayName); - } - - AZStd::string_view GraphVariable::GetDisplayName() const - { - return m_datum.GetLabel(); - } - void GraphVariable::SetScriptInputControlVisibility(const AZ::Crc32& inputControlVisibility) { m_inputControlVisibility = inputControlVisibility; diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Variable/GraphVariable.h b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Variable/GraphVariable.h index fd15ac95ee..dbc0a814c6 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Variable/GraphVariable.h +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Variable/GraphVariable.h @@ -134,9 +134,6 @@ namespace ScriptCanvas void SetVariableName(AZStd::string_view displayName); AZStd::string_view GetVariableName() const; - void SetDisplayName(const AZStd::string& displayName); - AZStd::string_view GetDisplayName() const; - void SetScriptInputControlVisibility(const AZ::Crc32& inputControlVisibility); AZ::Crc32 GetInputControlVisibility() const; diff --git a/Gems/ScriptCanvas/Code/scriptcanvasgem_common_files.cmake b/Gems/ScriptCanvas/Code/scriptcanvasgem_common_files.cmake index e1c12b7068..84ba39cc72 100644 --- a/Gems/ScriptCanvas/Code/scriptcanvasgem_common_files.cmake +++ b/Gems/ScriptCanvas/Code/scriptcanvasgem_common_files.cmake @@ -297,7 +297,6 @@ set(FILES Include/ScriptCanvas/Libraries/Core/UnaryOperator.h Include/ScriptCanvas/Libraries/Entity/Entity.cpp Include/ScriptCanvas/Libraries/Entity/Entity.h - Include/ScriptCanvas/Libraries/Entity/EntityIDNodes.h Include/ScriptCanvas/Libraries/Entity/EntityNodes.h Include/ScriptCanvas/Libraries/Entity/RotateMethod.cpp Include/ScriptCanvas/Libraries/Entity/RotateMethod.h diff --git a/Gems/ScriptCanvasTesting/Assets/ScriptCanvas/UnitTests/LY_SC_UnitTest_ForEachMultipleOutSyntaxOnEach.scriptcanvas b/Gems/ScriptCanvasTesting/Assets/ScriptCanvas/UnitTests/LY_SC_UnitTest_ForEachMultipleOutSyntaxOnEach.scriptcanvas new file mode 100644 index 0000000000..40e548eae0 --- /dev/null +++ b/Gems/ScriptCanvasTesting/Assets/ScriptCanvas/UnitTests/LY_SC_UnitTest_ForEachMultipleOutSyntaxOnEach.scriptcanvas @@ -0,0 +1,2215 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Gems/ScriptCanvasTesting/Code/CMakeLists.txt b/Gems/ScriptCanvasTesting/Code/CMakeLists.txt index dada433772..23c9627e83 100644 --- a/Gems/ScriptCanvasTesting/Code/CMakeLists.txt +++ b/Gems/ScriptCanvasTesting/Code/CMakeLists.txt @@ -113,7 +113,6 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) ) ly_add_googletest( NAME Gem::ScriptCanvasTesting.Editor.Tests - TEST_SUITE smoke ) endif() diff --git a/Gems/ScriptCanvasTesting/Code/Source/Framework/ScriptCanvasTestFixture.h b/Gems/ScriptCanvasTesting/Code/Source/Framework/ScriptCanvasTestFixture.h index b4712e5853..f45b74d4f1 100644 --- a/Gems/ScriptCanvasTesting/Code/Source/Framework/ScriptCanvasTestFixture.h +++ b/Gems/ScriptCanvasTesting/Code/Source/Framework/ScriptCanvasTestFixture.h @@ -136,11 +136,6 @@ namespace ScriptCanvasTests // don't hang on to dangling assets AZ::Data::AssetManager::Instance().DispatchEvents(); - if (AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance()) - { - fileIO->DestroyPath(k_tempCoreAssetDir); - } - if (s_application) { s_application->Stop(); diff --git a/Gems/ScriptCanvasTesting/Code/Source/Framework/ScriptCanvasTestUtilities.cpp b/Gems/ScriptCanvasTesting/Code/Source/Framework/ScriptCanvasTestUtilities.cpp index cb304fa6b5..f69412358f 100644 --- a/Gems/ScriptCanvasTesting/Code/Source/Framework/ScriptCanvasTestUtilities.cpp +++ b/Gems/ScriptCanvasTesting/Code/Source/Framework/ScriptCanvasTestUtilities.cpp @@ -34,14 +34,6 @@ namespace ScriptCanvasTests { using namespace ScriptCanvas; -#define SC_CORE_UNIT_TEST_DIR "@engroot@/LY_SC_UnitTest_ScriptCanvas_CoreCPP_Temporary" -#define SC_CORE_UNIT_TEST_NAME "serializationTest.scriptcanvas_compiled" - const char* k_tempCoreAssetDir = SC_CORE_UNIT_TEST_DIR; - const char* k_tempCoreAssetName = SC_CORE_UNIT_TEST_NAME; - const char* k_tempCoreAssetPath = SC_CORE_UNIT_TEST_DIR "/" SC_CORE_UNIT_TEST_NAME; -#undef SC_CORE_UNIT_TEST_DIR -#undef SC_CORE_UNIT_TEST_NAME - void ExpectParse(AZStd::string_view graphPath) { AZ_TEST_START_TRACE_SUPPRESSION; diff --git a/Gems/ScriptCanvasTesting/Code/Tests/ScriptCanvas_RuntimeInterpreted.cpp b/Gems/ScriptCanvasTesting/Code/Tests/ScriptCanvas_RuntimeInterpreted.cpp index df29a56fd5..c2fa59b037 100644 --- a/Gems/ScriptCanvasTesting/Code/Tests/ScriptCanvas_RuntimeInterpreted.cpp +++ b/Gems/ScriptCanvasTesting/Code/Tests/ScriptCanvas_RuntimeInterpreted.cpp @@ -84,11 +84,6 @@ public: } }; -TEST_F(ScriptCanvasTestFixture, ProveError) -{ - EXPECT_TRUE(false); -} - TEST_F(ScriptCanvasTestFixture, EntityIdInputForOnGraphStart) { RunUnitTestGraph("LY_SC_UnitTest_EntityIdInputForOnGraphStart"); diff --git a/Gems/ScriptEvents/Code/Builder/ScriptEventsBuilderWorker.cpp b/Gems/ScriptEvents/Code/Builder/ScriptEventsBuilderWorker.cpp index 4b135b3cc9..bc9ed886ea 100644 --- a/Gems/ScriptEvents/Code/Builder/ScriptEventsBuilderWorker.cpp +++ b/Gems/ScriptEvents/Code/Builder/ScriptEventsBuilderWorker.cpp @@ -119,7 +119,7 @@ namespace ScriptEventsBuilder AssetBuilderSDK::JobDescriptor jobDescriptor; jobDescriptor.m_priority = 2; jobDescriptor.m_critical = true; - jobDescriptor.m_jobKey = "Script Events"; + jobDescriptor.m_jobKey = ScriptEvents::k_builderJobKey; jobDescriptor.SetPlatformIdentifier(info.m_identifier.data()); jobDescriptor.m_additionalFingerprintInfo = GetFingerprintString(); diff --git a/Gems/ScriptEvents/Code/Include/ScriptEvents/Internal/VersionedProperty.h b/Gems/ScriptEvents/Code/Include/ScriptEvents/Internal/VersionedProperty.h index 07d85adf05..b308a074e0 100644 --- a/Gems/ScriptEvents/Code/Include/ScriptEvents/Internal/VersionedProperty.h +++ b/Gems/ScriptEvents/Code/Include/ScriptEvents/Internal/VersionedProperty.h @@ -109,7 +109,7 @@ namespace ScriptEventData { VersionedProperty property = VersionedProperty("Void"); property.Set(VoidType {}); - return AZStd::ref(property); + return property; } template diff --git a/Gems/ScriptEvents/Code/Include/ScriptEvents/ScriptEventsAsset.h b/Gems/ScriptEvents/Code/Include/ScriptEvents/ScriptEventsAsset.h index d6e16e8ed4..691b72a08e 100644 --- a/Gems/ScriptEvents/Code/Include/ScriptEvents/ScriptEventsAsset.h +++ b/Gems/ScriptEvents/Code/Include/ScriptEvents/ScriptEventsAsset.h @@ -22,6 +22,8 @@ namespace ScriptEvents { + constexpr const char* k_builderJobKey = "Script Events"; + class ScriptEventsAsset : public AZ::Data::AssetData { diff --git a/cmake/LYTestWrappers.cmake b/cmake/LYTestWrappers.cmake index 535f00f58d..efb19a20dd 100644 --- a/cmake/LYTestWrappers.cmake +++ b/cmake/LYTestWrappers.cmake @@ -219,8 +219,17 @@ function(ly_add_test) VS_DEBUGGER_COMMAND_ARGUMENTS "${test_arguments_line}" ) + # In the case where we are creating a custom target, we need to add dependency to the target + if(ly_add_test_PARENT_NAME AND NOT ${ly_add_test_NAME} STREQUAL ${ly_add_test_PARENT_NAME}) + ly_add_dependencies(${unaliased_test_name} ${ly_add_test_PARENT_NAME}) + endif() + endif() + # For test projects that are custom targets, pass a props file that sets the project as "Console" so + # it leaves the console open when it finishes + set_target_properties(${unaliased_test_name} PROPERTIES VS_USER_PROPS "${LY_ROOT_FOLDER}/cmake/Platform/Common/TestProject.props") + # Include additional dependencies if (ly_add_test_RUNTIME_DEPENDENCIES) ly_add_dependencies(${unaliased_test_name} ${ly_add_test_RUNTIME_DEPENDENCIES}) diff --git a/cmake/LyAutoGen.cmake b/cmake/LyAutoGen.cmake index a1357e15b4..64cb73a453 100644 --- a/cmake/LyAutoGen.cmake +++ b/cmake/LyAutoGen.cmake @@ -40,7 +40,6 @@ function(ly_add_autogen) ) set_target_properties(${ly_add_autogen_NAME} PROPERTIES AUTOGEN_INPUT_FILES "${AZCG_INPUTFILES}") set_target_properties(${ly_add_autogen_NAME} PROPERTIES AUTOGEN_OUTPUT_FILES "${AUTOGEN_OUTPUTS}") - set_target_properties(${ly_add_autogen_NAME} PROPERTIES VS_USER_PROPS "${LY_ROOT_FOLDER}/Code/Framework/AzAutoGen/AzAutoGen.props") target_sources(${ly_add_autogen_NAME} PRIVATE ${AUTOGEN_OUTPUTS}) endif() diff --git a/cmake/Platform/Common/Directory.Build.props b/cmake/Platform/Common/Directory.Build.props index 73aa984073..76e4b28922 100644 --- a/cmake/Platform/Common/Directory.Build.props +++ b/cmake/Platform/Common/Directory.Build.props @@ -17,7 +17,12 @@ SPDX-License-Identifier: Apache-2.0 OR MIT - TurnOffAllWarnings + + TurnOffAllWarnings + + \ No newline at end of file diff --git a/cmake/Platform/Common/Install_common.cmake b/cmake/Platform/Common/Install_common.cmake index 2157a632c7..612358f938 100644 --- a/cmake/Platform/Common/Install_common.cmake +++ b/cmake/Platform/Common/Install_common.cmake @@ -10,6 +10,15 @@ include(cmake/FileUtil.cmake) set(CMAKE_INSTALL_MESSAGE NEVER) # Simplify messages to reduce output noise +define_property(TARGET PROPERTY LY_INSTALL_GENERATE_RUN_TARGET + BRIEF_DOCS "Defines if a \"RUN\" targets should be created when installing this target Gem" + FULL_DOCS [[ + Property which is set on targets that should generate a "RUN" + target when installed. This \"RUN\" target helps to run the + binary from the installed location directly from the IDE. + ]] +) + ly_set(CMAKE_INSTALL_DEFAULT_COMPONENT_NAME Core) cmake_path(RELATIVE_PATH CMAKE_RUNTIME_OUTPUT_DIRECTORY BASE_DIRECTORY ${CMAKE_BINARY_DIR} OUTPUT_VARIABLE runtime_output_directory) @@ -117,15 +126,19 @@ function(ly_setup_target OUTPUT_CONFIGURED_TARGET ALIAS_TARGET_NAME absolute_tar set(NAMESPACE_PLACEHOLDER "") set(NAME_PLACEHOLDER ${TARGET_NAME}) endif() + get_target_property(should_create_helper ${TARGET_NAME} LY_INSTALL_GENERATE_RUN_TARGET) + if(should_create_helper) + set(NAME_PLACEHOLDER ${NAME_PLACEHOLDER}.Imported) + endif() set(TARGET_TYPE_PLACEHOLDER "") - get_target_property(target_type ${NAME_PLACEHOLDER} TYPE) + get_target_property(target_type ${TARGET_NAME} TYPE) # Remove the _LIBRARY since we dont need to pass that to ly_add_targets string(REPLACE "_LIBRARY" "" TARGET_TYPE_PLACEHOLDER ${target_type}) # For HEADER_ONLY libs we end up generating "INTERFACE" libraries, need to specify HEADERONLY instead string(REPLACE "INTERFACE" "HEADERONLY" TARGET_TYPE_PLACEHOLDER ${TARGET_TYPE_PLACEHOLDER}) if(TARGET_TYPE_PLACEHOLDER STREQUAL "MODULE") - get_target_property(gem_module ${NAME_PLACEHOLDER} GEM_MODULE) + get_target_property(gem_module ${TARGET_NAME} GEM_MODULE) if(gem_module) set(TARGET_TYPE_PLACEHOLDER "GEM_MODULE") endif() @@ -158,7 +171,6 @@ function(ly_setup_target OUTPUT_CONFIGURED_TARGET ALIAS_TARGET_NAME absolute_tar unset(RUNTIME_DEPENDENCIES_PLACEHOLDER) endif() - get_target_property(inteface_build_dependencies_props ${TARGET_NAME} INTERFACE_LINK_LIBRARIES) unset(INTERFACE_BUILD_DEPENDENCIES_PLACEHOLDER) if(inteface_build_dependencies_props) @@ -182,6 +194,23 @@ function(ly_setup_target OUTPUT_CONFIGURED_TARGET ALIAS_TARGET_NAME absolute_tar list(REMOVE_DUPLICATES INTERFACE_BUILD_DEPENDENCIES_PLACEHOLDER) string(REPLACE ";" "\n" INTERFACE_BUILD_DEPENDENCIES_PLACEHOLDER "${INTERFACE_BUILD_DEPENDENCIES_PLACEHOLDER}") + # If the target is an executable/application, add a custom target so we can debug the target in project-centric workflow + if(should_create_helper) + string(REPLACE ".Imported" "" RUN_TARGET_NAME ${NAME_PLACEHOLDER}) + set(target_types_with_debugging_helper EXECUTABLE APPLICATION) + if(NOT target_type IN_LIST target_types_with_debugging_helper) + message(FATAL_ERROR "Cannot generate a RUN target for ${TARGET_NAME}, type is ${target_type}") + endif() + set(TARGET_RUN_HELPER +"add_custom_target(${RUN_TARGET_NAME}) +set_target_properties(${RUN_TARGET_NAME} PROPERTIES + FOLDER \"CMakePredefinedTargets/SDK\" + VS_DEBUGGER_COMMAND \$> + VS_DEBUGGER_COMMAND_ARGUMENTS \"--project-path=\${LY_DEFAULT_PROJECT_PATH}\" +)" +) + endif() + # Config file set(target_file_contents "# Generated by O3DE install\n\n") if(NOT target_type STREQUAL INTERFACE_LIBRARY) @@ -194,13 +223,13 @@ function(ly_setup_target OUTPUT_CONFIGURED_TARGET ALIAS_TARGET_NAME absolute_tar set(target_location "\${LY_ROOT_FOLDER}/${library_output_directory}/${PAL_PLATFORM_NAME}/$/${target_library_output_subdirectory}/$") elseif(target_type STREQUAL SHARED_LIBRARY) string(APPEND target_file_contents -"set_property(TARGET ${TARGET_NAME} +"set_property(TARGET ${NAME_PLACEHOLDER} APPEND_STRING PROPERTY IMPORTED_IMPLIB $<$$:\"\${LY_ROOT_FOLDER}/${archive_output_directory}/${PAL_PLATFORM_NAME}/$/$\"$ ) ") string(APPEND target_file_contents -"set_property(TARGET ${TARGET_NAME} +"set_property(TARGET ${NAME_PLACEHOLDER} PROPERTY IMPORTED_IMPLIB_$> \"\${LY_ROOT_FOLDER}/${archive_output_directory}/${PAL_PLATFORM_NAME}/$/$\" ) @@ -212,11 +241,11 @@ function(ly_setup_target OUTPUT_CONFIGURED_TARGET ALIAS_TARGET_NAME absolute_tar if(target_location) string(APPEND target_file_contents -"set_property(TARGET ${TARGET_NAME} +"set_property(TARGET ${NAME_PLACEHOLDER} APPEND_STRING PROPERTY IMPORTED_LOCATION $<$$:${target_location}$ ) -set_property(TARGET ${TARGET_NAME} +set_property(TARGET ${NAME_PLACEHOLDER} PROPERTY IMPORTED_LOCATION_$> ${target_location} ) diff --git a/cmake/Platform/Common/TestProject.props b/cmake/Platform/Common/TestProject.props new file mode 100644 index 0000000000..dff509b7ff --- /dev/null +++ b/cmake/Platform/Common/TestProject.props @@ -0,0 +1,16 @@ + + + + + + + + Console + + + diff --git a/cmake/SettingsRegistry.cmake b/cmake/SettingsRegistry.cmake index b740fefd25..ebf2254dc6 100644 --- a/cmake/SettingsRegistry.cmake +++ b/cmake/SettingsRegistry.cmake @@ -159,10 +159,6 @@ function(ly_delayed_generate_settings_registry) message(FATAL_ERROR "Dependency ${gem_target} from ${target} does not exist") endif() - get_property(has_manually_added_dependencies TARGET ${gem_target} PROPERTY MANUALLY_ADDED_DEPENDENCIES SET) - get_target_property(target_type ${gem_target} TYPE) - - ly_get_gem_module_root(gem_module_root ${gem_target}) file(RELATIVE_PATH gem_module_root_relative_to_engine_root ${LY_ROOT_FOLDER} ${gem_module_root}) @@ -180,7 +176,8 @@ function(ly_delayed_generate_settings_registry) list(JOIN target_gem_dependencies_names ",\n" target_gem_dependencies_names) string(CONFIGURE ${gems_json_template} gem_json @ONLY) get_target_property(is_imported ${target} IMPORTED) - if(is_imported) + get_target_property(target_type ${target} TYPE) + if(is_imported OR target_type STREQUAL UTILITY) unset(target_dir) foreach(conf IN LISTS CMAKE_CONFIGURATION_TYPES) string(TOUPPER ${conf} UCONF) diff --git a/cmake/install/InstalledTarget.in b/cmake/install/InstalledTarget.in index 0503fd5f2b..a4f4fa4763 100644 --- a/cmake/install/InstalledTarget.in +++ b/cmake/install/InstalledTarget.in @@ -17,6 +17,8 @@ ly_add_target( @RUNTIME_DEPENDENCIES_PLACEHOLDER@ ) +@TARGET_RUN_HELPER@ + set(configs @CMAKE_CONFIGURATION_TYPES@) foreach(config ${configs}) include("@NAME_PLACEHOLDER@_${config}.cmake" OPTIONAL) diff --git a/scripts/o3de/o3de/manifest.py b/scripts/o3de/o3de/manifest.py index 5bc6b95358..7e504d29cd 100644 --- a/scripts/o3de/o3de/manifest.py +++ b/scripts/o3de/o3de/manifest.py @@ -236,15 +236,13 @@ def get_gems_from_subdirectories(external_subdirs: list) -> list: # Data query methods -def get_this_engine() -> dict: - json_data = load_o3de_manifest() - engine_data = find_engine_data(json_data) - return engine_data - - def get_engines() -> list: json_data = load_o3de_manifest() - return json_data['engines'] if 'engines' in json_data else [] + engine_list = json_data['engines'] if 'engines' in json_data else [] + # Convert each engine dict entry into a string entry + return list(map( + lambda engine_object: engine_object.get('path', '') if isinstance(engine_object, dict) else engine_object, + engine_list)) def get_projects() -> list: @@ -424,20 +422,6 @@ def get_templates_for_generic_creation(): # temporary until we have a better wa return list(filter(filter_project_and_gem_templates_out, get_all_templates())) -def find_engine_data(json_data: dict, - engine_path: str or pathlib.Path = None) -> dict or None: - if not engine_path: - engine_path = get_this_engine_path() - engine_path = pathlib.Path(engine_path).resolve() - - for engine_object in json_data['engines']: - engine_object_path = pathlib.Path(engine_object['path']).resolve() - if engine_path == engine_object_path: - return engine_object - - return None - - def get_engine_json_data(engine_name: str = None, engine_path: str or pathlib.Path = None) -> dict or None: if not engine_name and not engine_path: @@ -639,8 +623,13 @@ def get_registered(engine_name: str = None, # check global first then this engine if isinstance(engine_name, str): - for engine in json_data['engines']: - engine_path = pathlib.Path(engine['path']).resolve() + engines = get_engines() + for engine in engines: + if isinstance(engine, dict): + engine_path = pathlib.Path(engine['path']).resolve() + else: + engine_path = pathlib.Path(engine_object).resolve() + engine_json = engine_path / 'engine.json' with engine_json.open('r') as f: try: diff --git a/scripts/o3de/o3de/print_registration.py b/scripts/o3de/o3de/print_registration.py index 10316cc4a8..b164243b7c 100644 --- a/scripts/o3de/o3de/print_registration.py +++ b/scripts/o3de/o3de/print_registration.py @@ -40,61 +40,67 @@ def get_project_path(project_path: pathlib.Path, project_name: str) -> pathlib.P def print_this_engine(verbose: int) -> int: - engine_data = manifest.get_this_engine() - print(json.dumps(engine_data, indent=4)) - result = True + this_engine_path = manifest.get_this_engine_path() + print(f'This Engine:\n{json.dumps(str(this_engine_path), indent=4)}') if verbose > 0: - result = print_manifest_json_data(engine_data, 'engine.json', 'This Engine', + return print_manifest_json_data([this_engine_path], 'This Engine', manifest.get_engine_json_data, 'engine_path') - return 0 if result else 1 + return 0 def print_engines(verbose: int) -> None: engines_data = manifest.get_engines() - print(json.dumps(engines_data, indent=4)) + print(f'Engine Paths:\n{json.dumps(engines_data, indent=4)}') if verbose > 0: - return print_manifest_json_data(engines_data, 'engine.json', 'Engines', + return print_manifest_json_data(engines_data, 'Engine Jsons', manifest.get_engine_json_data, 'engine_path') return 0 def print_projects(verbose: int) -> int: projects_data = manifest.get_projects() - print(json.dumps(projects_data, indent=4)) + print(f'Project Paths:\n{json.dumps(projects_data, indent=4)}') if verbose > 0: - return print_manifest_json_data(projects_data, 'project.json', 'Projects', + return print_manifest_json_data(projects_data, 'Project Jsons', manifest.get_project_json_data, 'project_path') return 0 def print_gems(verbose: int) -> int: gems_data = manifest.get_gems() - print(json.dumps(gems_data, indent=4)) + print(f'Gem Paths:\n{json.dumps(gems_data, indent=4)}') if verbose > 0: - return print_manifest_json_data(gems_data, 'gem.json', 'Gems', + return print_manifest_json_data(gems_data, 'Gem Jsons', manifest.get_gem_json_data, 'gem_path') return 0 +def print_external_subdirectories(verbose: int) -> int: + external_subdirs_data = manifest.get_external_subdirectories() + print(f'External Subdirectories:\n{json.dumps(external_subdirs_data, indent=4)}') + return 0 + + + def print_templates(verbose: int) -> int: templates_data = manifest.get_templates() - print(json.dumps(templates_data, indent=4)) + print(f'Template Paths:\n{json.dumps(templates_data, indent=4)}') if verbose > 0: - return print_manifest_json_data(templates_data, 'template.json', 'Templates', + return print_manifest_json_data(templates_data, 'Template Jsons', manifest.get_template_json_data, 'template_path') return 0 def print_restricted(verbose: int) -> int: restricted_data = manifest.get_restricted() - print(json.dumps(restricted_data, indent=4)) + print(f'Restricted Paths:\n{json.dumps(restricted_data, indent=4)}') if verbose > 0: - return print_manifest_json_data(restricted_data, 'restricted.json', 'Restricted', + return print_manifest_json_data(restricted_data, 'Restricted Jsons', manifest.get_restricted_json_data, 'restricted_path') return 0 @@ -102,47 +108,47 @@ def print_restricted(verbose: int) -> int: # Engine output methods def print_engine_projects(verbose: int) -> int: engine_projects_data = manifest.get_engine_projects() - print(json.dumps(engine_projects_data, indent=4)) + print(f'Project Paths:\n{json.dumps(engine_projects_data, indent=4)}') if verbose > 0: - return print_manifest_json_data(engine_projects_data, 'project.json', 'Projects', + return print_manifest_json_data(engine_projects_data, 'Project Jsons', manifest.get_project_json_data, 'project_path') return 0 def print_engine_gems(verbose: int) -> int: engine_gems_data = manifest.get_engine_gems() - print(json.dumps(engine_gems_data, indent=4)) + print(f'Gem Paths:\n{json.dumps(engine_gems_data, indent=4)}') if verbose > 0: - return print_manifest_json_data(engine_gems_data, 'gem.json', 'Gems', + return print_manifest_json_data(engine_gems_data, 'Gem Jsons', manifest.get_gem_json_data, 'gem_path') return 0 def print_engine_templates(verbose: int) -> int: engine_templates_data = manifest.get_engine_templates() - print(json.dumps(engine_templates_data, indent=4)) + print(f'Template Paths:\n{json.dumps(engine_templates_data, indent=4)}') if verbose > 0: - return print_manifest_json_data(engine_templates_data, 'template.json', 'Templates', + return print_manifest_json_data(engine_templates_data, 'Template Jsons', manifest.get_template_json_data, 'template_path') return 0 def print_engine_restricted(verbose: int) -> int: engine_restricted_data = manifest.get_engine_restricted() - print(json.dumps(engine_restricted_data, indent=4)) + print(f'Restricted Paths:\n{json.dumps(engine_restricted_data, indent=4)}') if verbose > 0: - return print_manifest_json_data(engine_restricted_data, 'restricted.json', 'Restricted', + return print_manifest_json_data(engine_restricted_data, 'Restricted Jsons', manifest.get_restricted_json_data, 'restricted_path') return 0 -def print_engine_external_subdirectories() -> int: +def print_engine_external_subdirectories(verbose: int) -> int: external_subdirs_data = manifest.get_engine_external_subdirectories() - print(json.dumps(external_subdirs_data, indent=4)) + print(f'External Subdirectories:\n{json.dumps(external_subdirs_data, indent=4)}') return 0 @@ -153,21 +159,21 @@ def print_project_gems(verbose: int, project_path: pathlib.Path, project_name: s return 1 project_gems_data = manifest.get_project_gems(project_path) - print(json.dumps(project_gems_data, indent=4)) + print(f'Gem Paths:\n{json.dumps(project_gems_data, indent=4)}') if verbose > 0: - return print_manifest_json_data(project_gems_data, 'gem.json', 'Gems', + return print_manifest_json_data(project_gems_data, 'Gems Jsons', manifest.get_gem_json_data, 'gem_path') return 0 -def print_project_external_subdirectories(project_path: pathlib.Path, project_name: str) -> int: +def print_project_external_subdirectories(verbose: int, project_path: pathlib.Path, project_name: str) -> int: project_path = get_project_path(project_path, project_name) if not project_path: return 1 external_subdirs_data = manifest.get_project_external_subdirectories(project_path) - print(json.dumps(external_subdirs_data, indent=4)) + print(f'External Subdirectories:\n{json.dumps(external_subdirs_data, indent=4)}') return 0 @@ -177,9 +183,9 @@ def print_project_templates(verbose: int, project_path: pathlib.Path, project_na return 1 project_templates_data = manifest.get_project_templates(project_path) - print(json.dumps(project_templates_data, indent=4)) + print(f'Template Paths:\n{json.dumps(project_templates_data, indent=4)}') if verbose > 0: - return print_manifest_json_data(project_templates_data, 'template.json', 'Templates', + return print_manifest_json_data(project_templates_data, 'Template Jsons', manifest.get_template_json_data, 'template_path') return 0 @@ -190,73 +196,118 @@ def print_project_restricted(verbose: int, project_path: pathlib.Path, project_n return 1 project_restricted_data = manifest.get_project_restricted(project_path) - print(json.dumps(project_restricted_data, indent=4)) + print(f'Restricted Paths:\n{json.dumps(project_restricted_data, indent=4)}') if verbose > 0: - return print_manifest_json_data(project_restricted_data, 'restricted.json', 'Restricted', + return print_manifest_json_data(project_restricted_data, 'Restricted Jsons', manifest.get_restricted_json_data, 'restricted_path') return 0 def print_all_projects(verbose: int) -> int: all_projects_data = manifest.get_all_projects() - print(json.dumps(all_projects_data, indent=4)) + print(f'Project Paths:\n{json.dumps(all_projects_data, indent=4)}') if verbose > 0: - return print_manifest_json_data(all_projects_data, 'project.json', 'Projects', + return print_manifest_json_data(all_projects_data, 'Project Jsons', manifest.get_project_json_data, 'project_path') return 0 -def print_all_gems(verbose: int) -> int: - all_gems_data = manifest.get_all_gems() - print(json.dumps(all_gems_data, indent=4)) +def print_all_gems(verbose: int, project_path: pathlib.Path = None, project_name: str = None) -> int: + all_gems = manifest.get_gems() + all_gems.extend(manifest.get_engine_gems()) + + # If a project path or project name is supplied query the gems from that project, otherwise query the gems from + # all projects + project_path = get_project_path(project_path, project_name) if project_path or project_name else None + projects = [project_path] if project_path else manifest.get_all_projects() + for project in projects: + all_gems.extend(manifest.get_project_gems(project)) + + # Filter out duplicates + all_gems = list(dict.fromkeys(all_gems)) + print(f'Gem Paths:\n{json.dumps(all_gems, indent=4)}') if verbose > 0: - return print_manifest_json_data(all_gems_data, 'gem.json', 'Gems', + return print_manifest_json_data(all_gems, 'Gem Jsons', manifest.get_gem_json_data, 'gem_path') return 0 -def print_all_external_subdirectories() -> int: - all_external_subdirectories_data = manifest.get_all_external_subdirectories() - print(json.dumps(all_external_subdirectories_data, indent=4)) +def print_all_external_subdirectories(verbose: int, project_path: pathlib.Path = None, project_name: str = None) -> int: + all_external_subdirectories = manifest.get_external_subdirectories() + all_external_subdirectories.extend(manifest.get_engine_external_subdirectories()) + + # If a project path or project name is supplied query the external subdirectories from that project, + # otherwise query the external subdirectories from all projects + project_path = get_project_path(project_path, project_name) if project_path or project_name else None + projects = [project_path] if project_path else manifest.get_all_projects() + for project in projects: + all_external_subdirectories.extend(manifest.get_project_external_subdirectories(project)) + + # Filter out duplicates + all_external_subdirectories = list(dict.fromkeys(all_external_subdirectories)) + print(f'External Subdirectories:\n{json.dumps(all_external_subdirectories, indent=4)}') return 0 -def print_all_templates(verbose: int) -> int: - all_templates_data = manifest.get_all_templates() - print(json.dumps(all_templates_data, indent=4)) + +def print_all_templates(verbose: int, project_path: pathlib.Path = None, project_name: str = None) -> int: + all_templates = manifest.get_templates() + all_templates.extend(manifest.get_engine_templates()) + + # If a project path or project name is supplied query the templates from that project, + # otherwise query the templates from all projects + project_path = get_project_path(project_path, project_name) if project_path or project_name else None + projects = [project_path] if project_path else manifest.get_all_projects() + for project in projects: + all_templates.extend(manifest.get_project_templates(project)) + + # Filter out duplicates + all_templates = list(dict.fromkeys(all_templates)) + print(f'Template Paths:\n{json.dumps(all_templates, indent=4)}') if verbose > 0: - return print_manifest_json_data(all_templates_data, 'template.json', 'Templates', + return print_manifest_json_data(all_templates, 'Template Jsons', manifest.get_template_json_data, 'template_path') return 0 -def print_all_restricted(verbose: int) -> int: - all_restricted_data = manifest.get_all_restricted() - print(json.dumps(all_restricted_data, indent=4)) +def print_all_restricted(verbose: int, project_path: pathlib.Path = None, project_name: str = None) -> int: + all_restricted = manifest.get_restricted() + all_restricted.extend(manifest.get_engine_restricted()) + + # If a project path or project name is supplied query the restricted from that project, + # otherwise query the restricted from all projects + project_path = get_project_path(project_path, project_name) if project_path or project_name else None + projects = [project_path] if project_path else manifest.get_all_projects() + for project in projects: + all_restricted.extend(manifest.get_project_restricted(project)) + + # Filter out duplicates + all_restricted = list(dict.fromkeys(all_restricted)) + print(f'Restricted Paths:\n{json.dumps(all_restricted, indent=4)}') if verbose > 0: - return print_manifest_json_data(all_restricted_data, 'restricted.json', 'Restricted', + return print_manifest_json_data(all_restricted, 'Restricted Jsons', manifest.get_restricted_json_data, 'restricted_path') return 0 -def print_manifest_json_data(uri_json_data: dict, json_filename: str, +def print_manifest_json_data(uri_json_data: list, print_prefix: str, get_json_func: callable, get_json_data_kw: str) -> int: print('\n') print(f"{print_prefix}================================================") for manifest_uri in uri_json_data: # if it's not local it should be in the cache - parsed_uri = urllib.parse.urlparse(manifest_uri) + parsed_uri = urllib.parse.urlparse(pathlib.Path(manifest_uri).as_posix()) if parsed_uri.scheme in ['http', 'https', 'ftp', 'ftps']: repo_sha256 = hashlib.sha256(manifest_uri.encode()) cache_folder = manifest.get_o3de_cache_folder() manifest_json_path = cache_folder / str(repo_sha256.hexdigest() + '.json') else: - manifest_json_path = pathlib.Path(manifest_uri).resolve() / json_filename + manifest_json_path = pathlib.Path(manifest_uri).resolve() - json_data = get_json_func(**{get_json_data_kwargs: manifest_json_path}) + json_data = get_json_func(**{get_json_data_kw: manifest_json_path}) if json_data: print(manifest_json_path) print(json.dumps(json_data, indent=4) + '\n') @@ -284,29 +335,30 @@ def print_repos_data(repos_data: dict) -> int: return 0 -def register_show_repos(verbose: int) -> None: +def print_repos(verbose: int) -> int: repos_data = manifest.get_repos() print(json.dumps(repos_data, indent=4)) if verbose > 0: - return print_repos_data(repos_data) == 0 + return print_repos_data(repos_data) return 0 -def register_show(verbose: int) -> None: +def register_show(verbose: int, project_path: pathlib.Path = None, project_name: str = None) -> int: json_data = manifest.load_o3de_manifest() print(f"{manifest.get_o3de_manifest()}:") print(json.dumps(json_data, indent=4)) - result = True + result = 0 if verbose > 0: - result = print_manifest_json_data(manifest.get_engines()) == 0 and result - result = print_manifest_json_data(manifest.get_all_projects()) == 0 and result - result = print_manifest_json_data(manifest.get_gems()) == 0 and result - result = print_manifest_json_data(manifest.get_all_templates()) == 0 and result - result = print_manifest_json_data(manifest.get_all_restricted()) == 0 and result - result = print_repos_data(manifest.get_repos()) == 0 and result - return 0 if result else 1 + result = print_engines(verbose) or result + result = print_all_projects(verbose) or result + result = print_all_gems(verbose, project_path, project_name) or result + result = print_all_templates(verbose, project_path, project_name) or result + result = print_all_restricted(verbose, project_path, project_name) or result + result = print_repos(verbose) or result + + return result def _run_register_show(args: argparse) -> int: @@ -321,6 +373,8 @@ def _run_register_show(args: argparse) -> int: return print_projects(args.verbose) elif args.gems: return print_gems(args.verbose) + elif args.external_subdirectories: + return print_external_subdirectories(args.verbose) elif args.templates: return print_templates(args.verbose) elif args.repos: @@ -333,7 +387,7 @@ def _run_register_show(args: argparse) -> int: elif args.engine_gems: return print_engine_gems(args.verbose) elif args.engine_external_subdirectories: - return print_engine_external_subdirectories() + return print_engine_external_subdirectories(args.verbose) elif args.engine_templates: return print_engine_templates(args.verbose) elif args.engine_restricted: @@ -342,7 +396,7 @@ def _run_register_show(args: argparse) -> int: elif args.project_gems: return print_project_gems(args.verbose, args.project_path, args.project_name) elif args.project_external_subdirectories: - return print_project_external_subdirectories(args.project_path, args.project_name) + return print_project_external_subdirectories(args.verbose, args.project_path, args.project_name) elif args.project_templates: return print_project_templates(args.verbose, args.project_path, args.project_name) elif args.project_restricted: @@ -351,16 +405,16 @@ def _run_register_show(args: argparse) -> int: elif args.all_projects: return print_all_projects(args.verbose) elif args.all_gems: - return print_all_gems(args.verbose) + return print_all_gems(args.verbose, args.project_path, args.project_name) elif args.all_external_subdirectories: - return print_all_external_subdirectories() + return print_all_external_subdirectories(args.verbose, args.project_path, args.project_name) elif args.all_templates: - return print_all_templates(args.verbose) + return print_all_templates(args.verbose, args.project_path, args.project_name) elif args.all_restricted: - return print_all_restricted(args.verbose) + return print_all_restricted(args.verbose, args.project_path, args.project_name) else: - return register_show(args.verbose) + return register_show(args.verbose, args.project_path, args.project_name) def add_parser_args(parser): @@ -393,6 +447,9 @@ def add_parser_args(parser): group.add_argument('-rs', '--restricted', action='store_true', required=False, default=False, help='Output the restricted directories registered in the global ~/.o3de/o3de_manifest.json.') + group.add_argument('-es', '--external-subdirectories', action='store_true', required=False, + default=False, + help='Output the external subdirectories registered in the global ~/.o3de/o3de_manifest.json.') group.add_argument('-ep', '--engine-projects', action='store_true', required=False, default=False, @@ -428,16 +485,28 @@ def add_parser_args(parser): help='Output all projects registered in the ~/.o3de/o3de_manifest.json and the current engine.json. Ignores repos.') group.add_argument('-ag', '--all-gems', action='store_true', required=False, default=False, - help='Output all gems registered in the ~/.o3de/o3de_manifest.json and the current engine.json. Ignores repos') + help='Output all gems registered in the ~/.o3de/o3de_manifest.json and the current engine.json.' + ' If --project-path or --project-name option is supplied, outputs gems registered in' + ' that project\'s project.json otherwise outputs registered gems from all registered projects.' + ' Ignores repos') group.add_argument('-at', '--all-templates', action='store_true', required=False, default=False, - help='Output all templates registered in the ~/.o3de/o3de_manifest.json and the current engine.json. Ignores repos.') + help='Output all templates registered in the ~/.o3de/o3de_manifest.json and the current engine.json.' + ' If --project-path or --project-name option is supplied, outputs templates registered in' + ' that project\'s project.json otherwise outputs registered templates from all registered' + ' projects. Ignores repos') group.add_argument('-ares', '--all-restricted', action='store_true', required=False, default=False, - help='Output all restricted directory registered in the ~/.o3de/o3de_manifest.json and the current engine.json.') + help='Output all restricted directory registered in the ~/.o3de/o3de_manifest.json and the current engine.json.' + ' If --project-path or --project-name option is supplied, outputs restricted' + ' directories registered in that project\'s project.json otherwise outputs restricted' + ' directories registered from all registered projects. Ignores repos') group.add_argument('-aes', '--all-external-subdirectories', action='store_true', default=False, - help='Output all external subdirectories registered in the ~/.o3de/o3de_manifest.json and the current engine.json.') + help='Output all external subdirectories registered in the ~/.o3de/o3de_manifest.json and the current engine.json.' + ' If --project-path or --project-name options is supplied, outputs external' + ' subdirectories registered in that project\'s project.json otherwise outputs external' + ' subdirectories registered from all registered projects. Ignores repos') parser.add_argument('-v', '--verbose', action='count', required=False, default=0, diff --git a/scripts/o3de/o3de/register.py b/scripts/o3de/o3de/register.py index fb91cb5bea..8481c5fae0 100644 --- a/scripts/o3de/o3de/register.py +++ b/scripts/o3de/o3de/register.py @@ -261,43 +261,6 @@ def add_engine_name_to_path(json_data: dict, engine_path: pathlib.Path, force: b return 0 -def register_engine_path(json_data: dict, - engine_path: pathlib.Path, - remove: bool = False, - force: bool = False) -> int: - if not engine_path: - logger.error(f'Engine path cannot be empty.') - return 1 - engine_path = pathlib.Path(engine_path).resolve() - - for engine_object in json_data.get('engines', []): - if isinstance(engine_object, dict): - engine_object_path = pathlib.Path(engine_object['path']).resolve() - else: - engine_object_path = pathlib.Path(engine_object).resolve() - if engine_object_path == engine_path: - json_data['engines'].remove(engine_object) - - if remove: - return remove_engine_name_to_path(json_data, engine_path) - - if not engine_path.is_dir(): - logger.error(f'Engine path {engine_path} does not exist.') - return 1 - - engine_json = engine_path / 'engine.json' - if not validation.valid_o3de_engine_json(engine_json): - logger.error(f'Engine json {engine_json} is not valid.') - return 1 - - engine_object = {} - engine_object.update({'path': engine_path.as_posix()}) - - json_data.setdefault('engines', []).insert(0, engine_object) - - return add_engine_name_to_path(json_data, engine_path, force) - - def register_o3de_object_path(json_data: dict, o3de_object_path: str or pathlib.Path, o3de_object_key: str, @@ -343,7 +306,7 @@ def register_o3de_object_path(json_data: dict, try: paths_to_remove.append(o3de_object_path.relative_to(save_path.parent)) except ValueError: - pass # It is OK relative path cannot be formed + pass # It is not an error if a relative path cannot be formed manifest_data[o3de_object_key] = list(filter(lambda p: pathlib.Path(p) not in paths_to_remove, manifest_data.setdefault(o3de_object_key, []))) @@ -358,7 +321,7 @@ def register_o3de_object_path(json_data: dict, manifest_json_path = o3de_object_path / o3de_json_filename if validation_func and not validation_func(manifest_json_path): - logger.error(f'o3de json {manifest_json_path} is not valid.') + logger.error(f'Manifest at path {manifest_json_path} is not valid.') return 1 # if there is a save path make it relative the directory containing o3de object json file @@ -374,6 +337,27 @@ def register_o3de_object_path(json_data: dict, return 0 +def register_engine_path(json_data: dict, + engine_path: pathlib.Path, + remove: bool = False, + force: bool = False) -> int: + # If the o3de_manifest.json 'engines' key is list containing dictionary entries, transform it to a list of strings + engine_list = json_data.get('engines', []) + + def transform_engine_dict_to_string(engine): return engine.get('path', '') if isinstance(engine, dict) else engine + json_data['engines'] = list(map(transform_engine_dict_to_string, engine_list)) + + result = register_o3de_object_path(json_data, engine_path, 'engines', 'engine.json', + validation.valid_o3de_engine_json, remove) + if result != 0: + return result + + if remove: + return remove_engine_name_to_path(json_data, engine_path) + + return add_engine_name_to_path(json_data, engine_path, force) + + def register_external_subdirectory(json_data: dict, external_subdir_path: pathlib.Path, remove: bool = False, @@ -677,37 +661,30 @@ def remove_invalid_o3de_projects(manifest_path: pathlib.Path = None) -> int: return result def remove_invalid_o3de_objects() -> None: - json_data = manifest.load_o3de_manifest() - - for engine_object in json_data.get('engines', []): - engine_path = engine_object.get('path', '') + for engine_path in manifest.get_engines(): if not validation.valid_o3de_engine_json(pathlib.Path(engine_path).resolve() / 'engine.json'): logger.warn(f"Engine path {engine_path} is invalid.") register(engine_path=engine_path, remove=True) remove_invalid_o3de_projects() - for gem in json_data.get('gems', []): - if not validation.valid_o3de_gem_json(pathlib.Path(gem).resolve() / 'gem.json'): - logger.warn(f"Gem path {gem} is invalid.") - register(gem_path=gem, remove=True) - - for external in json_data.get('external_subdirectories', []): + for external in manifest.get_external_subdirectories(): external = pathlib.Path(external).resolve() if not external.is_dir(): logger.warn(f"External subdirectory {external} is invalid.") register(engine_path=engine_path, external_subdir_path=external, remove=True) - for template in json_data.get('templates', []): + for template in manifest.get_templates(): if not validation.valid_o3de_template_json(pathlib.Path(template).resolve() / 'template.json'): logger.warn(f"Template path {template} is invalid.") register(template_path=template, remove=True) - for restricted in json_data.get('restricted', []): + for restricted in manifest.get_restricted(): if not validation.valid_o3de_restricted_json(pathlib.Path(restricted).resolve() / 'restricted.json'): logger.warn(f"Restricted path {restricted} is invalid.") register(restricted_path=restricted, remove=True) + json_data = manifest.load_o3de_manifest() default_engines_folder = pathlib.Path(json_data.get('default_engines_folder', manifest.get_o3de_engines_folder())).resolve() if not default_engines_folder.is_dir(): new_default_engines_folder = manifest.get_o3de_folder() / 'Engines' diff --git a/scripts/o3de/tests/CMakeLists.txt b/scripts/o3de/tests/CMakeLists.txt index 00d0774c45..1cd6eac7ee 100644 --- a/scripts/o3de/tests/CMakeLists.txt +++ b/scripts/o3de/tests/CMakeLists.txt @@ -65,4 +65,11 @@ ly_add_pytest( PATH ${CMAKE_CURRENT_LIST_DIR}/unit_test_engine_template.py TEST_SUITE smoke EXCLUDE_TEST_RUN_TARGET_FROM_IDE -) \ No newline at end of file +) + +ly_add_pytest( + NAME o3de_register_show + PATH ${CMAKE_CURRENT_LIST_DIR}/unit_test_print_registration.py + TEST_SUITE smoke + EXCLUDE_TEST_RUN_TARGET_FROM_IDE +) diff --git a/scripts/o3de/tests/unit_test_print_registration.py b/scripts/o3de/tests/unit_test_print_registration.py new file mode 100644 index 0000000000..7e3e75edcd --- /dev/null +++ b/scripts/o3de/tests/unit_test_print_registration.py @@ -0,0 +1,383 @@ +# +# 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 +# +# + +import argparse +import json +import logging +import pytest +import pathlib +from unittest.mock import patch + +from o3de import print_registration + + +TEST_PROJECT_JSON_PAYLOAD = ''' +{ + "project_name": "MinimalProject", + "origin": "The primary repo for MinimalProject goes here: i.e. http://www.mydomain.com", + "license": "What license MinimalProject uses goes here: i.e. https://opensource.org/licenses/MIT", + "display_name": "MinimalProject", + "summary": "A short description of MinimalProject.", + "canonical_tags": [ + "Project" + ], + "user_tags": [ + "MinimalProject" + ], + "icon_path": "preview.png", + "engine": "o3de-install", + "external_subdirectories": [ + "D:/TestGem" + ] +} +''' + +TEST_ENGINE_JSON_PAYLOAD = ''' +{ + "engine_name": "o3de", + "restricted_name": "o3de", + "FileVersion": 1, + "O3DEVersion": "0.0.0.0", + "O3DECopyrightYear": 2021, + "O3DEBuildNumber": 0, + "external_subdirectories": [ + "Gems/TestGem2" + ], + "projects": [ + ], + "templates": [ + "Templates/MinimalProject" + ] +} +''' + +TEST_GEM_JSON_PAYLOAD = ''' +{ + "gem_name": "TestGem", + "display_name": "TestGem", + "license": "What license TestGem uses goes here: i.e. https://opensource.org/licenses/MIT", + "origin": "The primary repo for TestGem goes here: i.e. http://www.mydomain.com", + "type": "Code", + "summary": "A short description of TestGem.", + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "TestGem" + ], + "icon_path": "preview.png", + "requirements": "" +} +''' + +TEST_TEMPLATE_JSON_PAYLOAD = ''' +{ + "template_name": "AssetGem", + "origin": "The primary repo for AssetGem goes here: i.e. http://www.mydomain.com", + "license": "What license AssetGem uses goes here: i.e. https://opensource.org/licenses/MIT", + "display_name": "AssetGem", + "summary": "A short description of AssetGem template.", + "canonical_tags": [], + "user_tags": [ + "AssetGem" + ], + "icon_path": "preview.png", + "copyFiles": [ + { + "file": "CMakeLists.txt", + "origin": "CMakeLists.txt", + "isTemplated": true, + "isOptional": false + }, + { + "file": "gem.json", + "origin": "gem.json", + "isTemplated": true, + "isOptional": false + }, + { + "file": "preview.png", + "origin": "preview.png", + "isTemplated": false, + "isOptional": false + } + ], + "createDirectories": [ + { + "dir": "Assets", + "origin": "Assets" + } + ] +} +''' + +TEST_RESTRICTED_JSON_PAYLOAD = ''' +{ + "restricted_name": "o3de" +} +''' + +TEST_O3DE_MANIFEST_JSON_PAYLOAD = ''' +{ + "o3de_manifest_name": "testuser", + "origin": "C:/Users/testuser/.o3de", + "default_engines_folder": "C:/Users/testuser/.o3de/Engines", + "default_projects_folder": "C:/Users/testuser/.o3de/Projects", + "default_gems_folder": "C:/Users/testuser/.o3de/Gems", + "default_templates_folder": "C:/Users/testuser/.o3de/Templates", + "default_restricted_folder": "C:/Users/testuser/.o3de/Restricted", + "default_third_party_folder": "C:/Users/testuser/.o3de/3rdParty", + "projects": [ + "D:/MinimalProject" + ], + "external_subdirectories": [], + "templates": [], + "restricted": [], + "repos": [], + "engines": [ + "D:/o3de/o3de" + ], + "engines_path": { + "o3de": "D:/o3de/o3de" + } +} +''' + +class TestPrintRegistration: + @staticmethod + def load_manifest_json(): + return json.loads(TEST_O3DE_MANIFEST_JSON_PAYLOAD) + + @staticmethod + def get_engine_json_data(engine_path: pathlib.Path = None): + return json.loads(TEST_ENGINE_JSON_PAYLOAD) + + @staticmethod + def get_project_json_data(project_path: pathlib.Path = None): + return json.loads(TEST_PROJECT_JSON_PAYLOAD) + + @staticmethod + def get_gem_json_data(gem_path: pathlib.Path = None): + return json.loads(TEST_GEM_JSON_PAYLOAD) + + @staticmethod + def get_template_json_data(template_path: pathlib.Path = None): + return json.loads(TEST_TEMPLATE_JSON_PAYLOAD) + + @staticmethod + def get_restricted_json_data(restricted_path: pathlib.Path = None): + return json.loads(TEST_RESTRICTED_JSON_PAYLOAD) + + @pytest.mark.parametrize("project_path, verbose", [ + pytest.param(None, 0), + pytest.param(None, 1), + pytest.param(pathlib.Path("D:/MinimalProject"), 0), + pytest.param(pathlib.Path("D:/MinimalProject"), 1) + ]) + def test_print_registration_no_option(self, project_path, verbose): + parser = argparse.ArgumentParser() + + # Register the registration script subparsers with the current argument parser + print_registration.add_parser_args(parser) + arg_list = [] + if project_path: + arg_list += ['--project-path', project_path.as_posix()] + if verbose: + arg_list += ['-' + 'v' * verbose] + test_args = parser.parse_args(arg_list) + + with patch('o3de.manifest.load_o3de_manifest', side_effect=self.load_manifest_json) as load_manifest_patch, \ + patch('o3de.manifest.get_engine_json_data', + side_effect=self.get_engine_json_data) as get_engine_json_data_patch, \ + patch('o3de.manifest.get_project_json_data', + side_effect=self.get_project_json_data) as get_project_json_patch, \ + patch('o3de.manifest.get_gem_json_data', side_effect=self.get_gem_json_data) as get_gem_json_patch, \ + patch('o3de.manifest.get_template_json_data', side_effect=self.get_template_json_data) as get_template_json_patch, \ + patch('o3de.manifest.get_restricted_json_data', side_effect=self.get_restricted_json_data) as get_json_patch: + result = print_registration._run_register_show(test_args) + assert result == 0 + + + @pytest.mark.parametrize("engine_arg_option, verbose", [ + pytest.param("--this-engine", 0), + pytest.param("--this-engine", 1), + pytest.param("--engines", 0), + pytest.param("--engines", 1) + ]) + def test_print_engine_registration(self, engine_arg_option, verbose): + parser = argparse.ArgumentParser() + + # Register the registration script subparsers with the current argument parser + print_registration.add_parser_args(parser) + arg_list = [engine_arg_option] + if verbose: + arg_list += ['-' + 'v' * verbose] + test_args = parser.parse_args(arg_list) + + + with patch('o3de.manifest.load_o3de_manifest', side_effect=self.load_manifest_json) as load_manifest_patch, \ + patch('o3de.manifest.get_engine_json_data', side_effect=self.get_engine_json_data) as get_engine_json_data_patch: + result = print_registration._run_register_show(test_args) + assert result == 0 + + + @pytest.mark.parametrize("arg_option, verbose", [ + pytest.param("--projects", 0), + pytest.param("--projects", 1), + pytest.param("--engine-projects", 0), + pytest.param("--engine-projects", 1), + pytest.param("--all-projects", 0), + pytest.param("--all-projects", 1) + ]) + def test_print_project_registration(self, arg_option, verbose): + parser = argparse.ArgumentParser() + + # Register the registration script subparsers with the current argument parser + print_registration.add_parser_args(parser) + arg_list = [arg_option] + if verbose: + arg_list += ['-' + 'v' * verbose] + test_args = parser.parse_args(arg_list) + + + with patch('o3de.manifest.load_o3de_manifest', side_effect=self.load_manifest_json) as load_manifest_patch, \ + patch('o3de.manifest.get_project_json_data', side_effect=self.get_project_json_data) as get_json_data_patch: + result = print_registration._run_register_show(test_args) + assert result == 0 + + + @pytest.mark.parametrize("arg_option, project_path, verbose", [ + pytest.param("--gems", None, 0), + pytest.param("--gems", None, 1), + pytest.param("--engine-gems", None, 0), + pytest.param("--engine-gems", None, 1), + pytest.param("--project-gems", pathlib.Path("D:/MinimalProject"), 0), + pytest.param("--project-gems", pathlib.Path("D:/MinimalProject"), 1), + pytest.param("--all-gems", pathlib.Path("D:/MinimalProject"), 0), + pytest.param("--all-gems", None, 0), + pytest.param("--all-gems", pathlib.Path("D:/MinimalProject"), 1), + pytest.param("--all-gems", None, 1) + ]) + def test_print_gem_registration(self, arg_option, project_path, verbose): + parser = argparse.ArgumentParser() + + # Register the registration script subparsers with the current argument parser + print_registration.add_parser_args(parser) + arg_list = [arg_option] + if project_path: + arg_list += ['--project-path', project_path.as_posix()] + if verbose: + arg_list += ['-' + 'v' * verbose] + test_args = parser.parse_args(arg_list) + + # Patch the manifest.py function to locate gem.json files in external subdirectories + # to just return a fake path to a single test gem + def get_gems_from_subdirectories(external_subdirs: list) -> list: + return ["D:/TestGem"] + + with patch('o3de.manifest.load_o3de_manifest', side_effect=self.load_manifest_json) as load_manifest_patch, \ + patch('o3de.manifest.get_gem_json_data', side_effect=self.get_gem_json_data) as get_json_patch, \ + patch('o3de.manifest.get_project_json_data', side_effect=self.get_project_json_data) as get_project_json_patch, \ + patch('o3de.manifest.get_gems_from_subdirectories', side_effect=get_gems_from_subdirectories) as get_gems_from_subdirs_patch, \ + patch('o3de.print_registration.get_project_path', return_value=project_path) as get_project_path_patch: + result = print_registration._run_register_show(test_args) + assert result == 0 + + + @pytest.mark.parametrize("arg_option, project_path, verbose", [ + pytest.param("--templates", None, 0), + pytest.param("--templates", None, 1), + pytest.param("--engine-templates", None, 0), + pytest.param("--engine-templates", None, 1), + pytest.param("--project-templates", pathlib.Path("D:/MinimalProject"), 0), + pytest.param("--project-templates", pathlib.Path("D:/MinimalProject"), 1), + pytest.param("--all-templates", pathlib.Path("D:/MinimalProject"), 0), + pytest.param("--all-templates", None, 0), + pytest.param("--all-templates", pathlib.Path("D:/MinimalProject"), 1), + pytest.param("--all-templates", None, 1) + ]) + def test_print_template_registration(self, arg_option, project_path, verbose): + parser = argparse.ArgumentParser() + + # Register the registration script subparsers with the current argument parser + print_registration.add_parser_args(parser) + arg_list = [arg_option] + if project_path: + arg_list += ['--project-path', project_path.as_posix()] + if verbose: + arg_list += ['-' + 'v' * verbose] + test_args = parser.parse_args(arg_list) + + + with patch('o3de.manifest.load_o3de_manifest', side_effect=self.load_manifest_json) as load_manifest_patch, \ + patch('o3de.manifest.get_template_json_data', side_effect=self.get_template_json_data) as get_json_patch, \ + patch('o3de.manifest.get_project_json_data', side_effect=self.get_project_json_data) as get_project_json_patch, \ + patch('o3de.print_registration.get_project_path', return_value=project_path) as get_project_path_patch: + result = print_registration._run_register_show(test_args) + assert result == 0 + + + @pytest.mark.parametrize("arg_option, project_path, verbose", [ + pytest.param("--restricted", None, 0), + pytest.param("--restricted", None, 1), + pytest.param("--engine-restricted", None, 0), + pytest.param("--engine-restricted", None, 1), + pytest.param("--project-restricted", pathlib.Path("D:/MinimalProject"), 0), + pytest.param("--project-restricted", pathlib.Path("D:/MinimalProject"), 1), + pytest.param("--all-restricted", pathlib.Path("D:/MinimalProject"), 0), + pytest.param("--all-restricted", None, 0), + pytest.param("--all-restricted", pathlib.Path("D:/MinimalProject"), 1), + pytest.param("--all-restricted", None, 1) + ]) + def test_print_restricted_registration(self, arg_option, project_path, verbose): + parser = argparse.ArgumentParser() + + # Register the registration script subparsers with the current argument parser + print_registration.add_parser_args(parser) + arg_list = [arg_option] + if project_path: + arg_list += ['--project-path', project_path.as_posix()] + if verbose: + arg_list += ['-' + 'v' * verbose] + test_args = parser.parse_args(arg_list) + + with patch('o3de.manifest.load_o3de_manifest', side_effect=self.load_manifest_json) as load_manifest_patch, \ + patch('o3de.manifest.get_restricted_json_data', side_effect=self.get_restricted_json_data) as get_json_patch, \ + patch('o3de.manifest.get_project_json_data', side_effect=self.get_project_json_data) as get_project_json_patch, \ + patch('o3de.print_registration.get_project_path', return_value=project_path) as get_project_path_patch: + result = print_registration._run_register_show(test_args) + assert result == 0 + + + # Setting --verbose with the --*external-subdirectories option doesn't result in any additional output + # So it is only parameterized as 0 + @pytest.mark.parametrize("arg_option, project_path, verbose", [ + pytest.param("--external-subdirectories", None, 0), + pytest.param("--engine-external-subdirectories", None, 0), + pytest.param("--project-external-subdirectories", pathlib.Path("D:/MinimalProject"), 0), + pytest.param("--all-external-subdirectories", pathlib.Path("D:/MinimalProject"), 0), + pytest.param("--all-external-subdirectories", None, 0), + ]) + def test_print_external_subdirectories_registration(self, arg_option, project_path, verbose): + parser = argparse.ArgumentParser() + + # Register the registration script subparsers with the current argument parser + print_registration.add_parser_args(parser) + arg_list = [arg_option] + if project_path: + arg_list += ['--project-path', project_path.as_posix()] + if verbose: + arg_list += ['-' + 'v' * verbose] + test_args = parser.parse_args(arg_list) + + with patch('o3de.manifest.load_o3de_manifest', side_effect=self.load_manifest_json) as load_manifest_patch, \ + patch('o3de.manifest.get_project_json_data', + side_effect=self.get_project_json_data) as get_project_json_patch, \ + patch('o3de.print_registration.get_project_path', return_value=project_path) as get_project_path_patch: + result = print_registration._run_register_show(test_args) + assert result == 0