Skip to content

Commit

Permalink
Improving UI performance (#5216)
Browse files Browse the repository at this point in the history
* Added fast observable collection

* Updated to use 1 collection changed event per query

* Moved result updating to background thread

* Changed collapsed to hidden for virtualization

* Moved all token cancellations inside try catch

* Fixed freeze on deleting first letter

* nit fixes

* Moved update logic to plugin result loop

* Updated doc comment for AddResults function

* fix result clear on empty query
  • Loading branch information
dsrivastavv committed Jul 28, 2020
1 parent 5fb7d43 commit 87ae1c6
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 80 deletions.
74 changes: 71 additions & 3 deletions src/modules/launcher/PowerLauncher/Helper/ResultCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,69 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Windows;
using System.Windows.Threading;

namespace PowerLauncher.Helper
{
public class ResultCollection : ObservableCollection<ResultViewModel>
{
/// <summary>
/// This private variable holds the flag to
/// turn on and off the collection changed notification.
/// </summary>
private bool suspendCollectionChangeNotification;

/// <summary>
/// Initializes a new instance of the FastObservableCollection class.
/// </summary>
public ResultCollection()
: base()
{
this.suspendCollectionChangeNotification = false;
}

/// <summary>
/// This event is overriden CollectionChanged event of the observable collection.
/// </summary>
//public override event NotifyCollectionChangedEventHandler CollectionChanged;

public void RemoveAll(Predicate<ResultViewModel> predicate)
/// <summary>
/// Raises collection change event.
/// </summary>
public void NotifyChanges()
{
this.ResumeCollectionChangeNotification();
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}

/// <summary>
/// Resumes collection changed notification.
/// </summary>
public void ResumeCollectionChangeNotification()
{
this.suspendCollectionChangeNotification = false;
}

/// <summary>
/// Suspends collection changed notification.
/// </summary>
public void SuspendCollectionChangeNotification()
{
this.suspendCollectionChangeNotification = true;
}

/// <summary>
/// This method removes all items that match a predicate
/// </summary>
/// <param name="predicate">predicate</param>
public void RemovePredicate(Predicate<ResultViewModel> predicate)
{
CheckReentrancy();

this.SuspendCollectionChangeNotification();
for (int i = Count - 1; i >= 0; i--)
{
if (predicate(this[i]))
Expand All @@ -36,6 +89,7 @@ public void Update(List<ResultViewModel> newItems)
int oldCount = Items.Count;
int location = newCount > oldCount ? oldCount : newCount;

this.SuspendCollectionChangeNotification();
for (int i = 0; i < location; i++)
{
ResultViewModel oldResult = this[i];
Expand All @@ -50,7 +104,6 @@ public void Update(List<ResultViewModel> newItems)
}
}


if (newCount >= oldCount)
{
for (int i = oldCount; i < newCount; i++)
Expand All @@ -66,5 +119,20 @@ public void Update(List<ResultViewModel> newItems)
}
}
}

/// <summary>
/// This collection changed event performs thread safe event raising.
/// </summary>
/// <param name="e">The event argument.</param>
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
// Recommended is to avoid reentry
// in collection changed event while collection
// is getting changed on other thread.
if(!this.suspendCollectionChangeNotification)
{
base.OnCollectionChanged(e);
}
}
}
}
}
19 changes: 1 addition & 18 deletions src/modules/launcher/PowerLauncher/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -315,8 +315,6 @@ private void SuggestionsList_SelectionChanged(object sender, SelectionChangedEve
}
}

private const int millisecondsToWait = 100;
private static DateTime s_lastTimeOfTyping;
private bool disposedValue = false;

private void QueryTextBox_TextChanged(object sender, TextChangedEventArgs e)
Expand All @@ -335,22 +333,7 @@ private void QueryTextBox_TextChanged(object sender, TextChangedEventArgs e)
SearchBox.AutoCompleteTextBlock.Text = string.Empty;
}
_viewModel.QueryText = text;
var latestTimeOfTyping = DateTime.Now;

Task.Run(() => DelayedCheck(latestTimeOfTyping));
s_lastTimeOfTyping = latestTimeOfTyping;
}
}

private async Task DelayedCheck(DateTime latestTimeOfTyping)
{
await Task.Delay(millisecondsToWait).ConfigureAwait(false);
if (latestTimeOfTyping.Equals(s_lastTimeOfTyping))
{
await System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
_viewModel.Query();
}));
_viewModel.Query();
}
}

Expand Down
70 changes: 34 additions & 36 deletions src/modules/launcher/PowerLauncher/ViewModel/MainViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public class MainViewModel : BaseModel, ISavable, IDisposable
private readonly Internationalization _translator = InternationalizationManager.Instance;
private System.Diagnostics.Stopwatch hotkeyTimer = new System.Diagnostics.Stopwatch();

private readonly object _addResultsLock = new object();
#endregion

