diff --git a/Source/Editor/Windows/OutputLogWindow.cs b/Source/Editor/Windows/OutputLogWindow.cs
index 882730fed..f22befe9c 100644
--- a/Source/Editor/Windows/OutputLogWindow.cs
+++ b/Source/Editor/Windows/OutputLogWindow.cs
@@ -212,6 +212,11 @@ namespace FlaxEditor.Windows
Name = command,
Owner = this,
});
+ var flags = DebugCommands.GetCommandFlags(command);
+ if (flags.HasFlag(DebugCommands.CommandFlags.Exec))
+ lastItem.TintColor = new Color(0.85f, 0.85f, 1.0f, 1.0f);
+ else if (flags.HasFlag(DebugCommands.CommandFlags.Read) && !flags.HasFlag(DebugCommands.CommandFlags.Write))
+ lastItem.TintColor = new Color(0.85f, 0.85f, 0.85f, 1.0f);
lastItem.Focused += item =>
{
// Set command
@@ -259,6 +264,15 @@ namespace FlaxEditor.Windows
RootWindow.Window.LostFocus -= OnRootWindowLostFocus;
}
+ ///
+ public override void OnGotFocus()
+ {
+ // Precache debug commands to reduce time-to-interactive
+ DebugCommands.InitAsync();
+
+ base.OnGotFocus();
+ }
+
///
protected override void OnTextChanged()
{
diff --git a/Source/Engine/Debug/DebugCommands.cpp b/Source/Engine/Debug/DebugCommands.cpp
index a9ae862f2..b041e779f 100644
--- a/Source/Engine/Debug/DebugCommands.cpp
+++ b/Source/Engine/Debug/DebugCommands.cpp
@@ -5,6 +5,7 @@
#include "Engine/Core/Collections/Array.h"
#include "Engine/Engine/EngineService.h"
#include "Engine/Threading/Threading.h"
+#include "Engine/Threading/Task.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Scripting/BinaryModule.h"
#include "Engine/Scripting/Scripting.h"
@@ -103,6 +104,7 @@ namespace
{
CriticalSection Locker;
bool Inited = false;
+ Task* AsyncTask = nullptr;
Array Commands;
void FindDebugCommands(BinaryModule* module)
@@ -208,15 +210,45 @@ namespace
void InitCommands()
{
+ ASSERT_LOW_LAYER(!Inited);
PROFILE_CPU();
- Inited = true;
+
+ // Cache existing modules
const auto& modules = BinaryModule::GetModules();
for (BinaryModule* module : modules)
{
FindDebugCommands(module);
}
+
+ // Link for modules load/reload actions
Scripting::BinaryModuleLoaded.Bind(&FindDebugCommands);
Scripting::ScriptsReloading.Bind(&OnScriptsReloading);
+
+ // Mark as done
+ Locker.Lock();
+ Inited = true;
+ AsyncTask = nullptr;
+ Locker.Unlock();
+ }
+
+ void EnsureInited()
+ {
+ // Check current state
+ Locker.Lock();
+ bool inited = Inited;
+ Locker.Unlock();
+ if (inited)
+ return;
+
+ // Wait for any async task
+ if (AsyncTask)
+ AsyncTask->Wait();
+
+ // Do sync init if still not inited
+ Locker.Lock();
+ if (!Inited)
+ InitCommands();
+ Locker.Unlock();
}
}
@@ -231,6 +263,8 @@ public:
void Dispose() override
{
// Cleanup
+ if (AsyncTask)
+ AsyncTask->Wait();
ScopeLock lock(Locker);
Scripting::BinaryModuleLoaded.Unbind(&FindDebugCommands);
Scripting::ScriptsReloading.Unbind(&OnScriptsReloading);
@@ -264,9 +298,8 @@ void DebugCommands::Execute(StringView command)
}
// Ensure that commands cache has been created
+ EnsureInited();
ScopeLock lock(Locker);
- if (!Inited)
- InitCommands();
// Find command to run
for (const CommandData& cmd : Commands)
@@ -290,9 +323,8 @@ void DebugCommands::Search(StringView searchText, Array& matches, bo
String searchTextCopy = searchText;
searchText = searchTextCopy;
+ EnsureInited();
ScopeLock lock(Locker);
- if (!Inited)
- InitCommands();
if (startsWith)
{
@@ -316,13 +348,45 @@ void DebugCommands::Search(StringView searchText, Array& matches, bo
}
}
-bool DebugCommands::Iterate(const StringView& searchText, int32& index)
+void DebugCommands::InitAsync()
{
ScopeLock lock(Locker);
+ if (Inited)
+ return;
+ AsyncTask = Task::StartNew(InitCommands);
+}
+
+DebugCommands::CommandFlags DebugCommands::GetCommandFlags(StringView command)
+{
+ CommandFlags result = CommandFlags::None;
+ // TODO: fix missing string handle on 1st command execution (command gets invalid after InitCommands due to dotnet GC or dotnet interop handles flush)
+ String commandCopy = command;
+ command = commandCopy;
+ EnsureInited();
+ for (auto& e : Commands)
+ {
+ if (e.Name == command)
+ {
+ if (e.Method)
+ result |= CommandFlags::Exec;
+ else if (e.Field)
+ result |= CommandFlags::ReadWrite;
+ if (e.MethodGet)
+ result |= CommandFlags::Read;
+ if (e.MethodSet)
+ result |= CommandFlags::Write;
+ break;
+ }
+ }
+ return result;
+}
+
+bool DebugCommands::Iterate(const StringView& searchText, int32& index)
+{
+ EnsureInited();
if (index >= 0)
{
- if (!Inited)
- InitCommands();
+ ScopeLock lock(Locker);
while (index < Commands.Count())
{
auto& command = Commands.Get()[index];
diff --git a/Source/Engine/Debug/DebugCommands.h b/Source/Engine/Debug/DebugCommands.h
index f89cba371..e82963792 100644
--- a/Source/Engine/Debug/DebugCommands.h
+++ b/Source/Engine/Debug/DebugCommands.h
@@ -11,6 +11,21 @@ API_CLASS(static) class FLAXENGINE_API DebugCommands
{
DECLARE_SCRIPTING_TYPE_MINIMAL(DebugCommands);
+ // Types of debug command flags.
+ API_ENUM(Attributes="Flags") enum class CommandFlags
+ {
+ // Incorrect or missing command.
+ None = 0,
+ // Executable method.
+ Exec = 1,
+ // Can get value.
+ Read = 2,
+ // Can set value.
+ Write = 4,
+ // Can get and set value.
+ ReadWrite = Read | Write,
+ };
+
public:
///
/// Executes the command.
@@ -26,7 +41,20 @@ public:
/// True if filter commands that start with a specific search text, otherwise will return commands that contain a specific query.
API_FUNCTION() static void Search(StringView searchText, API_PARAM(Out) Array& matches, bool startsWith = false);
+ ///
+ /// Starts asynchronous debug commands caching. Cna be used to minimize time-to-interactive when using console interface or when using scripted actions.
+ ///
+ API_FUNCTION() static void InitAsync();
+
+ ///
+ /// Returns flags of the command.
+ ///
+ /// The full name of the command.
+ API_FUNCTION() static CommandFlags GetCommandFlags(StringView command);
+
public:
static bool Iterate(const StringView& searchText, int32& index);
static StringView GetCommandName(int32 index);
};
+
+DECLARE_ENUM_OPERATORS(DebugCommands::CommandFlags);