Compare commits

...

7 Commits
4.7.6 ... 4.7.7

Author SHA1 Message Date
83edd1b9bb Bump editor package 2022-04-24 02:12:57 +10:00
613be34e95 Bump UniverseLib 2022-04-24 02:04:08 +10:00
58c65b9b8b Make TypeCompleter asynchronous 2022-04-24 02:02:34 +10:00
6fcf6a521c Bump version 2022-04-24 02:02:29 +10:00
81a174f865 Remove call to obsolete methods 2022-04-24 01:59:53 +10:00
b5e3cc2ea5 Use separate TypeCompleters for different contexts 2022-04-24 01:59:03 +10:00
14f46ade6a Cancel pending generic when edit button pressed 2022-04-24 01:58:27 +10:00
12 changed files with 158 additions and 111 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "com.sinai-dev.unityexplorer", "name": "com.sinai-dev.unityexplorer",
"version": "4.7.3", "version": "4.7.7",
"displayName": "UnityExplorer", "displayName": "UnityExplorer",
"description": "An in-game UI for exploring, debugging and modifying Unity games.", "description": "An in-game UI for exploring, debugging and modifying Unity games.",
"unity": "2017.1", "unity": "2017.1",

View File

@ -14,7 +14,7 @@ namespace UnityExplorer
public static class ExplorerCore public static class ExplorerCore
{ {
public const string NAME = "UnityExplorer"; public const string NAME = "UnityExplorer";
public const string VERSION = "4.7.6"; public const string VERSION = "4.7.7";
public const string AUTHOR = "Sinai"; public const string AUTHOR = "Sinai";
public const string GUID = "com.sinai.unityexplorer"; public const string GUID = "com.sinai.unityexplorer";

View File

@ -39,6 +39,8 @@ namespace UnityExplorer.Hooks
internal static Type pendingGenericDefinition; internal static Type pendingGenericDefinition;
internal static MethodInfo pendingGenericMethod; internal static MethodInfo pendingGenericMethod;
public static bool PendingGeneric => pendingGenericDefinition != null || pendingGenericMethod != null;
// Hook Source Editor UI // Hook Source Editor UI
public static GameObject EditorRoot { get; private set; } public static GameObject EditorRoot { get; private set; }
public static Text EditingHookLabel { get; private set; } public static Text EditingHookLabel { get; private set; }

View File

@ -47,6 +47,9 @@ namespace UnityExplorer.Hooks
public static void EditPatchClicked(int index) public static void EditPatchClicked(int index)
{ {
if (HookCreator.PendingGeneric)
HookManagerPanel.genericArgsHandler.Cancel();
HookManagerPanel.Instance.SetPage(HookManagerPanel.Pages.HookSourceEditor); HookManagerPanel.Instance.SetPage(HookManagerPanel.Pages.HookSourceEditor);
HookInstance hook = (HookInstance)currentHooks[index]; HookInstance hook = (HookInstance)currentHooks[index];
HookCreator.SetEditedHook(hook); HookCreator.SetEditedHook(hook);

View File

@ -34,12 +34,15 @@ namespace UnityExplorer.ObjectExplorer
private ScrollPool<ButtonCell> resultsScrollPool; private ScrollPool<ButtonCell> resultsScrollPool;
private List<object> currentResults = new(); private List<object> currentResults = new();
//public TypeCompleter typeAutocompleter;
public TypeCompleter unityObjectTypeCompleter;
public TypeCompleter allTypesCompleter;
public override GameObject UIRoot => uiRoot; public override GameObject UIRoot => uiRoot;
private GameObject uiRoot; private GameObject uiRoot;
private GameObject sceneFilterRow; private GameObject sceneFilterRow;
private GameObject childFilterRow; private GameObject childFilterRow;
private GameObject classInputRow; private GameObject classInputRow;
public TypeCompleter typeAutocompleter;
private GameObject nameInputRow; private GameObject nameInputRow;
private InputFieldRef nameInputField; private InputFieldRef nameInputField;
private Text resultsLabel; private Text resultsLabel;
@ -98,14 +101,18 @@ namespace UnityExplorer.ObjectExplorer
nameInputRow.SetActive(context == SearchContext.UnityObject); nameInputRow.SetActive(context == SearchContext.UnityObject);
if (context == SearchContext.Class) switch (context)
typeAutocompleter.AllTypes = true;
else
{ {
typeAutocompleter.BaseType = context == SearchContext.UnityObject ? typeof(UnityEngine.Object) : typeof(object); case SearchContext.UnityObject:
typeAutocompleter.AllTypes = false; unityObjectTypeCompleter.Enabled = true;
allTypesCompleter.Enabled = false;
break;
case SearchContext.Singleton:
case SearchContext.Class:
allTypesCompleter.Enabled = true;
unityObjectTypeCompleter.Enabled = false;
break;
} }
typeAutocompleter.CacheTypes();
} }
private void OnSceneFilterDropChanged(int value) => sceneFilter = (SceneFilter)value; private void OnSceneFilterDropChanged(int value) => sceneFilter = (SceneFilter)value;
@ -185,7 +192,9 @@ namespace UnityExplorer.ObjectExplorer
InputFieldRef classInputField = UIFactory.CreateInputField(classInputRow, "ClassInput", "..."); InputFieldRef classInputField = UIFactory.CreateInputField(classInputRow, "ClassInput", "...");
UIFactory.SetLayoutElement(classInputField.UIRoot, minHeight: 25, flexibleHeight: 0, flexibleWidth: 9999); UIFactory.SetLayoutElement(classInputField.UIRoot, minHeight: 25, flexibleHeight: 0, flexibleWidth: 9999);
typeAutocompleter = new TypeCompleter(typeof(UnityEngine.Object), classInputField); unityObjectTypeCompleter = new(typeof(UnityEngine.Object), classInputField, true, false, true);
allTypesCompleter = new(null, classInputField, true, false, true);
allTypesCompleter.Enabled = false;
classInputField.OnValueChanged += OnTypeInputChanged; classInputField.OnValueChanged += OnTypeInputChanged;
//unityObjectClassRow.SetActive(false); //unityObjectClassRow.SetActive(false);

View File

@ -135,7 +135,7 @@ namespace UnityExplorer.ObjectExplorer
foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies()) foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies())
{ {
foreach (Type type in asm.TryGetTypes()) foreach (Type type in asm.GetTypes())
{ {
if (!string.IsNullOrEmpty(nameFilter) && !type.FullName.ContainsIgnoreCase(nameFilter)) if (!string.IsNullOrEmpty(nameFilter) && !type.FullName.ContainsIgnoreCase(nameFilter))
continue; continue;
@ -173,7 +173,7 @@ namespace UnityExplorer.ObjectExplorer
foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies()) foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies())
{ {
// Search all non-static, non-enum classes. // Search all non-static, non-enum classes.
foreach (Type type in asm.TryGetTypes().Where(it => !(it.IsSealed && it.IsAbstract) && !it.IsEnum)) foreach (Type type in asm.GetTypes().Where(it => !(it.IsSealed && it.IsAbstract) && !it.IsEnum))
{ {
try try
{ {

View File

@ -69,15 +69,22 @@ namespace UnityExplorer.UI.Panels
if (CurrentHandler == provider) if (CurrentHandler == provider)
{ {
Suggestions.Clear();
CurrentHandler = null; CurrentHandler = null;
UIRoot.SetActive(false); UIRoot.SetActive(false);
} }
} }
public void SetSuggestions(IEnumerable<Suggestion> suggestions) public void SetSuggestions(List<Suggestion> suggestions, bool jumpToTop = false)
{ {
Suggestions = suggestions as List<Suggestion> ?? suggestions.ToList(); Suggestions = suggestions;
SelectedIndex = 0;
if (jumpToTop)
{
SelectedIndex = 0;
if (scrollPool.DataSource.ItemCount > 0)
scrollPool.JumpToIndex(0, null);
}
if (!Suggestions.Any()) if (!Suggestions.Any())
base.UIRoot.SetActive(false); base.UIRoot.SetActive(false);
@ -86,7 +93,7 @@ namespace UnityExplorer.UI.Panels
base.UIRoot.SetActive(true); base.UIRoot.SetActive(true);
base.UIRoot.transform.SetAsLastSibling(); base.UIRoot.transform.SetAsLastSibling();
buttonListDataHandler.RefreshData(); buttonListDataHandler.RefreshData();
scrollPool.Refresh(true, true); scrollPool.Refresh(true, false);
} }
} }
@ -194,6 +201,12 @@ namespace UnityExplorer.UI.Panels
private void SetCell(ButtonCell cell, int index) private void SetCell(ButtonCell cell, int index)
{ {
if (CurrentHandler == null)
{
UIRoot.SetActive(false);
return;
}
if (index < 0 || index >= Suggestions.Count) if (index < 0 || index >= Suggestions.Count)
{ {
cell.Disable(); cell.Disable();

View File

@ -1,6 +1,9 @@
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Linq; using System.Linq;
using UnityEngine;
using UnityExplorer.UI.Panels; using UnityExplorer.UI.Panels;
using UniverseLib; using UniverseLib;
using UniverseLib.UI.Models; using UniverseLib.UI.Models;
@ -12,22 +15,22 @@ namespace UnityExplorer.UI.Widgets.AutoComplete
{ {
public bool Enabled public bool Enabled
{ {
get => _enabled; get => enabled;
set set
{ {
_enabled = value; enabled = value;
if (!_enabled) if (!enabled)
{
AutoCompleteModal.Instance.ReleaseOwnership(this); AutoCompleteModal.Instance.ReleaseOwnership(this);
if (getSuggestionsCoroutine != null)
RuntimeHelper.StopCoroutine(getSuggestionsCoroutine);
}
} }
} }
private bool _enabled = true; bool enabled = true;
public event Action<Suggestion> SuggestionClicked; public event Action<Suggestion> SuggestionClicked;
public Type BaseType { get; set; }
public Type[] GenericConstraints { get; set; }
public bool AllTypes { get; set; }
public InputFieldRef InputField { get; } public InputFieldRef InputField { get; }
public bool AnchorToCaretPosition => false; public bool AnchorToCaretPosition => false;
@ -35,11 +38,20 @@ namespace UnityExplorer.UI.Widgets.AutoComplete
readonly bool allowEnum; readonly bool allowEnum;
readonly bool allowGeneric; readonly bool allowGeneric;
private HashSet<Type> allowedTypes; public Type BaseType { get; set; }
HashSet<Type> allowedTypes;
string pendingInput;
Coroutine getSuggestionsCoroutine;
readonly Stopwatch cacheTypesStopwatch = new();
readonly List<Suggestion> suggestions = new(); readonly List<Suggestion> suggestions = new();
readonly HashSet<string> suggestedNames = new(); readonly HashSet<string> suggestedTypes = new();
private string chosenSuggestion; string chosenSuggestion;
readonly List<Suggestion> loadingSuggestions = new()
{
new("<color=grey>Loading...</color>", "")
};
bool ISuggestionProvider.AllowNavigation => false; bool ISuggestionProvider.AllowNavigation => false;
@ -76,106 +88,89 @@ namespace UnityExplorer.UI.Widgets.AutoComplete
inputField.OnValueChanged += OnInputFieldChanged; inputField.OnValueChanged += OnInputFieldChanged;
if (BaseType != null || AllTypes) CacheTypes();
CacheTypes();
}
public void CacheTypes()
{
if (!AllTypes)
allowedTypes = ReflectionUtility.GetImplementationsOf(BaseType, allowAbstract, allowGeneric, allowEnum);
else
allowedTypes = GetAllAllowedTypes();
// Check generic parameter constraints
if (GenericConstraints != null && GenericConstraints.Any())
{
List<Type> typesToRemove = new();
foreach (Type type in allowedTypes)
{
bool allowed = true;
foreach (Type constraint in GenericConstraints)
{
if (!constraint.IsAssignableFrom(type))
{
allowed = false;
break;
}
}
if (!allowed)
typesToRemove.Add(type);
}
foreach (Type type in typesToRemove)
allowedTypes.Remove(type);
}
}
HashSet<Type> GetAllAllowedTypes()
{
HashSet<Type> allAllowedTypes = new();
foreach (KeyValuePair<string, Type> entry in ReflectionUtility.AllTypes)
{
Type type = entry.Value;
if ((!allowAbstract && type.IsAbstract)
|| (!allowGeneric && type.IsGenericType)
|| (!allowEnum && type.IsEnum))
continue;
// skip <PrivateImplementationDetails> and <AnonymousClass> classes
if (type.FullName.Contains("PrivateImplementationDetails")
|| type.FullName.Contains("DisplayClass")
|| type.FullName.Contains('<'))
{
continue;
}
allAllowedTypes.Add(type);
}
return allAllowedTypes;
} }
public void OnSuggestionClicked(Suggestion suggestion) public void OnSuggestionClicked(Suggestion suggestion)
{ {
chosenSuggestion = suggestion.UnderlyingValue;
InputField.Text = suggestion.UnderlyingValue; InputField.Text = suggestion.UnderlyingValue;
SuggestionClicked?.Invoke(suggestion); SuggestionClicked?.Invoke(suggestion);
suggestions.Clear(); suggestions.Clear();
AutoCompleteModal.Instance.SetSuggestions(suggestions); //AutoCompleteModal.Instance.SetSuggestions(suggestions, true);
chosenSuggestion = suggestion.UnderlyingValue; AutoCompleteModal.Instance.ReleaseOwnership(this);
} }
private void OnInputFieldChanged(string value) public void CacheTypes()
{
allowedTypes = null;
cacheTypesStopwatch.Reset();
cacheTypesStopwatch.Start();
ReflectionUtility.GetImplementationsOf(BaseType, OnTypesCached, allowAbstract, allowGeneric, allowEnum);
}
void OnTypesCached(HashSet<Type> set)
{
allowedTypes = set;
// ExplorerCore.Log($"Cached {allowedTypes.Count} TypeCompleter types in {cacheTypesStopwatch.ElapsedMilliseconds * 0.001f} seconds.");
if (pendingInput != null)
{
GetSuggestions(pendingInput);
pendingInput = null;
}
}
void OnInputFieldChanged(string input)
{ {
if (!Enabled) if (!Enabled)
return; return;
if (string.IsNullOrEmpty(value) || value == chosenSuggestion) if (input != chosenSuggestion)
{
chosenSuggestion = null; chosenSuggestion = null;
if (string.IsNullOrEmpty(input) || input == chosenSuggestion)
{
if (getSuggestionsCoroutine != null)
RuntimeHelper.StopCoroutine(getSuggestionsCoroutine);
AutoCompleteModal.Instance.ReleaseOwnership(this); AutoCompleteModal.Instance.ReleaseOwnership(this);
} }
else else
{ {
GetSuggestions(value); GetSuggestions(input);
AutoCompleteModal.TakeOwnership(this);
AutoCompleteModal.Instance.SetSuggestions(suggestions);
} }
} }
private void GetSuggestions(string input) void GetSuggestions(string input)
{ {
suggestions.Clear(); if (allowedTypes == null)
suggestedNames.Clear();
if (!AllTypes && BaseType == null)
{ {
ExplorerCore.LogWarning("Autocompleter Base type is null!"); if (pendingInput != null)
{
AutoCompleteModal.TakeOwnership(this);
AutoCompleteModal.Instance.SetSuggestions(loadingSuggestions, true);
}
pendingInput = input;
return; return;
} }
if (getSuggestionsCoroutine != null)
RuntimeHelper.StopCoroutine(getSuggestionsCoroutine);
getSuggestionsCoroutine = RuntimeHelper.StartCoroutine(GetSuggestionsAsync(input));
}
IEnumerator GetSuggestionsAsync(string input)
{
suggestions.Clear();
suggestedTypes.Clear();
AutoCompleteModal.TakeOwnership(this);
AutoCompleteModal.Instance.SetSuggestions(suggestions, true);
// shorthand types all inherit from System.Object // shorthand types all inherit from System.Object
if (shorthandToType.TryGetValue(input, out Type shorthand) && allowedTypes.Contains(shorthand)) if (shorthandToType.TryGetValue(input, out Type shorthand) && allowedTypes.Contains(shorthand))
AddSuggestion(shorthand); AddSuggestion(shorthand);
@ -190,20 +185,47 @@ namespace UnityExplorer.UI.Widgets.AutoComplete
if (ReflectionUtility.GetTypeByName(input) is Type t && allowedTypes.Contains(t)) if (ReflectionUtility.GetTypeByName(input) is Type t && allowedTypes.Contains(t))
AddSuggestion(t); AddSuggestion(t);
if (!suggestions.Any())
AutoCompleteModal.Instance.SetSuggestions(loadingSuggestions, false);
else
AutoCompleteModal.Instance.SetSuggestions(suggestions, false);
Stopwatch sw = new();
sw.Start();
// ExplorerCore.Log($"Checking {allowedTypes.Count} types...");
foreach (Type entry in allowedTypes) foreach (Type entry in allowedTypes)
{ {
if (AutoCompleteModal.CurrentHandler == null)
yield break;
if (sw.ElapsedMilliseconds > 10)
{
yield return null;
if (suggestions.Any())
AutoCompleteModal.Instance.SetSuggestions(suggestions, false);
sw.Reset();
sw.Start();
}
if (entry.FullName.ContainsIgnoreCase(input)) if (entry.FullName.ContainsIgnoreCase(input))
AddSuggestion(entry); AddSuggestion(entry);
} }
AutoCompleteModal.Instance.SetSuggestions(suggestions, false);
// ExplorerCore.Log($"Fetched {suggestions.Count} TypeCompleter suggestions in {sw.ElapsedMilliseconds * 0.001f} seconds.");
} }
internal static readonly Dictionary<string, string> sharedTypeToLabel = new(); internal static readonly Dictionary<string, string> sharedTypeToLabel = new();
void AddSuggestion(Type type) void AddSuggestion(Type type)
{ {
if (suggestedNames.Contains(type.FullName)) if (suggestedTypes.Contains(type.FullName))
return; return;
suggestedNames.Add(type.FullName); suggestedTypes.Add(type.FullName);
if (!sharedTypeToLabel.ContainsKey(type.FullName)) if (!sharedTypeToLabel.ContainsKey(type.FullName))
sharedTypeToLabel.Add(type.FullName, SignatureHighlighter.Parse(type, true)); sharedTypeToLabel.Add(type.FullName, SignatureHighlighter.Parse(type, true));

View File

@ -15,22 +15,20 @@ namespace UnityExplorer.UI.Widgets
typeCompleter.Enabled = true; typeCompleter.Enabled = true;
typeCompleter.BaseType = this.genericArgument; typeCompleter.BaseType = this.genericArgument;
typeCompleter.CacheTypes();
Type[] constraints = this.genericArgument.GetGenericParameterConstraints(); Type[] constraints = this.genericArgument.GetGenericParameterConstraints();
typeCompleter.GenericConstraints = constraints;
typeCompleter.CacheTypes();
StringBuilder sb = new($"<color={SignatureHighlighter.CONST}>{this.genericArgument.Name}</color>"); StringBuilder sb = new($"<color={SignatureHighlighter.CONST}>{this.genericArgument.Name}</color>");
for (int j = 0; j < constraints.Length; j++) for (int i = 0; i < constraints.Length; i++)
{ {
if (j == 0) sb.Append(' ').Append('('); if (i == 0) sb.Append(' ').Append('(');
else sb.Append(',').Append(' '); else sb.Append(',').Append(' ');
sb.Append(SignatureHighlighter.Parse(constraints[j], false)); sb.Append(SignatureHighlighter.Parse(constraints[i], false));
if (j + 1 == constraints.Length) if (i + 1 == constraints.Length)
sb.Append(')'); sb.Append(')');
} }

View File

@ -79,11 +79,11 @@
<!-- il2cpp nuget --> <!-- il2cpp nuget -->
<ItemGroup Condition="'$(Configuration)'=='ML_Cpp_net6' or '$(Configuration)'=='ML_Cpp_net472' or '$(Configuration)'=='STANDALONE_Cpp' or '$(Configuration)'=='BIE_Cpp'"> <ItemGroup Condition="'$(Configuration)'=='ML_Cpp_net6' or '$(Configuration)'=='ML_Cpp_net472' or '$(Configuration)'=='STANDALONE_Cpp' or '$(Configuration)'=='BIE_Cpp'">
<PackageReference Include="Il2CppAssemblyUnhollower.BaseLib" Version="0.4.22" IncludeAssets="compile" /> <PackageReference Include="Il2CppAssemblyUnhollower.BaseLib" Version="0.4.22" IncludeAssets="compile" />
<PackageReference Include="UniverseLib.IL2CPP" Version="1.3.8" /> <PackageReference Include="UniverseLib.IL2CPP" Version="1.3.9" />
</ItemGroup> </ItemGroup>
<!-- mono nuget --> <!-- mono nuget -->
<ItemGroup Condition="'$(Configuration)'=='BIE6_Mono' or '$(Configuration)'=='BIE5_Mono' or '$(Configuration)'=='ML_Mono' or '$(Configuration)'=='STANDALONE_Mono'"> <ItemGroup Condition="'$(Configuration)'=='BIE6_Mono' or '$(Configuration)'=='BIE5_Mono' or '$(Configuration)'=='ML_Mono' or '$(Configuration)'=='STANDALONE_Mono'">
<PackageReference Include="UniverseLib.Mono" Version="1.3.8" /> <PackageReference Include="UniverseLib.Mono" Version="1.3.9" />
</ItemGroup> </ItemGroup>
<!-- ~~~~~ ASSEMBLY REFERENCES ~~~~~ --> <!-- ~~~~~ ASSEMBLY REFERENCES ~~~~~ -->