using UnityEngine; using System.Collections.Generic; using System.ComponentModel; using System; using Unity.DebugDisplay; using Unity.Sample.Core; public interface IConsoleUI { void Init(string buildId, string buildUnityVersion); void Shutdown(); void OutputString(string message); bool IsOpen(); void SetOpen(bool open); void ConsoleUpdate(); void ConsoleLateUpdate(); void SetPrompt(string prompt); } public class ConsoleNullUI : IConsoleUI { public void ConsoleUpdate() { } public void ConsoleLateUpdate() { } public void Init(string buildId, string buildUnityVersion) { } public void Shutdown() { } public bool IsOpen() { return false; } public void OutputString(string message) { } public void SetOpen(bool open) { } public void SetPrompt(string prompt) { } } public class Console { public delegate void MethodDelegate(string[] args); static IConsoleUI s_ConsoleUI; public static void Init(string buildId, string buildUnityVersion,IConsoleUI consoleUI) { GameDebug.Assert(s_ConsoleUI == null); s_ConsoleUI = consoleUI; s_ConsoleUI.Init(buildId, buildUnityVersion); AddCommand("help", CmdHelp, "Show available commands"); AddCommand("vars", CmdVars, "Show available variables"); AddCommand("wait", CmdWait, "Wait for next frame or level"); // AddCommand("waitload", CmdWaitLoad, "Wait for level load"); AddCommand("exec", CmdExec, "Executes commands from file"); Write("Console ready"); } public static void Shutdown() { s_ConsoleUI.Shutdown(); s_ConsoleUI = null; } static void OutputString(string message) { if (s_ConsoleUI != null) s_ConsoleUI.OutputString(message); } static string lastMsg = ""; static double timeLastMsg; public static void Write(string msg) { // Have to condition on cvar being null as this may run before cvar system is initialized if (consoleShowLastLine != null && consoleShowLastLine.IntValue > 0) { lastMsg = msg; timeLastMsg = s_FrameTime; } OutputString(msg); } public static void AddCommand(string name, MethodDelegate method, string description, int tag = 0) { name = name.ToLower(); if (s_Commands.ContainsKey(name)) { OutputString("Cannot add command " + name + " twice"); return; } s_Commands.Add(name, new ConsoleCommand(name, method, description, tag)); } public static bool RemoveCommand(string name) { return s_Commands.Remove(name.ToLower()); } public static void RemoveCommandsWithTag(int tag) { var removals = new List(); foreach (var c in s_Commands) { if (c.Value.tag == tag) removals.Add(c.Key); } foreach (var c in removals) RemoveCommand(c); } public static void ProcessCommandLineArguments(string[] arguments) { // Process arguments that have '+' prefix as console commands. Ignore all other arguments Debug.Log("ProcessCommandLineArguments: " + string.Join(" ", arguments)); var commands = new List(); foreach (var argument in arguments) { var newCommandStarting = argument.StartsWith("+") || argument.StartsWith("-"); // Skip leading arguments before we have seen '-' or '+' if (commands.Count == 0 && !newCommandStarting) continue; if (newCommandStarting) commands.Add(argument); else commands[commands.Count - 1] += " " + argument; } foreach (var command in commands) { Debug.Log("Command " + command); if (command.StartsWith("+")) EnqueueCommandNoHistory(command.Substring(1)); } } public static bool IsOpen() { return s_ConsoleUI.IsOpen(); } public static void SetOpen(bool open) { s_ConsoleUI.SetOpen(open); } public static void SetPrompt(string prompt) { s_ConsoleUI.SetPrompt(prompt); } public static void SetFrameTime(double frameTime) { s_FrameTime = frameTime; } public static void ConsoleUpdate() { var lastMsgTime = s_FrameTime - timeLastMsg; if (lastMsgTime < 1.0) Overlay.Managed.Write(0, 0, lastMsg); s_ConsoleUI.ConsoleUpdate(); while (s_PendingCommands.Count > 0) { // if (s_PendingCommandsWaitForFrames > 0) // { // s_PendingCommandsWaitForFrames--; // break; // } // if (s_PendingCommandsWaitForLoad) // { // if (!isCurrentLevelLoaded) // break; // s_PendingCommandsWaitForLoad = false; // } // Remove before executing as we may hit an 'exec' command that wants to insert commands var cmd = s_PendingCommands[0]; s_PendingCommands.RemoveAt(0); ExecuteCommand(cmd); } } public static void ConsoleLateUpdate() { s_ConsoleUI.ConsoleLateUpdate(); } static void SkipWhite(string input, ref int pos) { while (pos < input.Length && " \t".IndexOf(input[pos]) > -1) { pos++; } } static string ParseQuoted(string input, ref int pos) { pos++; int startPos = pos; while (pos < input.Length) { if (input[pos] == '"' && input[pos - 1] != '\\') { pos++; return input.Substring(startPos, pos - startPos - 1); } pos++; } return input.Substring(startPos); } static string Parse(string input, ref int pos) { int startPos = pos; while (pos < input.Length) { if (" \t".IndexOf(input[pos]) > -1) { return input.Substring(startPos, pos - startPos); } pos++; } return input.Substring(startPos); } static List Tokenize(string input) { var pos = 0; var res = new List(); var c = 0; while (pos < input.Length && c++ < 10000) { SkipWhite(input, ref pos); if (pos == input.Length) break; if (input[pos] == '"' && (pos == 0 || input[pos - 1] != '\\')) { res.Add(ParseQuoted(input, ref pos)); } else res.Add(Parse(input, ref pos)); } return res; } public static void ExecuteCommand(string command) { var tokens = Tokenize(command); if (tokens.Count < 1) return; OutputString('>' + command); var commandName = tokens[0].ToLower(); ConsoleCommand consoleCommand; ConfigVar configVar; if (s_Commands.TryGetValue(commandName, out consoleCommand)) { var arguments = tokens.GetRange(1, tokens.Count - 1).ToArray(); consoleCommand.method(arguments); } else if (ConfigVar.ConfigVars.TryGetValue(commandName, out configVar)) { if (tokens.Count == 2) { configVar.Value = tokens[1]; } else if (tokens.Count == 1) { // Print value OutputString(string.Format("{0} = {1}", configVar.name, configVar.Value)); } else { OutputString("Too many arguments"); } } else { OutputString("Unknown command: " + tokens[0]); } } static void CmdHelp(string[] arguments) { OutputString("Available commands:"); foreach (var c in s_Commands) OutputString(c.Value.name + ": " + c.Value.description); } static void CmdVars(string[] arguments) { var varNames = new List(ConfigVar.ConfigVars.Keys); varNames.Sort(); foreach (var v in varNames) { var cv = ConfigVar.ConfigVars[v]; OutputString(string.Format("{0} = {1}", cv.name, cv.Value)); } } static void CmdWait(string[] arguments) { if (arguments.Length == 0) { s_PendingCommandsWaitForFrames = 1; } else if (arguments.Length == 1) { int f = 0; if (int.TryParse(arguments[0], out f)) { s_PendingCommandsWaitForFrames = f; } } else { OutputString("Usage: wait [n] \nWait for next n frames. Default is 1\n"); } } // TODO (mogensh) Can we somehow define these outside console as they depend on level load // static void CmdWaitLoad(string[] arguments) // { // if (arguments.Length != 0) // { // OutputString("Usage: waitload\nWait for level load\n"); // return; // } // if (!Game.game.levelManager.IsLoadingLevel()) // { // OutputString("waitload: not loading level; ignoring\n"); // return; // } // s_PendingCommandsWaitForLoad = true; // } static void CmdExec(string[] arguments) { bool silent = false; string filename = ""; if (arguments.Length == 1) { filename = arguments[0]; } else if (arguments.Length == 2 && arguments[0] == "-s") { silent = true; filename = arguments[1]; } else { OutputString("Usage: exec [-s] "); return; } try { var lines = System.IO.File.ReadAllLines(filename); s_PendingCommands.InsertRange(0, lines); if (s_PendingCommands.Count > 128) { s_PendingCommands.Clear(); OutputString("Command overflow. Flushing pending commands!!!"); } } catch (Exception e) { if (!silent) OutputString("Exec failed: " + e.Message); } } public static void EnqueueCommandNoHistory(string command) { GameDebug.Log("cmd: " + command); if (s_PendingCommands.Contains(command)) { GameDebug.Log("Duplicate command found! Not Enqueuing " + command); return; } s_PendingCommands.Add(command); } public static void EnqueueCommand(string command) { s_History[s_HistoryNextIndex % k_HistoryCount] = command; s_HistoryNextIndex++; s_HistoryIndex = s_HistoryNextIndex; EnqueueCommandNoHistory(command); } public static string TabComplete(string prefix) { // Look for possible tab completions List matches = new List(); foreach (var c in s_Commands) { var name = c.Key; if (!name.StartsWith(prefix, true, null)) continue; matches.Add(name); } foreach (var v in ConfigVar.ConfigVars) { var name = v.Key; if (!name.StartsWith(prefix, true, null)) continue; matches.Add(name); } if (matches.Count == 0) return prefix; // Look for longest common prefix int lcp = matches[0].Length; for (var i = 0; i < matches.Count - 1; i++) { lcp = Mathf.Min(lcp, CommonPrefix(matches[i], matches[i + 1])); } prefix += matches[0].Substring(prefix.Length, lcp - prefix.Length); if (matches.Count > 1) { // write list of possible completions for (var i = 0; i < matches.Count; i++) Console.Write(" " + matches[i]); } else { prefix += " "; } return prefix; } public static string HistoryUp(string current) { if (s_HistoryIndex == 0 || s_HistoryNextIndex - s_HistoryIndex >= k_HistoryCount - 1) return ""; if (s_HistoryIndex == s_HistoryNextIndex) { s_History[s_HistoryIndex % k_HistoryCount] = current; } s_HistoryIndex--; return s_History[s_HistoryIndex % k_HistoryCount]; } public static string HistoryDown() { if (s_HistoryIndex == s_HistoryNextIndex) return ""; s_HistoryIndex++; return s_History[s_HistoryIndex % k_HistoryCount]; } // Returns length of largest common prefix of two strings static int CommonPrefix(string a, string b) { int minl = Mathf.Min(a.Length, b.Length); for (int i = 1; i <= minl; i++) { if (!a.StartsWith(b.Substring(0, i), true, null)) return i - 1; } return minl; } class ConsoleCommand { public string name; public MethodDelegate method; public string description; public int tag; public ConsoleCommand(string name, MethodDelegate method, string description, int tag) { this.name = name; this.method = method; this.description = description; this.tag = tag; } } #pragma warning disable 649 [ConfigVar(Name = "config.showlastline", DefaultValue = "0", Description = "Show last logged line briefly at top of screen")] static ConfigVar consoleShowLastLine; #pragma warning restore 649 static double s_FrameTime; static List s_PendingCommands = new List(); public static int s_PendingCommandsWaitForFrames = 0; // public static bool s_PendingCommandsWaitForLoad = false; static Dictionary s_Commands = new Dictionary(); const int k_HistoryCount = 50; static string[] s_History = new string[k_HistoryCount]; static int s_HistoryNextIndex = 0; static int s_HistoryIndex = 0; }