Make TypeCompleter asynchronous

This commit is contained in:
Sinai 2022-04-24 02:02:34 +10:00
parent 6fcf6a521c
commit 58c65b9b8b
2 changed files with 125 additions and 90 deletions

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;
if (jumpToTop)
{ {
Suggestions = suggestions as List<Suggestion> ?? suggestions.ToList();
SelectedIndex = 0; 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);
}
}
void GetSuggestions(string input)
{
if (allowedTypes == null)
{
if (pendingInput != null)
{
AutoCompleteModal.TakeOwnership(this); AutoCompleteModal.TakeOwnership(this);
AutoCompleteModal.Instance.SetSuggestions(suggestions); AutoCompleteModal.Instance.SetSuggestions(loadingSuggestions, true);
}
} }
private void GetSuggestions(string input) pendingInput = input;
{
suggestions.Clear();
suggestedNames.Clear();
if (!AllTypes && BaseType == null)
{
ExplorerCore.LogWarning("Autocompleter Base type is null!");
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));