mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
Merge pull request #450 from Flow-Launcher/imageOptimize
No need for the wrapped Lazy instance in image loading.
This commit is contained in:
commit
5aa74121d7
3 changed files with 47 additions and 75 deletions
|
|
@ -2,6 +2,7 @@ using System;
|
|||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Media;
|
||||
|
||||
|
|
@ -26,7 +27,8 @@ namespace Flow.Launcher.Infrastructure.Image
|
|||
private const int MaxCached = 50;
|
||||
public ConcurrentDictionary<string, ImageUsage> Data { get; private set; } = new ConcurrentDictionary<string, ImageUsage>();
|
||||
private const int permissibleFactor = 2;
|
||||
|
||||
private SemaphoreSlim semaphore = new(1, 1);
|
||||
|
||||
public void Initialization(Dictionary<string, int> usage)
|
||||
{
|
||||
foreach (var key in usage.Keys)
|
||||
|
|
@ -60,20 +62,29 @@ namespace Flow.Launcher.Infrastructure.Image
|
|||
}
|
||||
);
|
||||
|
||||
// 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
|
||||
// This is done so that we don't constantly perform this resizing operation and also maintain the image cache size at the same time
|
||||
if (Data.Count > permissibleFactor * MaxCached)
|
||||
SliceExtra();
|
||||
|
||||
async void SliceExtra()
|
||||
{
|
||||
// To delete the images from the data dictionary based on the resizing of the Usage Dictionary.
|
||||
foreach (var key in Data.OrderBy(x => x.Value.usage).Take(Data.Count - MaxCached).Select(x => x.Key))
|
||||
Data.TryRemove(key, out _);
|
||||
// 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
|
||||
// This is done so that we don't constantly perform this resizing operation and also maintain the image cache size at the same time
|
||||
if (Data.Count > permissibleFactor * MaxCached)
|
||||
{
|
||||
await semaphore.WaitAsync().ConfigureAwait(false);
|
||||
// To delete the images from the data dictionary based on the resizing of the Usage Dictionary
|
||||
// Double Check to avoid concurrent remove
|
||||
if (Data.Count > permissibleFactor * MaxCached)
|
||||
foreach (var key in Data.OrderBy(x => x.Value.usage).Take(Data.Count - MaxCached).Select(x => x.Key).ToArray())
|
||||
Data.TryRemove(key, out _);
|
||||
semaphore.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool ContainsKey(string key)
|
||||
{
|
||||
return Data.ContainsKey(key) && Data[key].imageSource != null;
|
||||
return key is not null && Data.ContainsKey(key) && Data[key].imageSource != null;
|
||||
}
|
||||
|
||||
public int CacheSize()
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@
|
|||
<ColumnDefinition Width="0" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Image x:Name="ImageIcon" Width="32" Height="32" HorizontalAlignment="Left"
|
||||
Source="{Binding Image.Value}" />
|
||||
Source="{Binding Image}" />
|
||||
<Grid Margin="5 0 5 0" Grid.Column="1" HorizontalAlignment="Stretch">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
|
|
|
|||
|
|
@ -11,62 +11,12 @@ namespace Flow.Launcher.ViewModel
|
|||
{
|
||||
public class ResultViewModel : BaseModel
|
||||
{
|
||||
public class LazyAsync<T> : Lazy<ValueTask<T>>
|
||||
{
|
||||
private readonly T defaultValue;
|
||||
|
||||
private readonly Action _updateCallback;
|
||||
public new T Value
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!IsValueCreated)
|
||||
{
|
||||
_ = Exercute(); // manually use callback strategy
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
if (!base.Value.IsCompletedSuccessfully)
|
||||
return defaultValue;
|
||||
|
||||
return base.Value.Result;
|
||||
|
||||
// If none of the variables captured by the local function are captured by other lambdas,
|
||||
// the compiler can avoid heap allocations.
|
||||
async ValueTask Exercute()
|
||||
{
|
||||
await base.Value.ConfigureAwait(false);
|
||||
_updateCallback();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
public LazyAsync(Func<ValueTask<T>> factory, T defaultValue, Action updateCallback) : base(factory)
|
||||
{
|
||||
if (defaultValue != null)
|
||||
{
|
||||
this.defaultValue = defaultValue;
|
||||
}
|
||||
|
||||
_updateCallback = updateCallback;
|
||||
}
|
||||
}
|
||||
|
||||
public ResultViewModel(Result result, Settings settings)
|
||||
{
|
||||
if (result != null)
|
||||
{
|
||||
Result = result;
|
||||
|
||||
Image = new LazyAsync<ImageSource>(
|
||||
SetImage,
|
||||
ImageLoader.DefaultImage,
|
||||
() =>
|
||||
{
|
||||
OnPropertyChanged(nameof(Image));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Settings = settings;
|
||||
}
|
||||
|
|
@ -85,44 +35,55 @@ namespace Flow.Launcher.ViewModel
|
|||
? Result.SubTitle
|
||||
: Result.SubTitleToolTip;
|
||||
|
||||
public LazyAsync<ImageSource> Image { get; set; }
|
||||
private volatile bool ImageLoaded;
|
||||
|
||||
private async ValueTask<ImageSource> SetImage()
|
||||
private ImageSource image = ImageLoader.DefaultImage;
|
||||
|
||||
public ImageSource Image
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!ImageLoaded)
|
||||
{
|
||||
ImageLoaded = true;
|
||||
_ = LoadImageAsync();
|
||||
}
|
||||
return image;
|
||||
}
|
||||
private set => image = value;
|
||||
}
|
||||
private async ValueTask LoadImageAsync()
|
||||
{
|
||||
var imagePath = Result.IcoPath;
|
||||
if (string.IsNullOrEmpty(imagePath) && Result.Icon != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Result.Icon();
|
||||
image = Result.Icon();
|
||||
return;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Exception($"|ResultViewModel.Image|IcoPath is empty and exception when calling Icon() for result <{Result.Title}> of plugin <{Result.PluginDirectory}>", e);
|
||||
return ImageLoader.DefaultImage;
|
||||
}
|
||||
}
|
||||
|
||||
if (ImageLoader.CacheContainImage(imagePath))
|
||||
{
|
||||
// will get here either when icoPath has value\icon delegate is null\when had exception in delegate
|
||||
return ImageLoader.Load(imagePath);
|
||||
image = ImageLoader.Load(imagePath);
|
||||
return;
|
||||
}
|
||||
|
||||
return await Task.Run(() => ImageLoader.Load(imagePath));
|
||||
// We need to modify the property not field here to trigger the OnPropertyChanged event
|
||||
Image = await Task.Run(() => ImageLoader.Load(imagePath)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public Result Result { get; }
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
var r = obj as ResultViewModel;
|
||||
if (r != null)
|
||||
{
|
||||
return Result.Equals(r.Result);
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return obj is ResultViewModel r && Result.Equals(r.Result);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
|
|
|
|||
Loading…
Reference in a new issue