Skip to content

Commit

Permalink
Bring back search performance improvement (#15789)
Browse files Browse the repository at this point in the history
  • Loading branch information
pinzart90 authored Jan 29, 2025
1 parent 3aeb33b commit 5ef1f7b
Show file tree
Hide file tree
Showing 10 changed files with 305 additions and 10 deletions.
1 change: 1 addition & 0 deletions src/DynamoCore/Engine/EngineController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ public void Dispose()

liveRunnerServices.Dispose();
codeCompletionServices = null;
CompilationServices = null;
}

/// <summary>
Expand Down
4 changes: 4 additions & 0 deletions src/DynamoCore/Models/DynamoModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,10 @@ public void ShutDown(bool shutdownHost)

AnalyticsService.ShutDown();

LuceneSearch.LuceneUtilityNodeSearch = null;
LuceneSearch.LuceneUtilityNodeAutocomplete = null;
LuceneSearch.LuceneUtilityPackageManager = null;

State = DynamoModelState.NotStarted;
OnShutdownCompleted(); // Notify possible event handlers.
}
Expand Down
1 change: 1 addition & 0 deletions src/DynamoCoreWpf/DynamoCoreWpf.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,7 @@
<Compile Include="ViewModels\Preview\ConnectorAnchorViewModel.cs" />
<Compile Include="ViewModels\Preview\ConnectorContextMenuViewModel.cs" />
<Compile Include="ViewModels\RunSettingsViewModel.cs" />
<Compile Include="Utilities\ActionDebouncer.cs" />
<Compile Include="ViewModels\Search\BrowserInternalElementViewModel.cs" />
<Compile Include="ViewModels\Search\BrowserItemViewModel.cs" />
<Compile Include="ViewModels\Search\NodeAutoCompleteSearchViewModel.cs" />
Expand Down
1 change: 0 additions & 1 deletion src/DynamoCoreWpf/UI/SharedResourceDictionary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ public static class SharedDictionaryManager
private static ResourceDictionary outPortsDictionary;
private static ResourceDictionary inPortsDictionary;
private static ResourceDictionary _liveChartDictionary;


public static string ThemesDirectory
{
Expand Down
61 changes: 61 additions & 0 deletions src/DynamoCoreWpf/Utilities/ActionDebouncer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using Dynamo.Logging;
using System;
using System.Threading;
using System.Threading.Tasks;

namespace Dynamo.Wpf.Utilities
{
/// <summary>
/// The ActionDebouncer class offers a means to reduce the number of UI notifications for a specified time.
/// It is meant to be used in UI elements where too many UI updates can cause perfomance issues.
/// </summary>
internal class ActionDebouncer(ILogger logger) : IDisposable
{
private readonly ILogger logger = logger;
private CancellationTokenSource cts;

public void Cancel()
{
if (cts != null)
{
cts.Cancel();
cts.Dispose();
cts = null;
}
}

/// <summary>
/// Delays the "action" for a "timeout" number of milliseconds
/// The input Action will run on same syncronization context as the Debounce method call.
/// </summary>
/// <param name="timeout">Number of milliseconds to wait</param>
/// <param name="action">The action to execute after the timeout runs out.</param>
/// <returns>A task that finishes when the deboucing is cancelled or the input action has completed (successfully or not). Should be discarded in most scenarios.</returns>
public void Debounce(int timeout, Action action)
{
Cancel();
cts = new CancellationTokenSource();

Task.Delay(timeout, cts.Token).ContinueWith((t) =>
{
try
{
if (t.Status == TaskStatus.RanToCompletion)
{
action();
}
}
catch (Exception ex)
{
logger?.Log("Failed to run debounce action with the following error:");
logger?.Log(ex.ToString());
}
}, TaskScheduler.FromCurrentSynchronizationContext());
}

public void Dispose()
{
Cancel();
}
}
}
5 changes: 0 additions & 5 deletions src/DynamoCoreWpf/ViewModels/Core/WorkspaceViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -606,11 +606,6 @@ public WorkspaceViewModel(WorkspaceModel model, DynamoViewModel dynamoViewModel)
foreach (NoteModel note in Model.Notes) Model_NoteAdded(note);
foreach (AnnotationModel annotation in Model.Annotations) Model_AnnotationAdded(annotation);
foreach (ConnectorModel connector in Model.Connectors) Connectors_ConnectorAdded(connector);

NodeAutoCompleteSearchViewModel = new NodeAutoCompleteSearchViewModel(DynamoViewModel)
{
Visible = true
};

geoScalingViewModel = new GeometryScalingViewModel(this.DynamoViewModel);
geoScalingViewModel.ScaleValue = Convert.ToInt32(Math.Log10(Model.ScaleFactor));
Expand Down
44 changes: 42 additions & 2 deletions src/DynamoCoreWpf/ViewModels/Search/SearchViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
using Dynamo.UI;
using Dynamo.Utilities;
using Dynamo.Wpf.Services;
using Dynamo.Wpf.Utilities;
using Dynamo.Wpf.ViewModels;
using DynamoUtilities;

namespace Dynamo.ViewModels
{
Expand Down Expand Up @@ -73,7 +75,11 @@ public bool BrowserVisibility
set { browserVisibility = value; RaisePropertyChanged("BrowserVisibility"); }
}

private string searchText;
internal int searchDelayTimeout = 150;
// Feature flags activated debouncer for the search UI.
internal ActionDebouncer searchDebouncer = null;

private string searchText = string.Empty;
/// <summary>
/// SearchText property
/// </summary>
Expand All @@ -86,10 +92,28 @@ public string SearchText
set
{
searchText = value;
OnSearchTextChanged(this, EventArgs.Empty);

RaisePropertyChanged("SearchText");
RaisePropertyChanged("BrowserRootCategories");
RaisePropertyChanged("CurrentMode");

// The searchText is set multiple times before the control becomes visible and interactable.
// To prevent any debounces from triggering at some unexpected point before or after the control
// becomes visible, this flag is only set once the searchText value is set by the user
// (unless it somehow gets set somewhere else)
//
// pinzart: The search text is set multiple times with an empty value. Seems sufficient to only use the debouncer
// if we get a non-empty value.
if (!string.IsNullOrEmpty(searchText) && searchDebouncer != null)
{
searchDebouncer.Debounce(searchDelayTimeout, () => OnSearchTextChanged(this, EventArgs.Empty));
}
else
{
// Make sure any previously scheduled debounces are cancelled
searchDebouncer?.Cancel();
OnSearchTextChanged(this, EventArgs.Empty);
}
}
}

