From 58c65b9b8bf2772bd2f02f1206ab9358f5a19756 Mon Sep 17 00:00:00 2001 From: Sinai <49360850+sinai-dev@users.noreply.github.com> Date: Sun, 24 Apr 2022 02:02:34 +1000 Subject: [PATCH] Make TypeCompleter asynchronous --- src/UI/Panels/AutoCompleteModal.cs | 21 +- src/UI/Widgets/AutoComplete/TypeCompleter.cs | 194 +++++++++++-------- 2 files changed, 125 insertions(+), 90 deletions(-) diff --git a/src/UI/Panels/AutoCompleteModal.cs b/src/UI/Panels/AutoCompleteModal.cs index 4e73329..e9e14a6 100644 --- a/src/UI/Panels/AutoCompleteModal.cs +++ b/src/UI/Panels/AutoCompleteModal.cs @@ -69,15 +69,22 @@ namespace UnityExplorer.UI.Panels if (CurrentHandler == provider) { + Suggestions.Clear(); CurrentHandler = null; UIRoot.SetActive(false); } } - public void SetSuggestions(IEnumerable suggestions) + public void SetSuggestions(List suggestions, bool jumpToTop = false) { - Suggestions = suggestions as List ?? suggestions.ToList(); - SelectedIndex = 0; + Suggestions = suggestions; + + if (jumpToTop) + { + SelectedIndex = 0; + if (scrollPool.DataSource.ItemCount > 0) + scrollPool.JumpToIndex(0, null); + } if (!Suggestions.Any()) base.UIRoot.SetActive(false); @@ -86,7 +93,7 @@ namespace UnityExplorer.UI.Panels base.UIRoot.SetActive(true); base.UIRoot.transform.SetAsLastSibling(); 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) { + if (CurrentHandler == null) + { + UIRoot.SetActive(false); + return; + } + if (index < 0 || index >= Suggestions.Count) { cell.Disable(); diff --git a/src/UI/Widgets/AutoComplete/TypeCompleter.cs b/src/UI/Widgets/AutoComplete/TypeCompleter.cs index c4f9625..0338b53 100644 --- a/src/UI/Widgets/AutoComplete/TypeCompleter.cs +++ b/src/UI/Widgets/AutoComplete/TypeCompleter.cs @@ -1,6 +1,9 @@ using System; +using System.Collections; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; +using UnityEngine; using UnityExplorer.UI.Panels; using UniverseLib; using UniverseLib.UI.Models; @@ -12,22 +15,22 @@ namespace UnityExplorer.UI.Widgets.AutoComplete { public bool Enabled { - get => _enabled; + get => enabled; set { - _enabled = value; - if (!_enabled) + enabled = value; + if (!enabled) + { AutoCompleteModal.Instance.ReleaseOwnership(this); + if (getSuggestionsCoroutine != null) + RuntimeHelper.StopCoroutine(getSuggestionsCoroutine); + } } } - private bool _enabled = true; + bool enabled = true; public event Action SuggestionClicked; - public Type BaseType { get; set; } - public Type[] GenericConstraints { get; set; } - public bool AllTypes { get; set; } - public InputFieldRef InputField { get; } public bool AnchorToCaretPosition => false; @@ -35,11 +38,20 @@ namespace UnityExplorer.UI.Widgets.AutoComplete readonly bool allowEnum; readonly bool allowGeneric; - private HashSet allowedTypes; + public Type BaseType { get; set; } + HashSet allowedTypes; + string pendingInput; + Coroutine getSuggestionsCoroutine; + readonly Stopwatch cacheTypesStopwatch = new(); readonly List suggestions = new(); - readonly HashSet suggestedNames = new(); - private string chosenSuggestion; + readonly HashSet suggestedTypes = new(); + string chosenSuggestion; + + readonly List loadingSuggestions = new() + { + new("Loading...", "") + }; bool ISuggestionProvider.AllowNavigation => false; @@ -76,106 +88,89 @@ namespace UnityExplorer.UI.Widgets.AutoComplete inputField.OnValueChanged += OnInputFieldChanged; - if (BaseType != null || AllTypes) - 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 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 GetAllAllowedTypes() - { - HashSet allAllowedTypes = new(); - foreach (KeyValuePair entry in ReflectionUtility.AllTypes) - { - Type type = entry.Value; - - if ((!allowAbstract && type.IsAbstract) - || (!allowGeneric && type.IsGenericType) - || (!allowEnum && type.IsEnum)) - continue; - - // skip and classes - if (type.FullName.Contains("PrivateImplementationDetails") - || type.FullName.Contains("DisplayClass") - || type.FullName.Contains('<')) - { - continue; - } - allAllowedTypes.Add(type); - } - - return allAllowedTypes; + CacheTypes(); } public void OnSuggestionClicked(Suggestion suggestion) { + chosenSuggestion = suggestion.UnderlyingValue; InputField.Text = suggestion.UnderlyingValue; SuggestionClicked?.Invoke(suggestion); suggestions.Clear(); - AutoCompleteModal.Instance.SetSuggestions(suggestions); - chosenSuggestion = suggestion.UnderlyingValue; + //AutoCompleteModal.Instance.SetSuggestions(suggestions, true); + 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 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) return; - if (string.IsNullOrEmpty(value) || value == chosenSuggestion) - { + if (input != chosenSuggestion) chosenSuggestion = null; + + if (string.IsNullOrEmpty(input) || input == chosenSuggestion) + { + if (getSuggestionsCoroutine != null) + RuntimeHelper.StopCoroutine(getSuggestionsCoroutine); AutoCompleteModal.Instance.ReleaseOwnership(this); } else { - GetSuggestions(value); - - AutoCompleteModal.TakeOwnership(this); - AutoCompleteModal.Instance.SetSuggestions(suggestions); + GetSuggestions(input); } } - private void GetSuggestions(string input) + void GetSuggestions(string input) { - suggestions.Clear(); - suggestedNames.Clear(); - - if (!AllTypes && BaseType == null) + if (allowedTypes == null) { - ExplorerCore.LogWarning("Autocompleter Base type is null!"); + if (pendingInput != null) + { + AutoCompleteModal.TakeOwnership(this); + AutoCompleteModal.Instance.SetSuggestions(loadingSuggestions, true); + } + + pendingInput = input; 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 if (shorthandToType.TryGetValue(input, out Type shorthand) && allowedTypes.Contains(shorthand)) AddSuggestion(shorthand); @@ -190,20 +185,47 @@ namespace UnityExplorer.UI.Widgets.AutoComplete if (ReflectionUtility.GetTypeByName(input) is Type t && allowedTypes.Contains(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) { + 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)) AddSuggestion(entry); } + + AutoCompleteModal.Instance.SetSuggestions(suggestions, false); + + // ExplorerCore.Log($"Fetched {suggestions.Count} TypeCompleter suggestions in {sw.ElapsedMilliseconds * 0.001f} seconds."); } internal static readonly Dictionary sharedTypeToLabel = new(); void AddSuggestion(Type type) { - if (suggestedNames.Contains(type.FullName)) + if (suggestedTypes.Contains(type.FullName)) return; - suggestedNames.Add(type.FullName); + suggestedTypes.Add(type.FullName); if (!sharedTypeToLabel.ContainsKey(type.FullName)) sharedTypeToLabel.Add(type.FullName, SignatureHighlighter.Parse(type, true));