Added a deferred queue to the AZ Console class (#3298)
* Added a deferred queue to the AZ Console class An AZ Console instance will now store any console commands that could be dispatched from a configuration file into a deferred queue, that can be invoked later. This can be used to defer execution of console commands in configuration files such as .cfg, .setreg and .setregpatch files that are defined in gem modules that have not been loaded yet. The defered execution can then be invoked at any point later in the application Updated the Component Application CreateCommon function to invoke deferred console commands after all the gems have loaded fixes #2062 Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Fixed variable shadowing in the Console Deferred Command Test Updated commit for the ClearDeferredQueue function to just mention clearing the queue Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Filtered out execution of the ConsoleRootCommandKey as a console command The AZ::Console notification handler is tracking changes to the fields of "/Amazon/AzCore/Runtime/ConsoleCommands" and it's children. Now the "/Amazon/AzCore/Runtime/ConsoleCommands" field is the ConsoleRootCommandKey and not an actual console command so it shouldn't attempt to be invoked Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Moved the execution of deferred console commands after linking deferred functors Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Moved the execution of deferred console commands into CreateModuleClass hook Any module that loads using the ModuleManager system will attempt to execute any deferred console commands to allow newly registered commands from that module to be dispatched. Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
90845313fb
commit
586678a5f9
@@ -174,12 +174,28 @@ namespace AZ
|
||||
}
|
||||
}
|
||||
|
||||
bool Console::HasCommand(const char* command)
|
||||
bool Console::ExecuteDeferredConsoleCommands()
|
||||
{
|
||||
auto DeferredCommandCallable = [this](const DeferredCommand& deferredCommand)
|
||||
{
|
||||
return this->DispatchCommand(deferredCommand.m_command, deferredCommand.m_arguments, deferredCommand.m_silentMode,
|
||||
deferredCommand.m_invokedFrom, deferredCommand.m_requiredSet, deferredCommand.m_requiredClear);
|
||||
};
|
||||
// Attempt to invoke the deferred command and remove it from the queue if successful
|
||||
return AZStd::erase_if(m_deferredCommands, DeferredCommandCallable) != 0;
|
||||
}
|
||||
|
||||
void Console::ClearDeferredConsoleCommands()
|
||||
{
|
||||
m_deferredCommands = {};
|
||||
}
|
||||
|
||||
bool Console::HasCommand(AZStd::string_view command)
|
||||
{
|
||||
return FindCommand(command) != nullptr;
|
||||
}
|
||||
|
||||
ConsoleFunctorBase* Console::FindCommand(const char* command)
|
||||
ConsoleFunctorBase* Console::FindCommand(AZStd::string_view command)
|
||||
{
|
||||
CVarFixedString lowerName(command);
|
||||
AZStd::transform(lowerName.begin(), lowerName.end(), lowerName.begin(), [](char value) { return std::tolower(value); });
|
||||
@@ -200,11 +216,9 @@ namespace AZ
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AZStd::string Console::AutoCompleteCommand(const char* command, AZStd::vector<AZStd::string>* matches)
|
||||
AZStd::string Console::AutoCompleteCommand(AZStd::string_view command, AZStd::vector<AZStd::string>* matches)
|
||||
{
|
||||
const size_t commandLength = strlen(command);
|
||||
|
||||
if (commandLength <= 0)
|
||||
if (command.empty())
|
||||
{
|
||||
return command;
|
||||
}
|
||||
@@ -219,7 +233,7 @@ namespace AZ
|
||||
continue;
|
||||
}
|
||||
|
||||
if (StringFunc::Equal(curr->m_name, command, false, commandLength))
|
||||
if (StringFunc::StartsWith(curr->m_name, command, false))
|
||||
{
|
||||
AZLOG_INFO("- %s : %s\n", curr->m_name, curr->m_desc);
|
||||
commandSubset.push_back(curr->m_name);
|
||||
@@ -498,7 +512,8 @@ namespace AZ
|
||||
|
||||
AZ::IO::PathView consoleRootCommandKey{ IConsole::ConsoleRootCommandKey, AZ::IO::PosixPathSeparator };
|
||||
AZ::IO::PathView inputKey{ path, AZ::IO::PosixPathSeparator };
|
||||
if (inputKey.IsRelativeTo(consoleRootCommandKey))
|
||||
// The ConsoleRootComamndKey is not a command itself so strictly children keys are being examined
|
||||
if (inputKey.IsRelativeTo(consoleRootCommandKey) && inputKey != consoleRootCommandKey)
|
||||
{
|
||||
FixedValueString command = inputKey.LexicallyRelative(consoleRootCommandKey).Native();
|
||||
ConsoleCommandContainer commandArgs;
|
||||
@@ -560,7 +575,24 @@ namespace AZ
|
||||
commandTrace += commandArg;
|
||||
}
|
||||
|
||||
m_console.PerformCommand(command, commandArgs, ConsoleSilentMode::NotSilent, ConsoleInvokedFrom::AzConsole, ConsoleFunctorFlags::Null, ConsoleFunctorFlags::Null);
|
||||
if (!m_console.PerformCommand(command, commandArgs, ConsoleSilentMode::NotSilent,
|
||||
ConsoleInvokedFrom::AzConsole, ConsoleFunctorFlags::Null, ConsoleFunctorFlags::Null))
|
||||
{
|
||||
// If the command could not be dispatched at this time add it to the
|
||||
// deferred commands queue
|
||||
using DeferredCommand = Console::DeferredCommand;
|
||||
DeferredCommand deferredCommand
|
||||
{
|
||||
AZStd::string_view{command},
|
||||
DeferredCommand::DeferredArguments{commandArgs.begin(), commandArgs.end()},
|
||||
ConsoleSilentMode::NotSilent,
|
||||
ConsoleInvokedFrom::AzConsole,
|
||||
ConsoleFunctorFlags::Null,
|
||||
ConsoleFunctorFlags::Null
|
||||
};
|
||||
|
||||
m_console.m_deferredCommands.emplace_back(AZStd::move(deferredCommand));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -60,9 +60,13 @@ namespace AZ
|
||||
) override;
|
||||
void ExecuteConfigFile(AZStd::string_view configFileName) override;
|
||||
void ExecuteCommandLine(const AZ::CommandLine& commandLine) override;
|
||||
bool HasCommand(const char* command) override;
|
||||
ConsoleFunctorBase* FindCommand(const char* command) override;
|
||||
AZStd::string AutoCompleteCommand(const char* command, AZStd::vector<AZStd::string>* matches = nullptr) override;
|
||||
bool ExecuteDeferredConsoleCommands() override;
|
||||
|
||||
void ClearDeferredConsoleCommands() override;
|
||||
|
||||
bool HasCommand(AZStd::string_view command) override;
|
||||
ConsoleFunctorBase* FindCommand(AZStd::string_view command) override;
|
||||
AZStd::string AutoCompleteCommand(AZStd::string_view command, AZStd::vector<AZStd::string>* matches = nullptr) override;
|
||||
void VisitRegisteredFunctors(const FunctorVisitor& visitor) override;
|
||||
void RegisterFunctor(ConsoleFunctorBase* functor) override;
|
||||
void UnregisterFunctor(ConsoleFunctorBase* functor) override;
|
||||
@@ -98,7 +102,20 @@ namespace AZ
|
||||
using CommandMap = AZStd::unordered_map<CVarFixedString, AZStd::vector<ConsoleFunctorBase*>>;
|
||||
CommandMap m_commands;
|
||||
AZ::SettingsRegistryInterface::NotifyEventHandler m_consoleCommandKeyHandler;
|
||||
struct DeferredCommand
|
||||
{
|
||||
using DeferredArguments = AZStd::vector<AZStd::string>;
|
||||
AZStd::string m_command;
|
||||
DeferredArguments m_arguments;
|
||||
ConsoleSilentMode m_silentMode;
|
||||
ConsoleInvokedFrom m_invokedFrom;
|
||||
ConsoleFunctorFlags m_requiredSet;
|
||||
ConsoleFunctorFlags m_requiredClear;
|
||||
};
|
||||
using DeferredCommandQueue = AZStd::deque<DeferredCommand>;
|
||||
DeferredCommandQueue m_deferredCommands;
|
||||
|
||||
friend struct ConsoleCommandKeyNotificationHandler;
|
||||
friend class ConsoleFunctorBase;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -94,22 +94,30 @@ namespace AZ
|
||||
//! @param commandLine the concatenated command-line string to execute
|
||||
virtual void ExecuteCommandLine(const AZ::CommandLine& commandLine) = 0;
|
||||
|
||||
//! Attempts to invoke a "deferred console command", which is a console command
|
||||
//! that has failed to execute previously due to the command not being registered yet.
|
||||
//! @return boolean true if any deferred console commands have executed, false otherwise
|
||||
virtual bool ExecuteDeferredConsoleCommands() = 0;
|
||||
|
||||
//! Clear out any deferred console commands queue
|
||||
virtual void ClearDeferredConsoleCommands() = 0;
|
||||
|
||||
//! HasCommand is used to determine if the console knows about a command.
|
||||
//! @param command the command we are checking for
|
||||
//! @return boolean true on if the command is registered, false otherwise
|
||||
virtual bool HasCommand(const char* command) = 0;
|
||||
virtual bool HasCommand(AZStd::string_view command) = 0;
|
||||
|
||||
//! FindCommand finds the console command with the specified console string.
|
||||
//! @param command the command that is being searched for
|
||||
//! @return non-null pointer to the console command if found
|
||||
virtual ConsoleFunctorBase* FindCommand(const char* command) = 0;
|
||||
virtual ConsoleFunctorBase* FindCommand(AZStd::string_view command) = 0;
|
||||
|
||||
//! Finds all commands where the input command is a prefix and returns
|
||||
//! the longest matching substring prefix the results have in common.
|
||||
//! @param command The prefix string to find all matching commands for.
|
||||
//! @param matches The list of all commands that match the input prefix.
|
||||
//! @return The longest matching substring prefix the results have in common.
|
||||
virtual AZStd::string AutoCompleteCommand(const char* command,
|
||||
virtual AZStd::string AutoCompleteCommand(AZStd::string_view command,
|
||||
AZStd::vector<AZStd::string>* matches = nullptr) = 0;
|
||||
|
||||
//! Retrieves the value of the requested cvar.
|
||||
@@ -117,7 +125,7 @@ namespace AZ
|
||||
//! @param outValue reference to the instance to write the current cvar value to
|
||||
//! @return GetValueResult::Success if the operation succeeded, or an error result if the operation failed
|
||||
template<typename RETURN_TYPE>
|
||||
GetValueResult GetCvarValue(const char* command, RETURN_TYPE& outValue);
|
||||
GetValueResult GetCvarValue(AZStd::string_view command, RETURN_TYPE& outValue);
|
||||
|
||||
//! Visits all registered console functors.
|
||||
//! @param visitor the instance to visit all functors with
|
||||
@@ -176,7 +184,7 @@ namespace AZ
|
||||
}
|
||||
|
||||
template<typename RETURN_TYPE>
|
||||
inline GetValueResult IConsole::GetCvarValue(const char* command, RETURN_TYPE& outValue)
|
||||
inline GetValueResult IConsole::GetCvarValue(AZStd::string_view command, RETURN_TYPE& outValue)
|
||||
{
|
||||
ConsoleFunctorBase* cvarFunctor = FindCommand(command);
|
||||
if (cvarFunctor == nullptr)
|
||||
|
||||
@@ -98,19 +98,26 @@ namespace AZ
|
||||
///
|
||||
/// \param MODULE_NAME Name of module.
|
||||
/// \param MODULE_CLASSNAME Name of AZ::Module class (include namespace).
|
||||
///
|
||||
/// Execute any deferred console commands after linking any new deferred functors
|
||||
/// This allows deferred console commands defined within the module to now execute
|
||||
/// at this point now that the module has been loaded
|
||||
#if defined(AZ_MONOLITHIC_BUILD)
|
||||
# define AZ_DECLARE_MODULE_CLASS(MODULE_NAME, MODULE_CLASSNAME) \
|
||||
extern "C" AZ::Module * CreateModuleClass_##MODULE_NAME() { return aznew MODULE_CLASSNAME; }
|
||||
#else
|
||||
# define AZ_DECLARE_MODULE_CLASS(MODULE_NAME, MODULE_CLASSNAME) \
|
||||
AZ_DECLARE_MODULE_INITIALIZATION \
|
||||
extern "C" AZ_DLL_EXPORT AZ::Module * CreateModuleClass() \
|
||||
extern "C" AZ_DLL_EXPORT AZ::Module* CreateModuleClass() \
|
||||
{ \
|
||||
AZ::ConsoleFunctorBase*& deferredHead = AZ::ConsoleFunctorBase::GetDeferredHead(); \
|
||||
AZ::Interface<AZ::IConsole>::Get()->LinkDeferredFunctors(deferredHead); \
|
||||
if (auto console = AZ::Interface<AZ::IConsole>::Get(); console != nullptr) \
|
||||
{ \
|
||||
console->LinkDeferredFunctors(AZ::ConsoleFunctorBase::GetDeferredHead()); \
|
||||
console->ExecuteDeferredConsoleCommands(); \
|
||||
} \
|
||||
return aznew MODULE_CLASSNAME; \
|
||||
} \
|
||||
extern "C" AZ_DLL_EXPORT void DestroyModuleClass(AZ::Module * module) { delete module; }
|
||||
extern "C" AZ_DLL_EXPORT void DestroyModuleClass(AZ::Module* module) { delete module; }
|
||||
#endif
|
||||
|
||||
#endif // AZCORE_MODULE_INCLUDE_H
|
||||
|
||||
@@ -507,6 +507,70 @@ namespace ConsoleSettingsRegistryTests
|
||||
AZ::Interface<AZ::IConsole>::Unregister(&testConsole);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
using ConsoleDataWrapper = AZ::ConsoleDataWrapper<T, ConsoleThreadSafety<T>>;
|
||||
TEST_P(ConsoleSettingsRegistryFixture, Console_RecordsUnregisteredCommands_And_IsAbleToDeferDispatchCommand_Successfully)
|
||||
{
|
||||
AZ::Console testConsole(*m_registry);
|
||||
AZ::Interface<AZ::IConsole>::Register(&testConsole);
|
||||
// GetDeferredHead is invoked for the side effect of to set the s_deferredHeadInvoked value to true
|
||||
// This allows scoped console variables to be attached immediately
|
||||
[[maybe_unused]] auto deferredHead = AZ::ConsoleFunctorBase::GetDeferredHead();
|
||||
|
||||
|
||||
ConsoleDataWrapper<int32_t> localTestInit{ {}, nullptr, "testInit", "", AZ::ConsoleFunctorFlags::Null };
|
||||
ConsoleDataWrapper<char> localTestChar{ {}, nullptr, "testChar", "", AZ::ConsoleFunctorFlags::Null };
|
||||
ConsoleDataWrapper<bool> localTestBool{ {}, nullptr, "testBool", "", AZ::ConsoleFunctorFlags::Null };
|
||||
|
||||
s_consoleFreeFunctionInvoked = false;
|
||||
|
||||
// Invoke the Commands for Scoped CVar variables above
|
||||
auto configFileParams = GetParam();
|
||||
auto testFilePath = m_testFolder / configFileParams.m_testConfigFileName;
|
||||
EXPECT_TRUE(AZ::IO::SystemFile::Exists(testFilePath.c_str()));
|
||||
testConsole.ExecuteConfigFile(testFilePath.Native());
|
||||
|
||||
EXPECT_EQ(3, localTestInit);
|
||||
EXPECT_TRUE(static_cast<bool>(localTestBool));
|
||||
EXPECT_EQ('Q', localTestChar);
|
||||
|
||||
// The following commands from the config files should have been deferred
|
||||
ConsoleDataWrapper<int8_t> localTestInt8{ {}, nullptr, "testInt8", "", AZ::ConsoleFunctorFlags::Null };
|
||||
ConsoleDataWrapper<int16_t> localTestInt16{ {}, nullptr, "testInt16", "", AZ::ConsoleFunctorFlags::Null };
|
||||
ConsoleDataWrapper<int32_t> localTestInt32{ {}, nullptr, "testInt32", "", AZ::ConsoleFunctorFlags::Null };
|
||||
ConsoleDataWrapper<int64_t> localTestInt64{ {}, nullptr, "testInt64", "", AZ::ConsoleFunctorFlags::Null };
|
||||
ConsoleDataWrapper<uint8_t> localTestUInt8{ {}, nullptr, "testUInt8", "", AZ::ConsoleFunctorFlags::Null };
|
||||
ConsoleDataWrapper<uint16_t> localTestUInt16{ {}, nullptr, "testUInt16", "", AZ::ConsoleFunctorFlags::Null };
|
||||
ConsoleDataWrapper<uint32_t> localTestUInt32{ {}, nullptr, "testUInt32", "", AZ::ConsoleFunctorFlags::Null };
|
||||
ConsoleDataWrapper<uint64_t> localTestUInt64{ {}, nullptr, "testUInt64", "", AZ::ConsoleFunctorFlags::Null };
|
||||
ConsoleDataWrapper<float> localTestFloat{ {}, nullptr, "testFloat", "", AZ::ConsoleFunctorFlags::Null };
|
||||
ConsoleDataWrapper<double> localTestDouble{ {}, nullptr, "testDouble", "", AZ::ConsoleFunctorFlags::Null };
|
||||
ConsoleDataWrapper<AZ::CVarFixedString> localTestString{ {}, nullptr, "testString", "", AZ::ConsoleFunctorFlags::Null };
|
||||
|
||||
|
||||
// The scoped cvars just above should have all been deferred for execution
|
||||
// Each of them should have executed resulting in the expected return value
|
||||
EXPECT_TRUE(testConsole.ExecuteDeferredConsoleCommands());
|
||||
|
||||
EXPECT_EQ(24, localTestInt8);
|
||||
EXPECT_EQ(-32, localTestInt16);
|
||||
EXPECT_EQ(41, localTestInt32);
|
||||
EXPECT_EQ(-51, localTestInt64);
|
||||
EXPECT_EQ(3, localTestUInt8);
|
||||
EXPECT_EQ(5, localTestUInt16);
|
||||
EXPECT_EQ(6, localTestUInt32);
|
||||
EXPECT_EQ(0xFFFF'FFFF'FFFF'FFFF, localTestUInt64);
|
||||
EXPECT_FLOAT_EQ(1.0f, localTestFloat);
|
||||
EXPECT_DOUBLE_EQ(2, localTestDouble);
|
||||
EXPECT_STREQ("Stable", static_cast<AZ::CVarFixedString>(localTestString).c_str());
|
||||
|
||||
// All of the deferred console commands should have executed at this point
|
||||
// Therefore this invocation should return false
|
||||
EXPECT_FALSE(testConsole.ExecuteDeferredConsoleCommands());
|
||||
|
||||
AZ::Interface<AZ::IConsole>::Unregister(&testConsole);
|
||||
}
|
||||
|
||||
|
||||
static constexpr AZStd::string_view UserINIStyleContent =
|
||||
R"(
|
||||
|
||||
Reference in New Issue
Block a user