Skip to content

Commit

Permalink
Remove IImageCache usage from GuidedInstaller
Browse files Browse the repository at this point in the history
  • Loading branch information
erri120 committed Dec 16, 2024
1 parent e36e0fb commit 478db3c
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 15 deletions.
2 changes: 1 addition & 1 deletion src/Games/NexusMods.Games.FOMOD.UI/GuidedInstallerUi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ private static void SetupStep(
Percent progress)
{
var viewModel = window.ViewModel!;
viewModel.ActiveStepViewModel ??= currentScope.ServiceProvider.GetRequiredService<IGuidedInstallerStepViewModel>();
viewModel.ActiveStepViewModel ??= new GuidedInstallerStepViewModel(currentScope.ServiceProvider);

var activeStepViewModel = viewModel.ActiveStepViewModel;
activeStepViewModel.ModName = viewModel.WindowName;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public class GuidedInstallerStepViewModel : AViewModel<IGuidedInstallerStepViewM

[Reactive] public IGuidedInstallerOptionViewModel? HighlightedOptionViewModel { get; set; }

private IDisposable? _imageDisposable;
private readonly ObservableAsPropertyHelper<IImage?> _highlightedOptionImage;
public IImage? HighlightedOptionImage => _highlightedOptionImage.Value;

Expand All @@ -41,8 +42,11 @@ public class GuidedInstallerStepViewModel : AViewModel<IGuidedInstallerStepViewM

private Percent _previousProgress = Percent.Zero;

public GuidedInstallerStepViewModel(IImageCache imageCache)
public GuidedInstallerStepViewModel(IServiceProvider serviceProvider)
{
var remoteImagePipeline = ImagePipelines.GetGuidedInstallerRemoteImagePipeline(serviceProvider);
var fileImagePipeline = ImagePipelines.GetGuidedInstallerFileImagePipeline(serviceProvider);

_groupsSource
.Connect()
.Bind(out _groups)
Expand All @@ -54,21 +58,41 @@ public GuidedInstallerStepViewModel(IImageCache imageCache)
.WhereNotNull()
.Select(optionVM => optionVM.Option.Image)
.WhereNotNull()
.Select(optionImage =>
.OffUi()
.SelectMany(async optionImage =>
{
try
{
if (optionImage.TryPickT0(out var uri, out var imageStoredFile))
{
return await remoteImagePipeline.LoadResourceAsync(uri, CancellationToken.None);
}
else
{
return await fileImagePipeline.LoadResourceAsync(imageStoredFile.FileHash, CancellationToken.None);
}
}
catch (Exception e)
{
return null;
}
})
.Select(static resource => resource?.Data)
.Do(lifetime =>
{
return optionImage.Match(
f0: uri => new ImageIdentifier(uri),
f1: imageStoredFile => new ImageIdentifier(imageStoredFile.FileHash)
);
_imageDisposable?.Dispose();
_imageDisposable = lifetime;
})
.OffUi()
.SelectMany(imageCache.GetImage)
.Select(static lifetime => lifetime?.Value)
.WhereNotNull()
.OnUI()
.ToProperty(this, vm => vm.HighlightedOptionImage);

var goToNextCommand = ReactiveCommand.Create(() =>
{
_imageDisposable?.Dispose();
_imageDisposable = null;

// NOTE(erri120): On the last step, we don't set the result but instead show a "installation complete"-screen.
if (InstallationStep!.HasNextStep || ShowInstallationCompleteScreen)
{
Expand All @@ -85,6 +109,9 @@ public GuidedInstallerStepViewModel(IImageCache imageCache)

var goToPrevCommand = ReactiveCommand.Create(() =>
{
_imageDisposable?.Dispose();
_imageDisposable = null;

if (ShowInstallationCompleteScreen)
{
ShowInstallationCompleteScreen = false;
Expand Down Expand Up @@ -180,7 +207,11 @@ public GuidedInstallerStepViewModel(IImageCache imageCache)
.BindToVM(this, vm => vm.FooterStepperViewModel.Progress)
.DisposeWith(disposables);

Disposable.Create(() => _highlightedOptionImage.Dispose()).DisposeWith(disposables);
Disposable.Create(() =>
{
_imageDisposable?.Dispose();
_highlightedOptionImage.Dispose();
}).DisposeWith(disposables);
});
}

Expand Down
73 changes: 68 additions & 5 deletions src/NexusMods.App.UI/ImagePipelines.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using BitFaster.Caching;
using BitFaster.Caching.Lru;
using Microsoft.Extensions.DependencyInjection;
using NexusMods.Abstractions.IO;
using NexusMods.Abstractions.NexusModsLibrary.Models;
using NexusMods.Abstractions.Resources;
using NexusMods.Abstractions.Resources.Caching;
using NexusMods.Abstractions.Resources.DB;
using NexusMods.Abstractions.Resources.IO;
using NexusMods.Abstractions.Resources.Resilience;
Expand All @@ -13,12 +17,14 @@

namespace NexusMods.App.UI;

internal static class ImagePipelines
public static class ImagePipelines
{
private const byte ImagePartitionId = 10;
private const string CollectionTileImagePipelineKey = nameof(CollectionTileImagePipelineKey);
private const string CollectionBackgroundImagePipelineKey = nameof(CollectionBackgroundImagePipelineKey);
private const string UserAvatarPipelineKey = nameof(UserAvatarPipelineKey);
private const string GuidedInstallerRemoteImagePipelineKey = nameof(GuidedInstallerRemoteImagePipelineKey);
private const string GuidedInstallerFileImagePipelineKey = nameof(GuidedInstallerFileImagePipelineKey);

private static readonly Bitmap CollectionTileFallback = new(AssetLoader.Open(new Uri("avares://NexusMods.App.UI/Assets/collection-tile-fallback.png")));
private static readonly Bitmap CollectionBackgroundFallback = new(AssetLoader.Open(new Uri("avares://NexusMods.App.UI/Assets/black-box.png")));
Expand All @@ -39,20 +45,35 @@ public static IServiceCollection AddImagePipelines(this IServiceCollection servi
.AddKeyedSingleton<IResourceLoader<EntityId, Bitmap>>(
serviceKey: UserAvatarPipelineKey,
implementationFactory: static (serviceProvider, _) => CreateUserAvatarPipeline(
httpClient: serviceProvider.GetRequiredService<HttpClient>(),
connection: serviceProvider.GetRequiredService<IConnection>()
)
)
.AddKeyedSingleton<IResourceLoader<EntityId, Bitmap>>(
serviceKey: CollectionTileImagePipelineKey,
implementationFactory: static (serviceProvider, _) => CreateCollectionTileImagePipeline(
httpClient: serviceProvider.GetRequiredService<HttpClient>(),
connection: serviceProvider.GetRequiredService<IConnection>()
)
)
.AddKeyedSingleton<IResourceLoader<EntityId, Bitmap>>(
serviceKey: CollectionBackgroundImagePipelineKey,
implementationFactory: static (serviceProvider, _) => CreateCollectionBackgroundImagePipeline(
httpClient: serviceProvider.GetRequiredService<HttpClient>(),
connection: serviceProvider.GetRequiredService<IConnection>()
)
)
.AddKeyedSingleton<IResourceLoader<Uri, Lifetime<Bitmap>>>(
serviceKey: GuidedInstallerRemoteImagePipelineKey,
implementationFactory: static (serviceProvider, _) => CreateGuidedInstallerRemoteImagePipeline(
httpClient: serviceProvider.GetRequiredService<HttpClient>()
)
)
.AddKeyedSingleton<IResourceLoader<Hash, Lifetime<Bitmap>>>(
serviceKey: GuidedInstallerFileImagePipelineKey,
implementationFactory: static (serviceProvider, _) => CreateGuidedInstallerFileImagePipeline(
fileStore: serviceProvider.GetRequiredService<IFileStore>()
)
);
}

Expand All @@ -71,9 +92,21 @@ public static IResourceLoader<EntityId, Bitmap> GetCollectionBackgroundImagePipe
return serviceProvider.GetRequiredKeyedService<IResourceLoader<EntityId, Bitmap>>(serviceKey: CollectionBackgroundImagePipelineKey);
}

private static IResourceLoader<EntityId, Bitmap> CreateUserAvatarPipeline(IConnection connection)
public static IResourceLoader<Uri, Lifetime<Bitmap>> GetGuidedInstallerRemoteImagePipeline(IServiceProvider serviceProvider)
{
return serviceProvider.GetRequiredKeyedService<IResourceLoader<Uri, Lifetime<Bitmap>>>(serviceKey: GuidedInstallerRemoteImagePipelineKey);
}

public static IResourceLoader<Hash, Lifetime<Bitmap>> GetGuidedInstallerFileImagePipeline(IServiceProvider serviceProvider)
{
var pipeline = new HttpLoader(new HttpClient())
return serviceProvider.GetRequiredKeyedService<IResourceLoader<Hash, Lifetime<Bitmap>>>(serviceKey: GuidedInstallerFileImagePipelineKey);
}

private static IResourceLoader<EntityId, Bitmap> CreateUserAvatarPipeline(
HttpClient httpClient,
IConnection connection)
{
var pipeline = new HttpLoader(httpClient)
.ChangeIdentifier<ValueTuple<EntityId, Uri>, Uri, byte[]>(static tuple => tuple.Item2)
.PersistInDb(
connection: connection,
Expand All @@ -93,9 +126,10 @@ private static IResourceLoader<EntityId, Bitmap> CreateUserAvatarPipeline(IConne
}

private static IResourceLoader<EntityId, Bitmap> CreateCollectionTileImagePipeline(
HttpClient httpClient,
IConnection connection)
{
var pipeline = new HttpLoader(new HttpClient())
var pipeline = new HttpLoader(httpClient)
.ChangeIdentifier<ValueTuple<EntityId, Uri>, Uri, byte[]>(static tuple => tuple.Item2)
.PersistInDb(
connection: connection,
Expand All @@ -115,9 +149,10 @@ private static IResourceLoader<EntityId, Bitmap> CreateCollectionTileImagePipeli
}

private static IResourceLoader<EntityId, Bitmap> CreateCollectionBackgroundImagePipeline(
HttpClient httpClient,
IConnection connection)
{
var pipeline = new HttpLoader(new HttpClient())
var pipeline = new HttpLoader(httpClient)
.ChangeIdentifier<ValueTuple<EntityId, Uri>, Uri, byte[]>(static tuple => tuple.Item2)
.PersistInDb(
connection: connection,
Expand All @@ -135,4 +170,32 @@ private static IResourceLoader<EntityId, Bitmap> CreateCollectionBackgroundImage

return pipeline;
}

private static IResourceLoader<Uri, Lifetime<Bitmap>> CreateGuidedInstallerRemoteImagePipeline(HttpClient httpClient)
{
var pipeline = new HttpLoader(httpClient)
.Decode(decoderType: DecoderType.Skia)
.ToAvaloniaBitmap()
.UseScopedCache(
keyGenerator: static uri => uri,
keyComparer: EqualityComparer<Uri>.Default,
capacityPartition: new FavorWarmPartition(totalCapacity: 10)
);

return pipeline;
}

private static IResourceLoader<Hash, Lifetime<Bitmap>> CreateGuidedInstallerFileImagePipeline(IFileStore fileStore)
{
var pipeline = new FileStoreLoader(fileStore)
.Decode(decoderType: DecoderType.Skia)
.ToAvaloniaBitmap()
.UseScopedCache(
keyGenerator: static hash => hash,
keyComparer: EqualityComparer<Hash>.Default,
capacityPartition: new FavorWarmPartition(totalCapacity: 10)
);

return pipeline;
}
}

0 comments on commit 478db3c

Please sign in to comment.