524 lines
17 KiB
C#
524 lines
17 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using FlaxEditor;
|
|
using FlaxEngine;
|
|
|
|
namespace Game
|
|
{
|
|
public class ConsoleLine
|
|
{
|
|
public string content;
|
|
|
|
internal ConsoleLine(string line)
|
|
{
|
|
content = line;
|
|
}
|
|
|
|
public static implicit operator string(ConsoleLine line)
|
|
{
|
|
return line.content;
|
|
}
|
|
|
|
public static explicit operator ConsoleLine(string line)
|
|
{
|
|
return new ConsoleLine(line);
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return content;
|
|
}
|
|
}
|
|
|
|
public static class Console
|
|
{
|
|
private static ConsoleInstance instance;
|
|
|
|
// Returns if Console window open right now.
|
|
public static bool IsOpen => instance.IsOpen;
|
|
|
|
// For debugging only: Returns true when Console was not closed during the same frame.
|
|
// Needed when Escape-key both closes the console and exits the game.
|
|
public static bool IsSafeToQuit => instance.IsSafeToQuit;
|
|
|
|
// Called when Console is opened.
|
|
public static Action OnOpen
|
|
{
|
|
get => instance.OnOpen;
|
|
set => instance.OnOpen = value;
|
|
}
|
|
|
|
// Called when Console is closed.
|
|
public static Action OnClose
|
|
{
|
|
get => instance.OnClose;
|
|
set => instance.OnClose = value;
|
|
}
|
|
|
|
// Called when a line of text was printed in Console.
|
|
public static Action<string> OnPrint
|
|
{
|
|
get => instance.OnPrint;
|
|
set => instance.OnPrint = value;
|
|
}
|
|
|
|
public static bool ShowExecutedLines => instance.ShowExecutedLines;
|
|
|
|
public static int DebugVerbosity
|
|
{
|
|
get => instance.DebugVerbosity;
|
|
set => instance.DebugVerbosity = value;
|
|
}
|
|
|
|
public static string LinePrefix => instance.LinePrefix;
|
|
|
|
public static IReadOnlyCollection<ConsoleLine> Lines => instance.Lines;
|
|
|
|
public static void Init()
|
|
{
|
|
if (instance != null)
|
|
return;
|
|
|
|
Destroy();
|
|
instance = new ConsoleInstance();
|
|
instance.InitConsoleSubsystems();
|
|
|
|
#if FLAX_EDITOR
|
|
ScriptsBuilder.ScriptsReload += Destroy;
|
|
Editor.Instance.PlayModeEnd += OnEditorPlayModeChanged;
|
|
#endif
|
|
}
|
|
|
|
public static void Destroy()
|
|
{
|
|
if (instance != null)
|
|
{
|
|
instance.Dispose();
|
|
instance = null;
|
|
|
|
#if FLAX_EDITOR
|
|
ScriptsBuilder.ScriptsReload -= Destroy;
|
|
Editor.Instance.PlayModeEnd -= OnEditorPlayModeChanged;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
#if FLAX_EDITOR
|
|
private static void OnEditorPlayModeChanged()
|
|
{
|
|
//AssetManager.Globals.ResetValues();
|
|
// Clear console buffer when leaving play mode
|
|
if (instance != null)
|
|
Clear();
|
|
}
|
|
#endif
|
|
|
|
public static string GetBufferHistory(int index)
|
|
{
|
|
return instance.GetBufferHistory(index);
|
|
}
|
|
|
|
// Echoes text to Console
|
|
public static void Print(string text)
|
|
{
|
|
instance.Print(text);
|
|
}
|
|
|
|
// Echoes warning text to Console
|
|
public static void PrintWarning(string text)
|
|
{
|
|
instance.PrintWarning(text);
|
|
}
|
|
|
|
// Echoes error text to Console
|
|
public static void PrintError(string text)
|
|
{
|
|
instance.PrintError(text);
|
|
}
|
|
|
|
// Echoes developer/debug text to Console
|
|
public static void PrintDebug(string text)
|
|
{
|
|
instance.PrintDebug(1, false, text);
|
|
}
|
|
|
|
// Echoes developer/debug text to Console
|
|
public static void PrintDebug(int verbosity, string text)
|
|
{
|
|
instance.PrintDebug(verbosity, false, text);
|
|
}
|
|
|
|
// Echoes developer/debug text to Console
|
|
public static void PrintDebug(int verbosity, bool noRepeat, string text)
|
|
{
|
|
instance.PrintDebug(verbosity, noRepeat, text);
|
|
}
|
|
|
|
// Opens the Console
|
|
public static void Open()
|
|
{
|
|
instance.Open();
|
|
}
|
|
|
|
// Closes the Console
|
|
public static void Close()
|
|
{
|
|
instance.Close();
|
|
}
|
|
|
|
// Clears the content of the Console
|
|
public static void Clear()
|
|
{
|
|
instance.Clear();
|
|
}
|
|
|
|
public static void Execute(string str, bool bufferInput = false, bool noOutput = false)
|
|
{
|
|
instance.Execute(str, bufferInput, noOutput);
|
|
}
|
|
|
|
public static string GetVariable(string variableName)
|
|
{
|
|
return instance.GetVariable(variableName);
|
|
}
|
|
}
|
|
|
|
public class ConsoleInstance : IDisposable
|
|
{
|
|
private readonly List<string> consoleBufferHistory = new List<string>();
|
|
private readonly Dictionary<string, ConsoleCommand> consoleCommands = new Dictionary<string, ConsoleCommand>();
|
|
|
|
//private static List<string> consoleLines = new List<string>();
|
|
private readonly List<ConsoleLine> consoleLines = new List<ConsoleLine>();
|
|
|
|
private readonly Dictionary<string, ConsoleVariable> consoleVariables =
|
|
new Dictionary<string, ConsoleVariable>();
|
|
|
|
private readonly Stopwatch stopwatch = Stopwatch.StartNew();
|
|
|
|
// Echoes developer/debug text to Console
|
|
private string debugLastLine = "";
|
|
public Action OnClose;
|
|
|
|
public Action OnOpen;
|
|
public Action<string> OnPrint;
|
|
|
|
public bool ShowExecutedLines = true;
|
|
|
|
internal ConsoleInstance()
|
|
{
|
|
}
|
|
|
|
public bool IsOpen { get; internal set; } = true;
|
|
|
|
public bool IsSafeToQuit => stopwatch.Elapsed.TotalSeconds > 0.1;
|
|
|
|
public int DebugVerbosity { get; set; } = 1;
|
|
public string LinePrefix { get; internal set; } = "]";
|
|
|
|
public IReadOnlyCollection<ConsoleLine> Lines => consoleLines.AsReadOnly();
|
|
|
|
public void Dispose()
|
|
{
|
|
}
|
|
|
|
// Initializes the Console system.
|
|
internal void InitConsoleSubsystems()
|
|
{
|
|
//AppDomain currentDomain = AppDomain.CurrentDomain;
|
|
#if USE_NETCORE
|
|
var assemblies = Utils.GetAssemblies();
|
|
#else
|
|
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
|
|
#endif
|
|
|
|
foreach (Assembly assembly in assemblies)
|
|
{
|
|
// Skip common assemblies
|
|
string assemblyName = assembly.GetName().Name;
|
|
if (assemblyName == "System" ||
|
|
assemblyName.StartsWith("System.") ||
|
|
assemblyName.StartsWith("Mono.") ||
|
|
assemblyName == "mscorlib" ||
|
|
assemblyName == "Newtonsoft.Json" ||
|
|
assemblyName.StartsWith("FlaxEngine.") ||
|
|
assemblyName.StartsWith("JetBrains.") ||
|
|
assemblyName.StartsWith("Microsoft.") ||
|
|
assemblyName.StartsWith("nunit."))
|
|
continue;
|
|
|
|
foreach (Type type in assembly.GetTypes())
|
|
{
|
|
var cmdParsed = new Dictionary<string, ConsoleCommand>();
|
|
var cmdMethods = new Dictionary<string, List<MethodInfo>>();
|
|
MethodInfo cmdInitializer = null;
|
|
|
|
foreach (MethodInfo method in type.GetMethods())
|
|
{
|
|
if (!method.IsStatic)
|
|
continue;
|
|
|
|
var attributes = Attribute.GetCustomAttributes(method);
|
|
|
|
foreach (Attribute attr in attributes)
|
|
if (attr is ConsoleCommandAttribute cmdAttribute)
|
|
{
|
|
//Console.Print("found cmd '" + cmdAttribute.name + "' bound to field '" + method.Name + "'");
|
|
|
|
// Defer constructing the command until we have parsed all the methods for it in this assembly.
|
|
|
|
List<MethodInfo> methods;
|
|
if (!cmdMethods.TryGetValue(cmdAttribute.name, out methods))
|
|
{
|
|
methods = new List<MethodInfo>();
|
|
cmdMethods.Add(cmdAttribute.name, methods);
|
|
}
|
|
|
|
methods.Add(method);
|
|
|
|
ConsoleCommand cmd = new ConsoleCommand(cmdAttribute.name, null);
|
|
if (!cmdParsed.ContainsKey(cmdAttribute.name))
|
|
cmdParsed.Add(cmdAttribute.name, cmd);
|
|
foreach (string alias in cmdAttribute.aliases)
|
|
{
|
|
if (!cmdParsed.ContainsKey(alias))
|
|
cmdParsed.Add(alias, cmd);
|
|
|
|
List<MethodInfo> aliasMethods;
|
|
if (!cmdMethods.TryGetValue(alias, out aliasMethods))
|
|
{
|
|
aliasMethods = new List<MethodInfo>();
|
|
cmdMethods.Add(alias, aliasMethods);
|
|
}
|
|
|
|
aliasMethods.Add(method);
|
|
}
|
|
}
|
|
else if (attr is ConsoleSubsystemInitializer)
|
|
{
|
|
cmdInitializer = method;
|
|
}
|
|
}
|
|
|
|
foreach (var kv in cmdParsed)
|
|
{
|
|
var methods = cmdMethods[kv.Key];
|
|
ConsoleCommand definition = kv.Value;
|
|
|
|
ConsoleCommand cmd = new ConsoleCommand(definition.name, methods.ToArray());
|
|
consoleCommands.Add(kv.Key, cmd);
|
|
}
|
|
|
|
foreach (FieldInfo field in type.GetFields())
|
|
{
|
|
if (!field.IsStatic)
|
|
continue;
|
|
|
|
var attributes = Attribute.GetCustomAttributes(field);
|
|
|
|
foreach (Attribute attr in attributes)
|
|
if (attr is ConsoleVariableAttribute cvarAttribute)
|
|
{
|
|
//Console.Print("found cvar '" + cvarAttribute.name + "' bound to field '" + field.Name + "'");
|
|
consoleVariables.Add(cvarAttribute.name,
|
|
new ConsoleVariable(cvarAttribute.name, cvarAttribute.flags, field));
|
|
foreach (string alias in cvarAttribute.aliases)
|
|
consoleVariables.Add(alias,
|
|
new ConsoleVariable(cvarAttribute.name,
|
|
cvarAttribute.flags | ConsoleFlags.NoSerialize, field));
|
|
}
|
|
}
|
|
|
|
foreach (PropertyInfo prop in type.GetProperties())
|
|
{
|
|
MethodInfo getter = prop.GetGetMethod();
|
|
MethodInfo setter = prop.GetSetMethod();
|
|
if (getter == null || setter == null || !getter.IsStatic || !setter.IsStatic)
|
|
continue;
|
|
|
|
var attributes = Attribute.GetCustomAttributes(prop);
|
|
|
|
foreach (Attribute attr in attributes)
|
|
if (attr is ConsoleVariableAttribute cvarAttribute)
|
|
{
|
|
//Console.Print("found cvar '" + cvarAttribute.name + "' bound to field '" + field.Name + "'");
|
|
consoleVariables.Add(cvarAttribute.name,
|
|
new ConsoleVariable(cvarAttribute.name, cvarAttribute.flags, getter, setter));
|
|
foreach (string alias in cvarAttribute.aliases)
|
|
consoleVariables.Add(alias,
|
|
new ConsoleVariable(cvarAttribute.name,
|
|
cvarAttribute.flags | ConsoleFlags.NoSerialize, getter, setter));
|
|
}
|
|
}
|
|
|
|
if (cmdInitializer != null)
|
|
{
|
|
Console.PrintDebug(2, "Initializing " + type.Name);
|
|
cmdInitializer.Invoke(null, null);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public string GetBufferHistory(int index)
|
|
{
|
|
if (consoleBufferHistory.Count == 0)
|
|
return null;
|
|
|
|
if (index > consoleBufferHistory.Count - 1 || index < 0)
|
|
return null;
|
|
|
|
return consoleBufferHistory[index];
|
|
}
|
|
|
|
// Echoes text to Console
|
|
public void Print(string text)
|
|
{
|
|
debugLastLine = text;
|
|
|
|
foreach (string line in text.Split('\n'))
|
|
{
|
|
ConsoleLine lineEntry = new ConsoleLine(line);
|
|
consoleLines.Add(lineEntry);
|
|
OnPrint?.Invoke(text);
|
|
}
|
|
}
|
|
|
|
// Echoes warning text to Console
|
|
public void PrintWarning(string text)
|
|
{
|
|
foreach (string line in text.Split('\n'))
|
|
{
|
|
ConsoleLine lineEntry = new ConsoleLine(line);
|
|
consoleLines.Add(lineEntry);
|
|
OnPrint?.Invoke(text);
|
|
}
|
|
}
|
|
|
|
// Echoes error text to Console
|
|
public void PrintError(string text)
|
|
{
|
|
foreach (string line in text.Split('\n'))
|
|
{
|
|
ConsoleLine lineEntry = new ConsoleLine(line);
|
|
consoleLines.Add(lineEntry);
|
|
OnPrint?.Invoke(text);
|
|
}
|
|
|
|
if (Debugger.IsAttached)
|
|
Debugger.Break();
|
|
else
|
|
throw new Exception(text);
|
|
}
|
|
|
|
public void PrintDebug(int verbosity, bool noRepeat, string text)
|
|
{
|
|
if (DebugVerbosity < verbosity)
|
|
return;
|
|
|
|
if (noRepeat)
|
|
if (debugLastLine.Length == text.Length && debugLastLine == text)
|
|
return;
|
|
|
|
debugLastLine = text;
|
|
|
|
foreach (string line in text.Split('\n'))
|
|
{
|
|
ConsoleLine lineEntry = new ConsoleLine(line);
|
|
consoleLines.Add(lineEntry);
|
|
OnPrint?.Invoke(text);
|
|
}
|
|
}
|
|
|
|
// Opens the Console
|
|
public void Open()
|
|
{
|
|
if (IsOpen)
|
|
return;
|
|
|
|
IsOpen = true;
|
|
OnOpen?.Invoke();
|
|
}
|
|
|
|
// Closes the Console
|
|
public void Close()
|
|
{
|
|
if (!IsOpen)
|
|
return;
|
|
|
|
IsOpen = false;
|
|
OnClose?.Invoke();
|
|
|
|
stopwatch.Restart();
|
|
}
|
|
|
|
// Clears the content of the Console
|
|
public void Clear()
|
|
{
|
|
consoleLines.Clear();
|
|
}
|
|
|
|
public void Execute(string str, bool bufferInput = false, bool noOutput = false)
|
|
{
|
|
if (bufferInput)
|
|
consoleBufferHistory.Insert(0, str);
|
|
|
|
str = str.Trim();
|
|
|
|
if (ShowExecutedLines && !noOutput)
|
|
Console.Print(LinePrefix + str);
|
|
|
|
string[] strs = str.Split(' ');
|
|
string execute = strs[0];
|
|
string executeLower = execute.ToLowerInvariant();
|
|
string value = strs.Length > 1 ? str.Substring(execute.Length + 1) : null;
|
|
|
|
//Console.PrintDebug("Executed '" + execute + "' with params: '" + value + "'");
|
|
|
|
try
|
|
{
|
|
if (consoleCommands.TryGetValue(executeLower, out ConsoleCommand cmd))
|
|
{
|
|
string[] values = strs.Skip(1).ToArray();
|
|
if (values.Length > 0)
|
|
cmd.Invoke(values);
|
|
else
|
|
cmd.Invoke();
|
|
//Console.Print("Command bound to '" + execute + "' is '" + cmd.method.Name + "'");
|
|
}
|
|
else if (consoleVariables.TryGetValue(executeLower, out ConsoleVariable cvar))
|
|
{
|
|
if (value != null)
|
|
cvar.SetValue(value);
|
|
|
|
if (!noOutput)
|
|
Console.Print("'" + execute + "' is '" + cvar.GetValueString() + "'");
|
|
}
|
|
else
|
|
{
|
|
Console.Print("Unknown command '" + execute + "'");
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
var message = e.InnerException != null ? e.InnerException.Message : e.Message;
|
|
Console.Print("Command failed: " + message);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
public string GetVariable(string variableName)
|
|
{
|
|
if (consoleVariables.TryGetValue(variableName, out ConsoleVariable cvar))
|
|
{
|
|
string value = cvar.GetValueString();
|
|
return value;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|
|
} |