DebugLogConsole.cs 53 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521
  1. #if UNITY_EDITOR || UNITY_STANDALONE
  2. // Unity's Text component doesn't render <b> tag correctly on mobile devices
  3. #define USE_BOLD_COMMAND_SIGNATURES
  4. #endif
  5. using UnityEngine;
  6. using System;
  7. using System.Collections;
  8. using System.Collections.Generic;
  9. using System.Globalization;
  10. using System.Reflection;
  11. using System.Text;
  12. using Object = UnityEngine.Object;
  13. #if UNITY_EDITOR && UNITY_2021_1_OR_NEWER
  14. using SystemInfo = UnityEngine.Device.SystemInfo; // To support Device Simulator on Unity 2021.1+
  15. #endif
  16. // Manages the console commands, parses console input and handles execution of commands
  17. // Supported method parameter types: int, float, bool, string, Vector2, Vector3, Vector4
  18. // Helper class to store important information about a command
  19. namespace IngameDebugConsole
  20. {
  21. public class ConsoleMethodInfo
  22. {
  23. public readonly MethodInfo method;
  24. public readonly Type[] parameterTypes;
  25. public readonly object instance;
  26. public readonly string command;
  27. public readonly string signature;
  28. public readonly string[] parameters;
  29. public ConsoleMethodInfo( MethodInfo method, Type[] parameterTypes, object instance, string command, string signature, string[] parameters )
  30. {
  31. this.method = method;
  32. this.parameterTypes = parameterTypes;
  33. this.instance = instance;
  34. this.command = command;
  35. this.signature = signature;
  36. this.parameters = parameters;
  37. }
  38. public bool IsValid()
  39. {
  40. if( !method.IsStatic && ( instance == null || instance.Equals( null ) ) )
  41. return false;
  42. return true;
  43. }
  44. }
  45. public static class DebugLogConsole
  46. {
  47. public delegate bool ParseFunction( string input, out object output );
  48. public delegate void CommandExecutedDelegate( string command, object[] parameters );
  49. public static event CommandExecutedDelegate OnCommandExecuted;
  50. // All the commands
  51. private static readonly List<ConsoleMethodInfo> methods = new List<ConsoleMethodInfo>();
  52. private static readonly List<ConsoleMethodInfo> matchingMethods = new List<ConsoleMethodInfo>( 4 );
  53. // All the parse functions
  54. private static readonly Dictionary<Type, ParseFunction> parseFunctions = new Dictionary<Type, ParseFunction>()
  55. {
  56. { typeof( string ), ParseString },
  57. { typeof( bool ), ParseBool },
  58. { typeof( int ), ParseInt },
  59. { typeof( uint ), ParseUInt },
  60. { typeof( long ), ParseLong },
  61. { typeof( ulong ), ParseULong },
  62. { typeof( byte ), ParseByte },
  63. { typeof( sbyte ), ParseSByte },
  64. { typeof( short ), ParseShort },
  65. { typeof( ushort ), ParseUShort },
  66. { typeof( char ), ParseChar },
  67. { typeof( float ), ParseFloat },
  68. { typeof( double ), ParseDouble },
  69. { typeof( decimal ), ParseDecimal },
  70. { typeof( Vector2 ), ParseVector2 },
  71. { typeof( Vector3 ), ParseVector3 },
  72. { typeof( Vector4 ), ParseVector4 },
  73. { typeof( Quaternion ), ParseQuaternion },
  74. { typeof( Color ), ParseColor },
  75. { typeof( Color32 ), ParseColor32 },
  76. { typeof( Rect ), ParseRect },
  77. { typeof( RectOffset ), ParseRectOffset },
  78. { typeof( Bounds ), ParseBounds },
  79. { typeof( GameObject ), ParseGameObject },
  80. { typeof( Vector2Int ), ParseVector2Int },
  81. { typeof( Vector3Int ), ParseVector3Int },
  82. { typeof( RectInt ), ParseRectInt },
  83. { typeof( BoundsInt ), ParseBoundsInt },
  84. };
  85. // All the readable names of accepted types
  86. private static readonly Dictionary<Type, string> typeReadableNames = new Dictionary<Type, string>()
  87. {
  88. { typeof( string ), "String" },
  89. { typeof( bool ), "Boolean" },
  90. { typeof( int ), "Integer" },
  91. { typeof( uint ), "Unsigned Integer" },
  92. { typeof( long ), "Long" },
  93. { typeof( ulong ), "Unsigned Long" },
  94. { typeof( byte ), "Byte" },
  95. { typeof( sbyte ), "Short Byte" },
  96. { typeof( short ), "Short" },
  97. { typeof( ushort ), "Unsigned Short" },
  98. { typeof( char ), "Char" },
  99. { typeof( float ), "Float" },
  100. { typeof( double ), "Double" },
  101. { typeof( decimal ), "Decimal" }
  102. };
  103. // Split arguments of an entered command
  104. private static readonly List<string> commandArguments = new List<string>( 8 );
  105. // Command parameter delimeter groups
  106. private static readonly string[] inputDelimiters = new string[] { "\"\"", "''", "{}", "()", "[]" };
  107. // CompareInfo used for case-insensitive command name comparison
  108. internal static readonly CompareInfo caseInsensitiveComparer = new CultureInfo( "en-US" ).CompareInfo;
  109. [RuntimeInitializeOnLoadMethod( RuntimeInitializeLoadType.SubsystemRegistration )] // Configurable Enter Play Mode: https://docs.unity3d.com/Manual/DomainReloading.html
  110. private static void ResetStatics()
  111. {
  112. methods.Clear();
  113. OnCommandExecuted = null;
  114. #if !IDG_DISABLE_HELP_COMMAND
  115. AddCommand( "help", "Prints all commands", LogAllCommands );
  116. AddCommand<string>( "help", "Prints all matching commands", LogAllCommandsWithName );
  117. #endif
  118. #if IDG_ENABLE_HELPER_COMMANDS || IDG_ENABLE_SYSINFO_COMMAND
  119. AddCommand( "sysinfo", "Prints system information", LogSystemInfo );
  120. #endif
  121. #if UNITY_EDITOR || !NETFX_CORE
  122. // Find all [ConsoleMethod] functions
  123. // Don't search built-in assemblies for console methods since they can't have any
  124. string[] ignoredAssemblies = new string[]
  125. {
  126. "Unity",
  127. "System",
  128. "Mono.",
  129. "mscorlib",
  130. "netstandard",
  131. "TextMeshPro",
  132. "Microsoft.GeneratedCode",
  133. "I18N",
  134. "Boo.",
  135. "UnityScript.",
  136. "ICSharpCode.",
  137. "ExCSS.Unity",
  138. #if UNITY_EDITOR
  139. "Assembly-CSharp-Editor",
  140. "Assembly-UnityScript-Editor",
  141. "nunit.",
  142. "SyntaxTree.",
  143. "AssetStoreTools",
  144. #endif
  145. };
  146. #endif
  147. #if UNITY_EDITOR || !NETFX_CORE
  148. foreach( Assembly assembly in AppDomain.CurrentDomain.GetAssemblies() )
  149. #else
  150. foreach( Assembly assembly in new Assembly[] { typeof( DebugLogConsole ).Assembly } ) // On UWP, at least search this plugin's Assembly for console methods
  151. #endif
  152. {
  153. #if( NET_4_6 || NET_STANDARD_2_0 ) && ( UNITY_EDITOR || !NETFX_CORE )
  154. if( assembly.IsDynamic )
  155. continue;
  156. #endif
  157. #if UNITY_EDITOR || !NETFX_CORE
  158. string assemblyName = assembly.GetName().Name;
  159. bool ignoreAssembly = false;
  160. for( int i = 0; i < ignoredAssemblies.Length; i++ )
  161. {
  162. if( caseInsensitiveComparer.IsPrefix( assemblyName, ignoredAssemblies[i], CompareOptions.IgnoreCase ) )
  163. {
  164. ignoreAssembly = true;
  165. break;
  166. }
  167. }
  168. if( ignoreAssembly )
  169. continue;
  170. #endif
  171. SearchAssemblyForConsoleMethods( assembly );
  172. }
  173. }
  174. public static void SearchAssemblyForConsoleMethods( Assembly assembly )
  175. {
  176. try
  177. {
  178. List<ConsoleAttribute> methods = new List<ConsoleAttribute>();
  179. foreach( Type type in assembly.GetExportedTypes() )
  180. {
  181. foreach( MethodInfo method in type.GetMethods( BindingFlags.Static | BindingFlags.Public | BindingFlags.DeclaredOnly ) )
  182. {
  183. foreach( ConsoleAttribute consoleAttribute in method.GetCustomAttributes( typeof(ConsoleAttribute), false ) )
  184. {
  185. consoleAttribute.SetMethod(method);
  186. methods.Add(consoleAttribute);
  187. }
  188. }
  189. }
  190. methods.Sort((a, b) => a.Order.CompareTo(b.Order));
  191. for (int i = 0; i < methods.Count; i++)
  192. {
  193. methods[i].Load();
  194. }
  195. }
  196. catch( NotSupportedException ) { }
  197. catch( System.IO.FileNotFoundException ) { }
  198. catch( ReflectionTypeLoadException ) { }
  199. catch( Exception e )
  200. {
  201. Debug.LogError( "Couldn't search assembly for [ConsoleMethod] attributes: " + assembly.GetName().Name + "\n" + e.ToString() );
  202. }
  203. }
  204. public static List<ConsoleMethodInfo> GetAllCommands()
  205. {
  206. return methods;
  207. }
  208. // Logs the list of available commands
  209. public static void LogAllCommands()
  210. {
  211. int length = 25;
  212. for( int i = 0; i < methods.Count; i++ )
  213. {
  214. if( methods[i].IsValid() )
  215. length += methods[i].signature.Length + 7;
  216. }
  217. StringBuilder stringBuilder = new StringBuilder( length );
  218. stringBuilder.Append( "Available commands:" );
  219. for( int i = 0; i < methods.Count; i++ )
  220. {
  221. if( methods[i].IsValid() )
  222. stringBuilder.Append( "\n - " ).Append( methods[i].signature );
  223. }
  224. Debug.Log( stringBuilder.ToString() );
  225. // After typing help, the log that lists all the commands should automatically be expanded for better UX
  226. if( DebugLogManager.Instance )
  227. DebugLogManager.Instance.AdjustLatestPendingLog( true, true );
  228. }
  229. // Logs the list of available commands that are either equal to commandName or contain commandName as substring
  230. public static void LogAllCommandsWithName( string commandName )
  231. {
  232. matchingMethods.Clear();
  233. // First, try to find commands that exactly match the commandName. If there are no such commands, try to find
  234. // commands that contain commandName as substring
  235. FindCommands( commandName, false, matchingMethods );
  236. if( matchingMethods.Count == 0 )
  237. FindCommands( commandName, true, matchingMethods );
  238. if( matchingMethods.Count == 0 )
  239. Debug.LogWarning( string.Concat( "ERROR: can't find command '", commandName, "'" ) );
  240. else
  241. {
  242. int commandsLength = 25;
  243. for( int i = 0; i < matchingMethods.Count; i++ )
  244. commandsLength += matchingMethods[i].signature.Length + 7;
  245. StringBuilder stringBuilder = new StringBuilder( commandsLength );
  246. stringBuilder.Append( "Matching commands:" );
  247. for( int i = 0; i < matchingMethods.Count; i++ )
  248. stringBuilder.Append( "\n - " ).Append( matchingMethods[i].signature );
  249. Debug.Log( stringBuilder.ToString() );
  250. if( DebugLogManager.Instance )
  251. DebugLogManager.Instance.AdjustLatestPendingLog( true, true );
  252. }
  253. }
  254. // Logs system information
  255. public static void LogSystemInfo()
  256. {
  257. StringBuilder stringBuilder = new StringBuilder( 1024 );
  258. stringBuilder.Append( "Rig: " ).AppendSysInfoIfPresent( SystemInfo.deviceModel ).AppendSysInfoIfPresent( SystemInfo.processorType )
  259. .AppendSysInfoIfPresent( SystemInfo.systemMemorySize, "MB RAM" ).Append( SystemInfo.processorCount ).Append( " cores\n" );
  260. stringBuilder.Append( "OS: " ).Append( SystemInfo.operatingSystem ).Append( "\n" );
  261. stringBuilder.Append( "GPU: " ).Append( SystemInfo.graphicsDeviceName ).Append( " " ).Append( SystemInfo.graphicsMemorySize )
  262. .Append( "MB " ).Append( SystemInfo.graphicsDeviceVersion )
  263. .Append( SystemInfo.graphicsMultiThreaded ? " multi-threaded\n" : "\n" );
  264. stringBuilder.Append( "Data Path: " ).Append( Application.dataPath ).Append( "\n" );
  265. stringBuilder.Append( "Persistent Data Path: " ).Append( Application.persistentDataPath ).Append( "\n" );
  266. stringBuilder.Append( "StreamingAssets Path: " ).Append( Application.streamingAssetsPath ).Append( "\n" );
  267. stringBuilder.Append( "Temporary Cache Path: " ).Append( Application.temporaryCachePath ).Append( "\n" );
  268. stringBuilder.Append( "Device ID: " ).Append( SystemInfo.deviceUniqueIdentifier ).Append( "\n" );
  269. stringBuilder.Append( "Max Texture Size: " ).Append( SystemInfo.maxTextureSize ).Append( "\n" );
  270. stringBuilder.Append( "Max Cubemap Size: " ).Append( SystemInfo.maxCubemapSize ).Append( "\n" );
  271. stringBuilder.Append( "Accelerometer: " ).Append( SystemInfo.supportsAccelerometer ? "supported\n" : "not supported\n" );
  272. stringBuilder.Append( "Gyro: " ).Append( SystemInfo.supportsGyroscope ? "supported\n" : "not supported\n" );
  273. stringBuilder.Append( "Location Service: " ).Append( SystemInfo.supportsLocationService ? "supported\n" : "not supported\n" );
  274. stringBuilder.Append( "Compute Shaders: " ).Append( SystemInfo.supportsComputeShaders ? "supported\n" : "not supported\n" );
  275. stringBuilder.Append( "Shadows: " ).Append( SystemInfo.supportsShadows ? "supported\n" : "not supported\n" );
  276. stringBuilder.Append( "Instancing: " ).Append( SystemInfo.supportsInstancing ? "supported\n" : "not supported\n" );
  277. stringBuilder.Append( "Motion Vectors: " ).Append( SystemInfo.supportsMotionVectors ? "supported\n" : "not supported\n" );
  278. stringBuilder.Append( "3D Textures: " ).Append( SystemInfo.supports3DTextures ? "supported\n" : "not supported\n" );
  279. stringBuilder.Append( "3D Render Textures: " ).Append( SystemInfo.supports3DRenderTextures ? "supported\n" : "not supported\n" );
  280. stringBuilder.Append( "2D Array Textures: " ).Append( SystemInfo.supports2DArrayTextures ? "supported\n" : "not supported\n" );
  281. stringBuilder.Append( "Cubemap Array Textures: " ).Append( SystemInfo.supportsCubemapArrayTextures ? "supported" : "not supported" );
  282. Debug.Log( stringBuilder.ToString() );
  283. // After typing sysinfo, the log that lists system information should automatically be expanded for better UX
  284. if( DebugLogManager.Instance )
  285. DebugLogManager.Instance.AdjustLatestPendingLog( true, true );
  286. }
  287. private static StringBuilder AppendSysInfoIfPresent( this StringBuilder sb, string info, string postfix = null )
  288. {
  289. if( info != SystemInfo.unsupportedIdentifier )
  290. {
  291. sb.Append( info );
  292. if( postfix != null )
  293. sb.Append( postfix );
  294. sb.Append( " " );
  295. }
  296. return sb;
  297. }
  298. private static StringBuilder AppendSysInfoIfPresent( this StringBuilder sb, int info, string postfix = null )
  299. {
  300. if( info > 0 )
  301. {
  302. sb.Append( info );
  303. if( postfix != null )
  304. sb.Append( postfix );
  305. sb.Append( " " );
  306. }
  307. return sb;
  308. }
  309. // Add a custom Type to the list of recognized command parameter Types
  310. public static void AddCustomParameterType( Type type, ParseFunction parseFunction, string typeReadableName = null )
  311. {
  312. if( type == null )
  313. {
  314. Debug.LogError( "Parameter type can't be null!" );
  315. return;
  316. }
  317. else if( parseFunction == null )
  318. {
  319. Debug.LogError( "Parameter parseFunction can't be null!" );
  320. return;
  321. }
  322. parseFunctions[type] = parseFunction;
  323. if( !string.IsNullOrEmpty( typeReadableName ) )
  324. typeReadableNames[type] = typeReadableName;
  325. }
  326. // Remove a custom Type from the list of recognized command parameter Types
  327. public static void RemoveCustomParameterType( Type type )
  328. {
  329. parseFunctions.Remove( type );
  330. typeReadableNames.Remove( type );
  331. }
  332. // Add a command related with an instance method (i.e. non static method)
  333. public static void AddCommandInstance( string command, string description, string methodName, object instance, params string[] parameterNames )
  334. {
  335. if( instance == null )
  336. {
  337. Debug.LogError( "Instance can't be null!" );
  338. return;
  339. }
  340. AddCommand( command, description, methodName, instance.GetType(), instance, parameterNames );
  341. }
  342. // Add a command related with a static method (i.e. no instance is required to call the method)
  343. public static void AddCommandStatic( string command, string description, string methodName, Type ownerType, params string[] parameterNames )
  344. {
  345. AddCommand( command, description, methodName, ownerType, null, parameterNames );
  346. }
  347. // Add a command that can be related to either a static or an instance method
  348. public static void AddCommand( string command, string description, Action method ) { AddCommand( command, description, method.Method, method.Target, null ); }
  349. public static void AddCommand<T1>( string command, string description, Action<T1> method ) { AddCommand( command, description, method.Method, method.Target, null ); }
  350. public static void AddCommand<T1>( string command, string description, Func<T1> method ) { AddCommand( command, description, method.Method, method.Target, null ); }
  351. public static void AddCommand<T1, T2>( string command, string description, Action<T1, T2> method ) { AddCommand( command, description, method.Method, method.Target, null ); }
  352. public static void AddCommand<T1, T2>( string command, string description, Func<T1, T2> method ) { AddCommand( command, description, method.Method, method.Target, null ); }
  353. public static void AddCommand<T1, T2, T3>( string command, string description, Action<T1, T2, T3> method ) { AddCommand( command, description, method.Method, method.Target, null ); }
  354. public static void AddCommand<T1, T2, T3>( string command, string description, Func<T1, T2, T3> method ) { AddCommand( command, description, method.Method, method.Target, null ); }
  355. public static void AddCommand<T1, T2, T3, T4>( string command, string description, Action<T1, T2, T3, T4> method ) { AddCommand( command, description, method.Method, method.Target, null ); }
  356. public static void AddCommand<T1, T2, T3, T4>( string command, string description, Func<T1, T2, T3, T4> method ) { AddCommand( command, description, method.Method, method.Target, null ); }
  357. public static void AddCommand<T1, T2, T3, T4, T5>( string command, string description, Func<T1, T2, T3, T4, T5> method ) { AddCommand( command, description, method.Method, method.Target, null ); }
  358. public static void AddCommand( string command, string description, Delegate method ) { AddCommand( command, description, method.Method, method.Target, null ); }
  359. // Add a command with custom parameter names
  360. public static void AddCommand<T1>( string command, string description, Action<T1> method, string parameterName ) { AddCommand( command, description, method.Method, method.Target, new string[1] { parameterName } ); }
  361. public static void AddCommand<T1, T2>( string command, string description, Action<T1, T2> method, string parameterName1, string parameterName2 ) { AddCommand( command, description, method.Method, method.Target, new string[2] { parameterName1, parameterName2 } ); }
  362. public static void AddCommand<T1, T2>( string command, string description, Func<T1, T2> method, string parameterName ) { AddCommand( command, description, method.Method, method.Target, new string[1] { parameterName } ); }
  363. public static void AddCommand<T1, T2, T3>( string command, string description, Action<T1, T2, T3> method, string parameterName1, string parameterName2, string parameterName3 ) { AddCommand( command, description, method.Method, method.Target, new string[3] { parameterName1, parameterName2, parameterName3 } ); }
  364. public static void AddCommand<T1, T2, T3>( string command, string description, Func<T1, T2, T3> method, string parameterName1, string parameterName2 ) { AddCommand( command, description, method.Method, method.Target, new string[2] { parameterName1, parameterName2 } ); }
  365. public static void AddCommand<T1, T2, T3, T4>( string command, string description, Action<T1, T2, T3, T4> method, string parameterName1, string parameterName2, string parameterName3, string parameterName4 ) { AddCommand( command, description, method.Method, method.Target, new string[4] { parameterName1, parameterName2, parameterName3, parameterName4 } ); }
  366. public static void AddCommand<T1, T2, T3, T4>( string command, string description, Func<T1, T2, T3, T4> method, string parameterName1, string parameterName2, string parameterName3 ) { AddCommand( command, description, method.Method, method.Target, new string[3] { parameterName1, parameterName2, parameterName3 } ); }
  367. public static void AddCommand<T1, T2, T3, T4, T5>( string command, string description, Func<T1, T2, T3, T4, T5> method, string parameterName1, string parameterName2, string parameterName3, string parameterName4 ) { AddCommand( command, description, method.Method, method.Target, new string[4] { parameterName1, parameterName2, parameterName3, parameterName4 } ); }
  368. public static void AddCommand( string command, string description, Delegate method, params string[] parameterNames ) { AddCommand( command, description, method.Method, method.Target, parameterNames ); }
  369. // Create a new command and set its properties
  370. private static void AddCommand( string command, string description, string methodName, Type ownerType, object instance, string[] parameterNames )
  371. {
  372. // Get the method from the class
  373. MethodInfo method = ownerType.GetMethod( methodName, BindingFlags.Public | BindingFlags.NonPublic | ( instance != null ? BindingFlags.Instance : BindingFlags.Static ) );
  374. if( method == null )
  375. {
  376. Debug.LogError( methodName + " does not exist in " + ownerType );
  377. return;
  378. }
  379. AddCommand( command, description, method, instance, parameterNames );
  380. }
  381. internal static void AddCommand( string command, string description, MethodInfo method, object instance, string[] parameterNames )
  382. {
  383. if( string.IsNullOrEmpty( command ) )
  384. {
  385. Debug.LogError( "Command name can't be empty!" );
  386. return;
  387. }
  388. command = command.Trim();
  389. if( command.IndexOf( ' ' ) >= 0 )
  390. {
  391. Debug.LogError( "Command name can't contain whitespace: " + command );
  392. return;
  393. }
  394. // Fetch the parameters of the class
  395. ParameterInfo[] parameters = method.GetParameters();
  396. if( parameters == null )
  397. parameters = new ParameterInfo[0];
  398. // Store the parameter types in an array
  399. Type[] parameterTypes = new Type[parameters.Length];
  400. for( int i = 0; i < parameters.Length; i++ )
  401. {
  402. if( parameters[i].ParameterType.IsByRef )
  403. {
  404. Debug.LogError( "Command can't have 'out' or 'ref' parameters" );
  405. return;
  406. }
  407. Type parameterType = parameters[i].ParameterType;
  408. if( parseFunctions.ContainsKey( parameterType ) || typeof( Component ).IsAssignableFrom( parameterType ) || parameterType.IsEnum || IsSupportedArrayType( parameterType ) )
  409. parameterTypes[i] = parameterType;
  410. else
  411. {
  412. Debug.LogError( string.Concat( "Parameter ", parameters[i].Name, "'s Type ", parameterType, " isn't supported" ) );
  413. return;
  414. }
  415. }
  416. int commandIndex = FindCommandIndex( command );
  417. if( commandIndex < 0 )
  418. commandIndex = ~commandIndex;
  419. else
  420. {
  421. int commandFirstIndex = commandIndex;
  422. int commandLastIndex = commandIndex;
  423. while( commandFirstIndex > 0 && caseInsensitiveComparer.Compare( methods[commandFirstIndex - 1].command, command, CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace ) == 0 )
  424. commandFirstIndex--;
  425. while( commandLastIndex < methods.Count - 1 && caseInsensitiveComparer.Compare( methods[commandLastIndex + 1].command, command, CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace ) == 0 )
  426. commandLastIndex++;
  427. commandIndex = commandFirstIndex;
  428. for( int i = commandFirstIndex; i <= commandLastIndex; i++ )
  429. {
  430. int parameterCountDiff = methods[i].parameterTypes.Length - parameterTypes.Length;
  431. if( parameterCountDiff <= 0 )
  432. {
  433. // We are sorting the commands in 2 steps:
  434. // 1: Sorting by their 'command' names which is handled by FindCommandIndex
  435. // 2: Sorting by their parameter counts which is handled here (parameterCountDiff <= 0)
  436. commandIndex = i + 1;
  437. // Check if this command has been registered before and if it is, overwrite that command
  438. if( parameterCountDiff == 0 )
  439. {
  440. int j = 0;
  441. while( j < parameterTypes.Length && parameterTypes[j] == methods[i].parameterTypes[j] )
  442. j++;
  443. if( j >= parameterTypes.Length )
  444. {
  445. commandIndex = i;
  446. commandLastIndex--;
  447. methods.RemoveAt( i-- );
  448. continue;
  449. }
  450. }
  451. }
  452. }
  453. }
  454. // Create the command
  455. StringBuilder methodSignature = new StringBuilder( 256 );
  456. string[] parameterSignatures = new string[parameterTypes.Length];
  457. #if USE_BOLD_COMMAND_SIGNATURES
  458. methodSignature.Append( "<b>" );
  459. #endif
  460. methodSignature.Append( command );
  461. if( parameterTypes.Length > 0 )
  462. {
  463. methodSignature.Append( " " );
  464. for( int i = 0; i < parameterTypes.Length; i++ )
  465. {
  466. int parameterSignatureStartIndex = methodSignature.Length;
  467. methodSignature.Append( "[" ).Append( GetTypeReadableName( parameterTypes[i] ) ).Append( " " ).Append( ( parameterNames != null && i < parameterNames.Length && !string.IsNullOrEmpty( parameterNames[i] ) ) ? parameterNames[i] : parameters[i].Name ).Append( "]" );
  468. if( i < parameterTypes.Length - 1 )
  469. methodSignature.Append( " " );
  470. parameterSignatures[i] = methodSignature.ToString( parameterSignatureStartIndex, methodSignature.Length - parameterSignatureStartIndex );
  471. }
  472. }
  473. #if USE_BOLD_COMMAND_SIGNATURES
  474. methodSignature.Append( "</b>" );
  475. #endif
  476. if( !string.IsNullOrEmpty( description ) )
  477. methodSignature.Append( ": " ).Append( description );
  478. methods.Insert( commandIndex, new ConsoleMethodInfo( method, parameterTypes, instance, command, methodSignature.ToString(), parameterSignatures ) );
  479. }
  480. // Remove all commands with the matching command name from the console
  481. public static void RemoveCommand( string command )
  482. {
  483. if( !string.IsNullOrEmpty( command ) )
  484. {
  485. for( int i = methods.Count - 1; i >= 0; i-- )
  486. {
  487. if( caseInsensitiveComparer.Compare( methods[i].command, command, CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace ) == 0 )
  488. methods.RemoveAt( i );
  489. }
  490. }
  491. }
  492. // Remove all commands with the matching method from the console
  493. public static void RemoveCommand( Action method ) { RemoveCommand( method.Method ); }
  494. public static void RemoveCommand<T1>( Action<T1> method ) { RemoveCommand( method.Method ); }
  495. public static void RemoveCommand<T1>( Func<T1> method ) { RemoveCommand( method.Method ); }
  496. public static void RemoveCommand<T1, T2>( Action<T1, T2> method ) { RemoveCommand( method.Method ); }
  497. public static void RemoveCommand<T1, T2>( Func<T1, T2> method ) { RemoveCommand( method.Method ); }
  498. public static void RemoveCommand<T1, T2, T3>( Action<T1, T2, T3> method ) { RemoveCommand( method.Method ); }
  499. public static void RemoveCommand<T1, T2, T3>( Func<T1, T2, T3> method ) { RemoveCommand( method.Method ); }
  500. public static void RemoveCommand<T1, T2, T3, T4>( Action<T1, T2, T3, T4> method ) { RemoveCommand( method.Method ); }
  501. public static void RemoveCommand<T1, T2, T3, T4>( Func<T1, T2, T3, T4> method ) { RemoveCommand( method.Method ); }
  502. public static void RemoveCommand<T1, T2, T3, T4, T5>( Func<T1, T2, T3, T4, T5> method ) { RemoveCommand( method.Method ); }
  503. public static void RemoveCommand( Delegate method ) { RemoveCommand( method.Method ); }
  504. public static void RemoveCommand( MethodInfo method )
  505. {
  506. if( method != null )
  507. {
  508. for( int i = methods.Count - 1; i >= 0; i-- )
  509. {
  510. if( methods[i].method == method )
  511. methods.RemoveAt( i );
  512. }
  513. }
  514. }
  515. // Returns the first command that starts with the entered argument
  516. public static string GetAutoCompleteCommand( string commandStart, string previousSuggestion )
  517. {
  518. int commandIndex = FindCommandIndex( !string.IsNullOrEmpty( previousSuggestion ) ? previousSuggestion : commandStart );
  519. if( commandIndex < 0 )
  520. {
  521. commandIndex = ~commandIndex;
  522. return ( commandIndex < methods.Count && caseInsensitiveComparer.IsPrefix( methods[commandIndex].command, commandStart, CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace ) ) ? methods[commandIndex].command : null;
  523. }
  524. // Find the next command that starts with commandStart and is different from previousSuggestion
  525. for( int i = commandIndex + 1; i < methods.Count; i++ )
  526. {
  527. if( caseInsensitiveComparer.Compare( methods[i].command, previousSuggestion, CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace ) == 0 )
  528. continue;
  529. else if( caseInsensitiveComparer.IsPrefix( methods[i].command, commandStart, CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace ) )
  530. return methods[i].command;
  531. else
  532. break;
  533. }
  534. // Couldn't find a command that follows previousSuggestion and satisfies commandStart, loop back to the beginning of the autocomplete suggestions
  535. string result = null;
  536. for( int i = commandIndex - 1; i >= 0 && caseInsensitiveComparer.IsPrefix( methods[i].command, commandStart, CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace ); i-- )
  537. result = methods[i].command;
  538. return result;
  539. }
  540. // Parse the command and try to execute it
  541. public static void ExecuteCommand( string command )
  542. {
  543. if( command == null )
  544. return;
  545. command = command.Trim();
  546. if( command.Length == 0 )
  547. return;
  548. // Split the command's arguments
  549. commandArguments.Clear();
  550. FetchArgumentsFromCommand( command, commandArguments );
  551. // Find all matching commands
  552. matchingMethods.Clear();
  553. bool parameterCountMismatch = false;
  554. int commandIndex = FindCommandIndex( commandArguments[0] );
  555. if( commandIndex >= 0 )
  556. {
  557. string _command = commandArguments[0];
  558. int commandLastIndex = commandIndex;
  559. while( commandIndex > 0 && caseInsensitiveComparer.Compare( methods[commandIndex - 1].command, _command, CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace ) == 0 )
  560. commandIndex--;
  561. while( commandLastIndex < methods.Count - 1 && caseInsensitiveComparer.Compare( methods[commandLastIndex + 1].command, _command, CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace ) == 0 )
  562. commandLastIndex++;
  563. while( commandIndex <= commandLastIndex )
  564. {
  565. if( !methods[commandIndex].IsValid() )
  566. {
  567. methods.RemoveAt( commandIndex );
  568. commandLastIndex--;
  569. }
  570. else
  571. {
  572. // Check if number of parameters match
  573. if( methods[commandIndex].parameterTypes.Length == commandArguments.Count - 1 )
  574. matchingMethods.Add( methods[commandIndex] );
  575. else
  576. parameterCountMismatch = true;
  577. commandIndex++;
  578. }
  579. }
  580. }
  581. if( matchingMethods.Count == 0 )
  582. {
  583. string _command = commandArguments[0];
  584. FindCommands( _command, !parameterCountMismatch, matchingMethods );
  585. if( matchingMethods.Count == 0 )
  586. Debug.LogWarning( string.Concat( "ERROR: can't find command '", _command, "'" ) );
  587. else
  588. {
  589. int commandsLength = _command.Length + 75;
  590. for( int i = 0; i < matchingMethods.Count; i++ )
  591. commandsLength += matchingMethods[i].signature.Length + 7;
  592. StringBuilder stringBuilder = new StringBuilder( commandsLength );
  593. if( parameterCountMismatch )
  594. stringBuilder.Append( "ERROR: '" ).Append( _command ).Append( "' doesn't take " ).Append( commandArguments.Count - 1 ).Append( " parameter(s). Available command(s):" );
  595. else
  596. stringBuilder.Append( "ERROR: can't find command '" ).Append( _command ).Append( "'. Did you mean:" );
  597. for( int i = 0; i < matchingMethods.Count; i++ )
  598. stringBuilder.Append( "\n - " ).Append( matchingMethods[i].signature );
  599. Debug.LogWarning( stringBuilder.ToString() );
  600. // The log that lists method signature(s) for this command should automatically be expanded for better UX
  601. if( DebugLogManager.Instance )
  602. DebugLogManager.Instance.AdjustLatestPendingLog( true, true );
  603. }
  604. return;
  605. }
  606. ConsoleMethodInfo methodToExecute = null;
  607. object[] parameters = new object[commandArguments.Count - 1];
  608. string errorMessage = null;
  609. for( int i = 0; i < matchingMethods.Count && methodToExecute == null; i++ )
  610. {
  611. ConsoleMethodInfo methodInfo = matchingMethods[i];
  612. // Parse the parameters into objects
  613. bool success = true;
  614. for( int j = 0; j < methodInfo.parameterTypes.Length && success; j++ )
  615. {
  616. try
  617. {
  618. string argument = commandArguments[j + 1];
  619. Type parameterType = methodInfo.parameterTypes[j];
  620. object val;
  621. if( ParseArgument( argument, parameterType, out val ) )
  622. parameters[j] = val;
  623. else
  624. {
  625. success = false;
  626. errorMessage = string.Concat( "ERROR: couldn't parse ", argument, " to ", GetTypeReadableName( parameterType ) );
  627. }
  628. }
  629. catch( Exception e )
  630. {
  631. success = false;
  632. errorMessage = "ERROR: " + e.ToString();
  633. }
  634. }
  635. if( success )
  636. methodToExecute = methodInfo;
  637. }
  638. if( methodToExecute == null )
  639. Debug.LogWarning( !string.IsNullOrEmpty( errorMessage ) ? errorMessage : "ERROR: something went wrong" );
  640. else
  641. {
  642. // Execute the method associated with the command
  643. object result = methodToExecute.method.Invoke( methodToExecute.instance, parameters );
  644. if( methodToExecute.method.ReturnType != typeof( void ) )
  645. {
  646. // Print the returned value to the console
  647. if( result == null || result.Equals( null ) )
  648. Debug.Log( "Returned: null" );
  649. else
  650. Debug.Log( "Returned: " + result.ToString() );
  651. }
  652. if( OnCommandExecuted != null )
  653. OnCommandExecuted( methodToExecute.command, parameters );
  654. }
  655. }
  656. public static void FetchArgumentsFromCommand( string command, List<string> commandArguments )
  657. {
  658. for( int i = 0; i < command.Length; i++ )
  659. {
  660. if( char.IsWhiteSpace( command[i] ) )
  661. continue;
  662. int delimiterIndex = IndexOfDelimiterGroup( command[i] );
  663. if( delimiterIndex >= 0 )
  664. {
  665. int endIndex = IndexOfDelimiterGroupEnd( command, delimiterIndex, i + 1 );
  666. commandArguments.Add( command.Substring( i + 1, endIndex - i - 1 ) );
  667. i = ( endIndex < command.Length - 1 && command[endIndex + 1] == ',' ) ? endIndex + 1 : endIndex;
  668. }
  669. else
  670. {
  671. int endIndex = IndexOfChar( command, ' ', i + 1 );
  672. commandArguments.Add( command.Substring( i, command[endIndex - 1] == ',' ? endIndex - 1 - i : endIndex - i ) );
  673. i = endIndex;
  674. }
  675. }
  676. }
  677. public static void FindCommands( string commandName, bool allowSubstringMatching, List<ConsoleMethodInfo> matchingCommands )
  678. {
  679. if( allowSubstringMatching )
  680. {
  681. for( int i = 0; i < methods.Count; i++ )
  682. {
  683. if( methods[i].IsValid() && caseInsensitiveComparer.IndexOf( methods[i].command, commandName, CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace ) >= 0 )
  684. matchingCommands.Add( methods[i] );
  685. }
  686. }
  687. else
  688. {
  689. for( int i = 0; i < methods.Count; i++ )
  690. {
  691. if( methods[i].IsValid() && caseInsensitiveComparer.Compare( methods[i].command, commandName, CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace ) == 0 )
  692. matchingCommands.Add( methods[i] );
  693. }
  694. }
  695. }
  696. // Finds all commands that have a matching signature with command
  697. // - caretIndexIncrements: indices inside "string command" that separate two arguments in the command. This is used to
  698. // figure out which argument the caret is standing on
  699. // - commandName: command's name (first argument)
  700. internal static void GetCommandSuggestions( string command, List<ConsoleMethodInfo> matchingCommands, List<int> caretIndexIncrements, ref string commandName, out int numberOfParameters )
  701. {
  702. bool commandNameCalculated = false;
  703. bool commandNameFullyTyped = false;
  704. numberOfParameters = -1;
  705. for( int i = 0; i < command.Length; i++ )
  706. {
  707. if( char.IsWhiteSpace( command[i] ) )
  708. continue;
  709. int delimiterIndex = IndexOfDelimiterGroup( command[i] );
  710. if( delimiterIndex >= 0 )
  711. {
  712. int endIndex = IndexOfDelimiterGroupEnd( command, delimiterIndex, i + 1 );
  713. if( !commandNameCalculated )
  714. {
  715. commandNameCalculated = true;
  716. commandNameFullyTyped = command.Length > endIndex;
  717. int commandNameLength = endIndex - i - 1;
  718. if( commandName == null || commandNameLength == 0 || commandName.Length != commandNameLength || caseInsensitiveComparer.IndexOf( command, commandName, i + 1, commandNameLength, CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace ) != i + 1 )
  719. commandName = command.Substring( i + 1, commandNameLength );
  720. }
  721. i = ( endIndex < command.Length - 1 && command[endIndex + 1] == ',' ) ? endIndex + 1 : endIndex;
  722. caretIndexIncrements.Add( i + 1 );
  723. }
  724. else
  725. {
  726. int endIndex = IndexOfChar( command, ' ', i + 1 );
  727. if( !commandNameCalculated )
  728. {
  729. commandNameCalculated = true;
  730. commandNameFullyTyped = command.Length > endIndex;
  731. int commandNameLength = command[endIndex - 1] == ',' ? endIndex - 1 - i : endIndex - i;
  732. if( commandName == null || commandNameLength == 0 || commandName.Length != commandNameLength || caseInsensitiveComparer.IndexOf( command, commandName, i, commandNameLength, CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace ) != i )
  733. commandName = command.Substring( i, commandNameLength );
  734. }
  735. i = endIndex;
  736. caretIndexIncrements.Add( i );
  737. }
  738. numberOfParameters++;
  739. }
  740. if( !commandNameCalculated )
  741. commandName = string.Empty;
  742. if( !string.IsNullOrEmpty( commandName ) )
  743. {
  744. int commandIndex = FindCommandIndex( commandName );
  745. if( commandIndex < 0 )
  746. commandIndex = ~commandIndex;
  747. int commandLastIndex = commandIndex;
  748. if( !commandNameFullyTyped )
  749. {
  750. // Match all commands that start with commandName
  751. if( commandIndex < methods.Count && caseInsensitiveComparer.IsPrefix( methods[commandIndex].command, commandName, CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace ) )
  752. {
  753. while( commandIndex > 0 && caseInsensitiveComparer.IsPrefix( methods[commandIndex - 1].command, commandName, CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace ) )
  754. commandIndex--;
  755. while( commandLastIndex < methods.Count - 1 && caseInsensitiveComparer.IsPrefix( methods[commandLastIndex + 1].command, commandName, CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace ) )
  756. commandLastIndex++;
  757. }
  758. else
  759. commandLastIndex = -1;
  760. }
  761. else
  762. {
  763. // Match only the commands that are equal to commandName
  764. if( commandIndex < methods.Count && caseInsensitiveComparer.Compare( methods[commandIndex].command, commandName, CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace ) == 0 )
  765. {
  766. while( commandIndex > 0 && caseInsensitiveComparer.Compare( methods[commandIndex - 1].command, commandName, CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace ) == 0 )
  767. commandIndex--;
  768. while( commandLastIndex < methods.Count - 1 && caseInsensitiveComparer.Compare( methods[commandLastIndex + 1].command, commandName, CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace ) == 0 )
  769. commandLastIndex++;
  770. }
  771. else
  772. commandLastIndex = -1;
  773. }
  774. for( ; commandIndex <= commandLastIndex; commandIndex++ )
  775. {
  776. if( methods[commandIndex].parameterTypes.Length >= numberOfParameters )
  777. matchingCommands.Add( methods[commandIndex] );
  778. }
  779. }
  780. }
  781. // Find the index of the delimiter group that 'c' belongs to
  782. private static int IndexOfDelimiterGroup( char c )
  783. {
  784. for( int i = 0; i < inputDelimiters.Length; i++ )
  785. {
  786. if( c == inputDelimiters[i][0] )
  787. return i;
  788. }
  789. return -1;
  790. }
  791. private static int IndexOfDelimiterGroupEnd( string command, int delimiterIndex, int startIndex )
  792. {
  793. char startChar = inputDelimiters[delimiterIndex][0];
  794. char endChar = inputDelimiters[delimiterIndex][1];
  795. // Check delimiter's depth for array support (e.g. [[1 2] [3 4]] for Vector2 array)
  796. int depth = 1;
  797. for( int i = startIndex; i < command.Length; i++ )
  798. {
  799. char c = command[i];
  800. if( c == endChar && --depth <= 0 )
  801. return i;
  802. else if( c == startChar )
  803. depth++;
  804. }
  805. return command.Length;
  806. }
  807. // Find the index of char in the string, or return the length of string instead of -1
  808. private static int IndexOfChar( string command, char c, int startIndex )
  809. {
  810. int result = command.IndexOf( c, startIndex );
  811. if( result < 0 )
  812. result = command.Length;
  813. return result;
  814. }
  815. // Find command's index in the list of registered commands using binary search
  816. private static int FindCommandIndex( string command )
  817. {
  818. int min = 0;
  819. int max = methods.Count - 1;
  820. while( min <= max )
  821. {
  822. int mid = ( min + max ) / 2;
  823. int comparison = caseInsensitiveComparer.Compare( command, methods[mid].command, CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace );
  824. if( comparison == 0 )
  825. return mid;
  826. else if( comparison < 0 )
  827. max = mid - 1;
  828. else
  829. min = mid + 1;
  830. }
  831. return ~min;
  832. }
  833. public static bool IsSupportedArrayType( Type type )
  834. {
  835. if( type.IsArray )
  836. {
  837. if( type.GetArrayRank() != 1 )
  838. return false;
  839. type = type.GetElementType();
  840. }
  841. else if( type.IsGenericType )
  842. {
  843. if( type.GetGenericTypeDefinition() != typeof( List<> ) )
  844. return false;
  845. type = type.GetGenericArguments()[0];
  846. }
  847. else
  848. return false;
  849. return parseFunctions.ContainsKey( type ) || typeof( Component ).IsAssignableFrom( type ) || type.IsEnum;
  850. }
  851. public static string GetTypeReadableName( Type type )
  852. {
  853. string result;
  854. if( typeReadableNames.TryGetValue( type, out result ) )
  855. return result;
  856. if( IsSupportedArrayType( type ) )
  857. {
  858. Type elementType = type.IsArray ? type.GetElementType() : type.GetGenericArguments()[0];
  859. if( typeReadableNames.TryGetValue( elementType, out result ) )
  860. return result + "[]";
  861. else
  862. return elementType.Name + "[]";
  863. }
  864. return type.Name;
  865. }
  866. public static bool ParseArgument( string input, Type argumentType, out object output )
  867. {
  868. ParseFunction parseFunction;
  869. if( parseFunctions.TryGetValue( argumentType, out parseFunction ) )
  870. return parseFunction( input, out output );
  871. else if( typeof( Component ).IsAssignableFrom( argumentType ) )
  872. return ParseComponent( input, argumentType, out output );
  873. else if( argumentType.IsEnum )
  874. return ParseEnum( input, argumentType, out output );
  875. else if( IsSupportedArrayType( argumentType ) )
  876. return ParseArray( input, argumentType, out output );
  877. else
  878. {
  879. output = null;
  880. return false;
  881. }
  882. }
  883. public static bool ParseString( string input, out object output )
  884. {
  885. output = input;
  886. return true;
  887. }
  888. public static bool ParseBool( string input, out object output )
  889. {
  890. if( input == "1" || input.ToLowerInvariant() == "true" )
  891. {
  892. output = true;
  893. return true;
  894. }
  895. if( input == "0" || input.ToLowerInvariant() == "false" )
  896. {
  897. output = false;
  898. return true;
  899. }
  900. output = false;
  901. return false;
  902. }
  903. public static bool ParseInt( string input, out object output )
  904. {
  905. int value;
  906. bool result = int.TryParse( input, out value );
  907. output = value;
  908. return result;
  909. }
  910. public static bool ParseUInt( string input, out object output )
  911. {
  912. uint value;
  913. bool result = uint.TryParse( input, out value );
  914. output = value;
  915. return result;
  916. }
  917. public static bool ParseLong( string input, out object output )
  918. {
  919. long value;
  920. bool result = long.TryParse( !input.EndsWith( "L", StringComparison.OrdinalIgnoreCase ) ? input : input.Substring( 0, input.Length - 1 ), out value );
  921. output = value;
  922. return result;
  923. }
  924. public static bool ParseULong( string input, out object output )
  925. {
  926. ulong value;
  927. bool result = ulong.TryParse( !input.EndsWith( "L", StringComparison.OrdinalIgnoreCase ) ? input : input.Substring( 0, input.Length - 1 ), out value );
  928. output = value;
  929. return result;
  930. }
  931. public static bool ParseByte( string input, out object output )
  932. {
  933. byte value;
  934. bool result = byte.TryParse( input, out value );
  935. output = value;
  936. return result;
  937. }
  938. public static bool ParseSByte( string input, out object output )
  939. {
  940. sbyte value;
  941. bool result = sbyte.TryParse( input, out value );
  942. output = value;
  943. return result;
  944. }
  945. public static bool ParseShort( string input, out object output )
  946. {
  947. short value;
  948. bool result = short.TryParse( input, out value );
  949. output = value;
  950. return result;
  951. }
  952. public static bool ParseUShort( string input, out object output )
  953. {
  954. ushort value;
  955. bool result = ushort.TryParse( input, out value );
  956. output = value;
  957. return result;
  958. }
  959. public static bool ParseChar( string input, out object output )
  960. {
  961. char value;
  962. bool result = char.TryParse( input, out value );
  963. output = value;
  964. return result;
  965. }
  966. public static bool ParseFloat( string input, out object output )
  967. {
  968. float value;
  969. bool result = float.TryParse( !input.EndsWith( "f", StringComparison.OrdinalIgnoreCase ) ? input : input.Substring( 0, input.Length - 1 ), NumberStyles.Float, CultureInfo.InvariantCulture, out value );
  970. output = value;
  971. return result;
  972. }
  973. public static bool ParseDouble( string input, out object output )
  974. {
  975. double value;
  976. bool result = double.TryParse( !input.EndsWith( "f", StringComparison.OrdinalIgnoreCase ) ? input : input.Substring( 0, input.Length - 1 ), NumberStyles.Float, CultureInfo.InvariantCulture, out value );
  977. output = value;
  978. return result;
  979. }
  980. public static bool ParseDecimal( string input, out object output )
  981. {
  982. decimal value;
  983. bool result = decimal.TryParse( !input.EndsWith( "f", StringComparison.OrdinalIgnoreCase ) ? input : input.Substring( 0, input.Length - 1 ), NumberStyles.Float, CultureInfo.InvariantCulture, out value );
  984. output = value;
  985. return result;
  986. }
  987. public static bool ParseVector2( string input, out object output )
  988. {
  989. return ParseVector( input, typeof( Vector2 ), out output );
  990. }
  991. public static bool ParseVector3( string input, out object output )
  992. {
  993. return ParseVector( input, typeof( Vector3 ), out output );
  994. }
  995. public static bool ParseVector4( string input, out object output )
  996. {
  997. return ParseVector( input, typeof( Vector4 ), out output );
  998. }
  999. public static bool ParseQuaternion( string input, out object output )
  1000. {
  1001. return ParseVector( input, typeof( Quaternion ), out output );
  1002. }
  1003. public static bool ParseColor( string input, out object output )
  1004. {
  1005. return ParseVector( input, typeof( Color ), out output );
  1006. }
  1007. public static bool ParseColor32( string input, out object output )
  1008. {
  1009. return ParseVector( input, typeof( Color32 ), out output );
  1010. }
  1011. public static bool ParseRect( string input, out object output )
  1012. {
  1013. return ParseVector( input, typeof( Rect ), out output );
  1014. }
  1015. public static bool ParseRectOffset( string input, out object output )
  1016. {
  1017. return ParseVector( input, typeof( RectOffset ), out output );
  1018. }
  1019. public static bool ParseBounds( string input, out object output )
  1020. {
  1021. return ParseVector( input, typeof( Bounds ), out output );
  1022. }
  1023. public static bool ParseVector2Int( string input, out object output )
  1024. {
  1025. return ParseVector( input, typeof( Vector2Int ), out output );
  1026. }
  1027. public static bool ParseVector3Int( string input, out object output )
  1028. {
  1029. return ParseVector( input, typeof( Vector3Int ), out output );
  1030. }
  1031. public static bool ParseRectInt( string input, out object output )
  1032. {
  1033. return ParseVector( input, typeof( RectInt ), out output );
  1034. }
  1035. public static bool ParseBoundsInt( string input, out object output )
  1036. {
  1037. return ParseVector( input, typeof( BoundsInt ), out output );
  1038. }
  1039. public static bool ParseGameObject( string input, out object output )
  1040. {
  1041. output = input == "null" ? null : GameObject.Find( input );
  1042. return true;
  1043. }
  1044. public static bool ParseComponent( string input, Type componentType, out object output )
  1045. {
  1046. GameObject gameObject = input == "null" ? null : GameObject.Find( input );
  1047. output = gameObject ? gameObject.GetComponent( componentType ) : null;
  1048. return true;
  1049. }
  1050. public static bool ParseEnum( string input, Type enumType, out object output )
  1051. {
  1052. const int NONE = 0, OR = 1, AND = 2;
  1053. int outputInt = 0;
  1054. int operation = NONE; // 0: nothing, 1: OR with outputInt, 2: AND with outputInt
  1055. for( int i = 0; i < input.Length; i++ )
  1056. {
  1057. string enumStr;
  1058. int orIndex = input.IndexOf( '|', i );
  1059. int andIndex = input.IndexOf( '&', i );
  1060. if( orIndex < 0 )
  1061. enumStr = input.Substring( i, ( andIndex < 0 ? input.Length : andIndex ) - i ).Trim();
  1062. else
  1063. enumStr = input.Substring( i, ( andIndex < 0 ? orIndex : Mathf.Min( andIndex, orIndex ) ) - i ).Trim();
  1064. int value;
  1065. if( !int.TryParse( enumStr, out value ) )
  1066. {
  1067. try
  1068. {
  1069. // Case-insensitive enum parsing
  1070. value = Convert.ToInt32( Enum.Parse( enumType, enumStr, true ) );
  1071. }
  1072. catch
  1073. {
  1074. output = null;
  1075. return false;
  1076. }
  1077. }
  1078. if( operation == NONE )
  1079. outputInt = value;
  1080. else if( operation == OR )
  1081. outputInt |= value;
  1082. else
  1083. outputInt &= value;
  1084. if( orIndex >= 0 )
  1085. {
  1086. if( andIndex > orIndex )
  1087. {
  1088. operation = AND;
  1089. i = andIndex;
  1090. }
  1091. else
  1092. {
  1093. operation = OR;
  1094. i = orIndex;
  1095. }
  1096. }
  1097. else if( andIndex >= 0 )
  1098. {
  1099. operation = AND;
  1100. i = andIndex;
  1101. }
  1102. else
  1103. i = input.Length;
  1104. }
  1105. output = Enum.ToObject( enumType, outputInt );
  1106. return true;
  1107. }
  1108. public static bool ParseArray( string input, Type arrayType, out object output )
  1109. {
  1110. List<string> valuesToParse = new List<string>( 2 );
  1111. FetchArgumentsFromCommand( input, valuesToParse );
  1112. IList result = (IList) Activator.CreateInstance( arrayType, new object[1] { valuesToParse.Count } );
  1113. output = result;
  1114. if( arrayType.IsArray )
  1115. {
  1116. Type elementType = arrayType.GetElementType();
  1117. for( int i = 0; i < valuesToParse.Count; i++ )
  1118. {
  1119. object obj;
  1120. if( !ParseArgument( valuesToParse[i], elementType, out obj ) )
  1121. return false;
  1122. result[i] = obj;
  1123. }
  1124. }
  1125. else
  1126. {
  1127. Type elementType = arrayType.GetGenericArguments()[0];
  1128. for( int i = 0; i < valuesToParse.Count; i++ )
  1129. {
  1130. object obj;
  1131. if( !ParseArgument( valuesToParse[i], elementType, out obj ) )
  1132. return false;
  1133. result.Add( obj );
  1134. }
  1135. }
  1136. return true;
  1137. }
  1138. // Create a vector of specified type (fill the blank slots with 0 or ignore unnecessary slots)
  1139. private static bool ParseVector( string input, Type vectorType, out object output )
  1140. {
  1141. List<string> tokens = new List<string>( input.Replace( ',', ' ' ).Trim().Split( ' ' ) );
  1142. for( int i = tokens.Count - 1; i >= 0; i-- )
  1143. {
  1144. tokens[i] = tokens[i].Trim();
  1145. if( tokens[i].Length == 0 )
  1146. tokens.RemoveAt( i );
  1147. }
  1148. float[] tokenValues = new float[tokens.Count];
  1149. for( int i = 0; i < tokens.Count; i++ )
  1150. {
  1151. object val;
  1152. if( !ParseFloat( tokens[i], out val ) )
  1153. {
  1154. if( vectorType == typeof( Vector3 ) )
  1155. output = Vector3.zero;
  1156. else if( vectorType == typeof( Vector2 ) )
  1157. output = Vector2.zero;
  1158. else
  1159. output = Vector4.zero;
  1160. return false;
  1161. }
  1162. tokenValues[i] = (float) val;
  1163. }
  1164. if( vectorType == typeof( Vector3 ) )
  1165. {
  1166. Vector3 result = Vector3.zero;
  1167. for( int i = 0; i < tokenValues.Length && i < 3; i++ )
  1168. result[i] = tokenValues[i];
  1169. output = result;
  1170. }
  1171. else if( vectorType == typeof( Vector2 ) )
  1172. {
  1173. Vector2 result = Vector2.zero;
  1174. for( int i = 0; i < tokenValues.Length && i < 2; i++ )
  1175. result[i] = tokenValues[i];
  1176. output = result;
  1177. }
  1178. else if( vectorType == typeof( Vector4 ) )
  1179. {
  1180. Vector4 result = Vector4.zero;
  1181. for( int i = 0; i < tokenValues.Length && i < 4; i++ )
  1182. result[i] = tokenValues[i];
  1183. output = result;
  1184. }
  1185. else if( vectorType == typeof( Quaternion ) )
  1186. {
  1187. Quaternion result = Quaternion.identity;
  1188. for( int i = 0; i < tokenValues.Length && i < 4; i++ )
  1189. result[i] = tokenValues[i];
  1190. output = result;
  1191. }
  1192. else if( vectorType == typeof( Color ) )
  1193. {
  1194. Color result = Color.black;
  1195. for( int i = 0; i < tokenValues.Length && i < 4; i++ )
  1196. result[i] = tokenValues[i];
  1197. output = result;
  1198. }
  1199. else if( vectorType == typeof( Color32 ) )
  1200. {
  1201. Color32 result = new Color32( 0, 0, 0, 255 );
  1202. if( tokenValues.Length > 0 )
  1203. result.r = (byte) Mathf.RoundToInt( tokenValues[0] );
  1204. if( tokenValues.Length > 1 )
  1205. result.g = (byte) Mathf.RoundToInt( tokenValues[1] );
  1206. if( tokenValues.Length > 2 )
  1207. result.b = (byte) Mathf.RoundToInt( tokenValues[2] );
  1208. if( tokenValues.Length > 3 )
  1209. result.a = (byte) Mathf.RoundToInt( tokenValues[3] );
  1210. output = result;
  1211. }
  1212. else if( vectorType == typeof( Rect ) )
  1213. {
  1214. Rect result = Rect.zero;
  1215. if( tokenValues.Length > 0 )
  1216. result.x = tokenValues[0];
  1217. if( tokenValues.Length > 1 )
  1218. result.y = tokenValues[1];
  1219. if( tokenValues.Length > 2 )
  1220. result.width = tokenValues[2];
  1221. if( tokenValues.Length > 3 )
  1222. result.height = tokenValues[3];
  1223. output = result;
  1224. }
  1225. else if( vectorType == typeof( RectOffset ) )
  1226. {
  1227. RectOffset result = new RectOffset();
  1228. if( tokenValues.Length > 0 )
  1229. result.left = Mathf.RoundToInt( tokenValues[0] );
  1230. if( tokenValues.Length > 1 )
  1231. result.right = Mathf.RoundToInt( tokenValues[1] );
  1232. if( tokenValues.Length > 2 )
  1233. result.top = Mathf.RoundToInt( tokenValues[2] );
  1234. if( tokenValues.Length > 3 )
  1235. result.bottom = Mathf.RoundToInt( tokenValues[3] );
  1236. output = result;
  1237. }
  1238. else if( vectorType == typeof( Bounds ) )
  1239. {
  1240. Vector3 center = Vector3.zero;
  1241. for( int i = 0; i < tokenValues.Length && i < 3; i++ )
  1242. center[i] = tokenValues[i];
  1243. Vector3 size = Vector3.zero;
  1244. for( int i = 3; i < tokenValues.Length && i < 6; i++ )
  1245. size[i - 3] = tokenValues[i];
  1246. output = new Bounds( center, size );
  1247. }
  1248. else if( vectorType == typeof( Vector3Int ) )
  1249. {
  1250. Vector3Int result = Vector3Int.zero;
  1251. for( int i = 0; i < tokenValues.Length && i < 3; i++ )
  1252. result[i] = Mathf.RoundToInt( tokenValues[i] );
  1253. output = result;
  1254. }
  1255. else if( vectorType == typeof( Vector2Int ) )
  1256. {
  1257. Vector2Int result = Vector2Int.zero;
  1258. for( int i = 0; i < tokenValues.Length && i < 2; i++ )
  1259. result[i] = Mathf.RoundToInt( tokenValues[i] );
  1260. output = result;
  1261. }
  1262. else if( vectorType == typeof( RectInt ) )
  1263. {
  1264. RectInt result = new RectInt();
  1265. if( tokenValues.Length > 0 )
  1266. result.x = Mathf.RoundToInt( tokenValues[0] );
  1267. if( tokenValues.Length > 1 )
  1268. result.y = Mathf.RoundToInt( tokenValues[1] );
  1269. if( tokenValues.Length > 2 )
  1270. result.width = Mathf.RoundToInt( tokenValues[2] );
  1271. if( tokenValues.Length > 3 )
  1272. result.height = Mathf.RoundToInt( tokenValues[3] );
  1273. output = result;
  1274. }
  1275. else if( vectorType == typeof( BoundsInt ) )
  1276. {
  1277. Vector3Int center = Vector3Int.zero;
  1278. for( int i = 0; i < tokenValues.Length && i < 3; i++ )
  1279. center[i] = Mathf.RoundToInt( tokenValues[i] );
  1280. Vector3Int size = Vector3Int.zero;
  1281. for( int i = 3; i < tokenValues.Length && i < 6; i++ )
  1282. size[i - 3] = Mathf.RoundToInt( tokenValues[i] );
  1283. output = new BoundsInt( center, size );
  1284. }
  1285. else
  1286. {
  1287. output = null;
  1288. return false;
  1289. }
  1290. return true;
  1291. }
  1292. }
  1293. }