From 22435176bffd7660b8b003e960592042cd81e807 Mon Sep 17 00:00:00 2001 From: Sinai Date: Thu, 6 May 2021 04:02:42 +1000 Subject: [PATCH] Fix some issues in IL2CPP, improve type cache efficiency, reduce alloc --- src/Core/ReflectionUtility.cs | 141 ++++++++++++++---- src/Core/Runtime/Il2Cpp/Il2CppReflection.cs | 40 +++-- src/Core/Runtime/ReflectionProvider.cs | 2 + src/UI/CacheObject/CacheField.cs | 2 +- src/UI/CacheObject/CacheKeyValuePair.cs | 2 +- src/UI/CacheObject/CacheMember.cs | 4 +- src/UI/CacheObject/CacheMethod.cs | 6 +- src/UI/CacheObject/CacheObjectBase.cs | 13 +- src/UI/CacheObject/CacheProperty.cs | 6 +- .../Views/CacheKeyValuePairCell.cs | 2 +- src/UI/CacheObject/Views/EvaluateWidget.cs | 21 ++- src/UI/IValues/InteractiveDictionary.cs | 2 +- src/UI/IValues/InteractiveList.cs | 2 +- src/UI/Inspectors/GameObjectInspector.cs | 2 +- src/UI/Inspectors/ReflectionInspector.cs | 6 +- src/UI/ObjectExplorer/ObjectSearch.cs | 6 +- src/UI/UIManager.cs | 9 ++ src/UI/Utility/SignatureHighlighter.cs | 53 +++---- src/UI/Utility/ToStringUtility.cs | 2 +- src/UI/Widgets/AutoComplete/TypeCompleter.cs | 80 +++------- src/UI/Widgets/ScrollPool/DataHeightCache.cs | 82 ++++++---- 21 files changed, 300 insertions(+), 183 deletions(-) diff --git a/src/Core/ReflectionUtility.cs b/src/Core/ReflectionUtility.cs index 54b99d3..c56d589 100644 --- a/src/Core/ReflectionUtility.cs +++ b/src/Core/ReflectionUtility.cs @@ -17,36 +17,48 @@ namespace UnityExplorer foreach (var asm in AppDomain.CurrentDomain.GetAssemblies()) CacheTypes(asm); + allTypeNames.Sort(); + AppDomain.CurrentDomain.AssemblyLoad += AssemblyLoaded; } - private static readonly Dictionary allCachedTypes = new Dictionary(); - - private static void CacheTypes(Assembly asm) - { - foreach (var type in asm.TryGetTypes()) - { - if (allCachedTypes.ContainsKey(type.FullName)) - continue; - - if (type.FullName.ContainsIgnoreCase("PrivateImplementationDetails")) - continue; - - allCachedTypes.Add(type.FullName, type); - } - } + /// Key: Type.FullName + public static readonly SortedDictionary AllTypes = new SortedDictionary(StringComparer.OrdinalIgnoreCase); + private static readonly List allTypeNames = new List(); private static void AssemblyLoaded(object sender, AssemblyLoadEventArgs args) { if (args.LoadedAssembly == null) return; - s_cachedTypeInheritance.Clear(); - s_cachedGenericParameterInheritance.Clear(); - CacheTypes(args.LoadedAssembly); + allTypeNames.Sort(); } + private static void CacheTypes(Assembly asm) + { + foreach (var type in asm.TryGetTypes()) + { + if (AllTypes.ContainsKey(type.FullName)) + AllTypes[type.FullName] = type; + else + { + AllTypes.Add(type.FullName, type); + allTypeNames.Add(type.FullName); + } + + foreach (var key in s_cachedTypeInheritance.Keys) + { + try + { + var baseType = AllTypes[key]; + if (baseType.IsAssignableFrom(type) && !s_cachedTypeInheritance[key].Contains(type)) + s_cachedTypeInheritance[key].Add(type); + } + catch { } + } + } + } public const BF AllFlags = BF.Public | BF.Instance | BF.NonPublic | BF.Static; @@ -156,7 +168,9 @@ namespace UnityExplorer /// The Type if found, otherwise null. public static Type GetTypeByName(string fullName) { - allCachedTypes.TryGetValue(fullName, out Type type); + + + AllTypes.TryGetValue(fullName, out Type type); return type; } @@ -200,6 +214,21 @@ namespace UnityExplorer internal static readonly Dictionary> s_cachedTypeInheritance = new Dictionary>(); internal static readonly Dictionary> s_cachedGenericParameterInheritance = new Dictionary>(); + public static string GetImplementationKey(Type type) + { + if (!type.IsGenericParameter) + return type.FullName; + else + { + var sb = new StringBuilder(); + sb.Append(type.GenericParameterAttributes) + .Append('|'); + foreach (var c in type.GetGenericParameterConstraints()) + sb.Append(c.FullName).Append(','); + return sb.ToString(); + } + } + /// /// Get all non-abstract implementations of the provided type (include itself, if not abstract) in the current AppDomain. /// Also works for generic parameters by analyzing the constraints. @@ -208,38 +237,94 @@ namespace UnityExplorer /// All implementations of the type in the current AppDomain. public static HashSet GetImplementationsOf(this Type baseType, bool allowAbstract, bool allowGeneric) { - var key = baseType.AssemblyQualifiedName; + var key = GetImplementationKey(baseType); //baseType.FullName; + if (!baseType.IsGenericParameter) + return GetImplementations(key, baseType, allowAbstract, allowGeneric); + else + return GetGenericParameterImplementations(key, baseType, allowAbstract, allowGeneric); + } + + private static HashSet GetImplementations(string key, Type baseType, bool allowAbstract, bool allowGeneric) + { if (!s_cachedTypeInheritance.ContainsKey(key)) { var set = new HashSet(); - - if (!baseType.IsAbstract && !baseType.IsInterface) - set.Add(baseType); - - var keys = allCachedTypes.Keys.ToArray(); - for (int i = 0; i < keys.Length; i++) + for (int i = 0; i < allTypeNames.Count; i++) { - var type = allCachedTypes[keys[i]]; + var type = AllTypes[allTypeNames[i]]; + //type = ReflectionProvider.Instance.GetDeobfuscatedType(type); try { - if ((type.IsAbstract && type.IsSealed) // ignore static classes + if (set.Contains(type) + || (type.IsAbstract && type.IsSealed) // ignore static classes || (!allowAbstract && type.IsAbstract) || (!allowGeneric && (type.IsGenericType || type.IsGenericTypeDefinition))) continue; + if (type.FullName.Contains("PrivateImplementationDetails") + || type.FullName.Contains("DisplayClass") + || type.FullName.Contains('<')) + continue; + if (baseType.IsAssignableFrom(type) && !set.Contains(type)) set.Add(type); } catch { } } + //set. + s_cachedTypeInheritance.Add(key, set); } return s_cachedTypeInheritance[key]; } + private static HashSet GetGenericParameterImplementations(string key, Type baseType, bool allowAbstract, bool allowGeneric) + { + if (!s_cachedGenericParameterInheritance.ContainsKey(key)) + { + var set = new HashSet(); + + for (int i = 0; i < allTypeNames.Count; i++) + { + var type = AllTypes[allTypeNames[i]]; + try + { + if (set.Contains(type) + || (type.IsAbstract && type.IsSealed) // ignore static classes + || (!allowAbstract && type.IsAbstract) + || (!allowGeneric && (type.IsGenericType || type.IsGenericTypeDefinition))) + continue; + + if (type.FullName.Contains("PrivateImplementationDetails") + || type.FullName.Contains("DisplayClass") + || type.FullName.Contains('<')) + continue; + + if (baseType.GenericParameterAttributes.HasFlag(GenericParameterAttributes.NotNullableValueTypeConstraint) + && type.IsClass) + continue; + + if (baseType.GenericParameterAttributes.HasFlag(GenericParameterAttributes.ReferenceTypeConstraint) + && type.IsValueType) + continue; + + if (baseType.GetGenericParameterConstraints().Any(it => !it.IsAssignableFrom(type))) + continue; + + set.Add(type); + } + catch { } + } + + s_cachedGenericParameterInheritance.Add(key, set); + } + + return s_cachedGenericParameterInheritance[key]; + } + /// /// Safely get all valid Types inside an Assembly. /// diff --git a/src/Core/Runtime/Il2Cpp/Il2CppReflection.cs b/src/Core/Runtime/Il2Cpp/Il2CppReflection.cs index 1a9d739..614b857 100644 --- a/src/Core/Runtime/Il2Cpp/Il2CppReflection.cs +++ b/src/Core/Runtime/Il2Cpp/Il2CppReflection.cs @@ -53,6 +53,11 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp } } + public override bool IsString(object obj) + { + return obj is string || obj is Il2CppSystem.String; + } + public override void BoxStringToType(ref object value, Type castTo) { if (castTo == typeof(Il2CppSystem.String)) @@ -82,10 +87,12 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp { try { - var cppType = Il2CppType.From(type); - var monoType = GetMonoType(cppType); - if (monoType != null) - return monoType; + if (Il2CppToMonoType.ContainsKey(type.AssemblyQualifiedName)) + return Il2CppToMonoType[type.AssemblyQualifiedName]; + //var cppType = Il2CppType.From(type); + //var monoType = GetMonoType(cppType); + //if (monoType != null) + // return monoType; } catch { } @@ -116,7 +123,7 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp try { - if ((Il2CppSystem.Object)obj is Il2CppSystem.Object cppObject) + if (obj is Il2CppSystem.Object cppObject) { // weird specific case - if the object is an Il2CppSystem.Type, then return so manually. if (cppObject is CppType) @@ -131,6 +138,9 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp IntPtr classPtr = il2cpp_object_get_class(cppObject.Pointer); if (RuntimeSpecificsStore.IsInjected(classPtr)) { + // Note: This will fail on injected subclasses. + // - {Namespace}.{Class}.{Subclass} would be {Namespace}.{Subclass} when injected. + // Not sure on solution yet. var typeByName = ReflectionUtility.GetTypeByName(cppType.FullName); if (typeByName != null) return typeByName; @@ -142,9 +152,9 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp return getType; } } - catch (Exception ex) + catch //(Exception ex) { - ExplorerCore.LogWarning("Exception in GetActualType: " + ex); + //ExplorerCore.LogWarning("Exception in GetActualType: " + ex); } return type; @@ -176,7 +186,7 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp { var cppType = Il2CppType.From(type); - if (!Il2CppToMonoType.ContainsKey(cppType.FullName)) + if (!Il2CppToMonoType.ContainsKey(cppType.AssemblyQualifiedName)) { Il2CppToMonoType.Add(cppType.AssemblyQualifiedName, type); s_deobfuscatedTypeNames.Add(cppType.FullName, type.FullName); @@ -234,12 +244,22 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp return null; if (RuntimeSpecificsStore.IsInjected(castToPtr)) - return UnhollowerBaseLib.Runtime.ClassInjectorBase.GetMonoObjectFromIl2CppPointer(cppObj.Pointer); + { + var injectedObj = UnhollowerBaseLib.Runtime.ClassInjectorBase.GetMonoObjectFromIl2CppPointer(cppObj.Pointer); + return injectedObj ?? obj; + } if (castTo == typeof(string)) return cppObj.ToString(); - return Activator.CreateInstance(castTo, cppObj.Pointer); + try + { + return Activator.CreateInstance(castTo, cppObj.Pointer); + } + catch + { + return obj; + } } /// diff --git a/src/Core/Runtime/ReflectionProvider.cs b/src/Core/Runtime/ReflectionProvider.cs index 068752d..80fca0f 100644 --- a/src/Core/Runtime/ReflectionProvider.cs +++ b/src/Core/Runtime/ReflectionProvider.cs @@ -32,6 +32,8 @@ namespace UnityExplorer.Core.Runtime public abstract bool LoadModule(string module); + public virtual bool IsString(object obj) => obj is string; + public abstract void BoxStringToType(ref object _string, Type castTo); public virtual string UnboxString(object value) => (string)value; diff --git a/src/UI/CacheObject/CacheField.cs b/src/UI/CacheObject/CacheField.cs index d56a541..c3ad47f 100644 --- a/src/UI/CacheObject/CacheField.cs +++ b/src/UI/CacheObject/CacheField.cs @@ -26,7 +26,7 @@ namespace UnityExplorer.UI.CacheObject { try { - var ret = FieldInfo.GetValue(this.Owner.Target.TryCast(this.DeclaringType)); + var ret = FieldInfo.GetValue(DeclaringInstance); HadException = false; LastException = null; return ret; diff --git a/src/UI/CacheObject/CacheKeyValuePair.cs b/src/UI/CacheObject/CacheKeyValuePair.cs index d8ab727..880ef22 100644 --- a/src/UI/CacheObject/CacheKeyValuePair.cs +++ b/src/UI/CacheObject/CacheKeyValuePair.cs @@ -42,7 +42,7 @@ namespace UnityExplorer.UI.CacheObject { KeyInputWanted = true; KeyInputText = key.ToString(); - KeyInputTypeText = SignatureHighlighter.ParseType(type, false); + KeyInputTypeText = SignatureHighlighter.Parse(type, false); } else { diff --git a/src/UI/CacheObject/CacheMember.cs b/src/UI/CacheObject/CacheMember.cs index a55b5fd..e9ae5dc 100644 --- a/src/UI/CacheObject/CacheMember.cs +++ b/src/UI/CacheObject/CacheMember.cs @@ -18,6 +18,8 @@ namespace UnityExplorer.UI.CacheObject public abstract Type DeclaringType { get; } public string NameForFiltering { get; protected set; } + public object DeclaringInstance => IsStatic ? null : (m_declaringInstance ?? (m_declaringInstance = Owner.Target.TryCast(DeclaringType))); + private object m_declaringInstance; public abstract bool IsStatic { get; } public override bool HasArguments => Arguments?.Length > 0 || GenericArguments.Length > 0; @@ -29,7 +31,7 @@ namespace UnityExplorer.UI.CacheObject public virtual void SetInspectorOwner(ReflectionInspector inspector, MemberInfo member) { this.Owner = inspector; - this.NameLabelText = SignatureHighlighter.ParseFullSyntax(member.DeclaringType, false, member); + this.NameLabelText = SignatureHighlighter.Parse(member.DeclaringType, false, member); this.NameForFiltering = $"{member.DeclaringType.Name}.{member.Name}"; } diff --git a/src/UI/CacheObject/CacheMethod.cs b/src/UI/CacheObject/CacheMethod.cs index c167b13..0d29c33 100644 --- a/src/UI/CacheObject/CacheMethod.cs +++ b/src/UI/CacheObject/CacheMethod.cs @@ -34,12 +34,10 @@ namespace UnityExplorer.UI.CacheObject if (methodInfo.IsGenericMethod) methodInfo = MethodInfo.MakeGenericMethod(Evaluator.TryParseGenericArguments()); - var target = MethodInfo.IsStatic ? null : Owner.Target.TryCast(DeclaringType); - if (Arguments.Length > 0) - return methodInfo.Invoke(target, Evaluator.TryParseArguments()); + return methodInfo.Invoke(DeclaringInstance, Evaluator.TryParseArguments()); - var ret = methodInfo.Invoke(target, new object[0]); + var ret = methodInfo.Invoke(DeclaringInstance, new object[0]); HadException = false; LastException = null; diff --git a/src/UI/CacheObject/CacheObjectBase.cs b/src/UI/CacheObject/CacheObjectBase.cs index c427da5..67ec399 100644 --- a/src/UI/CacheObject/CacheObjectBase.cs +++ b/src/UI/CacheObject/CacheObjectBase.cs @@ -5,6 +5,7 @@ using System.Reflection; using System.Text; using UnityEngine; using UnityEngine.UI; +using UnityExplorer.Core.Runtime; using UnityExplorer.UI.CacheObject.Views; using UnityExplorer.UI.IValues; using UnityExplorer.UI.ObjectPool; @@ -94,6 +95,8 @@ namespace UnityExplorer.UI.CacheObject // Updating and applying values + public abstract void SetUserValue(object value); + public virtual void SetValueFromSource(object value) { this.Value = value; @@ -118,8 +121,6 @@ namespace UnityExplorer.UI.CacheObject this.IValue.SetValue(Value); } - public abstract void SetUserValue(object value); - /// /// Process the CacheMember state when the value has been evaluated (or re-evaluated) /// @@ -137,7 +138,7 @@ namespace UnityExplorer.UI.CacheObject State = ValueState.Boolean; else if (type.IsPrimitive || type == typeof(decimal)) State = ValueState.Number; - else if (type == typeof(string)) + else if (ReflectionProvider.Instance.IsString(Value)) State = ValueState.String; else if (type.IsEnum) State = ValueState.Enum; @@ -162,14 +163,14 @@ namespace UnityExplorer.UI.CacheObject switch (State) { case ValueState.NotEvaluated: - label = $"{NOT_YET_EVAL} ({SignatureHighlighter.ParseType(FallbackType, true)})"; break; + label = $"{NOT_YET_EVAL} ({SignatureHighlighter.Parse(FallbackType, true)})"; break; case ValueState.Exception: label = $"{ReflectionUtility.ReflectionExToString(LastException)}"; break; case ValueState.Boolean: case ValueState.Number: label = null; break; case ValueState.String: - string s = Value as string; + string s = ReflectionProvider.Instance.UnboxString(Value); if (s.Length > 200) s = $"{s.Substring(0, 200)}..."; label = $"\"{s}\""; break; @@ -252,7 +253,7 @@ namespace UnityExplorer.UI.CacheObject cell.TypeLabel.gameObject.SetActive(args.typeLabelActive); if (args.typeLabelActive) - cell.TypeLabel.text = SignatureHighlighter.ParseType(Value.GetActualType(), false); + cell.TypeLabel.text = SignatureHighlighter.Parse(Value.GetActualType(), false); cell.Toggle.gameObject.SetActive(args.toggleActive); if (args.toggleActive) diff --git a/src/UI/CacheObject/CacheProperty.cs b/src/UI/CacheObject/CacheProperty.cs index 9bc92f5..e932927 100644 --- a/src/UI/CacheObject/CacheProperty.cs +++ b/src/UI/CacheObject/CacheProperty.cs @@ -28,12 +28,10 @@ namespace UnityExplorer.UI.CacheObject { try { - var target = IsStatic ? null : Owner.Target.TryCast(DeclaringType); - if (HasArguments) - return PropertyInfo.GetValue(target, this.Evaluator.TryParseArguments()); + return PropertyInfo.GetValue(DeclaringInstance, this.Evaluator.TryParseArguments()); - var ret = PropertyInfo.GetValue(target, null); + var ret = PropertyInfo.GetValue(DeclaringInstance, null); HadException = false; LastException = null; return ret; diff --git a/src/UI/CacheObject/Views/CacheKeyValuePairCell.cs b/src/UI/CacheObject/Views/CacheKeyValuePairCell.cs index 3d24848..1d2884c 100644 --- a/src/UI/CacheObject/Views/CacheKeyValuePairCell.cs +++ b/src/UI/CacheObject/Views/CacheKeyValuePairCell.cs @@ -38,7 +38,7 @@ namespace UnityExplorer.UI.CacheObject.Views Image = root.AddComponent(); - this.NameLayout.minWidth = 40; + this.NameLayout.minWidth = 55; this.NameLayout.flexibleWidth = 50; this.NameLayout.minHeight = 30; this.NameLayout.flexibleHeight = 0; diff --git a/src/UI/CacheObject/Views/EvaluateWidget.cs b/src/UI/CacheObject/Views/EvaluateWidget.cs index 913a3a5..af7d4dd 100644 --- a/src/UI/CacheObject/Views/EvaluateWidget.cs +++ b/src/UI/CacheObject/Views/EvaluateWidget.cs @@ -31,6 +31,7 @@ namespace UnityExplorer.UI.CacheObject.Views private GameObject genericArgHolder; private readonly List genericArgRows = new List(); private readonly List genericArgLabels = new List(); + private readonly List genericAutocompleters = new List(); private readonly List inputFieldCache = new List(); @@ -153,9 +154,12 @@ namespace UnityExplorer.UI.CacheObject.Views genericArgRows[i].SetActive(true); - var constraints = arg.GetGenericParameterConstraints(); + var autoCompleter = genericAutocompleters[i]; + autoCompleter.BaseType = arg; + autoCompleter.CacheTypes(); - // TODO show "class" constraints as they dont show up, "struct" does effectively. + var constraints = arg.GetGenericParameterConstraints(); + autoCompleter.GenericConstraints = constraints; var sb = new StringBuilder($"{arg.Name}"); @@ -164,7 +168,7 @@ namespace UnityExplorer.UI.CacheObject.Views if (j == 0) sb.Append(' ').Append('('); else sb.Append(',').Append(' '); - sb.Append(SignatureHighlighter.ParseType(constraints[j])); + sb.Append(SignatureHighlighter.Parse(constraints[j], false)); if (j + 1 == constraints.Length) sb.Append(')'); @@ -194,19 +198,19 @@ namespace UnityExplorer.UI.CacheObject.Views AddArgRow(i, false); argRows[i].SetActive(true); - argLabels[i].text = $"{SignatureHighlighter.ParseType(arg.ParameterType)} {arg.Name}"; + argLabels[i].text = $"{SignatureHighlighter.Parse(arg.ParameterType, false)} {arg.Name}"; } } private void AddArgRow(int index, bool generic) { if (!generic) - AddArgRow(index, argHolder, argRows, argLabels, argumentInput);//, false); + AddArgRow(index, argHolder, argRows, argLabels, argumentInput, false); else - AddArgRow(index, genericArgHolder, genericArgRows, genericArgLabels, genericInput);//, true); + AddArgRow(index, genericArgHolder, genericArgRows, genericArgLabels, genericInput, true); } - private void AddArgRow(int index, GameObject parent, List objectList, List labelList, string[] inputArray)//, bool autocomplete) + private void AddArgRow(int index, GameObject parent, List objectList, List labelList, string[] inputArray, bool autocomplete) { var horiGroup = UIFactory.CreateUIObject("ArgRow_" + index, parent); UIFactory.SetLayoutElement(horiGroup, minHeight: 25, flexibleHeight: 50, minWidth: 50, flexibleWidth: 9999); @@ -225,6 +229,9 @@ namespace UnityExplorer.UI.CacheObject.Views inputObj.AddComponent().verticalFit = ContentSizeFitter.FitMode.PreferredSize; inputField.onValueChanged.AddListener((string val) => { inputArray[index] = val; }); inputFieldCache.Add(inputField); + + if (autocomplete) + genericAutocompleters.Add(new TypeCompleter(null, inputField)); } public GameObject CreateContent(GameObject parent) diff --git a/src/UI/IValues/InteractiveDictionary.cs b/src/UI/IValues/InteractiveDictionary.cs index 106748b..eccebbf 100644 --- a/src/UI/IValues/InteractiveDictionary.cs +++ b/src/UI/IValues/InteractiveDictionary.cs @@ -86,7 +86,7 @@ namespace UnityExplorer.UI.IValues CacheEntries(value); - TopLabel.text = $"[{cachedEntries.Count}] {SignatureHighlighter.ParseType(type, false)}"; + TopLabel.text = $"[{cachedEntries.Count}] {SignatureHighlighter.Parse(type, false)}"; } diff --git a/src/UI/IValues/InteractiveList.cs b/src/UI/IValues/InteractiveList.cs index 8f4021c..a236d2e 100644 --- a/src/UI/IValues/InteractiveList.cs +++ b/src/UI/IValues/InteractiveList.cs @@ -78,7 +78,7 @@ namespace UnityExplorer.UI.IValues CacheEntries(value); - TopLabel.text = $"[{cachedEntries.Count}] {SignatureHighlighter.ParseType(type, false)}"; + TopLabel.text = $"[{cachedEntries.Count}] {SignatureHighlighter.Parse(type, false)}"; } //this.ScrollPoolLayout.minHeight = Math.Min(400f, 35f * values.Count); diff --git a/src/UI/Inspectors/GameObjectInspector.cs b/src/UI/Inspectors/GameObjectInspector.cs index 6f392c1..754a686 100644 --- a/src/UI/Inspectors/GameObjectInspector.cs +++ b/src/UI/Inspectors/GameObjectInspector.cs @@ -160,7 +160,7 @@ namespace UnityExplorer.UI.Inspectors if (!compToStringCache.ContainsKey(type.AssemblyQualifiedName)) { - compToStringCache.Add(type.AssemblyQualifiedName, SignatureHighlighter.ParseType(type, true)); + compToStringCache.Add(type.AssemblyQualifiedName, SignatureHighlighter.Parse(type, true)); } cell.Button.ButtonText.text = compToStringCache[type.AssemblyQualifiedName]; diff --git a/src/UI/Inspectors/ReflectionInspector.cs b/src/UI/Inspectors/ReflectionInspector.cs index 03e6e1c..6515d9a 100644 --- a/src/UI/Inspectors/ReflectionInspector.cs +++ b/src/UI/Inspectors/ReflectionInspector.cs @@ -128,8 +128,8 @@ namespace UnityExplorer.UI.Inspectors } // Setup main labels and tab text - Tab.TabText.text = $"{prefix} {SignatureHighlighter.ParseType(TargetType)}"; - NameText.text = SignatureHighlighter.ParseFullSyntax(TargetType, true); + Tab.TabText.text = $"{prefix} {SignatureHighlighter.Parse(TargetType, false)}"; + NameText.text = SignatureHighlighter.Parse(TargetType, true); string asmText; if (TargetType.Assembly != null && !string.IsNullOrEmpty(TargetType.Assembly.Location)) @@ -230,6 +230,7 @@ namespace UnityExplorer.UI.Inspectors private void UpdateDisplayedMembers()// bool onlyAutoUpdate) { + ExplorerCore.Log("Updating values..."); bool shouldRefresh = false; foreach (var cell in MemberScrollPool.CellPool) { @@ -238,6 +239,7 @@ namespace UnityExplorer.UI.Inspectors var member = cell.MemberOccupant; if (member.ShouldAutoEvaluate) // && (!onlyAutoUpdate || member.AutoUpdateWanted)) { + ExplorerCore.Log("Evaluating cell " + cell.MemberOccupant.NameForFiltering); shouldRefresh = true; member.Evaluate(); member.SetDataToCell(member.CellView); diff --git a/src/UI/ObjectExplorer/ObjectSearch.cs b/src/UI/ObjectExplorer/ObjectSearch.cs index 14c64bf..987a36f 100644 --- a/src/UI/ObjectExplorer/ObjectSearch.cs +++ b/src/UI/ObjectExplorer/ObjectSearch.cs @@ -79,9 +79,9 @@ namespace UnityExplorer.UI.Panels lastCheckedTypeInput = desiredTypeInput; //var type = ReflectionUtility.GetTypeByName(desiredTypeInput); - if (typeAutocompleter.AllTypes.TryGetValue(desiredTypeInput, out var cachedType)) + if (ReflectionUtility.AllTypes.TryGetValue(desiredTypeInput, out var cachedType)) { - var type = cachedType.Type; + var type = cachedType; lastTypeCanHaveGO = typeof(Component).IsAssignableFrom(type) || type == typeof(GameObject); sceneFilterRow.SetActive(lastTypeCanHaveGO); childFilterRow.SetActive(lastTypeCanHaveGO); @@ -134,7 +134,7 @@ namespace UnityExplorer.UI.Panels { string text; if (m_context == SearchContext.StaticClass) - text = SignatureHighlighter.ParseType(currentResults[index] as Type, true, true); + text = SignatureHighlighter.Parse(currentResults[index] as Type, true); else text = ToStringUtility.ToStringWithType(currentResults[index], currentResults[index]?.GetActualType()); diff --git a/src/UI/UIManager.cs b/src/UI/UIManager.cs index 0cfd857..4090ce2 100644 --- a/src/UI/UIManager.cs +++ b/src/UI/UIManager.cs @@ -108,6 +108,8 @@ namespace UnityExplorer.UI if (!ShowMenu) return; + gcLabel.text = "GC : " + (GC.GetTotalMemory(false) / 1024 / 1024) + "MB"; + if (InputManager.GetKeyDown(ConfigManager.Force_Unlock_Keybind.Value)) CursorUnlocker.Unlock = !CursorUnlocker.Unlock; @@ -221,6 +223,8 @@ namespace UnityExplorer.UI //private static float lastTimeSpeed; //private static bool pausing; + private static Text gcLabel; + private static void CreateTopNavBar() { var navbarPanel = UIFactory.CreateUIObject("MainNavbar", CanvasRoot); @@ -238,6 +242,11 @@ namespace UnityExplorer.UI var title = UIFactory.CreateLabel(navbarPanel, "Title", titleTxt, TextAnchor.MiddleLeft, default, true, 18); UIFactory.SetLayoutElement(title.gameObject, minWidth: 240, flexibleWidth: 0); + // temp debug + + gcLabel = UIFactory.CreateLabel(navbarPanel, "GCLabel", "GC: ", TextAnchor.MiddleLeft); + UIFactory.SetLayoutElement(gcLabel.gameObject, minWidth: 150, minHeight: 25, flexibleWidth: 0); + // TODO something nicer for this, maybe a 'Tools' dropout below the main navbar with a few helpers like this. //var btn = UIFactory.CreateButton(navbarPanel, "Button", "pause", new Color(0.2f, 0.2f, 0.2f)); diff --git a/src/UI/Utility/SignatureHighlighter.cs b/src/UI/Utility/SignatureHighlighter.cs index 458276b..f4e8c9f 100644 --- a/src/UI/Utility/SignatureHighlighter.cs +++ b/src/UI/Utility/SignatureHighlighter.cs @@ -68,7 +68,7 @@ namespace UnityExplorer.UI.Utility return ret; } - public static string ParseFullSyntax(Type type, bool includeNamespace, MemberInfo memberInfo = null) + public static string Parse(Type type, bool includeNamespace, MemberInfo memberInfo = null) { if (type == null) throw new ArgumentNullException("type"); @@ -136,30 +136,30 @@ namespace UnityExplorer.UI.Utility return syntaxBuilder.ToString(); } - public static string ParseType(Type type, bool includeNamespace = false, bool includeDllName = false) - { - var sb = new StringBuilder(); - - bool isGeneric = type.IsGenericParameter || (type.HasElementType && type.GetElementType().IsGenericParameter); - - if (!isGeneric && includeNamespace && GetNamespace(type, out string ns)) - //sb.Append($"{ns}{CLOSE_COLOR}."); - sb.Append(OPEN_COLOR).Append(NAMESPACE).Append('>').Append(ns).Append(CLOSE_COLOR).Append('.'); - - sb.Append(HighlightType(type)); - - if (includeDllName) - { - if (!string.IsNullOrEmpty(type.Assembly.Location)) - //sb.Append($" ({Path.GetFileName(type.Assembly.Location)})"); - sb.Append(' ').Append('(').Append(Path.GetFileName(type.Assembly.Location)).Append(')'); - else - //sb.Append($" ({type.Assembly.GetName().Name})"); - sb.Append(' ').Append('(').Append(type.Assembly.GetName().Name).Append(')'); - } - - return sb.ToString(); - } + //public static string ParseType(Type type, bool includeNamespace = false, bool includeDllName = false) + //{ + // var sb = new StringBuilder(); + // + // bool isGeneric = type.IsGenericParameter || (type.HasElementType && type.GetElementType().IsGenericParameter); + // + // if (!isGeneric && includeNamespace && GetNamespace(type, out string ns)) + // //sb.Append($"{ns}{CLOSE_COLOR}."); + // sb.Append(OPEN_COLOR).Append(NAMESPACE).Append('>').Append(ns).Append(CLOSE_COLOR).Append('.'); + // + // sb.Append(HighlightType(type)); + // + // if (includeDllName) + // { + // if (!string.IsNullOrEmpty(type.Assembly.Location)) + // //sb.Append($" ({Path.GetFileName(type.Assembly.Location)})"); + // sb.Append(' ').Append('(').Append(Path.GetFileName(type.Assembly.Location)).Append(')'); + // else + // //sb.Append($" ({type.Assembly.GetName().Name})"); + // sb.Append(' ').Append('(').Append(type.Assembly.GetName().Name).Append(')'); + // } + // + // return sb.ToString(); + //} private static readonly Dictionary typeToRichType = new Dictionary(); @@ -182,6 +182,7 @@ namespace UnityExplorer.UI.Utility private static string HighlightType(Type type) { string key = type.ToString(); + if (typeToRichType.ContainsKey(key)) return typeToRichType[key]; @@ -265,7 +266,7 @@ namespace UnityExplorer.UI.Utility } //ret += ParseType(args[i]); - sb.Append(ParseType(args[i])); + sb.Append(HighlightType(args[i])); } return sb.ToString(); diff --git a/src/UI/Utility/ToStringUtility.cs b/src/UI/Utility/ToStringUtility.cs index 7466bbe..0df196a 100644 --- a/src/UI/Utility/ToStringUtility.cs +++ b/src/UI/Utility/ToStringUtility.cs @@ -30,7 +30,7 @@ namespace UnityExplorer.UI.Utility Type type = value?.GetActualType() ?? fallbackType; - string richType = SignatureHighlighter.ParseFullSyntax(type, includeNamespace); + string richType = SignatureHighlighter.Parse(type, includeNamespace); _stringBuilder.Clear(); diff --git a/src/UI/Widgets/AutoComplete/TypeCompleter.cs b/src/UI/Widgets/AutoComplete/TypeCompleter.cs index 40d2aab..ec63955 100644 --- a/src/UI/Widgets/AutoComplete/TypeCompleter.cs +++ b/src/UI/Widgets/AutoComplete/TypeCompleter.cs @@ -8,17 +8,13 @@ using UnityEngine.UI; using UnityExplorer.Core.Runtime; using UnityExplorer.UI.Models; using UnityExplorer.UI.Panels; +using UnityExplorer.UI.Utility; namespace UnityExplorer.UI.Widgets.AutoComplete { public class TypeCompleter : ISuggestionProvider { - public class CachedType - { - public Type Type; - public string FullNameValue; - public string DisplayName; - } + internal static readonly Dictionary sharedTypeToLabel = new Dictionary(4096); public event Action SuggestionClicked; @@ -31,10 +27,7 @@ namespace UnityExplorer.UI.Widgets.AutoComplete private readonly List suggestions = new List(); private float timeOfLastCheck; - public Dictionary AllTypes = new Dictionary(); - - // cached type trees from all autocompleters - private static readonly Dictionary> typeCache = new Dictionary>(); + private HashSet allowedTypes; public TypeCompleter(Type baseType, InputField inputField) { @@ -47,6 +40,11 @@ namespace UnityExplorer.UI.Widgets.AutoComplete CacheTypes(); } + public void CacheTypes() + { + allowedTypes = ReflectionUtility.GetImplementationsOf(BaseType, true, false); + } + public void OnSuggestionClicked(Suggestion suggestion) { timeOfLastCheck = Time.realtimeSinceStartup; @@ -84,61 +82,29 @@ namespace UnityExplorer.UI.Widgets.AutoComplete { suggestions.Clear(); - var added = new HashSet(); - - // Check for exact match first - if (AllTypes.TryGetValue(value, out CachedType cache)) - AddSuggestion(cache); - - foreach (var entry in AllTypes.Values) - AddSuggestion(entry); - - void AddSuggestion(CachedType entry) + if (BaseType == null) { - if (entry.FullNameValue == null) - entry.FullNameValue = ReflectionProvider.Instance.GetDeobfuscatedType(entry.Type).FullName; - - if (added.Contains(entry.FullNameValue)) - return; - added.Add(entry.FullNameValue); - - if (entry.DisplayName == null) - entry.DisplayName = Utility.SignatureHighlighter.ParseFullSyntax(entry.Type, true); - - suggestions.Add(new Suggestion(entry.DisplayName, entry.FullNameValue)); - } - } - - public void CacheTypes() - { - var key = BaseType.AssemblyQualifiedName; - - if (typeCache.ContainsKey(key)) - { - AllTypes = typeCache[key]; + ExplorerCore.LogWarning("Autocompleter Base type is null!"); return; } - AllTypes = new Dictionary(); + // Check for exact match first + if (ReflectionUtility.AllTypes.TryGetValue(value, out Type t) && allowedTypes.Contains(t)) + AddSuggestion(t); - var list = ReflectionUtility.GetImplementationsOf(BaseType, true, false) - .Select(it => new CachedType() - { - Type = it, - FullNameValue = ReflectionProvider.Instance.GetDeobfuscatedType(it).FullName - }) - .ToList(); - - list.Sort((CachedType a, CachedType b) => a.FullNameValue.CompareTo(b.FullNameValue)); - - foreach (var cache in list) + foreach (var entry in allowedTypes) { - if (AllTypes.ContainsKey(cache.FullNameValue)) - continue; - AllTypes.Add(cache.FullNameValue, cache); + if (entry.FullName.ContainsIgnoreCase(value)) + AddSuggestion(entry); } + } - typeCache.Add(key, AllTypes); + void AddSuggestion(Type type) + { + if (!sharedTypeToLabel.ContainsKey(type.FullName)) + sharedTypeToLabel.Add(type.FullName, SignatureHighlighter.Parse(type, true)); + + suggestions.Add(new Suggestion(sharedTypeToLabel[type.FullName], type.FullName)); } } } diff --git a/src/UI/Widgets/ScrollPool/DataHeightCache.cs b/src/UI/Widgets/ScrollPool/DataHeightCache.cs index a789ddc..b4c2a49 100644 --- a/src/UI/Widgets/ScrollPool/DataHeightCache.cs +++ b/src/UI/Widgets/ScrollPool/DataHeightCache.cs @@ -40,6 +40,19 @@ namespace UnityExplorer.UI.Widgets public float DefaultHeight => m_defaultHeight ?? (float)(m_defaultHeight = ScrollPool.PrototypeHeight); private float? m_defaultHeight; + /// + /// Lookup table for "which data index first appears at this position"
+ /// Index: DefaultHeight * index from top of data
+ /// Value: the first data index at this position
+ ///
+ private readonly List rangeCache = new List(); + + /// Same as GetRangeIndexOfPosition, except this rounds up to the next division if there was remainder from the previous cell. + private int GetRangeCeilingOfPosition(float position) => (int)Math.Ceiling((decimal)position / (decimal)DefaultHeight); + + /// Get the first range (division of DefaultHeight) which the position appears in. + private int GetRangeFloorOfPosition(float position) => (int)Math.Floor((decimal)position / (decimal)DefaultHeight); + /// Get the data index at the specified position of the total height cache. public int GetFirstDataIndexAtPosition(float desiredHeight) => GetFirstDataIndexAtPosition(desiredHeight, out _); @@ -81,19 +94,6 @@ namespace UnityExplorer.UI.Widgets return dataIndex; } - /// - /// Lookup table for "which data index first appears at this position"
- /// Index: DefaultHeight * index from top of data
- /// Value: the first data index at this position
- ///
- private readonly List rangeCache = new List(); - - /// Get the first range (division of DefaultHeight) which the position appears in. - private int GetRangeFloorOfPosition(float position) => (int)Math.Floor((decimal)position / (decimal)DefaultHeight); - - /// Same as GetRangeIndexOfPosition, except this rounds up to the next division if there was remainder from the previous cell. - private int GetRangeCeilingOfPosition(float position) => (int)Math.Ceiling((decimal)position / (decimal)DefaultHeight); - /// /// Get the spread of the height, starting from the start position.

