DebugLogConsole.cs 52 KB

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