Skip to content

Commit 5aa7412

Browse files
authored
Merge pull request #450 from Flow-Launcher/imageOptimize
No need for the wrapped Lazy instance in image loading.
2 parents c344dea + 3623e11 commit 5aa7412

File tree

3 files changed

+47
-75
lines changed

3 files changed

+47
-75
lines changed

Flow.Launcher.Infrastructure/Image/ImageCache.cs

+19-8
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Concurrent;
33
using System.Collections.Generic;
44
using System.Linq;
5+
using System.Threading;
56
using System.Threading.Tasks;
67
using System.Windows.Media;
78

@@ -26,7 +27,8 @@ public class ImageCache
2627
private const int MaxCached = 50;
2728
public ConcurrentDictionary<string, ImageUsage> Data { get; private set; } = new ConcurrentDictionary<string, ImageUsage>();
2829
private const int permissibleFactor = 2;
29-
30+
private SemaphoreSlim semaphore = new(1, 1);
31+
3032
public void Initialization(Dictionary<string, int> usage)
3133
{
3234
foreach (var key in usage.Keys)
@@ -60,20 +62,29 @@ public ImageSource this[string path]
6062
}
6163
);
6264

63-
// To prevent the dictionary from drastically increasing in size by caching images, the dictionary size is not allowed to grow more than the permissibleFactor * maxCached size
64-
// This is done so that we don't constantly perform this resizing operation and also maintain the image cache size at the same time
65-
if (Data.Count > permissibleFactor * MaxCached)
65+
SliceExtra();
66+
67+
async void SliceExtra()
6668
{
67-
// To delete the images from the data dictionary based on the resizing of the Usage Dictionary.
68-
foreach (var key in Data.OrderBy(x => x.Value.usage).Take(Data.Count - MaxCached).Select(x => x.Key))
69-
Data.TryRemove(key, out _);
69+
// To prevent the dictionary from drastically increasing in size by caching images, the dictionary size is not allowed to grow more than the permissibleFactor * maxCached size
70+
// This is done so that we don't constantly perform this resizing operation and also maintain the image cache size at the same time
71+
if (Data.Count > permissibleFactor * MaxCached)
72+
{
73+
await semaphore.WaitAsync().ConfigureAwait(false);
74+
// To delete the images from the data dictionary based on the resizing of the Usage Dictionary
75+
// Double Check to avoid concurrent remove
76+
if (Data.Count > permissibleFactor * MaxCached)
77+
foreach (var key in Data.OrderBy(x => x.Value.usage).Take(Data.Count - MaxCached).Select(x => x.Key).ToArray())
78+
Data.TryRemove(key, out _);
79+
semaphore.Release();
80+
}
7081
}
7182
}
7283
}
7384

7485
public bool ContainsKey(string key)
7586
{
76-
return Data.ContainsKey(key) && Data[key].imageSource != null;
87+
return key is not null && Data.ContainsKey(key) && Data[key].imageSource != null;
7788
}
7889

7990
public int CacheSize()

Flow.Launcher/ResultListBox.xaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
<ColumnDefinition Width="0" />
4343
</Grid.ColumnDefinitions>
4444
<Image x:Name="ImageIcon" Width="32" Height="32" HorizontalAlignment="Left"
45-
Source="{Binding Image.Value}" />
45+
Source="{Binding Image}" />
4646
<Grid Margin="5 0 5 0" Grid.Column="1" HorizontalAlignment="Stretch">
4747
<Grid.RowDefinitions>
4848
<RowDefinition />

Flow.Launcher/ViewModel/ResultViewModel.cs

