From 59595d2c9adb7cd0bbe3c0b0127bb1d2a0324050 Mon Sep 17 00:00:00 2001 From: ShinodaNaoki Date: Mon, 12 Feb 2018 21:44:54 +0900 Subject: [PATCH 1/5] Create CUDLR_Relay.jslib --- WebGL/CUDLR_Relay.jslib | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 WebGL/CUDLR_Relay.jslib diff --git a/WebGL/CUDLR_Relay.jslib b/WebGL/CUDLR_Relay.jslib new file mode 100644 index 0000000..e45715b --- /dev/null +++ b/WebGL/CUDLR_Relay.jslib @@ -0,0 +1,37 @@ +var CUDLR_Relay = { + $cr_callback: function(){}, + + SetCudlrCallbackAndCreateInput: function(callbackPtr){ + cr_callback = callbackPtr; + + window.cc = function(command) { + var size = lengthBytesUTF8(command) + 1; + var buffer = _malloc(size); + stringToUTF8(command, buffer, size); + + var result = Runtime.dynCall('ii', cr_callback, [buffer]); + var text = UTF8ToString(result); + _free(buffer); + + return text; + } + + document.onclick = function(){ + if(!window.event.getModifierState("Control") + || !window.event.getModifierState("Alt")) return true; + + var text = cc(prompt()); + console.log(text); + + return true; + }; + + }, + + ShowLog: function(text){ + console.log(text); + }, + +} +autoAddDeps(CUDLR_Relay, '$cr_callback'); +mergeInto(LibraryManager.library, CUDLR_Relay); From eaacb7d2ad77e6cf3e77efc9d02cc2bc4815917f Mon Sep 17 00:00:00 2001 From: ShinodaNaoki Date: Mon, 12 Feb 2018 21:47:19 +0900 Subject: [PATCH 2/5] Implement WebGL console command support I made command support on WebGL build. Ctrl+Alt+Click anywhere blank to open prompt and Input command. Then the result will be shown in browser console. --- Console.cs | 388 +++++++++++++++++++++++++++++++++++++++++++++++++ Server.cs | 306 ++++++++++++++++++++++++++++++++++++++ WebGL_Relay.cs | 54 +++++++ 3 files changed, 748 insertions(+) create mode 100644 Console.cs create mode 100644 Server.cs create mode 100644 WebGL_Relay.cs diff --git a/Console.cs b/Console.cs new file mode 100644 index 0000000..c59954b --- /dev/null +++ b/Console.cs @@ -0,0 +1,388 @@ +using UnityEngine; +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using System.Linq; +using System.Reflection; +using System.Net; +using System.Collections; + +namespace CUDLR { + + struct QueuedCommand { + public CommandAttribute command; + public string[] args; + } + + public class Console { + + // Max number of lines in the console output + const int MAX_LINES = 100; + + // Maximum number of commands stored in the history + const int MAX_HISTORY = 50; + + // Prefix for user inputted command + const string COMMAND_OUTPUT_PREFIX = "> "; + + private static Console instance; + private CommandTree m_commands; + private List m_output; + private List m_history; + private Queue m_commandQueue; + + private Console() { + m_commands = new CommandTree(); + m_output = new List(); + m_history = new List(); + m_commandQueue = new Queue(); + + RegisterAttributes(); + } + + public static Console Instance { + get { + if (instance == null) instance = new Console(); + return instance; + } + } + + public static void Update() { + while (Instance.m_commandQueue.Count > 0) { + QueuedCommand cmd = Instance.m_commandQueue.Dequeue(); + cmd.command.m_callback( cmd.args ); + } + } + + /* Queue a command to be executed on update on the main thread */ + public static void Queue(CommandAttribute command, string[] args) { + QueuedCommand queuedCommand = new QueuedCommand(); + queuedCommand.command = command; + queuedCommand.args = args; + Instance.m_commandQueue.Enqueue( queuedCommand ); + } + + /* Execute a command */ + public static void Run(string str) { +#if UNITY_WEBGL && !UNITY_EDITOR + Instance.m_output.Clear(); +#endif + if (str.Length > 0) { + LogCommand(str); + Instance.RecordCommand(str); + Instance.m_commands.Run(str); + } + } + +#if UNITY_WEBGL && !UNITY_EDITOR + public static string GetResult() + { + Update(); + return string.Join("\n", Instance.m_output.ToArray()); + } +#endif + + /* Clear all output from console */ + [Command("clear", "clears console output", false)] + public static void Clear() { + Instance.m_output.Clear(); + } + + /* Print a list of all console commands */ + [Command("help", "prints commands", false)] + public static void Help() { + + string help = "Commands:"; + foreach (CommandAttribute cmd in Instance.m_commands.OrderBy(m=>m.m_command)) { + help += string.Format("\n{0} : {1}", cmd.m_command, cmd.m_help); + } + + Log("" + help + ""); + } + + /* Find command based on partial string */ + public static string Complete(string partialCommand) { + return Instance.m_commands.Complete( partialCommand ); + } + + /* Logs user input to output */ + public static void LogCommand(string cmd) { + Log(COMMAND_OUTPUT_PREFIX+cmd); + } + + /* Logs string to output */ + public static void Log(string str) { +#if UNITY_WEBGL && !UNITY_EDITOR + Instance.m_output.Add(str); +#else + LogCore(str); +#endif + } + + private static void LogCore(string str) + { +#if !UNITY_WEBGL || UNITY_EDITOR + Instance.m_output.Add(str); + if (Instance.m_output.Count > MAX_LINES) + Instance.m_output.RemoveAt(0); +#endif + } + + /* Callback for Unity logging */ + public static void LogCallback (string logString, string stackTrace, LogType type) { + if (type != LogType.Log) { +#if UNITY_WEBGL && !UNITY_EDITOR + Console.LogCore(logString); + Console.LogCore(stackTrace); +#else + Console.LogCore("" + logString); + Console.LogCore(stackTrace + ""); +#endif + } + else { + Console.LogCore(logString); + } + } + + /* Returns the output */ + public static string Output() { + return string.Join("\n", Instance.m_output.ToArray()); + } + + /* Register a new console command */ + public static void RegisterCommand(string command, string desc, CommandAttribute.Callback callback, bool runOnMainThread = true) { + if (command == null || command.Length == 0) { + throw new Exception("Command String cannot be empty"); + } + + CommandAttribute cmd = new CommandAttribute(command, desc, runOnMainThread); + cmd.m_callback = callback; + + Instance.m_commands.Add(cmd); + } + + private void RegisterAttributes() { + foreach(Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) { + // HACK: IL2CPP crashes if you attempt to get the methods of some classes in these assemblies. + if (assembly.FullName.StartsWith("System") || assembly.FullName.StartsWith("mscorlib")) { + continue; + } + foreach(Type type in assembly.GetTypes()) { + // FIXME add support for non-static methods (FindObjectByType?) + foreach(MethodInfo method in type.GetMethods(BindingFlags.Public|BindingFlags.Static)) { + CommandAttribute[] attrs = method.GetCustomAttributes(typeof(CommandAttribute), true) as CommandAttribute[]; + if (attrs.Length == 0) + continue; + + CommandAttribute.Callback cb = Delegate.CreateDelegate(typeof(CommandAttribute.Callback), method, false) as CommandAttribute.Callback; + if (cb == null) + { + CommandAttribute.CallbackSimple cbs = Delegate.CreateDelegate(typeof(CommandAttribute.CallbackSimple), method, false) as CommandAttribute.CallbackSimple; + if (cbs != null) { + cb = delegate(string[] args) { + cbs(); + }; + } + } + + if (cb == null) { + Debug.LogError(string.Format("Method {0}.{1} takes the wrong arguments for a console command.", type, method.Name)); + continue; + } + + // try with a bare action + foreach(CommandAttribute cmd in attrs) { + if (string.IsNullOrEmpty(cmd.m_command)) { + Debug.LogError(string.Format("Method {0}.{1} needs a valid command name.", type, method.Name)); + continue; + } + + cmd.m_callback = cb; + m_commands.Add(cmd); + } + } + } + } + } + + /* Get a previously ran command from the history */ + public static string PreviousCommand(int index) { + return index >= 0 && index < Instance.m_history.Count ? Instance.m_history[index] : null; + } + + /* Update history with a new command */ + private void RecordCommand(string command) { + m_history.Insert(0, command); + if (m_history.Count > MAX_HISTORY) + m_history.RemoveAt(m_history.Count - 1); + } + + // Our routes + [Route("^/console/out$")] + public static void Output(RequestContext context) { + context.Response.WriteString(Console.Output()); + } + + [Route("^/console/run$")] + public static void Run(RequestContext context) { + string command = Uri.UnescapeDataString(context.Request.QueryString.Get("command")); + if (!string.IsNullOrEmpty(command)) + Console.Run(command); + + context.Response.StatusCode = (int)HttpStatusCode.OK; + context.Response.StatusDescription = "OK"; + } + + [Route("^/console/commandHistory$")] + public static void History(RequestContext context) { + string index = context.Request.QueryString.Get("index"); + + string previous = null; + if (!string.IsNullOrEmpty(index)) + previous = Console.PreviousCommand(System.Int32.Parse(index)); + + context.Response.WriteString(previous); + } + + [Route("^/console/complete$")] + public static void Complete(RequestContext context) { + string partialCommand = context.Request.QueryString.Get("command"); + + string found = null; + if (partialCommand != null) + found = Console.Complete(partialCommand); + + context.Response.WriteString(found); + } + } + + class CommandTree : IEnumerable { + + private Dictionary m_subcommands; + private CommandAttribute m_command; + + public CommandTree() { + m_subcommands = new Dictionary(); + } + + public void Add(CommandAttribute cmd) { + _add(cmd.m_command.ToLower().Split(' '), 0, cmd); + } + + private void _add(string[] commands, int command_index, CommandAttribute cmd) { + if (commands.Length == command_index) { + m_command = cmd; + return; + } + + string token = commands[command_index]; + if (!m_subcommands.ContainsKey(token)){ + m_subcommands[token] = new CommandTree(); + } + m_subcommands[token]._add(commands, command_index + 1, cmd); + } + + public IEnumerator GetEnumerator() { + if (m_command != null && m_command.m_command != null) + yield return m_command; + + foreach(KeyValuePair entry in m_subcommands) { + foreach(CommandAttribute cmd in entry.Value) { + if (cmd != null && cmd.m_command != null) + yield return cmd; + } + } + } + + IEnumerator IEnumerable.GetEnumerator() { + return GetEnumerator(); + } + + public string Complete(string partialCommand) { + return _complete(partialCommand.Split(' '), 0, ""); + } + + public string _complete(string[] partialCommand, int index, string result) { + if (partialCommand.Length == index && m_command != null) { + // this is a valid command... so we do nothing + return result; + } else if (partialCommand.Length == index) { + // This is valid but incomplete.. print all of the subcommands + Console.LogCommand(result); + foreach (string key in m_subcommands.Keys.OrderBy(m=>m)) { + Console.Log(result + " " + key); + } + return result + " "; + } else if (partialCommand.Length == (index+1)) { + string partial = partialCommand[index]; + if (m_subcommands.ContainsKey(partial)) { + result += partial; + return m_subcommands[partial]._complete(partialCommand, index+1, result); + } + + // Find any subcommands that match our partial command + List matches = new List(); + foreach (string key in m_subcommands.Keys.OrderBy(m=>m)) { + if (key.StartsWith(partial)) { + matches.Add(key); + } + } + + if (matches.Count == 1) { + // Only one command found, log nothing and return the complete command for the user input + return result + matches[0] + " "; + } else if (matches.Count > 1) { + // list all the options for the user and return partial + Console.LogCommand(result + partial); + foreach (string match in matches) { + Console.Log(result + match); + } + } + return result + partial; + } + + string token = partialCommand[index]; + if (!m_subcommands.ContainsKey(token)) { + return result; + } + result += token + " "; + return m_subcommands[token]._complete( partialCommand, index + 1, result ); + } + + public void Run(string commandStr) { + // Split user input on spaces ignoring anything in qoutes + Regex regex = new Regex(@""".*?""|[^\s]+"); + MatchCollection matches = regex.Matches(commandStr); + string[] tokens = new string[matches.Count]; + for (int i = 0; i < tokens.Length; ++i) { + tokens[i] = matches[i].Value.Replace("\"",""); + } + _run(tokens, 0); + } + + static string[] emptyArgs = new string[0]{}; + private void _run(string[] commands, int index) { + if (commands.Length == index) { + RunCommand(emptyArgs); + return; + } + + string token = commands[index].ToLower(); + if (!m_subcommands.ContainsKey(token)) { + RunCommand(commands.Skip(index).ToArray()); + return; + } + m_subcommands[token]._run(commands, index + 1); + } + + private void RunCommand(string[] args) { + if (m_command == null) { + Console.Log("command not found"); + } else if (m_command.m_runOnMainThread) { + Console.Queue( m_command, args ); + } else { + m_command.m_callback(args); + } + } + } +} diff --git a/Server.cs b/Server.cs new file mode 100644 index 0000000..9ed2a94 --- /dev/null +++ b/Server.cs @@ -0,0 +1,306 @@ +using UnityEngine; +using System; +using System.IO; +using System.Net; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Text.RegularExpressions; +using System.Reflection; +using System.Linq; +using System.Threading; + +namespace CUDLR { + + public class RequestContext + { + public HttpListenerContext context; + public Match match; + public bool pass; + public string path; + public int currentRoute; + + public HttpListenerRequest Request { get { return context.Request; } } + public HttpListenerResponse Response { get { return context.Response; } } + + public RequestContext(HttpListenerContext ctx) + { +#if !UNITY_WEBGL || UNITY_EDITOR + context = ctx; + match = null; + pass = false; + path = WWW.UnEscapeURL(context.Request.Url.AbsolutePath); + if (path == "/") + path = "/index.html"; + currentRoute = 0; +#endif + } + } + + + public class Server : MonoBehaviour { + + [SerializeField] + public int Port = 55055; + + [SerializeField] + public bool RegisterLogCallback = false; + + private static Thread mainThread; + private static string fileRoot; + private static HttpListener listener; + private static List registeredRoutes; + private static Queue mainRequests = new Queue(); + + // List of supported files + // FIXME add an api to register new types + private static Dictionary fileTypes = new Dictionary { + {"js", "application/javascript"}, + {"json", "application/json"}, + {"jpg", "image/jpeg" }, + {"jpeg", "image/jpeg"}, + {"gif", "image/gif"}, + {"png", "image/png"}, + {"css", "text/css"}, + {"htm", "text/html"}, + {"html", "text/html"}, + {"ico", "image/x-icon"}, + }; + + public virtual void Awake() { +#if UNITY_WEBGL && !UNITY_EDITOR + WebGL_Relay.Initialize(); +#endif + mainThread = Thread.CurrentThread; + fileRoot = Path.Combine(Application.streamingAssetsPath, "CUDLR"); + +#if !UNITY_WEBGL || UNITY_EDITOR + + // Start server + Debug.Log("Starting CUDLR Server on port : " + Port); + listener = new HttpListener(); + listener.Prefixes.Add("http://*:"+Port+"/"); + listener.Start(); + listener.BeginGetContext(ListenerCallback, null); + + StartCoroutine(HandleRequests()); +#endif + } + + public void OnApplicationPause(bool paused) { + if (paused) { + listener.Stop(); + } + else { + listener.Start(); + listener.BeginGetContext(ListenerCallback, null); + } + } + + public virtual void OnDestroy() { + listener.Close(); + listener = null; + } + + private void RegisterRoutes() { + if ( registeredRoutes == null ) { + registeredRoutes = new List(); + + foreach(Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) { + foreach(Type type in assembly.GetTypes()) { + // FIXME add support for non-static methods (FindObjectByType?) + foreach(MethodInfo method in type.GetMethods(BindingFlags.Public|BindingFlags.Static)) { + RouteAttribute[] attrs = method.GetCustomAttributes(typeof(RouteAttribute), true) as RouteAttribute[]; + if (attrs.Length == 0) + continue; + + RouteAttribute.Callback cbm = Delegate.CreateDelegate(typeof(RouteAttribute.Callback), method, false) as RouteAttribute.Callback; + if (cbm == null) { + Debug.LogError(string.Format("Method {0}.{1} takes the wrong arguments for a console route.", type, method.Name)); + continue; + } + + // try with a bare action + foreach(RouteAttribute route in attrs) { + if (route.m_route == null) { + Debug.LogError(string.Format("Method {0}.{1} needs a valid route regexp.", type, method.Name)); + continue; + } + + route.m_callback = cbm; + registeredRoutes.Add(route); + } + } + } + } + RegisterFileHandlers(); + } + } + + static void FindFileType(RequestContext context, bool download, out string path, out string type) { + path = Path.Combine(fileRoot, context.match.Groups[1].Value); + + string ext = Path.GetExtension(path).ToLower().TrimStart(new char[] {'.'}); + if (download || !fileTypes.TryGetValue(ext, out type)) + type = "application/octet-stream"; + } + + + public delegate void FileHandlerDelegate(RequestContext context, bool download); + static void WWWFileHandler(RequestContext context, bool download) { + string path, type; + FindFileType(context, download, out path, out type); + + WWW req = new WWW(path); + while (!req.isDone) { + Thread.Sleep(0); + } + + if (string.IsNullOrEmpty(req.error)) { + context.Response.ContentType = type; + if (download) + context.Response.AddHeader("Content-disposition", string.Format("attachment; filename={0}", Path.GetFileName(path))); + + context.Response.WriteBytes(req.bytes); + return; + } + + if (req.error.StartsWith("Couldn't open file")) { + context.pass = true; + } + else { + context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; + context.Response.StatusDescription = string.Format("Fatal error:\n{0}", req.error); + } + } + + static void FileHandler(RequestContext context, bool download) { + string path, type; + FindFileType(context, download, out path, out type); + + if (File.Exists(path)) { + context.Response.WriteFile(path, type, download); + } + else { + context.pass = true; + } + } + + static void RegisterFileHandlers() { + string pattern = string.Format("({0})", string.Join("|", fileTypes.Select(x => x.Key).ToArray())); + RouteAttribute downloadRoute = new RouteAttribute(string.Format(@"^/download/(.*\.{0})$", pattern)); + RouteAttribute fileRoute = new RouteAttribute(string.Format(@"^/(.*\.{0})$", pattern)); + + bool needs_www = fileRoot.Contains("://"); + downloadRoute.m_runOnMainThread = needs_www; + fileRoute.m_runOnMainThread = needs_www; + + FileHandlerDelegate callback = FileHandler; + if (needs_www) + callback = WWWFileHandler; + + downloadRoute.m_callback = delegate(RequestContext context) { callback(context, true); }; + fileRoute.m_callback = delegate(RequestContext context) { callback(context, false); }; + + registeredRoutes.Add(downloadRoute); + registeredRoutes.Add(fileRoute); + } + +#if !UNITY_WEBGL || UNITY_EDITOR + void OnEnable() { + if (RegisterLogCallback) { + // Capture Console Logs +#if UNITY_5_3_OR_NEWER + Application.logMessageReceived += Console.LogCallback; +#else + Application.RegisterLogCallback(Console.LogCallback); +#endif + } + } + + void OnDisable() { + if (RegisterLogCallback) { +#if UNITY_5_3_OR_NEWER + Application.logMessageReceived -= Console.LogCallback; +#else + Application.RegisterLogCallback(null); +#endif + } + } +#endif + + void Update() { + Console.Update(); + } + + void ListenerCallback(IAsyncResult result) { + RequestContext context = new RequestContext(listener.EndGetContext(result)); + + HandleRequest(context); + + if (listener.IsListening) { + listener.BeginGetContext(ListenerCallback, null); + } + } + + void HandleRequest(RequestContext context) { + RegisterRoutes(); + + try { + bool handled = false; + + for (; context.currentRoute < registeredRoutes.Count; ++context.currentRoute) { + RouteAttribute route = registeredRoutes[context.currentRoute]; + Match match = route.m_route.Match(context.path); + if (!match.Success) + continue; + + if (!route.m_methods.IsMatch(context.Request.HttpMethod)) + continue; + + // Upgrade to main thread if necessary + if (route.m_runOnMainThread && Thread.CurrentThread != mainThread) { + lock (mainRequests) { + mainRequests.Enqueue(context); + } + return; + } + + context.match = match; + route.m_callback(context); + handled = !context.pass; + if (handled) + break; + } + + if (!handled) { + context.Response.StatusCode = (int)HttpStatusCode.NotFound; + context.Response.StatusDescription = "Not Found"; + } + } + catch (Exception exception) { + context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; + context.Response.StatusDescription = string.Format("Fatal error:\n{0}", exception); + + Debug.LogException(exception); + } + + context.Response.OutputStream.Close(); + } + + IEnumerator HandleRequests() { + while (true) { + while (mainRequests.Count == 0) { + yield return new WaitForEndOfFrame(); + } + + RequestContext context = null; + lock (mainRequests) { + context = mainRequests.Dequeue(); + } + + HandleRequest(context); + } + } + } +} diff --git a/WebGL_Relay.cs b/WebGL_Relay.cs new file mode 100644 index 0000000..ae3bb79 --- /dev/null +++ b/WebGL_Relay.cs @@ -0,0 +1,54 @@ +using AOT; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using UnityEngine; + +namespace CUDLR +{ + /// + /// WebGLとCUDLR.Consoleを中継する + /// + public static class WebGL_Relay + { + /// + /// 初期化。ブラウザ側 javascript にコールバック設定 + /// + public static void Initialize() + { +#if UNITY_WEBGL && !UNITY_EDITOR + Debug.LogFormat("Initialize CUDLR WebGL Relay"); + SetCudlrCallbackAndCreateInput(RunCommand); + Console.Update(); +#endif + } + + public static void Log(string text) + { +#if UNITY_WEBGL && !UNITY_EDITOR + ShowLog(text); +#endif + } + +#if UNITY_WEBGL && !UNITY_EDITOR + /// + /// 汎用コマンド実行コールバック + /// + /// + [MonoPInvokeCallback(typeof(Func))] + static private string RunCommand(string commandline) + { + Console.Run(commandline); + return Console.GetResult(); + } + + [DllImport("__Internal")] + static extern void SetCudlrCallbackAndCreateInput(Func callback); + + [DllImport("__Internal")] + static extern string ShowLog(string text); +#endif + } +} From 94435457acbf4246ca4a77c095fb27054e6b110d Mon Sep 17 00:00:00 2001 From: nao Date: Mon, 12 Feb 2018 21:53:15 +0900 Subject: [PATCH 3/5] Implement WebGL console command support --- CUDLR/Scripts/Console.cs | 44 +- CUDLR/Scripts/Server.cs | 10 + .../Scripts/WebGL_Relay.cs | 108 ++--- Console.cs | 388 ------------------ Server.cs | 306 -------------- 5 files changed, 100 insertions(+), 756 deletions(-) rename WebGL_Relay.cs => CUDLR/Scripts/WebGL_Relay.cs (96%) delete mode 100644 Console.cs delete mode 100644 Server.cs diff --git a/CUDLR/Scripts/Console.cs b/CUDLR/Scripts/Console.cs index ece8b7f..5f240cf 100644 --- a/CUDLR/Scripts/Console.cs +++ b/CUDLR/Scripts/Console.cs @@ -1,11 +1,11 @@ using UnityEngine; using System; -using System.Collections; using System.Collections.Generic; using System.Text.RegularExpressions; using System.Linq; using System.Reflection; using System.Net; +using System.Collections; namespace CUDLR { @@ -64,6 +64,9 @@ public static void Queue(CommandAttribute command, string[] args) { /* Execute a command */ public static void Run(string str) { +#if UNITY_WEBGL && !UNITY_EDITOR + Instance.m_output.Clear(); +#endif if (str.Length > 0) { LogCommand(str); Instance.RecordCommand(str); @@ -71,6 +74,14 @@ public static void Run(string str) { } } +#if UNITY_WEBGL && !UNITY_EDITOR + public static string GetResult() + { + Update(); + return string.Join("\n", Instance.m_output.ToArray()); + } +#endif + /* Clear all output from console */ [Command("clear", "clears console output", false)] public static void Clear() { @@ -101,18 +112,35 @@ public static void LogCommand(string cmd) { /* Logs string to output */ public static void Log(string str) { +#if UNITY_WEBGL && !UNITY_EDITOR + Instance.m_output.Add(str); +#else + LogCore(str); +#endif + } + + private static void LogCore(string str) + { +#if !UNITY_WEBGL || UNITY_EDITOR Instance.m_output.Add(str); if (Instance.m_output.Count > MAX_LINES) Instance.m_output.RemoveAt(0); +#endif } /* Callback for Unity logging */ public static void LogCallback (string logString, string stackTrace, LogType type) { - if (type != LogType.Log) { - Console.Log("" + logString); - Console.Log(stackTrace + ""); - } else { - Console.Log(logString); + if (type != LogType.Log) { +#if UNITY_WEBGL && !UNITY_EDITOR + Console.LogCore(logString); + Console.LogCore(stackTrace); +#else + Console.LogCore("" + logString); + Console.LogCore(stackTrace + ""); +#endif + } + else { + Console.LogCore(logString); } } @@ -174,7 +202,7 @@ private void RegisterAttributes() { } } } - } + } } /* Get a previously ran command from the history */ @@ -333,7 +361,7 @@ public void Run(string commandStr) { } static string[] emptyArgs = new string[0]{}; - private void _run(string[] commands, int index) { + private void _run(string[] commands, int index) { if (commands.Length == index) { RunCommand(emptyArgs); return; diff --git a/CUDLR/Scripts/Server.cs b/CUDLR/Scripts/Server.cs index 2d14c71..09fbe07 100644 --- a/CUDLR/Scripts/Server.cs +++ b/CUDLR/Scripts/Server.cs @@ -25,6 +25,7 @@ public class RequestContext public RequestContext(HttpListenerContext ctx) { +#if !UNITY_WEBGL || UNITY_EDITOR context = ctx; match = null; pass = false; @@ -32,6 +33,7 @@ public RequestContext(HttpListenerContext ctx) if (path == "/") path = "/index.html"; currentRoute = 0; +#endif } } @@ -66,9 +68,14 @@ public class Server : MonoBehaviour { }; public virtual void Awake() { +#if UNITY_WEBGL && !UNITY_EDITOR + WebGL_Relay.Initialize(); +#endif mainThread = Thread.CurrentThread; fileRoot = Path.Combine(Application.streamingAssetsPath, "CUDLR"); +#if !UNITY_WEBGL || UNITY_EDITOR + // Start server Debug.Log("Starting CUDLR Server on port : " + Port); listener = new HttpListener(); @@ -77,6 +84,7 @@ public virtual void Awake() { listener.BeginGetContext(ListenerCallback, null); StartCoroutine(HandleRequests()); +#endif } public void OnApplicationPause(bool paused) { @@ -198,6 +206,7 @@ static void RegisterFileHandlers() { registeredRoutes.Add(fileRoute); } +#if !UNITY_WEBGL || UNITY_EDITOR void OnEnable() { if (RegisterLogCallback) { // Capture Console Logs @@ -218,6 +227,7 @@ void OnDisable() { #endif } } +#endif void Update() { Console.Update(); diff --git a/WebGL_Relay.cs b/CUDLR/Scripts/WebGL_Relay.cs similarity index 96% rename from WebGL_Relay.cs rename to CUDLR/Scripts/WebGL_Relay.cs index ae3bb79..8209de2 100644 --- a/WebGL_Relay.cs +++ b/CUDLR/Scripts/WebGL_Relay.cs @@ -1,54 +1,54 @@ -using AOT; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.InteropServices; -using System.Text; -using UnityEngine; - -namespace CUDLR -{ - /// - /// WebGLとCUDLR.Consoleを中継する - /// - public static class WebGL_Relay - { - /// - /// 初期化。ブラウザ側 javascript にコールバック設定 - /// - public static void Initialize() - { -#if UNITY_WEBGL && !UNITY_EDITOR - Debug.LogFormat("Initialize CUDLR WebGL Relay"); - SetCudlrCallbackAndCreateInput(RunCommand); - Console.Update(); -#endif - } - - public static void Log(string text) - { -#if UNITY_WEBGL && !UNITY_EDITOR - ShowLog(text); -#endif - } - -#if UNITY_WEBGL && !UNITY_EDITOR - /// - /// 汎用コマンド実行コールバック - /// - /// - [MonoPInvokeCallback(typeof(Func))] - static private string RunCommand(string commandline) - { - Console.Run(commandline); - return Console.GetResult(); - } - - [DllImport("__Internal")] - static extern void SetCudlrCallbackAndCreateInput(Func callback); - - [DllImport("__Internal")] - static extern string ShowLog(string text); -#endif - } -} +using AOT; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using UnityEngine; + +namespace CUDLR +{ + /// + /// WebGLとCUDLR.Consoleを中継する + /// + public static class WebGL_Relay + { + /// + /// 初期化。ブラウザ側 javascript にコールバック設定 + /// + public static void Initialize() + { +#if UNITY_WEBGL && !UNITY_EDITOR + Debug.LogFormat("Initialize CUDLR WebGL Relay"); + SetCudlrCallbackAndCreateInput(RunCommand); + Console.Update(); +#endif + } + + public static void Log(string text) + { +#if UNITY_WEBGL && !UNITY_EDITOR + ShowLog(text); +#endif + } + +#if UNITY_WEBGL && !UNITY_EDITOR + /// + /// 汎用コマンド実行コールバック + /// + /// + [MonoPInvokeCallback(typeof(Func))] + static private string RunCommand(string commandline) + { + Console.Run(commandline); + return Console.GetResult(); + } + + [DllImport("__Internal")] + static extern void SetCudlrCallbackAndCreateInput(Func callback); + + [DllImport("__Internal")] + static extern string ShowLog(string text); +#endif + } +} diff --git a/Console.cs b/Console.cs deleted file mode 100644 index c59954b..0000000 --- a/Console.cs +++ /dev/null @@ -1,388 +0,0 @@ -using UnityEngine; -using System; -using System.Collections.Generic; -using System.Text.RegularExpressions; -using System.Linq; -using System.Reflection; -using System.Net; -using System.Collections; - -namespace CUDLR { - - struct QueuedCommand { - public CommandAttribute command; - public string[] args; - } - - public class Console { - - // Max number of lines in the console output - const int MAX_LINES = 100; - - // Maximum number of commands stored in the history - const int MAX_HISTORY = 50; - - // Prefix for user inputted command - const string COMMAND_OUTPUT_PREFIX = "> "; - - private static Console instance; - private CommandTree m_commands; - private List m_output; - private List m_history; - private Queue m_commandQueue; - - private Console() { - m_commands = new CommandTree(); - m_output = new List(); - m_history = new List(); - m_commandQueue = new Queue(); - - RegisterAttributes(); - } - - public static Console Instance { - get { - if (instance == null) instance = new Console(); - return instance; - } - } - - public static void Update() { - while (Instance.m_commandQueue.Count > 0) { - QueuedCommand cmd = Instance.m_commandQueue.Dequeue(); - cmd.command.m_callback( cmd.args ); - } - } - - /* Queue a command to be executed on update on the main thread */ - public static void Queue(CommandAttribute command, string[] args) { - QueuedCommand queuedCommand = new QueuedCommand(); - queuedCommand.command = command; - queuedCommand.args = args; - Instance.m_commandQueue.Enqueue( queuedCommand ); - } - - /* Execute a command */ - public static void Run(string str) { -#if UNITY_WEBGL && !UNITY_EDITOR - Instance.m_output.Clear(); -#endif - if (str.Length > 0) { - LogCommand(str); - Instance.RecordCommand(str); - Instance.m_commands.Run(str); - } - } - -#if UNITY_WEBGL && !UNITY_EDITOR - public static string GetResult() - { - Update(); - return string.Join("\n", Instance.m_output.ToArray()); - } -#endif - - /* Clear all output from console */ - [Command("clear", "clears console output", false)] - public static void Clear() { - Instance.m_output.Clear(); - } - - /* Print a list of all console commands */ - [Command("help", "prints commands", false)] - public static void Help() { - - string help = "Commands:"; - foreach (CommandAttribute cmd in Instance.m_commands.OrderBy(m=>m.m_command)) { - help += string.Format("\n{0} : {1}", cmd.m_command, cmd.m_help); - } - - Log("" + help + ""); - } - - /* Find command based on partial string */ - public static string Complete(string partialCommand) { - return Instance.m_commands.Complete( partialCommand ); - } - - /* Logs user input to output */ - public static void LogCommand(string cmd) { - Log(COMMAND_OUTPUT_PREFIX+cmd); - } - - /* Logs string to output */ - public static void Log(string str) { -#if UNITY_WEBGL && !UNITY_EDITOR - Instance.m_output.Add(str); -#else - LogCore(str); -#endif - } - - private static void LogCore(string str) - { -#if !UNITY_WEBGL || UNITY_EDITOR - Instance.m_output.Add(str); - if (Instance.m_output.Count > MAX_LINES) - Instance.m_output.RemoveAt(0); -#endif - } - - /* Callback for Unity logging */ - public static void LogCallback (string logString, string stackTrace, LogType type) { - if (type != LogType.Log) { -#if UNITY_WEBGL && !UNITY_EDITOR - Console.LogCore(logString); - Console.LogCore(stackTrace); -#else - Console.LogCore("" + logString); - Console.LogCore(stackTrace + ""); -#endif - } - else { - Console.LogCore(logString); - } - } - - /* Returns the output */ - public static string Output() { - return string.Join("\n", Instance.m_output.ToArray()); - } - - /* Register a new console command */ - public static void RegisterCommand(string command, string desc, CommandAttribute.Callback callback, bool runOnMainThread = true) { - if (command == null || command.Length == 0) { - throw new Exception("Command String cannot be empty"); - } - - CommandAttribute cmd = new CommandAttribute(command, desc, runOnMainThread); - cmd.m_callback = callback; - - Instance.m_commands.Add(cmd); - } - - private void RegisterAttributes() { - foreach(Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) { - // HACK: IL2CPP crashes if you attempt to get the methods of some classes in these assemblies. - if (assembly.FullName.StartsWith("System") || assembly.FullName.StartsWith("mscorlib")) { - continue; - } - foreach(Type type in assembly.GetTypes()) { - // FIXME add support for non-static methods (FindObjectByType?) - foreach(MethodInfo method in type.GetMethods(BindingFlags.Public|BindingFlags.Static)) { - CommandAttribute[] attrs = method.GetCustomAttributes(typeof(CommandAttribute), true) as CommandAttribute[]; - if (attrs.Length == 0) - continue; - - CommandAttribute.Callback cb = Delegate.CreateDelegate(typeof(CommandAttribute.Callback), method, false) as CommandAttribute.Callback; - if (cb == null) - { - CommandAttribute.CallbackSimple cbs = Delegate.CreateDelegate(typeof(CommandAttribute.CallbackSimple), method, false) as CommandAttribute.CallbackSimple; - if (cbs != null) { - cb = delegate(string[] args) { - cbs(); - }; - } - } - - if (cb == null) { - Debug.LogError(string.Format("Method {0}.{1} takes the wrong arguments for a console command.", type, method.Name)); - continue; - } - - // try with a bare action - foreach(CommandAttribute cmd in attrs) { - if (string.IsNullOrEmpty(cmd.m_command)) { - Debug.LogError(string.Format("Method {0}.{1} needs a valid command name.", type, method.Name)); - continue; - } - - cmd.m_callback = cb; - m_commands.Add(cmd); - } - } - } - } - } - - /* Get a previously ran command from the history */ - public static string PreviousCommand(int index) { - return index >= 0 && index < Instance.m_history.Count ? Instance.m_history[index] : null; - } - - /* Update history with a new command */ - private void RecordCommand(string command) { - m_history.Insert(0, command); - if (m_history.Count > MAX_HISTORY) - m_history.RemoveAt(m_history.Count - 1); - } - - // Our routes - [Route("^/console/out$")] - public static void Output(RequestContext context) { - context.Response.WriteString(Console.Output()); - } - - [Route("^/console/run$")] - public static void Run(RequestContext context) { - string command = Uri.UnescapeDataString(context.Request.QueryString.Get("command")); - if (!string.IsNullOrEmpty(command)) - Console.Run(command); - - context.Response.StatusCode = (int)HttpStatusCode.OK; - context.Response.StatusDescription = "OK"; - } - - [Route("^/console/commandHistory$")] - public static void History(RequestContext context) { - string index = context.Request.QueryString.Get("index"); - - string previous = null; - if (!string.IsNullOrEmpty(index)) - previous = Console.PreviousCommand(System.Int32.Parse(index)); - - context.Response.WriteString(previous); - } - - [Route("^/console/complete$")] - public static void Complete(RequestContext context) { - string partialCommand = context.Request.QueryString.Get("command"); - - string found = null; - if (partialCommand != null) - found = Console.Complete(partialCommand); - - context.Response.WriteString(found); - } - } - - class CommandTree : IEnumerable { - - private Dictionary m_subcommands; - private CommandAttribute m_command; - - public CommandTree() { - m_subcommands = new Dictionary(); - } - - public void Add(CommandAttribute cmd) { - _add(cmd.m_command.ToLower().Split(' '), 0, cmd); - } - - private void _add(string[] commands, int command_index, CommandAttribute cmd) { - if (commands.Length == command_index) { - m_command = cmd; - return; - } - - string token = commands[command_index]; - if (!m_subcommands.ContainsKey(token)){ - m_subcommands[token] = new CommandTree(); - } - m_subcommands[token]._add(commands, command_index + 1, cmd); - } - - public IEnumerator GetEnumerator() { - if (m_command != null && m_command.m_command != null) - yield return m_command; - - foreach(KeyValuePair entry in m_subcommands) { - foreach(CommandAttribute cmd in entry.Value) { - if (cmd != null && cmd.m_command != null) - yield return cmd; - } - } - } - - IEnumerator IEnumerable.GetEnumerator() { - return GetEnumerator(); - } - - public string Complete(string partialCommand) { - return _complete(partialCommand.Split(' '), 0, ""); - } - - public string _complete(string[] partialCommand, int index, string result) { - if (partialCommand.Length == index && m_command != null) { - // this is a valid command... so we do nothing - return result; - } else if (partialCommand.Length == index) { - // This is valid but incomplete.. print all of the subcommands - Console.LogCommand(result); - foreach (string key in m_subcommands.Keys.OrderBy(m=>m)) { - Console.Log(result + " " + key); - } - return result + " "; - } else if (partialCommand.Length == (index+1)) { - string partial = partialCommand[index]; - if (m_subcommands.ContainsKey(partial)) { - result += partial; - return m_subcommands[partial]._complete(partialCommand, index+1, result); - } - - // Find any subcommands that match our partial command - List matches = new List(); - foreach (string key in m_subcommands.Keys.OrderBy(m=>m)) { - if (key.StartsWith(partial)) { - matches.Add(key); - } - } - - if (matches.Count == 1) { - // Only one command found, log nothing and return the complete command for the user input - return result + matches[0] + " "; - } else if (matches.Count > 1) { - // list all the options for the user and return partial - Console.LogCommand(result + partial); - foreach (string match in matches) { - Console.Log(result + match); - } - } - return result + partial; - } - - string token = partialCommand[index]; - if (!m_subcommands.ContainsKey(token)) { - return result; - } - result += token + " "; - return m_subcommands[token]._complete( partialCommand, index + 1, result ); - } - - public void Run(string commandStr) { - // Split user input on spaces ignoring anything in qoutes - Regex regex = new Regex(@""".*?""|[^\s]+"); - MatchCollection matches = regex.Matches(commandStr); - string[] tokens = new string[matches.Count]; - for (int i = 0; i < tokens.Length; ++i) { - tokens[i] = matches[i].Value.Replace("\"",""); - } - _run(tokens, 0); - } - - static string[] emptyArgs = new string[0]{}; - private void _run(string[] commands, int index) { - if (commands.Length == index) { - RunCommand(emptyArgs); - return; - } - - string token = commands[index].ToLower(); - if (!m_subcommands.ContainsKey(token)) { - RunCommand(commands.Skip(index).ToArray()); - return; - } - m_subcommands[token]._run(commands, index + 1); - } - - private void RunCommand(string[] args) { - if (m_command == null) { - Console.Log("command not found"); - } else if (m_command.m_runOnMainThread) { - Console.Queue( m_command, args ); - } else { - m_command.m_callback(args); - } - } - } -} diff --git a/Server.cs b/Server.cs deleted file mode 100644 index 9ed2a94..0000000 --- a/Server.cs +++ /dev/null @@ -1,306 +0,0 @@ -using UnityEngine; -using System; -using System.IO; -using System.Net; -using System.Collections; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.Text.RegularExpressions; -using System.Reflection; -using System.Linq; -using System.Threading; - -namespace CUDLR { - - public class RequestContext - { - public HttpListenerContext context; - public Match match; - public bool pass; - public string path; - public int currentRoute; - - public HttpListenerRequest Request { get { return context.Request; } } - public HttpListenerResponse Response { get { return context.Response; } } - - public RequestContext(HttpListenerContext ctx) - { -#if !UNITY_WEBGL || UNITY_EDITOR - context = ctx; - match = null; - pass = false; - path = WWW.UnEscapeURL(context.Request.Url.AbsolutePath); - if (path == "/") - path = "/index.html"; - currentRoute = 0; -#endif - } - } - - - public class Server : MonoBehaviour { - - [SerializeField] - public int Port = 55055; - - [SerializeField] - public bool RegisterLogCallback = false; - - private static Thread mainThread; - private static string fileRoot; - private static HttpListener listener; - private static List registeredRoutes; - private static Queue mainRequests = new Queue(); - - // List of supported files - // FIXME add an api to register new types - private static Dictionary fileTypes = new Dictionary { - {"js", "application/javascript"}, - {"json", "application/json"}, - {"jpg", "image/jpeg" }, - {"jpeg", "image/jpeg"}, - {"gif", "image/gif"}, - {"png", "image/png"}, - {"css", "text/css"}, - {"htm", "text/html"}, - {"html", "text/html"}, - {"ico", "image/x-icon"}, - }; - - public virtual void Awake() { -#if UNITY_WEBGL && !UNITY_EDITOR - WebGL_Relay.Initialize(); -#endif - mainThread = Thread.CurrentThread; - fileRoot = Path.Combine(Application.streamingAssetsPath, "CUDLR"); - -#if !UNITY_WEBGL || UNITY_EDITOR - - // Start server - Debug.Log("Starting CUDLR Server on port : " + Port); - listener = new HttpListener(); - listener.Prefixes.Add("http://*:"+Port+"/"); - listener.Start(); - listener.BeginGetContext(ListenerCallback, null); - - StartCoroutine(HandleRequests()); -#endif - } - - public void OnApplicationPause(bool paused) { - if (paused) { - listener.Stop(); - } - else { - listener.Start(); - listener.BeginGetContext(ListenerCallback, null); - } - } - - public virtual void OnDestroy() { - listener.Close(); - listener = null; - } - - private void RegisterRoutes() { - if ( registeredRoutes == null ) { - registeredRoutes = new List(); - - foreach(Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) { - foreach(Type type in assembly.GetTypes()) { - // FIXME add support for non-static methods (FindObjectByType?) - foreach(MethodInfo method in type.GetMethods(BindingFlags.Public|BindingFlags.Static)) { - RouteAttribute[] attrs = method.GetCustomAttributes(typeof(RouteAttribute), true) as RouteAttribute[]; - if (attrs.Length == 0) - continue; - - RouteAttribute.Callback cbm = Delegate.CreateDelegate(typeof(RouteAttribute.Callback), method, false) as RouteAttribute.Callback; - if (cbm == null) { - Debug.LogError(string.Format("Method {0}.{1} takes the wrong arguments for a console route.", type, method.Name)); - continue; - } - - // try with a bare action - foreach(RouteAttribute route in attrs) { - if (route.m_route == null) { - Debug.LogError(string.Format("Method {0}.{1} needs a valid route regexp.", type, method.Name)); - continue; - } - - route.m_callback = cbm; - registeredRoutes.Add(route); - } - } - } - } - RegisterFileHandlers(); - } - } - - static void FindFileType(RequestContext context, bool download, out string path, out string type) { - path = Path.Combine(fileRoot, context.match.Groups[1].Value); - - string ext = Path.GetExtension(path).ToLower().TrimStart(new char[] {'.'}); - if (download || !fileTypes.TryGetValue(ext, out type)) - type = "application/octet-stream"; - } - - - public delegate void FileHandlerDelegate(RequestContext context, bool download); - static void WWWFileHandler(RequestContext context, bool download) { - string path, type; - FindFileType(context, download, out path, out type); - - WWW req = new WWW(path); - while (!req.isDone) { - Thread.Sleep(0); - } - - if (string.IsNullOrEmpty(req.error)) { - context.Response.ContentType = type; - if (download) - context.Response.AddHeader("Content-disposition", string.Format("attachment; filename={0}", Path.GetFileName(path))); - - context.Response.WriteBytes(req.bytes); - return; - } - - if (req.error.StartsWith("Couldn't open file")) { - context.pass = true; - } - else { - context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; - context.Response.StatusDescription = string.Format("Fatal error:\n{0}", req.error); - } - } - - static void FileHandler(RequestContext context, bool download) { - string path, type; - FindFileType(context, download, out path, out type); - - if (File.Exists(path)) { - context.Response.WriteFile(path, type, download); - } - else { - context.pass = true; - } - } - - static void RegisterFileHandlers() { - string pattern = string.Format("({0})", string.Join("|", fileTypes.Select(x => x.Key).ToArray())); - RouteAttribute downloadRoute = new RouteAttribute(string.Format(@"^/download/(.*\.{0})$", pattern)); - RouteAttribute fileRoute = new RouteAttribute(string.Format(@"^/(.*\.{0})$", pattern)); - - bool needs_www = fileRoot.Contains("://"); - downloadRoute.m_runOnMainThread = needs_www; - fileRoute.m_runOnMainThread = needs_www; - - FileHandlerDelegate callback = FileHandler; - if (needs_www) - callback = WWWFileHandler; - - downloadRoute.m_callback = delegate(RequestContext context) { callback(context, true); }; - fileRoute.m_callback = delegate(RequestContext context) { callback(context, false); }; - - registeredRoutes.Add(downloadRoute); - registeredRoutes.Add(fileRoute); - } - -#if !UNITY_WEBGL || UNITY_EDITOR - void OnEnable() { - if (RegisterLogCallback) { - // Capture Console Logs -#if UNITY_5_3_OR_NEWER - Application.logMessageReceived += Console.LogCallback; -#else - Application.RegisterLogCallback(Console.LogCallback); -#endif - } - } - - void OnDisable() { - if (RegisterLogCallback) { -#if UNITY_5_3_OR_NEWER - Application.logMessageReceived -= Console.LogCallback; -#else - Application.RegisterLogCallback(null); -#endif - } - } -#endif - - void Update() { - Console.Update(); - } - - void ListenerCallback(IAsyncResult result) { - RequestContext context = new RequestContext(listener.EndGetContext(result)); - - HandleRequest(context); - - if (listener.IsListening) { - listener.BeginGetContext(ListenerCallback, null); - } - } - - void HandleRequest(RequestContext context) { - RegisterRoutes(); - - try { - bool handled = false; - - for (; context.currentRoute < registeredRoutes.Count; ++context.currentRoute) { - RouteAttribute route = registeredRoutes[context.currentRoute]; - Match match = route.m_route.Match(context.path); - if (!match.Success) - continue; - - if (!route.m_methods.IsMatch(context.Request.HttpMethod)) - continue; - - // Upgrade to main thread if necessary - if (route.m_runOnMainThread && Thread.CurrentThread != mainThread) { - lock (mainRequests) { - mainRequests.Enqueue(context); - } - return; - } - - context.match = match; - route.m_callback(context); - handled = !context.pass; - if (handled) - break; - } - - if (!handled) { - context.Response.StatusCode = (int)HttpStatusCode.NotFound; - context.Response.StatusDescription = "Not Found"; - } - } - catch (Exception exception) { - context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; - context.Response.StatusDescription = string.Format("Fatal error:\n{0}", exception); - - Debug.LogException(exception); - } - - context.Response.OutputStream.Close(); - } - - IEnumerator HandleRequests() { - while (true) { - while (mainRequests.Count == 0) { - yield return new WaitForEndOfFrame(); - } - - RequestContext context = null; - lock (mainRequests) { - context = mainRequests.Dequeue(); - } - - HandleRequest(context); - } - } - } -} From 9b4840c9f36ac67f1105d29b41076c525a0484a5 Mon Sep 17 00:00:00 2001 From: ShinodaNaoki Date: Mon, 12 Feb 2018 22:02:15 +0900 Subject: [PATCH 4/5] English doc --- CUDLR/Scripts/WebGL_Relay.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CUDLR/Scripts/WebGL_Relay.cs b/CUDLR/Scripts/WebGL_Relay.cs index 8209de2..afe58fb 100644 --- a/CUDLR/Scripts/WebGL_Relay.cs +++ b/CUDLR/Scripts/WebGL_Relay.cs @@ -9,12 +9,12 @@ namespace CUDLR { /// - /// WebGLとCUDLR.Consoleを中継する + /// Relay between browser console and CUDLR.Console. /// public static class WebGL_Relay { /// - /// 初期化。ブラウザ側 javascript にコールバック設定 + /// Initialize by setting callback to a javascript on the browser. /// public static void Initialize() { @@ -34,7 +34,7 @@ public static void Log(string text) #if UNITY_WEBGL && !UNITY_EDITOR /// - /// 汎用コマンド実行コールバック + /// General command callback. /// /// [MonoPInvokeCallback(typeof(Func))] From e52983acc86c0a2565708462c9b5a34aff03a118 Mon Sep 17 00:00:00 2001 From: ShinodaNaoki <11781380+ShinodaNaoki@users.noreply.github.com> Date: Fri, 23 Feb 2018 11:00:43 +0900 Subject: [PATCH 5/5] Ctrl -> Shift --- WebGL/CUDLR_Relay.jslib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WebGL/CUDLR_Relay.jslib b/WebGL/CUDLR_Relay.jslib index e45715b..3b1e59f 100644 --- a/WebGL/CUDLR_Relay.jslib +++ b/WebGL/CUDLR_Relay.jslib @@ -17,7 +17,7 @@ var CUDLR_Relay = { } document.onclick = function(){ - if(!window.event.getModifierState("Control") + if(!window.event.getModifierState("Shift") || !window.event.getModifierState("Alt")) return true; var text = cc(prompt());