DebugLogConsole.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693
  1. using UnityEngine;
  2. using System.Reflection;
  3. using System.Collections.Generic;
  4. using System.Text;
  5. using System;
  6. // Manages the console commands, parses console input and handles execution of commands
  7. // Supported method parameter types: int, float, bool, string, Vector2, Vector3, Vector4
  8. // Helper class to store important information about a command
  9. namespace IngameDebugConsole
  10. {
  11. public class ConsoleMethodInfo
  12. {
  13. public readonly MethodInfo method;
  14. public readonly Type[] parameterTypes;
  15. public readonly object instance;
  16. public readonly string signature;
  17. public ConsoleMethodInfo( MethodInfo method, Type[] parameterTypes, object instance, string signature )
  18. {
  19. this.method = method;
  20. this.parameterTypes = parameterTypes;
  21. this.instance = instance;
  22. this.signature = signature;
  23. }
  24. public bool IsValid()
  25. {
  26. if( !method.IsStatic && ( instance == null || instance.Equals( null ) ) )
  27. return false;
  28. return true;
  29. }
  30. }
  31. public static class DebugLogConsole
  32. {
  33. public delegate bool ParseFunction( string input, out object output );
  34. // All the commands
  35. private static Dictionary<string, ConsoleMethodInfo> methods = new Dictionary<string, ConsoleMethodInfo>();
  36. // All the parse functions
  37. private static Dictionary<Type, ParseFunction> parseFunctions;
  38. // All the readable names of accepted types
  39. private static Dictionary<Type, string> typeReadableNames;
  40. // Split arguments of an entered command
  41. private static List<string> commandArguments = new List<string>( 8 );
  42. // Command parameter delimeter groups
  43. private static readonly string[] inputDelimiters = new string[] { "\"\"", "{}", "()", "[]" };
  44. static DebugLogConsole()
  45. {
  46. parseFunctions = new Dictionary<Type, ParseFunction>() {
  47. { typeof( string ), ParseString },
  48. { typeof( bool ), ParseBool },
  49. { typeof( int ), ParseInt },
  50. { typeof( uint ), ParseUInt },
  51. { typeof( long ), ParseLong },
  52. { typeof( ulong ), ParseULong },
  53. { typeof( byte ), ParseByte },
  54. { typeof( sbyte ), ParseSByte },
  55. { typeof( short ), ParseShort },
  56. { typeof( ushort ), ParseUShort },
  57. { typeof( char ), ParseChar },
  58. { typeof( float ), ParseFloat },
  59. { typeof( double ), ParseDouble },
  60. { typeof( decimal ), ParseDecimal },
  61. { typeof( Vector2 ), ParseVector2 },
  62. { typeof( Vector3 ), ParseVector3 },
  63. { typeof( Vector4 ), ParseVector4 },
  64. { typeof( GameObject ), ParseGameObject } };
  65. typeReadableNames = new Dictionary<Type, string>() {
  66. { typeof( string ), "String" },
  67. { typeof( bool ), "Boolean" },
  68. { typeof( int ), "Integer" },
  69. { typeof( uint ), "Unsigned Integer" },
  70. { typeof( long ), "Long" },
  71. { typeof( ulong ), "Unsigned Long" },
  72. { typeof( byte ), "Byte" },
  73. { typeof( sbyte ), "Short Byte" },
  74. { typeof( short ), "Short" },
  75. { typeof( ushort ), "Unsigned Short" },
  76. { typeof( char ), "Char" },
  77. { typeof( float ), "Float" },
  78. { typeof( double ), "Double" },
  79. { typeof( decimal ), "Decimal" },
  80. { typeof( Vector2 ), "Vector2" },
  81. { typeof( Vector3 ), "Vector3" },
  82. { typeof( Vector4 ), "Vector4" },
  83. { typeof( GameObject ), "GameObject" } };
  84. #if UNITY_EDITOR || !NETFX_CORE
  85. // Load commands in most common Unity assemblies
  86. HashSet<Assembly> assemblies = new HashSet<Assembly> { Assembly.GetAssembly( typeof( DebugLogConsole ) ) };
  87. try
  88. {
  89. assemblies.Add( Assembly.Load( "Assembly-CSharp" ) );
  90. }
  91. catch { }
  92. foreach( var assembly in assemblies )
  93. {
  94. foreach( var type in assembly.GetExportedTypes() )
  95. {
  96. foreach( var method in type.GetMethods( BindingFlags.Static | BindingFlags.Public | BindingFlags.DeclaredOnly ) )
  97. {
  98. foreach( var attribute in method.GetCustomAttributes( typeof( ConsoleMethodAttribute ), false ) )
  99. {
  100. ConsoleMethodAttribute consoleMethod = attribute as ConsoleMethodAttribute;
  101. if( consoleMethod != null )
  102. AddCommand( consoleMethod.Command, consoleMethod.Description, method );
  103. }
  104. }
  105. }
  106. }
  107. #endif
  108. AddCommandStatic( "help", "Prints all commands", "LogAllCommands", typeof( DebugLogConsole ) );
  109. AddCommandStatic( "sysinfo", "Prints system information", "LogSystemInfo", typeof( DebugLogConsole ) );
  110. }
  111. // Logs the list of available commands
  112. public static void LogAllCommands()
  113. {
  114. int length = 25;
  115. foreach( var entry in methods )
  116. {
  117. if( entry.Value.IsValid() )
  118. length += 3 + entry.Value.signature.Length;
  119. }
  120. StringBuilder stringBuilder = new StringBuilder( length );
  121. stringBuilder.Append( "Available commands:" );
  122. foreach( var entry in methods )
  123. {
  124. if( entry.Value.IsValid() )
  125. stringBuilder.Append( "\n- " ).Append( entry.Value.signature );
  126. }
  127. Debug.Log( stringBuilder.Append( "\n" ).ToString() );
  128. }
  129. // Logs system information
  130. public static void LogSystemInfo()
  131. {
  132. StringBuilder stringBuilder = new StringBuilder( 1024 );
  133. stringBuilder.Append( "Rig: " ).AppendSysInfoIfPresent( SystemInfo.deviceModel ).AppendSysInfoIfPresent( SystemInfo.processorType )
  134. .AppendSysInfoIfPresent( SystemInfo.systemMemorySize, "MB RAM" ).Append( SystemInfo.processorCount ).Append( " cores\n" );
  135. stringBuilder.Append( "OS: " ).Append( SystemInfo.operatingSystem ).Append( "\n" );
  136. stringBuilder.Append( "GPU: " ).Append( SystemInfo.graphicsDeviceName ).Append( " " ).Append( SystemInfo.graphicsMemorySize )
  137. .Append( "MB " ).Append( SystemInfo.graphicsDeviceVersion )
  138. .Append( SystemInfo.graphicsMultiThreaded ? " multi-threaded\n" : "\n" );
  139. stringBuilder.Append( "Data Path: " ).Append( Application.dataPath ).Append( "\n" );
  140. stringBuilder.Append( "Persistent Data Path: " ).Append( Application.persistentDataPath ).Append( "\n" );
  141. stringBuilder.Append( "StreamingAssets Path: " ).Append( Application.streamingAssetsPath ).Append( "\n" );
  142. stringBuilder.Append( "Temporary Cache Path: " ).Append( Application.temporaryCachePath ).Append( "\n" );
  143. stringBuilder.Append( "Device ID: " ).Append( SystemInfo.deviceUniqueIdentifier ).Append( "\n" );
  144. stringBuilder.Append( "Max Texture Size: " ).Append( SystemInfo.maxTextureSize ).Append( "\n" );
  145. stringBuilder.Append( "Max Cubemap Size: " ).Append( SystemInfo.maxCubemapSize ).Append( "\n" );
  146. stringBuilder.Append( "Accelerometer: " ).Append( SystemInfo.supportsAccelerometer ? "supported\n" : "not supported\n" );
  147. stringBuilder.Append( "Gyro: " ).Append( SystemInfo.supportsGyroscope ? "supported\n" : "not supported\n" );
  148. stringBuilder.Append( "Location Service: " ).Append( SystemInfo.supportsLocationService ? "supported\n" : "not supported\n" );
  149. #if !UNITY_2019_1_OR_NEWER
  150. stringBuilder.Append( "Image Effects: " ).Append( SystemInfo.supportsImageEffects ? "supported\n" : "not supported\n" );
  151. stringBuilder.Append( "RenderToCubemap: " ).Append( SystemInfo.supportsRenderToCubemap ? "supported\n" : "not supported\n" );
  152. #endif
  153. stringBuilder.Append( "Compute Shaders: " ).Append( SystemInfo.supportsComputeShaders ? "supported\n" : "not supported\n" );
  154. stringBuilder.Append( "Shadows: " ).Append( SystemInfo.supportsShadows ? "supported\n" : "not supported\n" );
  155. stringBuilder.Append( "Instancing: " ).Append( SystemInfo.supportsInstancing ? "supported\n" : "not supported\n" );
  156. stringBuilder.Append( "Motion Vectors: " ).Append( SystemInfo.supportsMotionVectors ? "supported\n" : "not supported\n" );
  157. stringBuilder.Append( "3D Textures: " ).Append( SystemInfo.supports3DTextures ? "supported\n" : "not supported\n" );
  158. stringBuilder.Append( "3D Render Textures: " ).Append( SystemInfo.supports3DRenderTextures ? "supported\n" : "not supported\n" );
  159. stringBuilder.Append( "2D Array Textures: " ).Append( SystemInfo.supports2DArrayTextures ? "supported\n" : "not supported\n" );
  160. stringBuilder.Append( "Cubemap Array Textures: " ).Append( SystemInfo.supportsCubemapArrayTextures ? "supported" : "not supported" );
  161. Debug.Log( stringBuilder.Append( "\n" ).ToString() );
  162. }
  163. private static StringBuilder AppendSysInfoIfPresent( this StringBuilder sb, string info, string postfix = null )
  164. {
  165. if( info != SystemInfo.unsupportedIdentifier )
  166. {
  167. sb.Append( info );
  168. if( postfix != null )
  169. sb.Append( postfix );
  170. sb.Append( " " );
  171. }
  172. return sb;
  173. }
  174. private static StringBuilder AppendSysInfoIfPresent( this StringBuilder sb, int info, string postfix = null )
  175. {
  176. if( info > 0 )
  177. {
  178. sb.Append( info );
  179. if( postfix != null )
  180. sb.Append( postfix );
  181. sb.Append( " " );
  182. }
  183. return sb;
  184. }
  185. // Add a command related with an instance method (i.e. non static method)
  186. public static void AddCommandInstance( string command, string description, string methodName, object instance )
  187. {
  188. if( instance == null )
  189. {
  190. Debug.LogError( "Instance can't be null!" );
  191. return;
  192. }
  193. AddCommand( command, description, methodName, instance.GetType(), instance );
  194. }
  195. // Add a command related with a static method (i.e. no instance is required to call the method)
  196. public static void AddCommandStatic( string command, string description, string methodName, Type ownerType )
  197. {
  198. AddCommand( command, description, methodName, ownerType );
  199. }
  200. // Remove a command from the console
  201. public static void RemoveCommand( string command )
  202. {
  203. if( !string.IsNullOrEmpty( command ) )
  204. methods.Remove( command );
  205. }
  206. // Returns the first command that starts with the entered argument
  207. public static string GetAutoCompleteCommand( string commandStart )
  208. {
  209. foreach( var entry in methods )
  210. {
  211. if( entry.Key.StartsWith( commandStart ) )
  212. return entry.Key;
  213. }
  214. return null;
  215. }
  216. // Create a new command and set its properties
  217. private static void AddCommand( string command, string description, string methodName, Type ownerType, object instance = null )
  218. {
  219. if( string.IsNullOrEmpty( command ) )
  220. {
  221. Debug.LogError( "Command name can't be empty!" );
  222. return;
  223. }
  224. command = command.Trim();
  225. if( command.IndexOf( ' ' ) >= 0 )
  226. {
  227. Debug.LogError( "Command name can't contain whitespace: " + command );
  228. return;
  229. }
  230. // Get the method from the class
  231. MethodInfo method = ownerType.GetMethod( methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static );
  232. if( method == null )
  233. {
  234. Debug.LogError( methodName + " does not exist in " + ownerType );
  235. return;
  236. }
  237. AddCommand( command, description, method, instance );
  238. }
  239. private static void AddCommand( string command, string description, MethodInfo method, object instance = null )
  240. {
  241. // Fetch the parameters of the class
  242. ParameterInfo[] parameters = method.GetParameters();
  243. if( parameters == null )
  244. parameters = new ParameterInfo[0];
  245. bool isMethodValid = true;
  246. // Store the parameter types in an array
  247. Type[] parameterTypes = new Type[parameters.Length];
  248. for( int k = 0; k < parameters.Length; k++ )
  249. {
  250. Type parameterType = parameters[k].ParameterType;
  251. if( parseFunctions.ContainsKey( parameterType ) )
  252. parameterTypes[k] = parameterType;
  253. else
  254. {
  255. isMethodValid = false;
  256. break;
  257. }
  258. }
  259. // If method is valid, associate it with the entered command
  260. if( isMethodValid )
  261. {
  262. StringBuilder methodSignature = new StringBuilder( 256 );
  263. methodSignature.Append( command ).Append( ": " );
  264. if( !string.IsNullOrEmpty( description ) )
  265. methodSignature.Append( description ).Append( " -> " );
  266. methodSignature.Append( method.DeclaringType.ToString() ).Append( "." ).Append( method.Name ).Append( "(" );
  267. for( int i = 0; i < parameterTypes.Length; i++ )
  268. {
  269. Type type = parameterTypes[i];
  270. string typeName;
  271. if( !typeReadableNames.TryGetValue( type, out typeName ) )
  272. typeName = type.Name;
  273. methodSignature.Append( typeName );
  274. if( i < parameterTypes.Length - 1 )
  275. methodSignature.Append( ", " );
  276. }
  277. methodSignature.Append( ")" );
  278. Type returnType = method.ReturnType;
  279. if( returnType != typeof( void ) )
  280. {
  281. string returnTypeName;
  282. if( !typeReadableNames.TryGetValue( returnType, out returnTypeName ) )
  283. returnTypeName = returnType.Name;
  284. methodSignature.Append( " : " ).Append( returnTypeName );
  285. }
  286. methods[command] = new ConsoleMethodInfo( method, parameterTypes, instance, methodSignature.ToString() );
  287. }
  288. }
  289. // Parse the command and try to execute it
  290. public static void ExecuteCommand( string command )
  291. {
  292. if( command == null )
  293. return;
  294. command = command.Trim();
  295. if( command.Length == 0 )
  296. return;
  297. // Parse the arguments
  298. commandArguments.Clear();
  299. int endIndex = IndexOfChar( command, ' ', 0 );
  300. commandArguments.Add( command.Substring( 0, endIndex ) );
  301. for( int i = endIndex + 1; i < command.Length; i++ )
  302. {
  303. if( command[i] == ' ' )
  304. continue;
  305. int delimiterIndex = IndexOfDelimiter( command[i] );
  306. if( delimiterIndex >= 0 )
  307. {
  308. endIndex = IndexOfChar( command, inputDelimiters[delimiterIndex][1], i + 1 );
  309. commandArguments.Add( command.Substring( i + 1, endIndex - i - 1 ) );
  310. }
  311. else
  312. {
  313. endIndex = IndexOfChar( command, ' ', i + 1 );
  314. commandArguments.Add( command.Substring( i, endIndex - i ) );
  315. }
  316. i = endIndex;
  317. }
  318. // Check if command exists
  319. ConsoleMethodInfo methodInfo;
  320. if( !methods.TryGetValue( commandArguments[0], out methodInfo ) )
  321. Debug.LogWarning( "Can't find command: " + commandArguments[0] );
  322. else if( !methodInfo.IsValid() )
  323. Debug.LogWarning( "Method no longer valid (instance dead): " + commandArguments[0] );
  324. else
  325. {
  326. // Check if number of parameter match
  327. if( methodInfo.parameterTypes.Length != commandArguments.Count - 1 )
  328. {
  329. Debug.LogWarning( "Parameter count mismatch: " + methodInfo.parameterTypes.Length + " parameters are needed" );
  330. return;
  331. }
  332. Debug.Log( "Executing command: " + commandArguments[0] );
  333. // Parse the parameters into objects
  334. object[] parameters = new object[methodInfo.parameterTypes.Length];
  335. for( int i = 0; i < methodInfo.parameterTypes.Length; i++ )
  336. {
  337. string argument = commandArguments[i + 1];
  338. Type parameterType = methodInfo.parameterTypes[i];
  339. ParseFunction parseFunction;
  340. if( !parseFunctions.TryGetValue( parameterType, out parseFunction ) )
  341. {
  342. Debug.LogError( "Unsupported parameter type: " + parameterType.Name );
  343. return;
  344. }
  345. object val;
  346. if( !parseFunction( argument, out val ) )
  347. {
  348. Debug.LogError( "Couldn't parse " + argument + " to " + parameterType.Name );
  349. return;
  350. }
  351. parameters[i] = val;
  352. }
  353. // Execute the method associated with the command
  354. object result = methodInfo.method.Invoke( methodInfo.instance, parameters );
  355. if( methodInfo.method.ReturnType != typeof( void ) )
  356. {
  357. // Print the returned value to the console
  358. if( result == null || result.Equals( null ) )
  359. Debug.Log( "Value returned: null" );
  360. else
  361. Debug.Log( "Value returned: " + result.ToString() );
  362. }
  363. }
  364. }
  365. // Find the index of the delimiter group that 'c' belongs to
  366. private static int IndexOfDelimiter( char c )
  367. {
  368. for( int i = 0; i < inputDelimiters.Length; i++ )
  369. {
  370. if( c == inputDelimiters[i][0] )
  371. return i;
  372. }
  373. return -1;
  374. }
  375. // Find the index of char in the string, or return the length of string instead of -1
  376. private static int IndexOfChar( string command, char c, int startIndex )
  377. {
  378. int result = command.IndexOf( c, startIndex );
  379. if( result < 0 )
  380. result = command.Length;
  381. return result;
  382. }
  383. private static bool ParseString( string input, out object output )
  384. {
  385. output = input;
  386. return input.Length > 0;
  387. }
  388. private static bool ParseBool( string input, out object output )
  389. {
  390. if( input == "1" || input.ToLowerInvariant() == "true" )
  391. {
  392. output = true;
  393. return true;
  394. }
  395. if( input == "0" || input.ToLowerInvariant() == "false" )
  396. {
  397. output = false;
  398. return true;
  399. }
  400. output = false;
  401. return false;
  402. }
  403. private static bool ParseInt( string input, out object output )
  404. {
  405. bool result;
  406. int value;
  407. result = int.TryParse( input, out value );
  408. output = value;
  409. return result;
  410. }
  411. private static bool ParseUInt( string input, out object output )
  412. {
  413. bool result;
  414. uint value;
  415. result = uint.TryParse( input, out value );
  416. output = value;
  417. return result;
  418. }
  419. private static bool ParseLong( string input, out object output )
  420. {
  421. bool result;
  422. long value;
  423. result = long.TryParse( input, out value );
  424. output = value;
  425. return result;
  426. }
  427. private static bool ParseULong( string input, out object output )
  428. {
  429. bool result;
  430. ulong value;
  431. result = ulong.TryParse( input, out value );
  432. output = value;
  433. return result;
  434. }
  435. private static bool ParseByte( string input, out object output )
  436. {
  437. bool result;
  438. byte value;
  439. result = byte.TryParse( input, out value );
  440. output = value;
  441. return result;
  442. }
  443. private static bool ParseSByte( string input, out object output )
  444. {
  445. bool result;
  446. sbyte value;
  447. result = sbyte.TryParse( input, out value );
  448. output = value;
  449. return result;
  450. }
  451. private static bool ParseShort( string input, out object output )
  452. {
  453. bool result;
  454. short value;
  455. result = short.TryParse( input, out value );
  456. output = value;
  457. return result;
  458. }
  459. private static bool ParseUShort( string input, out object output )
  460. {
  461. bool result;
  462. ushort value;
  463. result = ushort.TryParse( input, out value );
  464. output = value;
  465. return result;
  466. }
  467. private static bool ParseChar( string input, out object output )
  468. {
  469. bool result;
  470. char value;
  471. result = char.TryParse( input, out value );
  472. output = value;
  473. return result;
  474. }
  475. private static bool ParseFloat( string input, out object output )
  476. {
  477. bool result;
  478. float value;
  479. result = float.TryParse( input, out value );
  480. output = value;
  481. return result;
  482. }
  483. private static bool ParseDouble( string input, out object output )
  484. {
  485. bool result;
  486. double value;
  487. result = double.TryParse( input, out value );
  488. output = value;
  489. return result;
  490. }
  491. private static bool ParseDecimal( string input, out object output )
  492. {
  493. bool result;
  494. decimal value;
  495. result = decimal.TryParse( input, out value );
  496. output = value;
  497. return result;
  498. }
  499. private static bool ParseVector2( string input, out object output )
  500. {
  501. return CreateVectorFromInput( input, typeof( Vector2 ), out output );
  502. }
  503. private static bool ParseVector3( string input, out object output )
  504. {
  505. return CreateVectorFromInput( input, typeof( Vector3 ), out output );
  506. }
  507. private static bool ParseVector4( string input, out object output )
  508. {
  509. return CreateVectorFromInput( input, typeof( Vector4 ), out output );
  510. }
  511. private static bool ParseGameObject( string input, out object output )
  512. {
  513. output = GameObject.Find( input );
  514. return true;
  515. }
  516. // Create a vector of specified type (fill the blank slots with 0 or ignore unnecessary slots)
  517. private static bool CreateVectorFromInput( string input, Type vectorType, out object output )
  518. {
  519. List<string> tokens = new List<string>( input.Replace( ',', ' ' ).Trim().Split( ' ' ) );
  520. int i;
  521. for( i = tokens.Count - 1; i >= 0; i-- )
  522. {
  523. tokens[i] = tokens[i].Trim();
  524. if( tokens[i].Length == 0 )
  525. tokens.RemoveAt( i );
  526. }
  527. float[] tokenValues = new float[tokens.Count];
  528. for( i = 0; i < tokens.Count; i++ )
  529. {
  530. float val;
  531. if( !float.TryParse( tokens[i], out val ) )
  532. {
  533. if( vectorType == typeof( Vector3 ) )
  534. output = new Vector3();
  535. else if( vectorType == typeof( Vector2 ) )
  536. output = new Vector2();
  537. else
  538. output = new Vector4();
  539. return false;
  540. }
  541. tokenValues[i] = val;
  542. }
  543. if( vectorType == typeof( Vector3 ) )
  544. {
  545. Vector3 result = new Vector3();
  546. for( i = 0; i < tokenValues.Length && i < 3; i++ )
  547. result[i] = tokenValues[i];
  548. for( ; i < 3; i++ )
  549. result[i] = 0;
  550. output = result;
  551. }
  552. else if( vectorType == typeof( Vector2 ) )
  553. {
  554. Vector2 result = new Vector2();
  555. for( i = 0; i < tokenValues.Length && i < 2; i++ )
  556. result[i] = tokenValues[i];
  557. for( ; i < 2; i++ )
  558. result[i] = 0;
  559. output = result;
  560. }
  561. else
  562. {
  563. Vector4 result = new Vector4();
  564. for( i = 0; i < tokenValues.Length && i < 4; i++ )
  565. result[i] = tokenValues[i];
  566. for( ; i < 4; i++ )
  567. result[i] = 0;
  568. output = result;
  569. }
  570. return true;
  571. }
  572. }
  573. }