Expand Down Expand Up @@ -374,9 +398,19 @@ internal SearchViewModel(DynamoViewModel dynamoViewModel)

iconServices = new IconServices(pathManager);

DynamoFeatureFlagsManager.FlagsRetrieved += TryInitializeDebouncer;

InitializeCore();
}

private void TryInitializeDebouncer()
{
if (DynamoModel.FeatureFlags?.CheckFeatureFlag("searchbar_debounce", false) ?? false)
{
searchDebouncer ??= new ActionDebouncer(dynamoViewModel?.Model?.Logger);
}
}

// Just for tests. Please refer to LibraryTests.cs
internal SearchViewModel(NodeSearchModel model)
{
Expand Down Expand Up @@ -407,6 +441,9 @@ public override void Dispose()
Model.EntryUpdated -= UpdateEntry;
Model.EntryRemoved -= RemoveEntry;

searchDebouncer?.Dispose();
DynamoFeatureFlagsManager.FlagsRetrieved -= TryInitializeDebouncer;

base.Dispose();
}

Expand Down Expand Up @@ -434,6 +471,9 @@ private void InitializeCore()

DefineFullCategoryNames(LibraryRootCategories, "");
InsertClassesIntoTree(LibraryRootCategories);

// If feature flags are already cached, try to initialize the debouncer
TryInitializeDebouncer();
}

private void AddEntry(NodeSearchElement entry)
Expand Down
1 change: 1 addition & 0 deletions src/DynamoCoreWpf/Views/Core/DynamoView.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2081,6 +2081,7 @@ private void WindowClosed(object sender, EventArgs e)
this.dynamoViewModel.RequestEnableShortcutBarItems -= DynamoViewModel_RequestEnableShortcutBarItems;
this.dynamoViewModel.RequestExportWorkSpaceAsImage -= OnRequestExportWorkSpaceAsImage;
this.dynamoViewModel.RequestShorcutToolbarLoaded -= onRequestShorcutToolbarLoaded;
PythonEngineManager.Instance.AvailableEngines.CollectionChanged -= OnPythonEngineListUpdated;

if (homePage != null)
{
Expand Down
4 changes: 3 additions & 1 deletion src/Tools/DynamoFeatureFlags/FeatureFlagsClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,9 @@ internal FeatureFlagsClient(string userkey, string mobileKey = null, bool testMo
AllFlags = LdValue.ObjectFrom(new Dictionary<string,LdValue> { { "TestFlag1",LdValue.Of(true) },
{ "TestFlag2", LdValue.Of("I am a string") },
//in tests we want instancing on so we can test it.
{ "graphics-primitive-instancing", LdValue.Of(true) } });
{ "graphics-primitive-instancing", LdValue.Of(true) },
//in tests we want search debouncing on so we can test it.
{ "searchbar_debounce", LdValue.Of(true) } });
return;
}

Expand Down
Loading

0 comments on commit 5ef1f7b

Please sign in to comment.