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 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 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 consoleBufferHistory = new List(); private readonly Dictionary consoleCommands = new Dictionary(); //private static List consoleLines = new List(); private readonly List consoleLines = new List(); private readonly Dictionary consoleVariables = new Dictionary(); private readonly Stopwatch stopwatch = Stopwatch.StartNew(); // Echoes developer/debug text to Console private string debugLastLine = ""; public Action OnClose; public Action OnOpen; public Action 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 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(); var cmdMethods = new Dictionary>(); 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 methods; if (!cmdMethods.TryGetValue(cmdAttribute.name, out methods)) { methods = new List(); 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 aliasMethods; if (!cmdMethods.TryGetValue(alias, out aliasMethods)) { aliasMethods = new List(); 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; } } }