From 478db3cada65a18133d47a674717415a8b11cc55 Mon Sep 17 00:00:00 2001 From: erri120 Date: Mon, 16 Dec 2024 15:05:25 +0100 Subject: [PATCH] Remove IImageCache usage from GuidedInstaller --- .../GuidedInstallerUi.cs | 2 +- .../Step/GuidedInstallerStepViewModel.cs | 49 ++++++++++--- src/NexusMods.App.UI/ImagePipelines.cs | 73 +++++++++++++++++-- 3 files changed, 109 insertions(+), 15 deletions(-) diff --git a/src/Games/NexusMods.Games.FOMOD.UI/GuidedInstallerUi.cs b/src/Games/NexusMods.Games.FOMOD.UI/GuidedInstallerUi.cs index 4699d37c6b..786704636c 100644 --- a/src/Games/NexusMods.Games.FOMOD.UI/GuidedInstallerUi.cs +++ b/src/Games/NexusMods.Games.FOMOD.UI/GuidedInstallerUi.cs @@ -120,7 +120,7 @@ private static void SetupStep( Percent progress) { var viewModel = window.ViewModel!; - viewModel.ActiveStepViewModel ??= currentScope.ServiceProvider.GetRequiredService(); + viewModel.ActiveStepViewModel ??= new GuidedInstallerStepViewModel(currentScope.ServiceProvider); var activeStepViewModel = viewModel.ActiveStepViewModel; activeStepViewModel.ModName = viewModel.WindowName; diff --git a/src/Games/NexusMods.Games.FOMOD.UI/Step/GuidedInstallerStepViewModel.cs b/src/Games/NexusMods.Games.FOMOD.UI/Step/GuidedInstallerStepViewModel.cs index 15f27317c3..fdabe8dfe8 100644 --- a/src/Games/NexusMods.Games.FOMOD.UI/Step/GuidedInstallerStepViewModel.cs +++ b/src/Games/NexusMods.Games.FOMOD.UI/Step/GuidedInstallerStepViewModel.cs @@ -29,6 +29,7 @@ public class GuidedInstallerStepViewModel : AViewModel _highlightedOptionImage; public IImage? HighlightedOptionImage => _highlightedOptionImage.Value; @@ -41,8 +42,11 @@ public class GuidedInstallerStepViewModel : AViewModel 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) { @@ -85,6 +109,9 @@ public GuidedInstallerStepViewModel(IImageCache imageCache) var goToPrevCommand = ReactiveCommand.Create(() => { + _imageDisposable?.Dispose(); + _imageDisposable = null; + if (ShowInstallationCompleteScreen) { ShowInstallationCompleteScreen = false; @@ -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); }); } diff --git a/src/NexusMods.App.UI/ImagePipelines.cs b/src/NexusMods.App.UI/ImagePipelines.cs index 3989f9f550..e5a87fc68e 100644 --- a/src/NexusMods.App.UI/ImagePipelines.cs +++ b/src/NexusMods.App.UI/ImagePipelines.cs @@ -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; @@ -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"))); @@ -39,20 +45,35 @@ public static IServiceCollection AddImagePipelines(this IServiceCollection servi .AddKeyedSingleton>( serviceKey: UserAvatarPipelineKey, implementationFactory: static (serviceProvider, _) => CreateUserAvatarPipeline( + httpClient: serviceProvider.GetRequiredService(), connection: serviceProvider.GetRequiredService() ) ) .AddKeyedSingleton>( serviceKey: CollectionTileImagePipelineKey, implementationFactory: static (serviceProvider, _) => CreateCollectionTileImagePipeline( + httpClient: serviceProvider.GetRequiredService(), connection: serviceProvider.GetRequiredService() ) ) .AddKeyedSingleton>( serviceKey: CollectionBackgroundImagePipelineKey, implementationFactory: static (serviceProvider, _) => CreateCollectionBackgroundImagePipeline( + httpClient: serviceProvider.GetRequiredService(), connection: serviceProvider.GetRequiredService() ) + ) + .AddKeyedSingleton>>( + serviceKey: GuidedInstallerRemoteImagePipelineKey, + implementationFactory: static (serviceProvider, _) => CreateGuidedInstallerRemoteImagePipeline( + httpClient: serviceProvider.GetRequiredService() + ) + ) + .AddKeyedSingleton>>( + serviceKey: GuidedInstallerFileImagePipelineKey, + implementationFactory: static (serviceProvider, _) => CreateGuidedInstallerFileImagePipeline( + fileStore: serviceProvider.GetRequiredService() + ) ); } @@ -71,9 +92,21 @@ public static IResourceLoader GetCollectionBackgroundImagePipe return serviceProvider.GetRequiredKeyedService>(serviceKey: CollectionBackgroundImagePipelineKey); } - private static IResourceLoader CreateUserAvatarPipeline(IConnection connection) + public static IResourceLoader> GetGuidedInstallerRemoteImagePipeline(IServiceProvider serviceProvider) + { + return serviceProvider.GetRequiredKeyedService>>(serviceKey: GuidedInstallerRemoteImagePipelineKey); + } + + public static IResourceLoader> GetGuidedInstallerFileImagePipeline(IServiceProvider serviceProvider) { - var pipeline = new HttpLoader(new HttpClient()) + return serviceProvider.GetRequiredKeyedService>>(serviceKey: GuidedInstallerFileImagePipelineKey); + } + + private static IResourceLoader CreateUserAvatarPipeline( + HttpClient httpClient, + IConnection connection) + { + var pipeline = new HttpLoader(httpClient) .ChangeIdentifier, Uri, byte[]>(static tuple => tuple.Item2) .PersistInDb( connection: connection, @@ -93,9 +126,10 @@ private static IResourceLoader CreateUserAvatarPipeline(IConne } private static IResourceLoader CreateCollectionTileImagePipeline( + HttpClient httpClient, IConnection connection) { - var pipeline = new HttpLoader(new HttpClient()) + var pipeline = new HttpLoader(httpClient) .ChangeIdentifier, Uri, byte[]>(static tuple => tuple.Item2) .PersistInDb( connection: connection, @@ -115,9 +149,10 @@ private static IResourceLoader CreateCollectionTileImagePipeli } private static IResourceLoader CreateCollectionBackgroundImagePipeline( + HttpClient httpClient, IConnection connection) { - var pipeline = new HttpLoader(new HttpClient()) + var pipeline = new HttpLoader(httpClient) .ChangeIdentifier, Uri, byte[]>(static tuple => tuple.Item2) .PersistInDb( connection: connection, @@ -135,4 +170,32 @@ private static IResourceLoader CreateCollectionBackgroundImage return pipeline; } + + private static IResourceLoader> CreateGuidedInstallerRemoteImagePipeline(HttpClient httpClient) + { + var pipeline = new HttpLoader(httpClient) + .Decode(decoderType: DecoderType.Skia) + .ToAvaloniaBitmap() + .UseScopedCache( + keyGenerator: static uri => uri, + keyComparer: EqualityComparer.Default, + capacityPartition: new FavorWarmPartition(totalCapacity: 10) + ); + + return pipeline; + } + + private static IResourceLoader> CreateGuidedInstallerFileImagePipeline(IFileStore fileStore) + { + var pipeline = new FileStoreLoader(fileStore) + .Decode(decoderType: DecoderType.Skia) + .ToAvaloniaBitmap() + .UseScopedCache( + keyGenerator: static hash => hash, + keyComparer: EqualityComparer.Default, + capacityPartition: new FavorWarmPartition(totalCapacity: 10) + ); + + return pipeline; + } }