From 7dd7e82d861b9c9db96391c7af619b15e6ab6a6d Mon Sep 17 00:00:00 2001 From: Eric Phister <52085794+amzn-phist@users.noreply.github.com> Date: Wed, 28 Apr 2021 18:54:02 -0500 Subject: [PATCH] LYN-2723: Fixes issues with bad project or engine paths (#369) * Setup NativeUIRequests as an AZ::Interface. Adds a NativeUISystemComponent to AzFramework Application. * Renames NativeUISystemComponent (class) to NativeUISystem, since it's no longer a Component. * Minor update to SettingsRegistryInterface::Remove doc-comments for accuracy. * Fixes to make an early fatal shutdown of Editor occur without crash. * LYN-2723: Updates startup to handle errors: engine root is empty, no valid project.json found (mismatched engine name), or bad project path (launch project picker dialog). * LYN-2723: Minor formatting/spelling edits. * LYN-2723: Moves ParseCommandLine from ComponentApplication to SettingsRegistryMergeUtils so it can be used in more places. * Misc fixes. 'wait_for_connect' setting wasn't being properly applied to AP connection settings. Fix infinite loop in CCmdLine::Next. * LYN-2723: Addresses review feedback. * LYN-2723: Reverts some changes that caused a unit test to fail. * LYN-2723: Reverts one more change that was unnecessary. --- Code/Framework/AzCore/AzCore/AzCoreModule.cpp | 2 - .../AzCore/Component/ComponentApplication.cpp | 76 +++++----- .../AzCore/Component/ComponentApplication.h | 3 - .../AzCore/AzCore/NativeUI/NativeUIRequests.h | 84 +++++------ .../NativeUI/NativeUISystemComponent.cpp | 63 ++------- .../AzCore/NativeUI/NativeUISystemComponent.h | 54 +++----- .../AzCore/AzCore/Settings/SettingsRegistry.h | 2 +- .../Settings/SettingsRegistryMergeUtils.cpp | 131 +++++++++++++++--- .../Settings/SettingsRegistryMergeUtils.h | 6 + .../NativeUISystemComponent_Android.cpp | 2 +- .../NativeUISystemComponent_Unimplemented.cpp | 13 +- .../NativeUI/NativeUISystemComponent_Mac.mm | 2 +- .../NativeUISystemComponent_Windows.cpp | 2 +- .../NativeUI/NativeUISystemComponent_iOS.mm | 2 +- .../AzFramework/Application/Application.cpp | 17 ++- .../AzFramework/Application/Application.h | 2 + .../Asset/AssetSystemComponentHelper.cpp | 2 +- .../ProjectManager/ProjectManager.cpp | 14 +- .../Application/ToolsApplication.cpp | 4 + Code/Sandbox/Editor/CryEdit.cpp | 28 +++- Code/Sandbox/Editor/main.cpp | 11 +- Code/Tools/RC/ResourceCompiler/main.cpp | 2 + 22 files changed, 289 insertions(+), 233 deletions(-) diff --git a/Code/Framework/AzCore/AzCore/AzCoreModule.cpp b/Code/Framework/AzCore/AzCore/AzCoreModule.cpp index 3c93ae5561..3d074455cc 100644 --- a/Code/Framework/AzCore/AzCore/AzCoreModule.cpp +++ b/Code/Framework/AzCore/AzCore/AzCoreModule.cpp @@ -19,7 +19,6 @@ #include #include #include -#include #include #include #include @@ -43,7 +42,6 @@ namespace AZ AssetManagerComponent::CreateDescriptor(), UserSettingsComponent::CreateDescriptor(), Debug::FrameProfilerComponent::CreateDescriptor(), - NativeUI::NativeUISystemComponent::CreateDescriptor(), SliceComponent::CreateDescriptor(), SliceSystemComponent::CreateDescriptor(), SliceMetadataInfoComponent::CreateDescriptor(), diff --git a/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp b/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp index c55f565615..d0f277a6b8 100644 --- a/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp +++ b/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp @@ -28,6 +28,8 @@ #include #include +#include + #include #include #include @@ -424,7 +426,7 @@ namespace AZ // Now that the Allocators are initialized, the Command Line parameters can be parsed m_commandLine.Parse(m_argC, m_argV); - ParseCommandLine(m_commandLine); + SettingsRegistryMergeUtils::ParseCommandLine(m_commandLine); // Create the settings registry and register it with the AZ interface system // This is done after the AppRoot has been calculated so that the Bootstrap.cfg @@ -527,10 +529,42 @@ namespace AZ DestroyAllocator(); } + + void ReportBadEngineRoot() + { + AZStd::string errorMessage = {"Unable to determine a valid path to the engine.\n" + "Check parameters such as --project-path and --engine-path and make sure they are valid.\n"}; + if (auto registry = AZ::SettingsRegistry::Get(); registry != nullptr) + { + AZ::SettingsRegistryInterface::FixedValueString filePathErrorStr; + if (registry->Get(filePathErrorStr, AZ::SettingsRegistryMergeUtils::FilePathKey_ErrorText); !filePathErrorStr.empty()) + { + errorMessage += "Additional Info:\n"; + errorMessage += filePathErrorStr.c_str(); + } + } + + if (auto nativeUI = AZ::Interface::Get(); nativeUI != nullptr) + { + nativeUI->DisplayOkDialog("O3DE Fatal Error", errorMessage.c_str(), false); + } + else + { + AZ_Error("ComponentApplication", false, "O3DE Fatal Error: %s\n", errorMessage.c_str()); + } + } + + Entity* ComponentApplication::Create(const Descriptor& descriptor, const StartupParameters& startupParameters) { AZ_Assert(!m_isStarted, "Component application already started!"); + if (m_engineRoot.empty()) + { + ReportBadEngineRoot(); + return nullptr; + } + m_startupParameters = startupParameters; m_descriptor = descriptor; @@ -871,46 +905,6 @@ namespace AZ } } - void ComponentApplication::ParseCommandLine(const AZ::CommandLine& commandLine) - { - struct OptionKeyToRegsetKey - { - AZStd::string_view m_optionKey; - AZStd::string m_regsetKey; - }; - - // Provide overrides for the engine root, the project root and the project cache root - AZStd::array commandOptions = { - OptionKeyToRegsetKey{ "engine-path", AZStd::string::format("%s/engine_path", AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) }, - OptionKeyToRegsetKey{ "project-path", AZStd::string::format("%s/project_path", AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) }, - OptionKeyToRegsetKey{ "project-cache-path", AZStd::string::format("%s/project_cache_path", AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) } - }; - - AZStd::fixed_vector overrideArgs; - - for (auto&& [optionKey, regsetKey] : commandOptions) - { - if (size_t optionCount = commandLine.GetNumSwitchValues(optionKey); optionCount > 0) - { - // Use the last supplied command option value to override previous values - auto overrideArg = AZStd::string::format(R"(--regset="%s=%s")", regsetKey.c_str(), - commandLine.GetSwitchValue(optionKey, optionCount - 1).c_str()); - overrideArgs.emplace_back(AZStd::move(overrideArg)); - } - } - - if (!overrideArgs.empty()) - { - // Dump the input command line, add the additional option overrides - // and Parse the new command line into the Component Application command line - AZ::CommandLine::ParamContainer commandLineArgs; - commandLine.Dump(commandLineArgs); - commandLineArgs.insert(commandLineArgs.end(), AZStd::make_move_iterator(overrideArgs.begin()), - AZStd::make_move_iterator(overrideArgs.end())); - m_commandLine.Parse(commandLineArgs); - } - } - void ComponentApplication::MergeSettingsToRegistry(SettingsRegistryInterface& registry) { SettingsRegistryInterface::Specializations specializations; diff --git a/Code/Framework/AzCore/AzCore/Component/ComponentApplication.h b/Code/Framework/AzCore/AzCore/Component/ComponentApplication.h index 3ebcf39d95..8617aa5f2e 100644 --- a/Code/Framework/AzCore/AzCore/Component/ComponentApplication.h +++ b/Code/Framework/AzCore/AzCore/Component/ComponentApplication.h @@ -328,9 +328,6 @@ namespace AZ /// Create the drillers void CreateDrillers(); - /// Parse ComponentApplication specific command line arguments - void ParseCommandLine(const AZ::CommandLine& commandLine); - virtual void MergeSettingsToRegistry(SettingsRegistryInterface& registry); //! Sets the specializations that will be used when loading the Settings Registry. Extend this in derived diff --git a/Code/Framework/AzCore/AzCore/NativeUI/NativeUIRequests.h b/Code/Framework/AzCore/AzCore/NativeUI/NativeUIRequests.h index 5055540874..f45295d221 100644 --- a/Code/Framework/AzCore/AzCore/NativeUI/NativeUIRequests.h +++ b/Code/Framework/AzCore/AzCore/NativeUI/NativeUIRequests.h @@ -15,45 +15,49 @@ #include #include -namespace AZ +namespace AZ::NativeUI { - namespace NativeUI + enum AssertAction { - enum AssertAction - { - IGNORE_ASSERT = 0, - IGNORE_ALL_ASSERTS, - BREAK, - NONE, - }; - - class NativeUIRequests - : public AZ::EBusTraits - { - public: - ////////////////////////////////////////////////////////////////////////// - // EBusTraits overrides - static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; - static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; - using MutexType = AZStd::recursive_mutex; - - // Waits for user to select an option before execution continues - // Returns the option string selected by the user - virtual AZStd::string DisplayBlockingDialog(const AZStd::string& /*title*/, const AZStd::string& /*message*/, const AZStd::vector& /*options*/) const { return ""; }; - - // Waits for user to select an option ('Ok' or optionally 'Cancel') before execution continues - // Returns the option string selected by the user - virtual AZStd::string DisplayOkDialog(const AZStd::string& /*title*/, const AZStd::string& /*message*/, bool /*showCancel*/) const { return ""; }; - - // Waits for user to select an option ('Yes', 'No' or optionally 'Cancel') before execution continues - // Returns the option string selected by the user - virtual AZStd::string DisplayYesNoDialog(const AZStd::string& /*title*/, const AZStd::string& /*message*/, bool /*showCancel*/) const { return ""; }; - - // Displays an assert dialog box - // Returns the action selected by the user - virtual AssertAction DisplayAssertDialog(const AZStd::string& /*message*/) const { return AssertAction::NONE; }; - }; - - using NativeUIRequestBus = AZ::EBus; - } -} + IGNORE_ASSERT = 0, + IGNORE_ALL_ASSERTS, + BREAK, + NONE, + }; + + class NativeUIRequests + { + public: + AZ_RTTI(NativeUIRequests, "{48361EE6-C1E7-4965-A13A-7425B2691817}"); + virtual ~NativeUIRequests() = default; + + // Waits for user to select an option before execution continues + // Returns the option string selected by the user + virtual AZStd::string DisplayBlockingDialog(const AZStd::string& /*title*/, const AZStd::string& /*message*/, const AZStd::vector& /*options*/) const { return ""; }; + + // Waits for user to select an option ('Ok' or optionally 'Cancel') before execution continues + // Returns the option string selected by the user + virtual AZStd::string DisplayOkDialog(const AZStd::string& /*title*/, const AZStd::string& /*message*/, bool /*showCancel*/) const { return ""; }; + + // Waits for user to select an option ('Yes', 'No' or optionally 'Cancel') before execution continues + // Returns the option string selected by the user + virtual AZStd::string DisplayYesNoDialog(const AZStd::string& /*title*/, const AZStd::string& /*message*/, bool /*showCancel*/) const { return ""; }; + + // Displays an assert dialog box + // Returns the action selected by the user + virtual AssertAction DisplayAssertDialog(const AZStd::string& /*message*/) const { return AssertAction::NONE; }; + }; + + class NativeUIEBusTraits + : public AZ::EBusTraits + { + public: + ////////////////////////////////////////////////////////////////////////// + // EBusTraits overrides + static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; + static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; + using MutexType = AZStd::recursive_mutex; + }; + + using NativeUIRequestBus = AZ::EBus; +} // namespace AZ::NativeUI diff --git a/Code/Framework/AzCore/AzCore/NativeUI/NativeUISystemComponent.cpp b/Code/Framework/AzCore/AzCore/NativeUI/NativeUISystemComponent.cpp index cf0c2872f1..bed066018a 100644 --- a/Code/Framework/AzCore/AzCore/NativeUI/NativeUISystemComponent.cpp +++ b/Code/Framework/AzCore/AzCore/NativeUI/NativeUISystemComponent.cpp @@ -15,50 +15,19 @@ #include -namespace AZ +namespace AZ::NativeUI { - using namespace AZ::NativeUI; - - void NativeUISystemComponent::Reflect(AZ::ReflectContext* context) - { - if (AZ::SerializeContext* serialize = azrtti_cast(context)) - { - serialize->Class() - ->Version(0) - ; - - if (AZ::EditContext* ec = serialize->GetEditContext()) - { - ec->Class("NativeUI", "Adds basic support for native (platform specific) UI dialog boxes") - ->ClassElement(AZ::Edit::ClassElements::EditorData, "") - ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("System", 0xc94d118b)) - ->Attribute(AZ::Edit::Attributes::AutoExpand, true) - ; - } - } - } - - void NativeUISystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided) - { - provided.push_back(AZ_CRC("NativeUIService", 0x8ec25f87)); - } - - void NativeUISystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible) - { - incompatible.push_back(AZ_CRC("NativeUIService", 0x8ec25f87)); - } - - void NativeUISystemComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required) + NativeUISystem::NativeUISystem() { - (void)required; + NativeUIRequestBus::Handler::BusConnect(); } - void NativeUISystemComponent::GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent) + NativeUISystem::~NativeUISystem() { - (void)dependent; + NativeUIRequestBus::Handler::BusDisconnect(); } - AssertAction NativeUISystemComponent::DisplayAssertDialog(const AZStd::string& message) const + AssertAction NativeUISystem::DisplayAssertDialog(const AZStd::string& message) const { static const char* buttonNames[3] = { "Ignore", "Ignore All", "Break" }; AZStd::vector options; @@ -80,7 +49,7 @@ namespace AZ return AssertAction::NONE; } - AZStd::string NativeUISystemComponent::DisplayOkDialog(const AZStd::string& title, const AZStd::string& message, bool showCancel) const + AZStd::string NativeUISystem::DisplayOkDialog(const AZStd::string& title, const AZStd::string& message, bool showCancel) const { AZStd::vector options; @@ -93,7 +62,7 @@ namespace AZ return DisplayBlockingDialog(title, message, options); } - AZStd::string NativeUISystemComponent::DisplayYesNoDialog(const AZStd::string& title, const AZStd::string& message, bool showCancel) const + AZStd::string NativeUISystem::DisplayYesNoDialog(const AZStd::string& title, const AZStd::string& message, bool showCancel) const { AZStd::vector options; @@ -106,18 +75,4 @@ namespace AZ return DisplayBlockingDialog(title, message, options); } - - void NativeUISystemComponent::Init() - { - } - - void NativeUISystemComponent::Activate() - { - NativeUIRequestBus::Handler::BusConnect(); - } - - void NativeUISystemComponent::Deactivate() - { - NativeUIRequestBus::Handler::BusDisconnect(); - } -} +} // namespace AZ::NativeUI diff --git a/Code/Framework/AzCore/AzCore/NativeUI/NativeUISystemComponent.h b/Code/Framework/AzCore/AzCore/NativeUI/NativeUISystemComponent.h index 2d5cc36ffc..771b9a2af9 100644 --- a/Code/Framework/AzCore/AzCore/NativeUI/NativeUISystemComponent.h +++ b/Code/Framework/AzCore/AzCore/NativeUI/NativeUISystemComponent.h @@ -15,40 +15,24 @@ #include #include -namespace AZ +namespace AZ::NativeUI { - namespace NativeUI + class NativeUISystem + : public NativeUIRequestBus::Handler { - class NativeUISystemComponent - : public AZ::Component - , public NativeUIRequestBus::Handler - { - public: - AZ_COMPONENT(NativeUISystemComponent, "{E996C058-4AFE-4C8C-816F-98D864D8576D}"); - - static void Reflect(AZ::ReflectContext* context); - - static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided); - static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible); - static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required); - static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent); - - //////////////////////////////////////////////////////////////////////// - // NativeUIRequestBus interface implementation - AZStd::string DisplayBlockingDialog(const AZStd::string& title, const AZStd::string& message, const AZStd::vector& options) const override; - AZStd::string DisplayOkDialog(const AZStd::string& title, const AZStd::string& message, bool showCancel) const override; - AZStd::string DisplayYesNoDialog(const AZStd::string& title, const AZStd::string& message, bool showCancel) const override; - AssertAction DisplayAssertDialog(const AZStd::string& message) const override; - //////////////////////////////////////////////////////////////////////// - - protected: - - //////////////////////////////////////////////////////////////////////// - // AZ::Component interface implementation - void Init() override; - void Activate() override; - void Deactivate() override; - //////////////////////////////////////////////////////////////////////// - }; - } -} + public: + AZ_RTTI(NativeUISystem, "{FF534B2C-11BE-4DEA-A5B7-A4FA96FE1EDE}", NativeUIRequests); + AZ_CLASS_ALLOCATOR(NativeUISystem, AZ::OSAllocator, 0); + + NativeUISystem(); + ~NativeUISystem() override; + + //////////////////////////////////////////////////////////////////////// + // NativeUIRequestBus interface implementation + AZStd::string DisplayBlockingDialog(const AZStd::string& title, const AZStd::string& message, const AZStd::vector& options) const override; + AZStd::string DisplayOkDialog(const AZStd::string& title, const AZStd::string& message, bool showCancel) const override; + AZStd::string DisplayYesNoDialog(const AZStd::string& title, const AZStd::string& message, bool showCancel) const override; + AssertAction DisplayAssertDialog(const AZStd::string& message) const override; + //////////////////////////////////////////////////////////////////////// + }; +} // namespace AZ::NativeUI diff --git a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistry.h b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistry.h index 58cfa0d049..768841cc09 100644 --- a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistry.h +++ b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistry.h @@ -256,7 +256,7 @@ namespace AZ //! Remove the value at the provided path //! @param path The path to a value that should be removed - //! @return Whether or not the value was stored at the provided path. An invalid path will return false; + //! @return Whether or not the path was found and removed. An invalid path will return false; virtual bool Remove(AZStd::string_view path) = 0; //! Structure which contains configuration settings for how to parse a single command line argument diff --git a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.cpp b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.cpp index 27f6f222dd..ed56803ef2 100644 --- a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.cpp +++ b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.cpp @@ -32,17 +32,12 @@ namespace AZ::Internal { AZ::SettingsRegistryInterface::FixedValueString GetEngineMonikerForProject( - SettingsRegistryInterface& settingsRegistry, const AZ::IO::FixedMaxPath& projectPath) + SettingsRegistryInterface& settingsRegistry, const AZ::IO::FixedMaxPath& projectJsonPath) { // projectPath needs to be an absolute path here. using namespace AZ::SettingsRegistryMergeUtils; - bool projectJsonMerged = false; - auto projectJsonPath = projectPath / "project.json"; - if (AZ::IO::SystemFile::Exists(projectJsonPath.c_str())) - { - projectJsonMerged = settingsRegistry.MergeSettingsFile( - projectJsonPath.Native(), AZ::SettingsRegistryInterface::Format::JsonMergePatch, ProjectSettingsRootKey); - } + bool projectJsonMerged = settingsRegistry.MergeSettingsFile( + projectJsonPath.Native(), AZ::SettingsRegistryInterface::Format::JsonMergePatch, ProjectSettingsRootKey); AZ::SettingsRegistryInterface::FixedValueString engineMoniker; if (projectJsonMerged) @@ -105,12 +100,12 @@ namespace AZ::Internal const auto engineMonikerKey = AZ::SettingsRegistryInterface::FixedValueString::format("%s/engine_name", EngineSettingsRootKey); + AZStd::set projectPathsNotFound; + for (EngineInfo& engineInfo : pathVisitor.m_enginePaths) { - AZ::IO::FixedMaxPath engineSettingsPath{engineInfo.m_path}; - engineSettingsPath /= "engine.json"; - - if (AZ::IO::SystemFile::Exists(engineSettingsPath.c_str())) + if (auto engineSettingsPath = AZ::IO::FixedMaxPath{engineInfo.m_path} / "engine.json"; + AZ::IO::SystemFile::Exists(engineSettingsPath.c_str())) { if (settingsRegistry.MergeSettingsFile( engineSettingsPath.Native(), AZ::SettingsRegistryInterface::Format::JsonMergePatch, EngineSettingsRootKey)) @@ -119,12 +114,61 @@ namespace AZ::Internal } } - auto engineMoniker = Internal::GetEngineMonikerForProject(settingsRegistry, engineInfo.m_path / projectPath); - if (!engineMoniker.empty() && engineMoniker == engineInfo.m_moniker) + if (auto projectJsonPath = (engineInfo.m_path / projectPath / "project.json").LexicallyNormal(); + AZ::IO::SystemFile::Exists(projectJsonPath.c_str())) { - engineRoot = engineInfo.m_path; - break; + if (auto engineMoniker = Internal::GetEngineMonikerForProject(settingsRegistry, projectJsonPath); + !engineMoniker.empty() && engineMoniker == engineInfo.m_moniker) + { + engineRoot = engineInfo.m_path; + break; + } + } + else + { + projectPathsNotFound.insert(projectJsonPath); } + + // Continue looking for candidates, remove the previous engine and project settings that were merged above. + settingsRegistry.Remove(ProjectSettingsRootKey); + settingsRegistry.Remove(EngineSettingsRootKey); + } + + if (engineRoot.empty()) + { + AZStd::string errorStr; + if (!projectPathsNotFound.empty()) + { + // This case is usually encountered when a project path is given as a relative path, + // which is assumed to be relative to an engine root. + // When no project.json files are found this way, dump this error message about + // which project paths were checked. + AZStd::string projectPathsTested; + for (const auto& path : projectPathsNotFound) + { + projectPathsTested.append(AZStd::string::format(" %s\n", path.c_str())); + } + errorStr = AZStd::string::format("No valid project was found at these locations:\n%s" + "Please supply a valid --project-path to the application.", + projectPathsTested.c_str()); + } + else + { + // The other case is that a project.json was found, but after checking all the registered engines + // none of them matched the engine moniker. + AZStd::string enginePathsChecked; + for (const auto& engineInfo : pathVisitor.m_enginePaths) + { + enginePathsChecked.append(AZStd::string::format(" %s (%s)\n", engineInfo.m_path.c_str(), engineInfo.m_moniker.c_str())); + } + errorStr = AZStd::string::format( + "No engine was found in o3de_manifest.json with a name that matches the one set in the project.json.\n" + "Engines that were checked:\n%s" + "Please check that your engine and project have both been registered with scripts/o3de.py.", enginePathsChecked.c_str() + ); + } + + settingsRegistry.Set(FilePathKey_ErrorText, errorStr.c_str()); } } @@ -158,7 +202,7 @@ namespace AZ::Internal return {}; } - void InjectSettingToCommandLineFront(AZ::SettingsRegistryInterface& settingsRegistry, + void InjectSettingToCommandLineBack(AZ::SettingsRegistryInterface& settingsRegistry, AZStd::string_view path, AZStd::string_view value) { AZ::CommandLine commandLine; @@ -168,7 +212,7 @@ namespace AZ::Internal auto projectPathOverride = AZStd::string::format(R"(--regset="%.*s=%.*s")", aznumeric_cast(path.size()), path.data(), aznumeric_cast(value.size()), value.data()); - paramContainer.emplace(paramContainer.begin(), AZStd::move(projectPathOverride)); + paramContainer.emplace(paramContainer.end(), AZStd::move(projectPathOverride)); commandLine.Parse(paramContainer); AZ::SettingsRegistryMergeUtils::StoreCommandLineToRegistry(settingsRegistry, commandLine); } @@ -197,8 +241,8 @@ namespace AZ::SettingsRegistryMergeUtils if (!engineRoot.empty()) { settingsRegistry.Set(engineRootKey, engineRoot.Native()); - // Inject the engine root into the front of the command line settings - Internal::InjectSettingToCommandLineFront(settingsRegistry, engineRootKey, engineRoot.Native()); + // Inject the engine root at the end of the command line settings + Internal::InjectSettingToCommandLineBack(settingsRegistry, engineRootKey, engineRoot.Native()); return engineRoot; } } @@ -244,8 +288,8 @@ namespace AZ::SettingsRegistryMergeUtils if (!projectRoot.empty()) { settingsRegistry.Set(projectRootKey, projectRoot.c_str()); - // Inject the project root into the front of the command line settings - Internal::InjectSettingToCommandLineFront(settingsRegistry, projectRootKey, projectRoot.Native()); + // Inject the project root at the end of the command line settings + Internal::InjectSettingToCommandLineBack(settingsRegistry, projectRootKey, projectRoot.Native()); return projectRoot; } } @@ -875,6 +919,49 @@ namespace AZ::SettingsRegistryMergeUtils return true; } + void ParseCommandLine(AZ::CommandLine& commandLine) + { + struct OptionKeyToRegsetKey + { + AZStd::string_view m_optionKey; + AZStd::string m_regsetKey; + }; + + // Provide overrides for the engine root, the project root and the project cache root + AZStd::array commandOptions = { + OptionKeyToRegsetKey{ + "engine-path", AZStd::string::format("%s/engine_path", AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey)}, + OptionKeyToRegsetKey{ + "project-path", AZStd::string::format("%s/project_path", AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey)}, + OptionKeyToRegsetKey{ + "project-cache-path", + AZStd::string::format("%s/project_cache_path", AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey)}}; + + AZStd::fixed_vector overrideArgs; + + for (auto&& [optionKey, regsetKey] : commandOptions) + { + if (size_t optionCount = commandLine.GetNumSwitchValues(optionKey); optionCount > 0) + { + // Use the last supplied command option value to override previous values + auto overrideArg = AZStd::string::format( + R"(--regset="%s=%s")", regsetKey.c_str(), commandLine.GetSwitchValue(optionKey, optionCount - 1).c_str()); + overrideArgs.emplace_back(AZStd::move(overrideArg)); + } + } + + if (!overrideArgs.empty()) + { + // Dump the input command line, add the additional option overrides + // and Parse the new command line args (write back) into the input command line. + AZ::CommandLine::ParamContainer commandLineArgs; + commandLine.Dump(commandLineArgs); + commandLineArgs.insert( + commandLineArgs.end(), AZStd::make_move_iterator(overrideArgs.begin()), AZStd::make_move_iterator(overrideArgs.end())); + commandLine.Parse(commandLineArgs); + } + } + bool DumpSettingsRegistryToStream(SettingsRegistryInterface& registry, AZStd::string_view key, AZ::IO::GenericStream& stream, const DumperSettings& dumperSettings) { diff --git a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.h b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.h index 10b3c2f18b..4e00c0e6ec 100644 --- a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.h +++ b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.h @@ -55,6 +55,9 @@ namespace AZ::SettingsRegistryMergeUtils //! Development write storage path may be considered temporary or cache storage on some platforms inline static constexpr char FilePathKey_DevWriteStorage[] = "/Amazon/AzCore/Runtime/FilePaths/DevWriteStorage"; + //! Stores error text regarding engine boot sequence when engine and project roots cannot be determined + inline static constexpr char FilePathKey_ErrorText[] = "/Amazon/AzCore/Runtime/FilePaths/ErrorText"; + //! Root key for where command line are stored at within the settings registry inline static constexpr char CommandLineRootKey[] = "/Amazon/AzCore/Runtime/CommandLine"; //! Key set to trigger a notification that the CommandLine has been stored within the settings registry @@ -219,6 +222,9 @@ namespace AZ::SettingsRegistryMergeUtils //! into the AZ::CommandLine instance bool GetCommandLineFromRegistry(SettingsRegistryInterface& registry, AZ::CommandLine& commandLine); + //! Parse a CommandLine and transform certain options into formal "regset" options + void ParseCommandLine(AZ::CommandLine& commandLine); + //! Structure for configuring how values should be dumped from the Settings Registry struct DumperSettings { diff --git a/Code/Framework/AzCore/Platform/Android/AzCore/NativeUI/NativeUISystemComponent_Android.cpp b/Code/Framework/AzCore/Platform/Android/AzCore/NativeUI/NativeUISystemComponent_Android.cpp index b9f7e87b4b..b43044f0b3 100644 --- a/Code/Framework/AzCore/Platform/Android/AzCore/NativeUI/NativeUISystemComponent_Android.cpp +++ b/Code/Framework/AzCore/Platform/Android/AzCore/NativeUI/NativeUISystemComponent_Android.cpp @@ -22,7 +22,7 @@ namespace AZ { namespace NativeUI { - AZStd::string NativeUISystemComponent::DisplayBlockingDialog(const AZStd::string& title, const AZStd::string& message, const AZStd::vector& options) const + AZStd::string NativeUISystem::DisplayBlockingDialog(const AZStd::string& title, const AZStd::string& message, const AZStd::vector& options) const { AZ::Android::JNI::Object object("com/amazon/lumberyard/NativeUI/LumberyardNativeUI"); object.RegisterStaticMethod("DisplayDialog", "(Landroid/app/Activity;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;)V"); diff --git a/Code/Framework/AzCore/Platform/Common/Unimplemented/AzCore/NativeUI/NativeUISystemComponent_Unimplemented.cpp b/Code/Framework/AzCore/Platform/Common/Unimplemented/AzCore/NativeUI/NativeUISystemComponent_Unimplemented.cpp index 6a24415ec5..ceba9d2701 100644 --- a/Code/Framework/AzCore/Platform/Common/Unimplemented/AzCore/NativeUI/NativeUISystemComponent_Unimplemented.cpp +++ b/Code/Framework/AzCore/Platform/Common/Unimplemented/AzCore/NativeUI/NativeUISystemComponent_Unimplemented.cpp @@ -12,16 +12,11 @@ #include -namespace AZ +namespace AZ::NativeUI { - namespace NativeUI + AZStd::string NativeUISystem::DisplayBlockingDialog([[maybe_unused]] const AZStd::string& title, [[maybe_unused]] const AZStd::string& message, + [[maybe_unused]] const AZStd::vector& options) const { - AZStd::string NativeUISystemComponent::DisplayBlockingDialog(const AZStd::string& title, const AZStd::string& message, const AZStd::vector& options) const - { - AZ_UNUSED(title); - AZ_UNUSED(message); - AZ_UNUSED(options); - return ""; - } + return {}; } } diff --git a/Code/Framework/AzCore/Platform/Mac/AzCore/NativeUI/NativeUISystemComponent_Mac.mm b/Code/Framework/AzCore/Platform/Mac/AzCore/NativeUI/NativeUISystemComponent_Mac.mm index 4af67a1d54..801adaf4ea 100644 --- a/Code/Framework/AzCore/Platform/Mac/AzCore/NativeUI/NativeUISystemComponent_Mac.mm +++ b/Code/Framework/AzCore/Platform/Mac/AzCore/NativeUI/NativeUISystemComponent_Mac.mm @@ -26,7 +26,7 @@ namespace AZ { namespace NativeUI { - AZStd::string NativeUISystemComponent::DisplayBlockingDialog(const AZStd::string& title, const AZStd::string& message, const AZStd::vector& options) const + AZStd::string NativeUISystem::DisplayBlockingDialog(const AZStd::string& title, const AZStd::string& message, const AZStd::vector& options) const { __block NSModalResponse response = -1; diff --git a/Code/Framework/AzCore/Platform/Windows/AzCore/NativeUI/NativeUISystemComponent_Windows.cpp b/Code/Framework/AzCore/Platform/Windows/AzCore/NativeUI/NativeUISystemComponent_Windows.cpp index a5dd65512f..f88cea5313 100644 --- a/Code/Framework/AzCore/Platform/Windows/AzCore/NativeUI/NativeUISystemComponent_Windows.cpp +++ b/Code/Framework/AzCore/Platform/Windows/AzCore/NativeUI/NativeUISystemComponent_Windows.cpp @@ -245,7 +245,7 @@ namespace AZ { namespace NativeUI { - AZStd::string NativeUISystemComponent::DisplayBlockingDialog(const AZStd::string& title, const AZStd::string& message, const AZStd::vector& options) const + AZStd::string NativeUISystem::DisplayBlockingDialog(const AZStd::string& title, const AZStd::string& message, const AZStd::vector& options) const { if (options.size() >= MAX_ITEMS) { diff --git a/Code/Framework/AzCore/Platform/iOS/AzCore/NativeUI/NativeUISystemComponent_iOS.mm b/Code/Framework/AzCore/Platform/iOS/AzCore/NativeUI/NativeUISystemComponent_iOS.mm index 835a7fb5e7..62f07f7483 100644 --- a/Code/Framework/AzCore/Platform/iOS/AzCore/NativeUI/NativeUISystemComponent_iOS.mm +++ b/Code/Framework/AzCore/Platform/iOS/AzCore/NativeUI/NativeUISystemComponent_iOS.mm @@ -18,7 +18,7 @@ namespace AZ { namespace NativeUI { - AZStd::string NativeUISystemComponent::DisplayBlockingDialog(const AZStd::string& title, const AZStd::string& message, const AZStd::vector& options) const + AZStd::string NativeUISystem::DisplayBlockingDialog(const AZStd::string& title, const AZStd::string& message, const AZStd::vector& options) const { __block AZStd::string userSelection = ""; diff --git a/Code/Framework/AzFramework/AzFramework/Application/Application.cpp b/Code/Framework/AzFramework/AzFramework/Application/Application.cpp index 4b75a3d1cf..ba313812ce 100644 --- a/Code/Framework/AzFramework/AzFramework/Application/Application.cpp +++ b/Code/Framework/AzFramework/AzFramework/Application/Application.cpp @@ -175,7 +175,7 @@ namespace AzFramework } // Initializes the IArchive for reading archive(.pak) files - if (auto archive = AZ::Interface::Get(); !archive) + if (auto archive = AZ::Interface::Get(); archive == nullptr) { m_archive = AZStd::make_unique(); AZ::Interface::Register(m_archive.get()); @@ -189,6 +189,12 @@ namespace AzFramework SetFileIOAliases(); } + if (auto nativeUI = AZ::Interface::Get(); nativeUI == nullptr) + { + m_nativeUI = AZStd::make_unique(); + AZ::Interface::Register(m_nativeUI.get()); + } + ApplicationRequests::Bus::Handler::BusConnect(); AZ::UserSettingsFileLocatorBus::Handler::BusConnect(); NetSystemRequestBus::Handler::BusConnect(); @@ -205,12 +211,17 @@ namespace AzFramework AZ::UserSettingsFileLocatorBus::Handler::BusDisconnect(); ApplicationRequests::Bus::Handler::BusDisconnect(); + if (AZ::Interface::Get() == m_nativeUI.get()) + { + AZ::Interface::Unregister(m_nativeUI.get()); + } + m_nativeUI.reset(); + // Unset the Archive file IO if it is set as the direct instance if (AZ::IO::FileIOBase::GetInstance() == m_archiveFileIO.get()) { AZ::IO::FileIOBase::SetInstance(nullptr); } - m_archiveFileIO.reset(); // Destroy the IArchive instance @@ -303,7 +314,6 @@ namespace AzFramework azrtti_typeid(), azrtti_typeid(), azrtti_typeid(), - azrtti_typeid(), azrtti_typeid(), azrtti_typeid(), @@ -372,7 +382,6 @@ namespace AzFramework azrtti_typeid(), azrtti_typeid(), azrtti_typeid(), - azrtti_typeid(), azrtti_typeid(), azrtti_typeid(), diff --git a/Code/Framework/AzFramework/AzFramework/Application/Application.h b/Code/Framework/AzFramework/AzFramework/Application/Application.h index fdc0ddcfb7..4d7e45a423 100644 --- a/Code/Framework/AzFramework/AzFramework/Application/Application.h +++ b/Code/Framework/AzFramework/AzFramework/Application/Application.h @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -187,6 +188,7 @@ namespace AzFramework AZStd::unique_ptr m_archiveFileIO; ///> The Default file IO instance is a ArchiveFileIO. AZStd::unique_ptr m_archive; ///> The AZ::IO::Instance AZStd::unique_ptr m_pimpl; + AZStd::unique_ptr m_nativeUI; bool m_ownsConsole = false; bool m_exitMainLoopRequested = false; diff --git a/Code/Framework/AzFramework/AzFramework/Asset/AssetSystemComponentHelper.cpp b/Code/Framework/AzFramework/AzFramework/Asset/AssetSystemComponentHelper.cpp index 618fd92ac1..727d5051fa 100644 --- a/Code/Framework/AzFramework/AzFramework/Asset/AssetSystemComponentHelper.cpp +++ b/Code/Framework/AzFramework/AzFramework/Asset/AssetSystemComponentHelper.cpp @@ -220,7 +220,7 @@ namespace AzFramework { // Read the wait for connection boolean from the Settings Registry AZ::s64 waitForConnect64{}; - if (!AZ::SettingsRegistryMergeUtils::PlatformGet(*settingsRegistry, waitForConnect64, AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey, AzFramework::AssetSystem::WaitForConnect)) + if (AZ::SettingsRegistryMergeUtils::PlatformGet(*settingsRegistry, waitForConnect64, AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey, AzFramework::AssetSystem::WaitForConnect)) { outputConnectionSettings.m_waitForConnect = waitForConnect64 != 0; } diff --git a/Code/Framework/AzFramework/AzFramework/ProjectManager/ProjectManager.cpp b/Code/Framework/AzFramework/AzFramework/ProjectManager/ProjectManager.cpp index 7e968ffcd8..ea8fd75cfd 100644 --- a/Code/Framework/AzFramework/AzFramework/ProjectManager/ProjectManager.cpp +++ b/Code/Framework/AzFramework/AzFramework/ProjectManager/ProjectManager.cpp @@ -41,9 +41,10 @@ namespace AzFramework::ProjectManager // at the end of the function AZ::CommandLine commandLine; commandLine.Parse(argc, argv); - AZ::SettingsRegistryImpl settingsRegistry; - // Store the Command line to the Setting Registry + AZ::SettingsRegistryMergeUtils::ParseCommandLine(commandLine); + // Store the Command line to the Setting Registry + AZ::SettingsRegistryImpl settingsRegistry; AZ::SettingsRegistryMergeUtils::StoreCommandLineToRegistry(settingsRegistry, commandLine); AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_Bootstrap(settingsRegistry); AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_O3deUserRegistry(settingsRegistry, AZ_TRAIT_OS_PLATFORM_CODENAME, {}); @@ -68,7 +69,14 @@ namespace AzFramework::ProjectManager // If we were able to locate a path to a project, we're done if (!projectRootPath.empty()) { - return ProjectPathCheckResult::ProjectPathFound; + AZ::IO::FixedMaxPath projectJsonPath = engineRootPath / projectRootPath / "project.json"; + if (AZ::IO::SystemFile::Exists(projectJsonPath.c_str())) + { + return ProjectPathCheckResult::ProjectPathFound; + } + AZ_TracePrintf( + "ProjectManager", "Did not find a project file at location '%s', launching the Project Manager...", + projectJsonPath.c_str()); } if (LaunchProjectManager(engineRootPath)) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Application/ToolsApplication.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Application/ToolsApplication.cpp index 50315e9d7a..ed7fb1515f 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Application/ToolsApplication.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Application/ToolsApplication.cpp @@ -284,6 +284,10 @@ namespace AzToolsFramework void ToolsApplication::Start(const Descriptor& descriptor, const StartupParameters& startupParameters/* = StartupParameters()*/) { Application::Start(descriptor, startupParameters); + if (!m_isStarted) + { + return; + } m_editorEntityManager.Start(); diff --git a/Code/Sandbox/Editor/CryEdit.cpp b/Code/Sandbox/Editor/CryEdit.cpp index b3eaaddc42..68b7a8a2a2 100644 --- a/Code/Sandbox/Editor/CryEdit.cpp +++ b/Code/Sandbox/Editor/CryEdit.cpp @@ -5045,12 +5045,28 @@ extern "C" #pragma comment(lib, "Shell32.lib") #endif +struct CryAllocatorsRAII +{ + CryAllocatorsRAII() + { + AZ_Assert(!AZ::AllocatorInstance::IsReady(), "Expected allocator to not be initialized, hunt down the static that is initializing it"); + AZ_Assert(!AZ::AllocatorInstance::IsReady(), "Expected allocator to not be initialized, hunt down the static that is initializing it"); + + AZ::AllocatorInstance::Create(); + AZ::AllocatorInstance::Create(); + } + + ~CryAllocatorsRAII() + { + AZ::AllocatorInstance::Destroy(); + AZ::AllocatorInstance::Destroy(); + } +}; + + extern "C" int AZ_DLL_EXPORT CryEditMain(int argc, char* argv[]) { - AZ_Assert(!AZ::AllocatorInstance::IsReady(), "Expected allocator to not be initialized, hunt down the static that is initializing it"); - AZ::AllocatorInstance::Create(); - AZ_Assert(!AZ::AllocatorInstance::IsReady(), "Expected allocator to not be initialized, hunt down the static that is initializing it"); - AZ::AllocatorInstance::Create(); + CryAllocatorsRAII cryAllocatorsRAII; // ensure the EditorEventsBus context gets created inside EditorLib [[maybe_unused]] const auto& editorEventsContext = AzToolsFramework::EditorEvents::Bus::GetOrCreateContext(); @@ -5058,7 +5074,7 @@ extern "C" int AZ_DLL_EXPORT CryEditMain(int argc, char* argv[]) // connect relevant buses to global settings gSettings.Connect(); - CCryEditApp* theApp = new CCryEditApp(); + auto theApp = AZStd::make_unique(); // this does some magic to set the current directory... { QCoreApplication app(argc, argv); @@ -5145,8 +5161,6 @@ extern "C" int AZ_DLL_EXPORT CryEditMain(int argc, char* argv[]) } - delete theApp; - gSettings.Disconnect(); return ret; diff --git a/Code/Sandbox/Editor/main.cpp b/Code/Sandbox/Editor/main.cpp index 013dc4ff69..17c177e9d3 100644 --- a/Code/Sandbox/Editor/main.cpp +++ b/Code/Sandbox/Editor/main.cpp @@ -30,17 +30,14 @@ int main(int argc, char* argv[]) [[maybe_unused]] const bool loaded = handle->Load(true); AZ_Assert(loaded, "EditorLib could not be loaded"); + int ret = 1; if (auto fn = handle->GetFunction(CryEditMainName); fn != nullptr) { - const int ret = AZStd::invoke(fn, argc, argv); - - AZ::AllocatorInstance::Destroy(); - AZ::Environment::Detach(); - - return ret; + ret = AZStd::invoke(fn, argc, argv); } + handle = {}; AZ::AllocatorInstance::Destroy(); AZ::Environment::Detach(); - return 1; + return ret; } diff --git a/Code/Tools/RC/ResourceCompiler/main.cpp b/Code/Tools/RC/ResourceCompiler/main.cpp index 02dbd406d8..67a585aaa4 100644 --- a/Code/Tools/RC/ResourceCompiler/main.cpp +++ b/Code/Tools/RC/ResourceCompiler/main.cpp @@ -579,6 +579,8 @@ int rcmain(int argc, char** argv, [[maybe_unused]] char** envp) // on the command line AZ::CommandLine commandLine; commandLine.Parse(argc, argv); + AZ::SettingsRegistryMergeUtils::ParseCommandLine(commandLine); + AZ::SettingsRegistryImpl settingsRegistry; AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_Bootstrap(settingsRegistry); AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_O3deUserRegistry(settingsRegistry, AZ_TRAIT_OS_PLATFORM_CODENAME, {});