diff --git a/src/UI/Utility/SignatureHighlighter.cs b/src/UI/Utility/SignatureHighlighter.cs index 77eba82..37d4319 100644 --- a/src/UI/Utility/SignatureHighlighter.cs +++ b/src/UI/Utility/SignatureHighlighter.cs @@ -1,6 +1,8 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Reflection; +using System.Text; using UnityEngine; using UnityExplorer.Core.Runtime; @@ -29,7 +31,7 @@ namespace UnityExplorer.UI.Utility public static string CONST_VAR = "#92c470"; - internal static readonly Color s_silver = new Color(0.66f, 0.66f, 0.66f); + public static string NAMESPACE = "#a8a8a8"; internal static string GetClassColor(Type type) { @@ -43,72 +45,73 @@ namespace UnityExplorer.UI.Utility return CLASS_INSTANCE; } + private static readonly StringBuilder syntaxBuilder = new StringBuilder(8192); + public static string ParseFullSyntax(Type type, bool includeNamespace, MemberInfo memberInfo = null) { if (type == null) throw new ArgumentNullException("type"); - //type = ReflectionProvider.Instance.GetDeobfuscatedType(type); - - string ret = ""; + syntaxBuilder.Clear(); if (type.IsGenericParameter || (type.HasElementType && type.GetElementType().IsGenericParameter)) { - ret = $"{type.Name}"; + syntaxBuilder.Append($"{type.Name}"); } else { if (includeNamespace && !string.IsNullOrEmpty(type.Namespace)) - ret += $"{type.Namespace}."; + syntaxBuilder.Append($"{type.Namespace}."); var declaring = type.DeclaringType; while (declaring != null) { - ret += HighlightTypeName(declaring) + "."; + syntaxBuilder.Append(HighlightTypeName(declaring) + "."); declaring = declaring.DeclaringType; } - ret += HighlightTypeName(type); + syntaxBuilder.Append(HighlightTypeName(type)); } if (memberInfo != null) { - ret += "."; + syntaxBuilder.Append('.'); - string memberColor = GetMemberInfoColor(memberInfo, out bool isStatic); - string memberHighlight = $"{memberInfo.Name}"; + string memColor = GetMemberInfoColor(memberInfo, out bool isStatic); if (isStatic) - memberHighlight = $"{memberHighlight}"; + syntaxBuilder.Append(""); - ret += memberHighlight; + syntaxBuilder.Append($"{memberInfo.Name}"); + + if (isStatic) + syntaxBuilder.Append(""); - // generic method args if (memberInfo is MethodInfo method) - { - var gArgs = method.GetGenericArguments(); - ret += ParseGenericArgs(gArgs, true); - } + syntaxBuilder.Append(ParseGenericArgs(method.GetGenericArguments(), true)); } - return ret; + return syntaxBuilder.ToString(); } + private static readonly Dictionary typeToRichType = new Dictionary(); + public static string HighlightTypeName(Type type) { - //type = RuntimeProvider.Instance.Reflection.GetDeobfuscatedType(type); + if (typeToRichType.ContainsKey(type.AssemblyQualifiedName)) + return typeToRichType[type.AssemblyQualifiedName]; var typeName = type.Name; - var gArgs = type.GetGenericArguments(); + var args = type.GetGenericArguments(); - if (gArgs.Length > 0) + if (args.Length > 0) { // remove the `N from the end of the type name // this could actually be >9 in some cases, so get the length of the length string and use that. // eg, if it was "List`15", we would remove the ending 3 chars - int suffixLen = 1 + gArgs.Length.ToString().Length; + int suffixLen = 1 + args.Length.ToString().Length; // make sure the typename actually has expected "`N" format. if (typeName[typeName.Length - suffixLen] == '`') @@ -120,72 +123,78 @@ namespace UnityExplorer.UI.Utility typeName = $"{typeName}"; // parse the generic args, if any - if (gArgs.Length > 0) - typeName += ParseGenericArgs(gArgs); + if (args.Length > 0) + typeName += ParseGenericArgs(args); + + typeToRichType.Add(type.AssemblyQualifiedName, typeName); return typeName; } - public static string ParseGenericArgs(Type[] gArgs, bool allGeneric = false) - { - if (gArgs.Length < 1) - return ""; + private static readonly StringBuilder genericBuilder = new StringBuilder(4096); - var args = "<"; - for (int i = 0; i < gArgs.Length; i++) + public static string ParseGenericArgs(Type[] args, bool isGenericParams = false) + { + if (args.Length < 1) + return string.Empty; + + genericBuilder.Clear(); + genericBuilder.Append('<'); + + for (int i = 0; i < args.Length; i++) { if (i > 0) - args += ", "; + genericBuilder.Append(','); - var arg = gArgs[i]; - - if (allGeneric) + if (isGenericParams) { - args += $"{arg.Name}"; + genericBuilder.Append($"{args[i].Name}"); continue; } // using HighlightTypeName makes it recursive, so we can parse nested generic args. - args += HighlightTypeName(arg); + genericBuilder.Append(HighlightTypeName(args[i])); } - return args + ">"; + + genericBuilder.Append('>'); + return genericBuilder.ToString(); } public static string GetMemberInfoColor(MemberInfo memberInfo, out bool isStatic) { - string memberColor = ""; isStatic = false; if (memberInfo is FieldInfo fi) { if (fi.IsStatic) { isStatic = true; - memberColor = FIELD_STATIC; + return FIELD_STATIC; } else - memberColor = FIELD_INSTANCE; + return FIELD_INSTANCE; } else if (memberInfo is MethodInfo mi) { if (mi.IsStatic) { isStatic = true; - memberColor = METHOD_STATIC; + return METHOD_STATIC; } else - memberColor = METHOD_INSTANCE; + return METHOD_INSTANCE; } else if (memberInfo is PropertyInfo pi) { if (pi.GetAccessors(true)[0].IsStatic) { isStatic = true; - memberColor = PROP_STATIC; + return PROP_STATIC; } else - memberColor = PROP_INSTANCE; + return PROP_INSTANCE; } - return memberColor; + + throw new NotImplementedException(memberInfo.GetType().Name + " is not supported"); } } } diff --git a/src/UI/Utility/ToStringUtility.cs b/src/UI/Utility/ToStringUtility.cs index 8113f4a..4bcd8c9 100644 --- a/src/UI/Utility/ToStringUtility.cs +++ b/src/UI/Utility/ToStringUtility.cs @@ -14,40 +14,49 @@ namespace UnityExplorer.UI.Utility internal static Dictionary toStringMethods = new Dictionary(); internal static Dictionary toStringFormattedMethods = new Dictionary(); + // string allocs + private static readonly StringBuilder _stringBuilder = new StringBuilder(16384); + private const string unknownString = ""; + private const string nullString = "[null]"; + private const string destroyedString = "[Destroyed]"; + public static string ToString(object value, Type fallbackType, bool includeNamespace = true, bool includeName = true) { if (value == null && fallbackType == null) - return ""; + return unknownString; Type type = value?.GetActualType() ?? fallbackType; - var richType = SignatureHighlighter.ParseFullSyntax(type, includeNamespace); + // todo SB this too + string richType = SignatureHighlighter.ParseFullSyntax(type, includeNamespace); if (!includeName) return richType; + _stringBuilder.Clear(); + if (value.IsNullOrDestroyed()) { if (value == null) - return $"[null] ({richType})"; - else - return $"[Destroyed] ({richType})"; + { + _stringBuilder.Append(nullString); + AppendRichType(_stringBuilder, richType); + return _stringBuilder.ToString(); + } + else // destroyed unity object + { + _stringBuilder.Append(destroyedString); + AppendRichType(_stringBuilder, richType); + return _stringBuilder.ToString(); + } } - // value = value.TryCast(type); - - string label; - - // Two dirty fixes for TextAsset and EventSystem, which can have very long ToString results. - if (value is TextAsset textAsset) - { - label = $"{textAsset.name} ({richType})"; + if (value is UnityEngine.Object obj) + { + _stringBuilder.Append(obj.name); + AppendRichType(_stringBuilder, richType); } - else if (value is EventSystem es) - { - label = $"{es.name} ({richType})"; - } - else // For everything else... + else { if (!toStringMethods.ContainsKey(type)) { @@ -75,35 +84,64 @@ namespace UnityExplorer.UI.Utility else toString = (string)stdMethod.Invoke(value, new object[0]); - toString = toString ?? ""; - - string typeName = type.FullName; - if (typeName.StartsWith("Il2CppSystem.")) - typeName = typeName.Substring(6, typeName.Length - 6); - - toString = ReflectionProvider.Instance.ProcessTypeFullNameInString(type, toString, ref typeName); - - // If the ToString is just the type name, use our syntax highlighted type name instead. - if (toString == typeName) + if (toString == type.FullName || toString == $"Il2Cpp{type.FullName}") { - label = richType; + // the ToString was just the default object.ToString(), use our + // syntax highlighted type name instead. + _stringBuilder.Append(richType); } - else // Otherwise, parse the result and put our highlighted name in. + else // the ToString contains some actual implementation, use that value. { if (toString.Length > 200) - toString = toString.Substring(0, 200) + "..."; - - label = toString; - - var unityType = $"({type.FullName})"; - if (value is UnityEngine.Object && label.Contains(unityType)) - label = label.Replace(unityType, $"({richType})"); + _stringBuilder.Append(toString.Substring(0, 200)); else - label += $" ({richType})"; + _stringBuilder.Append(toString); + + AppendRichType(_stringBuilder, richType); } + + ////string toString; + + // + //toString = toString ?? ""; + // + //string typeName = type.FullName; + //if (typeName.StartsWith("Il2CppSystem.")) + // typeName = typeName.Substring(6, typeName.Length - 6); + // + //toString = ReflectionProvider.Instance.ProcessTypeFullNameInString(type, toString, ref typeName); + // + //// If the ToString is just the type name, use our syntax highlighted type name instead. + //if (toString == typeName) + //{ + // label = richType; + //} + //else // Otherwise, parse the result and put our highlighted name in. + //{ + // if (toString.Length > 200) + // toString = toString.Substring(0, 200) + "..."; + // + // label = toString; + // + // var unityType = $"({type.FullName})"; + // if (value is UnityEngine.Object && label.Contains(unityType)) + // label = label.Replace(unityType, $"({richType})"); + // else + // label += $" ({richType})"; + //} } - return label; + return _stringBuilder.ToString(); + } + + // Just a little optimization, append chars directly instead of allocating every time + // we want to do this. + private static void AppendRichType(StringBuilder sb, string richType) + { + sb.Append(' '); + sb.Append('('); + sb.Append(richType); + sb.Append(')'); } } }