Update Fallback commands async, once (#38157)

The problem: 

> * we need to go update all the Fallback commands. (these are ones that extensions can use to react to the search text - basically, "what the user typed wasn't found immediately, but here's something they can fall back on"
>   * this is wacky, because the way I had it, I update each item, and if it "changes visibility", then we need to update the main list, because we've already removed it from the list. So we need to re-update the list to account for that
>     * you missed it reading that (and i missed it writing it) but that basically means we re-populate the list F={num fallbacks} times, because each one sends the "do it again" message
>     * That results in us basically creating (F+1)*(N=num items+apps) view models, initializing them, and not needing most of them

The crux here being a single thread, to update all the fallback items,
that then only raises _one_ items changed at the very end.

I don't love this, one misbehaving fallback could stop all the others. In theory, we should do a parallel update of all these things, with a like, 1s timeout on each leg. 

But it has gotta be faster till we can do #38140 (or similar)

Closes: (not sure I filed one). But the first typed character _felt_ slow.
This commit is contained in:
Mike Griese
2025-03-26 06:36:37 -05:00
committed by GitHub
parent d597bd267d
commit 60bbf070e1
2 changed files with 58 additions and 29 deletions

View File

@@ -2,6 +2,7 @@
// The Microsoft Corporation licenses this file to you under the MIT license. // The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System.Collections.Immutable;
using System.Collections.Specialized; using System.Collections.Specialized;
using CommunityToolkit.Mvvm.Messaging; using CommunityToolkit.Mvvm.Messaging;
using Microsoft.CmdPal.Ext.Apps; using Microsoft.CmdPal.Ext.Apps;
@@ -101,12 +102,7 @@ public partial class MainListPage : DynamicListPage,
var commands = _tlcManager.TopLevelCommands; var commands = _tlcManager.TopLevelCommands;
lock (commands) lock (commands)
{ {
// This gets called on a background thread, because ListViewModel UpdateFallbacks(newSearch, commands.ToImmutableArray());
// updates the .SearchText of all extensions on a BG thread.
foreach (var command in commands)
{
command.TryUpdateFallbackText(newSearch);
}
// Cleared out the filter text? easy. Reset _filteredItems, and bail out. // Cleared out the filter text? easy. Reset _filteredItems, and bail out.
if (string.IsNullOrEmpty(newSearch)) if (string.IsNullOrEmpty(newSearch))
@@ -137,6 +133,26 @@ public partial class MainListPage : DynamicListPage,
} }
} }
private void UpdateFallbacks(string newSearch, IReadOnlyList<TopLevelViewModel> commands)
{
// fire and forget
_ = Task.Run(() =>
{
var needsToUpdate = false;
foreach (var command in commands)
{
var changedVisibility = command.SafeUpdateFallbackTextSynchronous(newSearch);
needsToUpdate = needsToUpdate || changedVisibility;
}
if (needsToUpdate)
{
RaiseItemsChanged();
}
});
}
private bool ActuallyLoading() private bool ActuallyLoading()
{ {
var tlcManager = _serviceProvider.GetService<TopLevelCommandManager>()!; var tlcManager = _serviceProvider.GetService<TopLevelCommandManager>()!;

View File

@@ -4,8 +4,7 @@
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging; using ManagedCommon;
using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CmdPal.UI.ViewModels.Settings; using Microsoft.CmdPal.UI.ViewModels.Settings;
using Microsoft.CommandPalette.Extensions; using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit; using Microsoft.CommandPalette.Extensions.Toolkit;
@@ -237,32 +236,46 @@ public sealed partial class TopLevelViewModel : ObservableObject, IListItem
} }
} }
public void TryUpdateFallbackText(string newQuery) internal bool SafeUpdateFallbackTextSynchronous(string newQuery)
{ {
if (!IsFallback) if (!IsFallback)
{ {
return; return false;
} }
_ = Task.Run(() => try
{ {
try return UnsafeUpdateFallbackSynchronous(newQuery);
{ }
var model = _commandItemViewModel.Model.Unsafe; catch (Exception ex)
if (model is IFallbackCommandItem fallback) {
{ Logger.LogError(ex.ToString());
var wasEmpty = string.IsNullOrEmpty(Title); }
fallback.FallbackHandler.UpdateQuery(newQuery);
var isEmpty = string.IsNullOrEmpty(Title); return false;
if (wasEmpty != isEmpty) }
{
WeakReferenceMessenger.Default.Send<UpdateFallbackItemsMessage>(); /// <summary>
} /// Calls UpdateQuery on our command, if we're a fallback item. This does
} /// RPC work, so make sure you're calling it on a BG thread.
} /// </summary>
catch (Exception) /// <param name="newQuery">The new search text to pass to the extension</param>
{ /// <returns>true if our Title changed across this call</returns>
} private bool UnsafeUpdateFallbackSynchronous(string newQuery)
}); {
var model = _commandItemViewModel.Model.Unsafe;
// RPC to check type
if (model is IFallbackCommandItem fallback)
{
var wasEmpty = string.IsNullOrEmpty(Title);
// RPC for method
fallback.FallbackHandler.UpdateQuery(newQuery);
var isEmpty = string.IsNullOrEmpty(Title);
return wasEmpty != isEmpty;
}
return false;
} }
} }