/// The "spread" begins at the start of the next interval of the DefaultHeight, then increases for @@ -116,6 +116,8 @@ namespace UnityExplorer.UI.Widgets /// Append a data index to the cache with the provided height value. public void Add(float value) { + value = (float)Math.Floor(value); + int spread = GetRangeSpread(totalHeight, value); heightCache.Add(new DataViewInfo() @@ -150,6 +152,8 @@ namespace UnityExplorer.UI.Widgets /// Set a given data index with the specified value. public void SetIndex(int dataIndex, float height) { + height = (float)Math.Floor(height); + // If the index being set is beyond the DataSource item count, prune and return. if (dataIndex >= ScrollPool.DataSource.ItemCount) { @@ -189,20 +193,17 @@ namespace UnityExplorer.UI.Widgets int spread = GetRangeSpread(cache.startPosition, height); // If the previous item in the range cache is not the previous data index, there is a gap. - if (dataIndex > 0 && rangeCache.Count > rangeIndex && rangeCache[rangeIndex - 1] != (dataIndex - 1)) + if (dataIndex > 0 && rangeCache[rangeIndex - 1] != (dataIndex - 1)) { - // Recalculate start positions up to this index. The gap could be anywhere. - RecalculateStartPositions(dataIndex); + // Recalculate start positions up to this index. The gap could be anywhere before here. + RecalculateStartPositions(dataIndex + 1); // Get the range index and spread again after rebuilding rangeIndex = GetRangeCeilingOfPosition(cache.startPosition); spread = GetRangeSpread(cache.startPosition, height); } - // Should never happen if (rangeCache.Count <= rangeIndex || rangeCache[rangeIndex] != dataIndex) - throw new Exception($"Trying to set range index but cache is corrupt after rebuild!\r\n" + - $"dataIndex: {dataIndex}, rangeIndex: {rangeIndex}, rangeCache.Count: {rangeCache.Count}, " + - $"startPos: {cache.startPosition}/{TotalHeight}"); + throw new Exception("ScrollPool data height cache is corrupt or invalid, rebuild failed!"); if (spread != cache.normalizedSpread) { @@ -217,13 +218,21 @@ namespace UnityExplorer.UI.Widgets { if (spreadDiff > 0) { - for (int i = 0; i < spreadDiff; i++) + while (rangeCache[rangeIndex] == dataIndex && spreadDiff > 0) + { rangeCache.Insert(rangeIndex, dataIndex); + spreadDiff--; + } } else { - for (int i = 0; i < -spreadDiff; i++) + while (rangeCache[rangeIndex] == dataIndex && spreadDiff < 0) + { rangeCache.RemoveAt(rangeIndex); + spreadDiff++; + } + //for (int i = 0; i < -spreadDiff; i++) + // rangeCache.RemoveAt(rangeIndex); } } @@ -233,20 +242,37 @@ namespace UnityExplorer.UI.Widgets return; DataViewInfo cache; - DataViewInfo prev = heightCache[0]; - for (int i = 1; i <= toIndex && i < heightCache.Count; i++) + DataViewInfo prev = null; + for (int i = 0; i <= toIndex && i < heightCache.Count; i++) { cache = heightCache[i]; - cache.startPosition = prev.startPosition + prev.height; + if (prev != null) + cache.startPosition = prev.startPosition + prev.height; + else + cache.startPosition = 0; - var prevSpread = cache.normalizedSpread; + var origSpread = cache.normalizedSpread; cache.normalizedSpread = GetRangeSpread(cache.startPosition, cache.height); - if (cache.normalizedSpread != prevSpread) - SetSpread(i, GetRangeCeilingOfPosition(cache.startPosition), cache.normalizedSpread - prevSpread); + if (cache.normalizedSpread != origSpread) + SetSpread(i, GetRangeCeilingOfPosition(cache.startPosition), cache.normalizedSpread - origSpread); prev = cache; } } + + //private void HardRebuildRanges() + //{ + // var tempList = new List(); + // for (int i = 0; i < heightCache.Count; i++) + // tempList.Add(heightCache[i]); + // + // heightCache.Clear(); + // rangeCache.Clear(); + // totalHeight = 0; + // + // for (int i = 0; i < tempList.Count; i++) + // SetIndex(i, tempList[i]); + //} } }