DebugLogConsole.cs 52 KB

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