// UltEvents // Copyright 2020 Kybernetik // using System; using System.Collections; using System.Collections.Generic; using System.Reflection; using System.Text; using UnityEngine; namespace UltEvents { /// Various utility methods used by . public static class UltEventUtils { /************************************************************************************************************************/ /// The sub-menu which all components are listed in. public const string ComponentMenuPrefix = "UltEvents/"; /// The address of the online documentation. public const string DocumentationURL = "https://kybernetik.com.au/ultevents"; /// The address of the API documentation. public const string APIDocumentationURL = DocumentationURL + "/api/UltEvents"; /************************************************************************************************************************/ #region Event Extensions /************************************************************************************************************************/ /// /// Calls e.Invoke if it isn't null. /// /// See also: and . /// public static void InvokeX(this UltEvent e) { if (e != null) e.Invoke(); } /************************************************************************************************************************/ /// /// Calls e.Invoke if it isn't null. /// /// See also: and . /// public static void InvokeX(this UltEvent e, T0 parameter0) { if (e != null) e.Invoke(parameter0); } /************************************************************************************************************************/ /// /// Calls e.Invoke if it isn't null. /// /// See also: and . /// public static void InvokeX(this UltEvent e, T0 parameter0, T1 parameter1) { if (e != null) e.Invoke(parameter0, parameter1); } /************************************************************************************************************************/ /// /// Calls e.Invoke if it isn't null. /// /// See also: and . /// public static void InvokeX(this UltEvent e, T0 parameter0, T1 parameter1, T2 parameter2) { if (e != null) e.Invoke(parameter0, parameter1, parameter2); } /************************************************************************************************************************/ /// /// Calls e.Invoke if it isn't null. /// /// See also: and . /// public static void InvokeX(this UltEvent e, T0 parameter0, T1 parameter1, T2 parameter2, T3 parameter3) { if (e != null) e.Invoke(parameter0, parameter1, parameter2, parameter3); } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #region Type Names /************************************************************************************************************************/ private static readonly Dictionary TypeNames = new Dictionary { { typeof(object), "object" }, { typeof(void), "void" }, { typeof(bool), "bool" }, { typeof(byte), "byte" }, { typeof(sbyte), "sbyte" }, { typeof(char), "char" }, { typeof(string), "string" }, { typeof(short), "short" }, { typeof(int), "int" }, { typeof(long), "long" }, { typeof(ushort), "ushort" }, { typeof(uint), "uint" }, { typeof(ulong), "ulong" }, { typeof(float), "float" }, { typeof(double), "double" }, { typeof(decimal), "decimal" }, }; private static readonly Dictionary FullTypeNames = new Dictionary(TypeNames); /************************************************************************************************************************/ /// /// Returns the name of a `type` as it would appear in C# code. /// /// For example, typeof(List<float>).FullName would give you: /// System.Collections.Generic.List`1[[System.Single, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]] /// /// This method would instead return System.Collections.Generic.List<float> if `fullName` is true, or /// just List<float> if it is false. /// /// Note that all returned values are stored in a dictionary to speed up repeated use. /// public static string GetNameCS(this Type type, bool fullName = true) { if (type == null) return ""; // Check if we have already got the name for that type. var names = fullName ? FullTypeNames : TypeNames; string name; if (names.TryGetValue(type, out name)) return name; var text = new StringBuilder(); if (type.IsArray)// Array = TypeName[]. { text.Append(type.GetElementType().GetNameCS(fullName)); text.Append('['); var dimensions = type.GetArrayRank(); while (dimensions-- > 1) text.Append(","); text.Append(']'); goto Return; } if (type.IsPointer)// Pointer = TypeName*. { text.Append(type.GetElementType().GetNameCS(fullName)); text.Append('*'); goto Return; } if (type.IsGenericParameter)// Generic Parameter = TypeName (for unspecified generic parameters). { text.Append(type.Name); goto Return; } var underlyingType = Nullable.GetUnderlyingType(type); if (underlyingType != null)// Nullable = TypeName?. { text.Append(underlyingType.GetNameCS(fullName)); text.Append('?'); goto Return; } // Other Type = Namespace.NestedTypes.TypeName. if (fullName && type.Namespace != null)// Namespace. { text.Append(type.Namespace); text.Append('.'); } var genericArguments = 0; if (type.DeclaringType != null)// Account for Nested Types. { // Count the nesting level. var nesting = 1; var declaringType = type.DeclaringType; while (declaringType.DeclaringType != null) { declaringType = declaringType.DeclaringType; nesting++; } // Append the name of each outer type, starting from the outside. while (nesting-- > 0) { // Walk out to the current nesting level. // This avoids the need to make a list of types in the nest or to insert type names instead of appending them. declaringType = type; for (int i = nesting; i >= 0; i--) declaringType = declaringType.DeclaringType; // Nested Type Name. genericArguments = AppendNameAndGenericArguments(text, declaringType, fullName, genericArguments); text.Append('.'); } } // Type Name. AppendNameAndGenericArguments(text, type, fullName, genericArguments); Return:// Remember and return the name. name = text.ToString(); names.Add(type, name); return name; } /************************************************************************************************************************/ /// /// Appends the generic arguments of `type` (after skipping the specified number). /// public static int AppendNameAndGenericArguments(StringBuilder text, Type type, bool fullName = true, int skipGenericArguments = 0) { text.Append(type.Name); if (type.IsGenericType) { var backQuote = type.Name.IndexOf('`'); if (backQuote >= 0) { text.Length -= type.Name.Length - backQuote; var genericArguments = type.GetGenericArguments(); if (skipGenericArguments < genericArguments.Length) { text.Append('<'); var firstArgument = genericArguments[skipGenericArguments]; skipGenericArguments++; if (firstArgument.IsGenericParameter) { while (skipGenericArguments < genericArguments.Length) { text.Append(','); skipGenericArguments++; } } else { text.Append(firstArgument.GetNameCS(fullName)); while (skipGenericArguments < genericArguments.Length) { text.Append(", "); text.Append(genericArguments[skipGenericArguments].GetNameCS(fullName)); skipGenericArguments++; } } text.Append('>'); } } } return skipGenericArguments; } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #region Member Names /************************************************************************************************************************/ /// /// Returns the full name of a `member` as it would appear in C# code. /// /// For example, passing this method info in as its own parameter would return ".GetNameCS". /// /// Note that when `member` is a , this method calls instead. /// public static string GetNameCS(this MemberInfo member, bool fullName = true) { if (member == null) return "null"; var type = member as Type; if (type != null) return type.GetNameCS(fullName); var text = new StringBuilder(); if (member.DeclaringType != null) { text.Append(member.DeclaringType.GetNameCS(fullName)); text.Append('.'); } text.Append(member.Name); return text.ToString(); } /************************************************************************************************************************/ /// /// Appends the full name of a `member` as it would appear in C# code. /// /// For example, passing this method info in as its own parameter would append ".AppendName". /// /// Note that when `member` is a , this method calls instead. /// public static StringBuilder AppendNameCS(this StringBuilder text, MemberInfo member, bool fullName = true) { if (member == null) { text.Append("null"); return text; } var type = member as Type; if (type != null) { text.Append(type.GetNameCS(fullName)); return text; } if (member.DeclaringType != null) { text.Append(member.DeclaringType.GetNameCS(fullName)); text.Append('.'); } text.Append(member.Name); return text; } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #region Deep to String /************************************************************************************************************************/ /// Returns a string containing the value of each element in `collection`. public static string DeepToString(this IEnumerable collection, string separator) { if (collection == null) return "null"; else return collection.GetEnumerator().DeepToString(separator); } /// Returns a string containing the value of each element in `collection` (each on a new line). public static string DeepToString(this IEnumerable collection) { return collection.DeepToString(Environment.NewLine); } /************************************************************************************************************************/ /// Each element returned by `enumerator` is appended to `text`. public static void AppendDeepToString(StringBuilder text, IEnumerator enumerator, string separator) { text.Append("[]"); var countIndex = text.Length - 1; var count = 0; if (enumerator != null) { while (enumerator.MoveNext()) { text.Append(separator); text.Append('['); text.Append(count); text.Append("] = "); text.Append(enumerator.Current); count++; } } text.Insert(countIndex, count); } /// Returns a string containing the value of each element in `enumerator`. public static string DeepToString(this IEnumerator enumerator, string separator) { var text = new StringBuilder(); AppendDeepToString(text, enumerator, separator); return text.ToString(); } /// Returns a string containing the value of each element in `enumerator` (each on a new line). public static string DeepToString(this IEnumerator enumerator) { return enumerator.DeepToString(Environment.NewLine); } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ /// Commonly used . public const BindingFlags AnyAccessBindings = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static, InstanceBindings = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, StaticBindings = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static; /************************************************************************************************************************/ /// /// Returns the for constructors or /// for regular methods. /// public static Type GetReturnType(this MethodBase method) { return method.IsConstructor ? method.DeclaringType : (method as MethodInfo).ReturnType; } /************************************************************************************************************************/ /// Returns "AssemblyQualifiedName.MethodName". public static string GetFullyQualifiedName(MethodBase method) { return method.DeclaringType.AssemblyQualifiedName + "." + method.Name; } /************************************************************************************************************************/ /// /// Calculate the number of removals, inserts, and replacements needed to turn `a` into `b`. /// public static int CalculateLevenshteinDistance(string a, string b) { if (string.IsNullOrEmpty(a)) { return string.IsNullOrEmpty(b) ? 0 : b.Length; } if (string.IsNullOrEmpty(b)) return a.Length; var n = a.Length; var m = b.Length; var d = new int[n + 1, m + 1]; // initialise the top and right of the table to 0, 1, 2, ... for (int i = 0; i <= n; d[i, 0] = i++) { // Execution is contained in the For statement. } for (int j = 0; j <= m; d[0, j] = j++) { // Execution is contained in the For statement. } for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { var cost = (b[j - 1] == a[i - 1]) ? 0 : 1; d[i, j] = Mathf.Min( Mathf.Min(d[i - 1, j] + 1, d[i, j - 1] + 1), d[i - 1, j - 1] + cost); } } return d[n, m]; } /************************************************************************************************************************/ /// /// Sorts `list`, maintaining the order of any elements with an identical comparison /// (unlike the standard method). /// public static void StableInsertionSort(IList list, Comparison comparison) { var count = list.Count; for (int j = 1; j < count; j++) { var key = list[j]; var i = j - 1; for (; i >= 0 && comparison(list[i], key) > 0; i--) { list[i + 1] = list[i]; } list[i + 1] = key; } } /// /// Sorts `list`, maintaining the order of any elements with an identical comparison /// (unlike the standard method). /// public static void StableInsertionSort(IList list) where T : IComparable { StableInsertionSort(list, (a, b) => a.CompareTo(b)); } /************************************************************************************************************************/ /// /// Translates a zero based index to a placement name: 0 = "1st", 1 = "2nd", etc. /// public static string GetPlacementName(int index) { switch (index) { case 0: return "1st"; case 1: return "2nd"; case 2: return "3rd"; default: return index + "th"; } } /************************************************************************************************************************/ } }