#region Constructor
Expand Down Expand Up @@ -301,13 +302,13 @@ private ResultsViewModel SelectedResults
_selectedResults = value;
if (SelectedIsFromQueryResults())
{
ContextMenu.Visibility = Visibility.Collapsed;
History.Visibility = Visibility.Collapsed;
ContextMenu.Visibility = Visibility.Hidden;
History.Visibility = Visibility.Hidden;
ChangeQueryText(_queryTextBeforeLeaveResults);
}
else
{
Results.Visibility = Visibility.Collapsed;
Results.Visibility = Visibility.Hidden;
_queryTextBeforeLeaveResults = QueryText;


Expand Down Expand Up @@ -440,54 +441,56 @@ private void QueryResults()
var currentCancellationToken = _updateSource.Token;
_updateToken = currentCancellationToken;

ProgressBarVisibility = Visibility.Hidden;
var query = QueryBuilder.Build(QueryText.Trim(), PluginManager.NonGlobalPlugins);
if (query != null)
{
// handle the exclusiveness of plugin using action keyword
RemoveOldQueryResults(query);

_lastQuery = query;
var plugins = PluginManager.ValidPluginsForQuery(query);

Task.Run(() =>
{
// so looping will stop once it was cancelled
var parallelOptions = new ParallelOptions { CancellationToken = currentCancellationToken };
Thread.Sleep(20);
RemoveOldQueryResults(query);
var plugins = PluginManager.ValidPluginsForQuery(query);
try
{
Parallel.ForEach(plugins, parallelOptions, plugin =>
currentCancellationToken.ThrowIfCancellationRequested();
foreach(PluginPair plugin in plugins)
{
if (!plugin.Metadata.Disabled)
if (!plugin.Metadata.Disabled && !currentCancellationToken.IsCancellationRequested)
{
var results = PluginManager.QueryForPlugin(plugin, query);
if (Application.Current.Dispatcher.CheckAccess())
currentCancellationToken.ThrowIfCancellationRequested();
lock (_addResultsLock)
{
UpdateResultView(results, plugin.Metadata, query);
}
else
{
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
UpdateResultView(results, plugin.Metadata, query);
}));
}
}
});
}
currentCancellationToken.ThrowIfCancellationRequested();
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
if (query.RawQuery == _lastQuery.RawQuery)
{
Results.Results.NotifyChanges();
}
if (Results.Results.Count > 0)
{
Results.Visibility = Visibility.Visible;
Results.SelectedIndex = 0;
}
else
{
Results.Visibility = Visibility.Hidden;
}
}));
}
catch (OperationCanceledException)
{
// nothing to do here
}
// this should happen once after all queries are done so progress bar should continue
// until the end of all querying
if (currentUpdateSource == _updateSource)
{ // update to hidden if this is still the current query
ProgressBarVisibility = Visibility.Hidden;
}
queryTimer.Stop();
var queryEvent = new LauncherQueryEvent()
{
Expand All @@ -505,8 +508,8 @@ private void QueryResults()
_updateSource?.Cancel();
_lastQuery = _emptyQuery;
Results.SelectedItem = null;
Results.Visibility = Visibility.Hidden;
Results.Clear();
Results.Visibility = Visibility.Collapsed;
}
}

Expand Down Expand Up @@ -710,11 +713,6 @@ public void UpdateResultView(List<Result> list, PluginMetadata metadata, Query o
{
Results.AddResults(list, metadata.ID);
}

if (Results.Visibility != Visibility.Visible && list.Count > 0)
{
Results.Visibility = Visibility.Visible;
}
}

public void ColdStartFix()
Expand Down
29 changes: 6 additions & 23 deletions src/modules/launcher/PowerLauncher/ViewModel/ResultsViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ public class ResultsViewModel : BaseModel

public ResultCollection Results { get; }

private readonly object _addResultsLock = new object();
private readonly object _collectionLock = new object();
private readonly Settings _settings;
// private int MaxResults => _settings?.MaxResultsToShow ?? 6;
Expand Down Expand Up @@ -154,12 +153,12 @@ public void Clear()

public void RemoveResultsExcept(PluginMetadata metadata)
{
Results.RemoveAll(r => r.Result.PluginID != metadata.ID);
Results.RemovePredicate(r => r.Result.PluginID != metadata.ID);
}

public void RemoveResultsFor(PluginMetadata metadata)
{
Results.RemoveAll(r => r.Result.PluginID == metadata.ID);
Results.RemovePredicate(r => r.Result.PluginID == metadata.ID);
}

public void SelectNextTabItem()
Expand Down Expand Up @@ -214,28 +213,12 @@ public bool IsContextMenuItemSelected()
}

/// <summary>
/// To avoid deadlock, this method should not called from main thread
/// Add new results to ResultCollection
/// </summary>
public void AddResults(List<Result> newRawResults, string resultId)
{
lock (_addResultsLock)
{
var newResults = NewResults(newRawResults, resultId);

// update UI in one run, so it can avoid UI flickering
Results.Update(newResults);

if (Results.Count > 0)
{
Margin = new Thickness { Top = 8 };
SelectedIndex = 0;
}
else
{
Margin = new Thickness { Top = 0 };
Visibility = Visibility.Collapsed;
}
}
{
var newResults = NewResults(newRawResults, resultId);
Results.Update(newResults);
}

private List<ResultViewModel> NewResults(List<Result> newRawResults, string resultId)
Expand Down

0 comments on commit 87ae1c6

Please sign in to comment.