Skip to content

Commit 990c3d7

Browse files
committed
throttle calls to search, closes #52
1 parent 7eb546b commit 990c3d7

2 files changed

Lines changed: 39 additions & 17 deletions

File tree

ArcExplorer/Tools/FileTree.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ private static FileNode CreateFileNode(ArcFile arcFile, ArcFileNode arcNode, Act
220220
// Lazy initialize the shared file list for performance reasons.
221221
List<string> getSharedFiles() => arcFile.GetSharedFilePaths(arcNode, ApplicationSettings.Instance.ArcRegion);
222222

223+
// TODO: Avoid caching this information since it may change when changing regions.
223224
var fileNode = new FileNode(arcNode.FileName, arcNode.Path, arcNode.Extension,
224225
arcNode.IsShared, arcNode.IsRegional, arcNode.Offset, arcNode.CompSize, arcNode.DecompSize, arcNode.IsCompressed,
225226
getSharedFiles);

ArcExplorer/ViewModels/MainWindowViewModel.cs

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
using System;
1010
using System.Collections.Generic;
1111
using System.Linq;
12+
using System.Reactive;
13+
using System.Reactive.Linq;
14+
using System.Threading;
1215
using System.Threading.Tasks;
1316

1417
namespace ArcExplorer.ViewModels
@@ -25,6 +28,8 @@ public AvaloniaList<FileGridItem> Files
2528
}
2629
private AvaloniaList<FileGridItem> files = new AvaloniaList<FileGridItem>();
2730

31+
private readonly object searchLock = new object();
32+
2833
public static Dictionary<Region, string> DescriptionByRegion { get; } = new Dictionary<Region, string>
2934
{
3035
{ Region.Japanese, "Japanese" },
@@ -177,11 +182,7 @@ public string ErrorDescription
177182
public string SearchText
178183
{
179184
get => searchText;
180-
set
181-
{
182-
this.RaiseAndSetIfChanged(ref searchText, value);
183-
SearchArcFile(searchText);
184-
}
185+
set => this.RaiseAndSetIfChanged(ref searchText, value);
185186
}
186187
private string searchText = "";
187188

@@ -201,6 +202,14 @@ public MainWindowViewModel()
201202
{
202203
Serilog.Log.Logger.Error("Failed to open Hashes file {@path}", hashesFile);
203204
}
205+
206+
// Throttle search calls to reduce the chance of searching while the user is still typing.
207+
// Subscribing to an asynchronous method described here:
208+
// https://stackoverflow.com/questions/27618401/what-is-the-best-way-to-call-async-methods-using-reactiveui-throttle
209+
this.WhenAnyValue(x => x.SearchText)
210+
.Throttle(TimeSpan.FromMilliseconds(500))
211+
.SelectMany(SearchArcFileAsync)
212+
.Subscribe();
204213
}
205214

206215
private void LogEventHandled(object? sender, EventArgs e)
@@ -257,19 +266,31 @@ private void InitializeArcFile(string arcPathText)
257266
LoadRootNodes(arcFile);
258267
}
259268

260-
private void SearchArcFile(string searchText)
269+
private async Task<Unit> SearchArcFileAsync(string searchText, CancellationToken cancel)
261270
{
262-
if (arcFile == null)
263-
return;
271+
await Task.Run(() => SearchArcFileThreaded(searchText));
272+
return Unit.Default;
273+
}
264274

265-
if (!string.IsNullOrEmpty(searchText))
266-
{
267-
var nodes = FileTree.SearchAllNodes(arcFile, BackgroundTaskStart, BackgroundTaskReportProgress, BackgroundTaskEnd, searchText, ApplicationSettings.Instance.MergeTrailingSlash);
268-
Files = new AvaloniaList<FileGridItem>(nodes.Select(n => new FileGridItem(n)));
269-
}
270-
else
275+
private void SearchArcFileThreaded(string searchText)
276+
{
277+
// This doesn't make all accesses to the file tree thread safe, but it does prevent multiple searches from running at once.
278+
// This is good enough for calling just this method from multiple threads.
279+
// Calling ARC methods should be inherently thread safe since we don't mutate the ARC struct or its wrapper type.
280+
lock (searchLock)
271281
{
272-
LoadRootNodes(arcFile);
282+
if (arcFile == null)
283+
return;
284+
285+
if (!string.IsNullOrEmpty(searchText))
286+
{
287+
var nodes = FileTree.SearchAllNodes(arcFile, BackgroundTaskStart, BackgroundTaskReportProgress, BackgroundTaskEnd, searchText, ApplicationSettings.Instance.MergeTrailingSlash);
288+
Files = new AvaloniaList<FileGridItem>(nodes.Select(n => new FileGridItem(n)));
289+
}
290+
else
291+
{
292+
LoadRootNodes(arcFile);
293+
}
273294
}
274295
}
275296

@@ -327,7 +348,7 @@ public void ExitFolder()
327348
}
328349

329350
// Go up one level in the file tree.
330-
var parent = FileTree.CreateNodeFromPath(arcFile, parentPath,
351+
var parent = FileTree.CreateNodeFromPath(arcFile, parentPath,
331352
BackgroundTaskStart, BackgroundTaskReportProgress, BackgroundTaskEnd, ApplicationSettings.Instance.MergeTrailingSlash);
332353
if (parent is FolderNode folder)
333354
LoadFolder(folder);
@@ -342,7 +363,7 @@ private void LoadFolder(string? path)
342363
{
343364
if (arcFile != null && path != null)
344365
{
345-
var parent = FileTree.CreateNodeFromPath(arcFile, path,
366+
var parent = FileTree.CreateNodeFromPath(arcFile, path,
346367
BackgroundTaskStart, BackgroundTaskReportProgress, BackgroundTaskEnd, ApplicationSettings.Instance.MergeTrailingSlash);
347368
LoadFolder(parent as FolderNode);
348369
}

0 commit comments

Comments
 (0)