More specific component error messaging and modes for native UI to prevent blocking dialog in some applications

This commit is contained in:
mgwynn
2021-06-23 21:05:17 -04:00
parent e357dea06c
commit fb3940fa31
16 changed files with 186 additions and 37 deletions
@@ -19,6 +19,7 @@
#include <AzCore/Component/TransformBus.h>
#include <AzCore/Component/NamedEntityId.h>
#include <AzCore/Interface/Interface.h>
#include <AzCore/NativeUI/NativeUIRequests.h>
#include <AzCore/Casting/lossy_cast.h>
#include <AzCore/Serialization/Json/RegistrationContext.h>
@@ -932,13 +933,39 @@ namespace AZ
return candidateInfo;
}
static constexpr AZStd::string_view GetExtendedDependencySortFailureMessage(const Entity::DependencySortResult code)
{
switch (code)
{
case Entity::DependencySortResult::MissingRequiredService:
return {
"One or more components that provide required services are not in the list of components to activate.\n"
"This can often happen when an AZ::Module containing the required service wasn't loaded, check the log for details.\n"
"\n"
"This can also be caused by misconfigured services on the component or related components.\n"
"Check that the ccomponent's service functions ('GetProvidedServices', 'GetIncompatibleServices' etc) are accurate.\n"};
case Entity::DependencySortResult::HasIncompatibleServices:
return {
"A component is incompatible with a service provided by another component.\n"
"Check that the component's service functions ('GetProvidedServices', 'GetIncompatibleServices' etc) are accurate.\n"};
case Entity::DependencySortResult::DescriptorNotRegistered:
return { "A component descriptor was not registered with the ComponentApplication.\n"
"Make sure the component's descriptor is registered by adding it to the appropriate\n"
"AZ::Module's m_descriptors list." };
default:
return {};
}
}
// Shortcut for returning a FailedSortDetails as an AZ::Failure.
static FailureValue<Entity::FailedSortDetails> FailureCode(Entity::DependencySortResult code, const char* formatMessage, ...)
{
va_list args;
va_start(args, formatMessage);
return Failure(Entity::FailedSortDetails{ code, AZStd::string::format_arg(formatMessage, args) });
auto failure = Failure(Entity::FailedSortDetails{ code, AZStd::string::format_arg(formatMessage, args),
GetExtendedDependencySortFailureMessage(code) });
va_end(args);
return failure;
}
// Function that creates a nice error message when incompatible components are found.
@@ -1071,7 +1098,7 @@ namespace AZ
ComponentDescriptorBus::EventResult(componentDescriptor, azrtti_typeid(component), &ComponentDescriptorBus::Events::GetDescriptor);
if (!componentDescriptor)
{
return FailureCode(DependencySortResult::MissingDescriptor, "No descriptor found for Component class '%s'.", component->RTTI_GetTypeName());
return FailureCode(DependencySortResult::DescriptorNotRegistered, "No descriptor registered for Component class '%s'.", component->RTTI_GetTypeName());
}
componentInfos.push_back();
@@ -78,7 +78,6 @@ namespace AZ
HasCyclicDependency, ///< A cycle in component service dependencies was detected.
HasIncompatibleServices, ///< A component is incompatible with a service provided by another component.
DescriptorNotRegistered, ///< A component descriptor was not registered with the AZ::ComponentApplication.
MissingDescriptor, ///< Cannot find a component's ComponentDescriptor
// Deprecated values
DSR_OK = Success,
@@ -320,6 +319,7 @@ namespace AZ
{
DependencySortResult m_code;
AZStd::string m_message;
AZStd::string m_extendedMessage;
};
using DependencySortOutcome = AZ::Outcome<void, FailedSortDetails>;
@@ -21,6 +21,7 @@
#include <AzCore/Component/Entity.h>
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/Component/ComponentApplication.h>
#include <AzCore/NativeUI/NativeUIRequests.h>
#include <AzCore/Script/ScriptSystemBus.h>
#include <AzCore/Script/ScriptContext.h>
@@ -29,7 +30,7 @@
namespace
{
static const char* s_moduleLoggingScope = "Module";
static const char* s_moduleLoggingScope = "Module Manager";
}
namespace AZ
@@ -601,6 +602,25 @@ namespace AZ
return {};
}
//=========================================================================
// HandleDependencySortError
//=========================================================================
void ModuleManager::HandleDependencySortError(const Entity::DependencySortOutcome& outcome)
{
// Print a short message to the log, and an extended message to the nativeUI (if available)
auto errorMessage = AZStd::string::format("Modules Entities cannot be activated.\n\n%s", outcome.GetError().m_message.c_str());
AZ_Error(s_moduleLoggingScope, false, errorMessage.c_str());
auto nativeUI = AZ::Interface<AZ::NativeUI::NativeUIRequests>::Get();
if (nativeUI)
{
errorMessage.append("\n\n");
errorMessage.append(outcome.GetError().m_extendedMessage);
auto choice = nativeUI->DisplayBlockingDialog(s_moduleLoggingScope, errorMessage, { "Quit", "Ignore" });
m_quitRequested = (choice == "Quit");
}
}
//=========================================================================
// OnEntityActivated
//=========================================================================
@@ -687,15 +707,24 @@ namespace AZ
const Entity::ComponentArrayType& systemEntityComponents = systemEntity->GetComponents();
componentsToActivate.insert(componentsToActivate.begin(), systemEntityComponents.begin(), systemEntityComponents.end());
}
// Topo sort components, activate them
Entity::DependencySortOutcome outcome = ModuleEntity::DependencySort(componentsToActivate);
if (!outcome.IsSuccess())
{
AZ_Error(s_moduleLoggingScope, false, "Modules Entities cannot be activated. %s", outcome.GetError().m_message.c_str());
HandleDependencySortError(outcome);
if (m_quitRequested)
{
// Before letting the application quit, all the module entities should be restored back to init state
// because they never fully exited the activating state.
for (auto& moduleData : modulesToInit)
{
moduleData->m_moduleEntity->SetState(Entity::State::Init);
}
}
return;
}
for (auto componentIt = componentsToActivate.begin(); componentIt != componentsToActivate.end(); )
{
Component* component = *componentIt;
@@ -711,7 +740,6 @@ namespace AZ
++componentIt;
}
}
// Activate the entities in the appropriate order
for (Component* component : componentsToActivate)
@@ -133,6 +133,9 @@ namespace AZ
// Get the split list of system component tags specified at startup
const AZStd::vector<Crc32>& GetSystemComponentTags() { return m_systemComponentTags; }
// Whether the user wants to quit the Application on errors rather than proceeding in a likely bad state
bool m_quitRequested = false;
protected:
////////////////////////////////////////////////////////////////////////
// ModuleManagerRequestBus
@@ -148,7 +151,10 @@ namespace AZ
//! @return shared ptr to an ModuleData structure if the module is loaded and managed by the ModuleManager
AZStd::shared_ptr<ModuleDataImpl> GetLoadedModule(AZStd::string_view modulePath);
////////////////////////////////////////////////////////////////////////
//! On dependency sort errors, display error message with details.
//! Additionally send the message to NativeUI (if available) and ask user what to do,
void HandleDependencySortError(const Entity::DependencySortOutcome& outcome);
////////////////////////////////////////////////////////////////////////
// EntityBus
@@ -17,7 +17,7 @@
namespace AZ::NativeUI
{
enum AssertAction
enum class AssertAction
{
IGNORE_ASSERT = 0,
IGNORE_ALL_ASSERTS,
@@ -25,27 +25,60 @@ namespace AZ::NativeUI
NONE,
};
enum class Mode
{
CONSOLE = 0,
UI,
};
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<AZStd::string>& /*options*/) const { return ""; };
//! Waits for user to select an option before execution continues
//! Returns the option string selected by the user
virtual AZStd::string DisplayBlockingDialog(
[[maybe_unused]] const AZStd::string&,
[[maybe_unused]] const AZStd::string&,
[[maybe_unused]] const AZStd::vector<AZStd::string>&) 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 ('Ok' or optionally 'Cancel') before execution continues
//! Returns the option string selected by the user
virtual AZStd::string DisplayOkDialog(
[[maybe_unused]] const AZStd::string&,
[[maybe_unused]] const AZStd::string&,
[[maybe_unused]] 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 ""; };
//! 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(
[[maybe_unused]] const AZStd::string&,
[[maybe_unused]] const AZStd::string&,
[[maybe_unused]] 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; };
//! Displays an assert dialog box
//! Returns the action selected by the user
virtual AssertAction DisplayAssertDialog([[maybe_unused]] const AZStd::string&) const { return AssertAction::NONE; }
//! Set the operation mode of the native UI systen
void SetMode(NativeUI::Mode mode)
{
m_mode = mode;
}
protected:
NativeUI::Mode m_mode = NativeUI::Mode::CONSOLE;
};
class NativeUIEBusTraits
@@ -29,6 +29,11 @@ namespace AZ::NativeUI
AssertAction NativeUISystem::DisplayAssertDialog(const AZStd::string& message) const
{
if (m_mode == NativeUI::Mode::CONSOLE)
{
return AssertAction::NONE;
}
static const char* buttonNames[3] = { "Ignore", "Ignore All", "Break" };
AZStd::vector<AZStd::string> options;
options.push_back(buttonNames[0]);
@@ -36,8 +41,8 @@ namespace AZ::NativeUI
options.push_back(buttonNames[1]);
#endif
options.push_back(buttonNames[2]);
AZStd::string result;
result = DisplayBlockingDialog("Assert Failed!", message, options);
AZStd::string result = DisplayBlockingDialog("Assert Failed!", message, options);
if (result.compare(buttonNames[0]) == 0)
return AssertAction::IGNORE_ASSERT;
@@ -51,9 +56,13 @@ namespace AZ::NativeUI
AZStd::string NativeUISystem::DisplayOkDialog(const AZStd::string& title, const AZStd::string& message, bool showCancel) const
{
AZStd::vector<AZStd::string> options;
if (m_mode == NativeUI::Mode::CONSOLE)
{
return {};
}
AZStd::vector<AZStd::string> options{ "OK" };
options.push_back("OK");
if (showCancel)
{
options.push_back("Cancel");
@@ -64,10 +73,13 @@ namespace AZ::NativeUI
AZStd::string NativeUISystem::DisplayYesNoDialog(const AZStd::string& title, const AZStd::string& message, bool showCancel) const
{
AZStd::vector<AZStd::string> options;
if (m_mode == NativeUI::Mode::CONSOLE)
{
return {};
}
AZStd::vector<AZStd::string> options{ "Yes", "No" };
options.push_back("Yes");
options.push_back("No");
if (showCancel)
{
options.push_back("Cancel");
@@ -24,6 +24,11 @@ namespace AZ
{
AZStd::string NativeUISystem::DisplayBlockingDialog(const AZStd::string& title, const AZStd::string& message, const AZStd::vector<AZStd::string>& options) const
{
if (m_mode == NativeUI::Mode::CONSOLE)
{
return {};
}
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");
object.RegisterStaticMethod("GetUserSelection", "()Ljava/lang/String;");
@@ -28,6 +28,11 @@ namespace AZ
{
AZStd::string NativeUISystem::DisplayBlockingDialog(const AZStd::string& title, const AZStd::string& message, const AZStd::vector<AZStd::string>& options) const
{
if (m_mode == NativeUI::Mode::CONSOLE)
{
return {};
}
__block NSModalResponse response = -1;
auto showDialog = ^()
@@ -247,6 +247,11 @@ namespace AZ
{
AZStd::string NativeUISystem::DisplayBlockingDialog(const AZStd::string& title, const AZStd::string& message, const AZStd::vector<AZStd::string>& options) const
{
if (m_mode == NativeUI::Mode::CONSOLE)
{
return {};
}
if (options.size() >= MAX_ITEMS)
{
AZ_Assert(false, "Cannot create dialog box with more than %d buttons", (MAX_ITEMS - 1));
@@ -20,6 +20,11 @@ namespace AZ
{
AZStd::string NativeUISystem::DisplayBlockingDialog(const AZStd::string& title, const AZStd::string& message, const AZStd::vector<AZStd::string>& options) const
{
if (m_mode == NativeUI::Mode::CONSOLE)
{
return {};
}
__block AZStd::string userSelection = "";
NSString* nsTitle = [NSString stringWithUTF8String:title.c_str()];
+2 -2
View File
@@ -909,14 +909,14 @@ namespace UnitTest
EXPECT_EQ(2, m_entity->GetComponents().size());
}
TEST_F(ComponentDependency, ComponentWithoutDescriptor_FailsDueToMissingDescriptor)
TEST_F(ComponentDependency, ComponentWithoutDescriptor_FailsDueToUnregisteredDescriptor)
{
CreateComponents_ABCDE();
// delete ComponentB's descriptor
ComponentDescriptorBus::Event(azrtti_typeid<ComponentB>(), &ComponentDescriptorBus::Events::ReleaseDescriptor);
EXPECT_EQ(Entity::DependencySortResult::MissingDescriptor, m_entity->EvaluateDependencies());
EXPECT_EQ(Entity::DependencySortResult::DescriptorNotRegistered, m_entity->EvaluateDependencies());
}
TEST_F(ComponentDependency, StableSort_GetsSameResultsEveryTime)
@@ -260,7 +260,7 @@ namespace AzFramework
systemEntity->Activate();
AZ_Assert(systemEntity->GetState() == AZ::Entity::State::Active, "System Entity failed to activate.");
m_isStarted = true;
m_isStarted = (systemEntity->GetState() == AZ::Entity::State::Active);
}
void Application::PreModuleLoad()
+13 -2
View File
@@ -557,7 +557,6 @@ public:
{
bool dummy;
QCommandLineParser parser;
QString appRootOverride;
parser.addHelpOption();
parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions);
parser.setApplicationDescription(QObject::tr("Open 3D Engine"));
@@ -643,7 +642,7 @@ public:
option.second = parser.value(option.first.valueName);
}
m_bExport = m_bExport | m_bExportTexture;
m_bExport = m_bExport || m_bExportTexture;
const QStringList positionalArgs = parser.positionalArguments();
@@ -4362,6 +4361,18 @@ extern "C" int AZ_DLL_EXPORT CryEditMain(int argc, char* argv[])
{
EditorInternal::EditorToolsApplication AZToolsApp(&argc, &argv);
{
CEditCommandLineInfo cmdInfo;
if (!cmdInfo.m_bAutotestMode && !cmdInfo.m_bConsoleMode && !cmdInfo.m_bExport && !cmdInfo.m_bExportTexture &&
!cmdInfo.m_bNullRenderer && !cmdInfo.m_bMatEditMode && !cmdInfo.m_bTest)
{
if (auto nativeUI = AZ::Interface<AZ::NativeUI::NativeUIRequests>::Get(); nativeUI != nullptr)
{
nativeUI->SetMode(AZ::NativeUI::Mode::UI);
}
}
}
// The settings registry has been created by the AZ::ComponentApplication constructor at this point
AZ::SettingsRegistryInterface& registry = *AZ::SettingsRegistry::Get();
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddBuildSystemTargetSpecialization(
@@ -91,10 +91,12 @@ namespace EditorInternal
void EditorToolsApplication::StartCommon(AZ::Entity* systemEntity)
{
AzToolsFramework::ToolsApplication::StartCommon(systemEntity);
m_StartupAborted = m_moduleManager->m_quitRequested;
if (systemEntity->GetState() != AZ::Entity::State::Active)
{
m_StartupAborted = true;
return;
}
}
@@ -106,6 +108,7 @@ namespace EditorInternal
AzToolsFramework::ToolsApplication::Start({}, params);
if (IsStartupAborted() || !m_systemEntity)
{
AzToolsFramework::ToolsApplication::Stop();
return false;
}
return true;
+2 -1
View File
@@ -190,12 +190,12 @@ namespace GraphCanvas
void GraphCanvasSystemComponent::Init()
{
RegisterAssetHandler();
m_translationDatabase.Init();
}
void GraphCanvasSystemComponent::Activate()
{
RegisterAssetHandler();
RegisterTranslationBuilder();
AzFramework::AssetCatalogEventBus::Handler::BusConnect();
@@ -233,6 +233,7 @@ namespace GraphCanvas
GraphCanvasRequestBus::Handler::BusDisconnect();
AZ::Data::AssetBus::MultiHandler::BusDisconnect();
m_translationAssetWorker.Deactivate();
UnregisterAssetHandler();
}
@@ -48,6 +48,14 @@ namespace GraphCanvas
}
}
void TranslationAssetWorker::Deactivate()
{
if (AZ::Data::AssetManager::Instance().GetHandler(AZ::Data::AssetType{ azrtti_typeid<TranslationAsset>() }))
{
AZ::Data::AssetManager::Instance().UnregisterHandler(m_assetHandler.get());
}
}
void TranslationAssetWorker::ShutDown()
{
m_isShuttingDown = true;