using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; namespace Cabrito { public class ConsoleLine { public string content; internal ConsoleLine(string line) { content = line; } public static implicit operator string(ConsoleLine line) => line.content; public static explicit operator ConsoleLine(string line) => new ConsoleLine(line); public override string ToString() => content.ToString(); } public static class Console { private static ConsoleInstance instance; public static void Init() { Destroy(); instance = new ConsoleInstance(); instance.InitConsoleSubsystems(); } public static void Destroy() { if (instance != null) { instance.Dispose(); instance = null; } } // 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 { return instance.OnOpen; } set { instance.OnOpen = value; } } // Called when Console is closed. public static Action OnClose { get { return instance.OnClose; } set { instance.OnClose = value; } } // Called when a line of text was printed in Console. public static Action OnPrint { get { return 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; // 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, text); // Echoes developer/debug text to Console public static void PrintDebug(int verbosity, string text) => instance.PrintDebug(verbosity, 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) => instance.Execute(str); public static string GetVariable(string variableName) => instance.GetVariable(variableName); } public class ConsoleInstance : IDisposable { public bool IsOpen { get; internal set; } = true; public bool IsSafeToQuit { get { return stopwatch.Elapsed.TotalSeconds > 0.1; } } private Stopwatch stopwatch = Stopwatch.StartNew(); public Action OnOpen; public Action OnClose; public Action OnPrint; public bool ShowExecutedLines = true; public int DebugVerbosity { get; set; } = 1; public string LinePrefix { get; internal set; } = "]"; //private static List consoleLines = new List(); private List consoleLines = new List(); private Dictionary consoleCommands = new Dictionary(); private Dictionary consoleVariables = new Dictionary(); internal ConsoleInstance() { } // Initializes the Console system. internal void InitConsoleSubsystems() { AppDomain currentDomain = AppDomain.CurrentDomain; Assembly[] assemblies = currentDomain.GetAssemblies(); foreach (var assembly in assemblies) { // Skip common assemblies var 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 (var type in assembly.GetTypes()) { Dictionary cmdParsed = new Dictionary(); Dictionary> cmdMethods = new Dictionary>(); MethodInfo cmdInitializer = null; foreach (MethodInfo method in type.GetMethods()) { if (!method.IsStatic) continue; Attribute[] 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 (var 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]; var 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; Attribute[] 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 (var 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; Attribute[] 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 (var 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 void Dispose() { } public IReadOnlyCollection Lines { get => consoleLines.AsReadOnly(); } // Echoes text to Console public void Print(string text) { foreach (var line in text.Split(new[] { '\n' })) { ConsoleLine lineEntry = new ConsoleLine(line); consoleLines.Add(lineEntry); OnPrint?.Invoke(text); } } // Echoes warning text to Console public void PrintWarning(string text) { foreach (var line in text.Split(new[] { '\n' })) { ConsoleLine lineEntry = new ConsoleLine(line); consoleLines.Add(lineEntry); OnPrint?.Invoke(text); } } // Echoes error text to Console public void PrintError(string text) { foreach (var line in text.Split(new[] { '\n' })) { ConsoleLine lineEntry = new ConsoleLine(line); consoleLines.Add(lineEntry); OnPrint?.Invoke(text); } if (Debugger.IsAttached) Debugger.Break(); else throw new Exception(text); } // Echoes developer/debug text to Console public void PrintDebug(int verbosity, string text) { if (DebugVerbosity < verbosity) return; foreach (var line in text.Split(new[] { '\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) { str = str.Trim(); if (ShowExecutedLines) 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 + "'"); 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); Console.Print("'" + execute + "' is '" + cvar.GetValueString() + "'"); } else Console.Print("Unknown command '" + execute + "'"); } public string GetVariable(string variableName) { if (consoleVariables.TryGetValue(variableName, out ConsoleVariable cvar)) { string value = cvar.GetValueString(); return value; } return null; } } }