+27-66
Original file line numberDiff line numberDiff line change
@@ -11,62 +11,12 @@ namespace Flow.Launcher.ViewModel
1111
{
1212
public class ResultViewModel : BaseModel
1313
{
14-
public class LazyAsync<T> : Lazy<ValueTask<T>>
15-
{
16-
private readonly T defaultValue;
17-
18-
private readonly Action _updateCallback;
19-
public new T Value
20-
{
21-
get
22-
{
23-
if (!IsValueCreated)
24-
{
25-
_ = Exercute(); // manually use callback strategy
26-
27-
return defaultValue;
28-
}
29-
30-
if (!base.Value.IsCompletedSuccessfully)
31-
return defaultValue;
32-
33-
return base.Value.Result;
34-
35-
// If none of the variables captured by the local function are captured by other lambdas,
36-
// the compiler can avoid heap allocations.
37-
async ValueTask Exercute()
38-
{
39-
await base.Value.ConfigureAwait(false);
40-
_updateCallback();
41-
}
42-
43-
}
44-
}
45-
public LazyAsync(Func<ValueTask<T>> factory, T defaultValue, Action updateCallback) : base(factory)
46-
{
47-
if (defaultValue != null)
48-
{
49-
this.defaultValue = defaultValue;
50-
}
51-
52-
_updateCallback = updateCallback;
53-
}
54-
}
55-
5614
public ResultViewModel(Result result, Settings settings)
5715
{
5816
if (result != null)
5917
{
6018
Result = result;
61-
62-
Image = new LazyAsync<ImageSource>(
63-
SetImage,
64-
ImageLoader.DefaultImage,
65-
() =>
66-
{
67-
OnPropertyChanged(nameof(Image));
68-
});
69-
}
19+
}
7020

7121
Settings = settings;
7222
}
@@ -85,44 +35,55 @@ public ResultViewModel(Result result, Settings settings)
8535
? Result.SubTitle
8636
: Result.SubTitleToolTip;
8737

88-
public LazyAsync<ImageSource> Image { get; set; }
38+
private volatile bool ImageLoaded;
39+
40+
private ImageSource image = ImageLoader.DefaultImage;
8941

90-
private async ValueTask<ImageSource> SetImage()
42+
public ImageSource Image
43+
{
44+
get
45+
{
46+
if (!ImageLoaded)
47+
{
48+
ImageLoaded = true;
49+
_ = LoadImageAsync();
50+
}
51+
return image;
52+
}
53+
private set => image = value;
54+
}
55+
private async ValueTask LoadImageAsync()
9156
{
9257
var imagePath = Result.IcoPath;
9358
if (string.IsNullOrEmpty(imagePath) && Result.Icon != null)
9459
{
9560
try
9661
{
97-
return Result.Icon();
62+
image = Result.Icon();
63+
return;
9864
}
9965
catch (Exception e)
10066
{
10167
Log.Exception($"|ResultViewModel.Image|IcoPath is empty and exception when calling Icon() for result <{Result.Title}> of plugin <{Result.PluginDirectory}>", e);
102-
return ImageLoader.DefaultImage;
10368
}
10469
}
10570

10671
if (ImageLoader.CacheContainImage(imagePath))
72+
{
10773
// will get here either when icoPath has value\icon delegate is null\when had exception in delegate
108-
return ImageLoader.Load(imagePath);
74+
image = ImageLoader.Load(imagePath);
75+
return;
76+
}
10977

110-
return await Task.Run(() => ImageLoader.Load(imagePath));
78+
// We need to modify the property not field here to trigger the OnPropertyChanged event
79+
Image = await Task.Run(() => ImageLoader.Load(imagePath)).ConfigureAwait(false);
11180
}
11281

11382
public Result Result { get; }
11483

11584
public override bool Equals(object obj)
11685
{
117-
var r = obj as ResultViewModel;
118-
if (r != null)
119-
{
120-
return Result.Equals(r.Result);
121-
}
122-
else
123-
{
124-
return false;
125-
}
86+
return obj is ResultViewModel r && Result.Equals(r.Result);
12687
}
12788

12889
public override int GetHashCode()

0 commit comments

Comments
 (0)