소스 검색

Preloader fine-tuning and corrections.

 Refactored `Preloader` and related classes: remove `ImageMagick` dependency, update method signatures, improve `PreloaderConfig` structure, enhance thread safety in `PreLoadValue`, and clean up redundant methods and properties.
Ruben 4 달 전
부모
커밋
c77e3aae29

+ 12 - 2
src/PicView.Avalonia/ImageHandling/GetImageModel.cs

@@ -25,8 +25,18 @@ public static class GetImageModel
         { ".svgz", ProcessSvgAsync },
         { ".b64", ProcessBase64Async }
     };
-
-    public static async Task<ImageModel> GetImageModelAsync(FileInfo fileInfo, MagickImage? magickImage = null)
+    
+    /// <inheritdoc cref="GetImageModelAsync(System.IO.FileInfo, MagickImage)"/>
+    public static async Task<ImageModel> GetImageModelAsync(FileInfo fileInfo) =>
+        await GetImageModelAsync(fileInfo, null).ConfigureAwait(false);
+
+    /// <summary>
+    /// Asynchronously retrieves an <see cref="ImageModel"/> instance based on the provided file and optional <see cref="MagickImage"/>.
+    /// </summary>
+    /// <param name="fileInfo">The file information of the image to process.</param>
+    /// <param name="magickImage">An optional <see cref="MagickImage"/> instance. If null, a new instance will be created internally.</param>
+    /// <returns>A task that represents the asynchronous operation. The task result contains the constructed <see cref="ImageModel"/>.</returns>
+    public static async Task<ImageModel> GetImageModelAsync(FileInfo fileInfo, MagickImage? magickImage)
     {
         if (fileInfo is null)
         {

+ 2 - 2
src/PicView.Avalonia/Navigation/ImageIterator.cs

@@ -544,7 +544,7 @@ public class ImageIterator : IAsyncDisposable
 
         if (skip100)
         {
-            if (ImagePaths.Count > PreLoader.MaxCount)
+            if (ImagePaths.Count > PreLoaderConfig.MaxCount)
             {
                 PreLoader.Clear();
             }
@@ -586,7 +586,7 @@ public class ImageIterator : IAsyncDisposable
 
             case NavigateTo.First:
             case NavigateTo.Last:
-                if (ImagePaths.Count > PreLoader.MaxCount)
+                if (ImagePaths.Count > PreLoaderConfig.MaxCount)
                 {
                     PreLoader.Clear();
                 }

+ 5 - 5
src/PicView.Core/Preloading/PreLoaderConfig.cs

@@ -1,13 +1,13 @@
 namespace PicView.Core.Preloading;
 
-public class PreLoaderConfig
+public static class PreLoaderConfig
 {
     public static int PositiveIterations => Settings.Navigation.PositiveIterations;
     public static int NegativeIterations => Settings.Navigation.NegativeIterations;
     
-    // Total items to preload forward and backward, plus the current item and a buffer.
-    public static int MaxCount => PositiveIterations + NegativeIterations + 2; 
+    /// Total items to preload forward and backward.
+    public static int MaxCount => PositiveIterations + NegativeIterations; 
 
-    // Leave a few cores for the UI thread and other system processes to ensure responsiveness.
-    public int MaxParallelism { get; } = Math.Max(1, Environment.ProcessorCount - 3);
+    /// Leave a few cores for the UI thread and other system processes to ensure responsiveness.
+    public static int MaxParallelism { get; } = Math.Max(1, Environment.ProcessorCount - 3);
 }

+ 8 - 5
src/PicView.Core/Preloading/PreloadValue.cs

@@ -7,8 +7,9 @@ namespace PicView.Core.Preloading;
 /// </summary>
 public class PreLoadValue
 {
-    private TaskCompletionSource<bool>? _loadingCompletionSource;
+    private readonly Lock _loadingLock = new();
     private bool _isLoading;
+    private TaskCompletionSource<bool>? _loadingCompletionSource;
 
     /// <summary>
     /// Initializes a new instance of the <see cref="PreLoadValue"/> class.
@@ -22,6 +23,7 @@ public class PreLoadValue
         {
             _loadingCompletionSource = new TaskCompletionSource<bool>();
         }
+
         IsLoading = isLoading;
     }
 
@@ -29,8 +31,6 @@ public class PreLoadValue
     /// Gets or sets the image model.
     /// </summary>
     public ImageModel ImageModel { get; set; }
-    
-    private readonly Lock _loadingLock = new();
 
     /// <summary>
     /// Gets or sets a value indicating whether the image is loading.
@@ -43,10 +43,13 @@ public class PreLoadValue
             lock (_loadingLock) // Ensure atomic operation
             {
                 var wasLoading = _isLoading;
-                if (wasLoading == value) return; // No change, exit early
+                if (wasLoading == value)
+                {
+                    return; // No change, exit early
+                }
 
                 _isLoading = value;
-            
+
                 // Signal completion when loading changes from true to false
                 if (wasLoading && !value && _loadingCompletionSource != null)
                 {

+ 49 - 31
src/PicView.Core/Preloading/Preloader.cs

@@ -1,6 +1,5 @@
 using System.Collections.Concurrent;
 using System.Diagnostics;
-using ImageMagick;
 using PicView.Core.DebugTools;
 using PicView.Core.Models;
 using static System.GC;
@@ -10,7 +9,7 @@ namespace PicView.Core.Preloading;
 /// <summary>
 /// The PreLoader class is responsible for preloading images asynchronously and caching them.
 /// </summary>
-public class PreLoader(Func<FileInfo, MagickImage, Task<ImageModel>> imageModelLoader) : IAsyncDisposable
+public class PreLoader(Func<FileInfo, Task<ImageModel>> imageModelLoader) : IAsyncDisposable
 {
 #if DEBUG
     // ReSharper disable once ConvertToConstant.Local
@@ -18,8 +17,6 @@ public class PreLoader(Func<FileInfo, MagickImage, Task<ImageModel>> imageModelL
     private static bool _showAddRemove = true;
 #endif
 
-    private readonly PreLoaderConfig _config = new();
-
     private readonly Lock _disposeLock = new();
 
     private readonly ConcurrentDictionary<int, PreLoadValue> _preLoadList = new();
@@ -28,20 +25,6 @@ public class PreLoader(Func<FileInfo, MagickImage, Task<ImageModel>> imageModelL
 
     private int _isRunningFlag; // 0 = idle, 1 = running
 
-    /// <summary>
-    ///     Gets the maximum count of preloaded images.
-    /// </summary>
-    public static int MaxCount => PreLoaderConfig.MaxCount;
-
-    /// <summary>
-    ///     Checks if a specific key exists in the preload list.
-    /// </summary>
-    /// <param name="key">The key to check.</param>
-    /// <param name="list">The list of image paths.</param>
-    /// <returns>True if the key exists; otherwise, false.</returns>
-    public bool Contains(int key, List<string> list) =>
-        list != null && key >= 0 && key < list.Count && _preLoadList.ContainsKey(key);
-
     #region Add
 
     /// <summary>
@@ -58,9 +41,12 @@ public class PreLoader(Func<FileInfo, MagickImage, Task<ImageModel>> imageModelL
             return false;
         }
 
-        if (_preLoadList.ContainsKey(index))
+        if (_preLoadList.TryGetValue(index, out var value))
         {
-            return false;
+            if (value.ImageModel?.Image is not null)
+            {
+                return false;
+            }
         }
 
         var imageModel = new ImageModel();
@@ -74,7 +60,7 @@ public class PreLoader(Func<FileInfo, MagickImage, Task<ImageModel>> imageModelL
         try
         {
             var fileInfo = imageModel.FileInfo = new FileInfo(list[index]);
-            imageModel = await imageModelLoader(fileInfo, null!).ConfigureAwait(false);
+            imageModel = await imageModelLoader(fileInfo).ConfigureAwait(false);
             preLoadValue.ImageModel = imageModel;
 #if DEBUG
             if (_showAddRemove)
@@ -96,6 +82,13 @@ public class PreLoader(Func<FileInfo, MagickImage, Task<ImageModel>> imageModelL
         }
     }
 
+    /// <summary>
+    /// Adds a preloaded image model to the preload list at the specified index.
+    /// </summary>
+    /// <param name="index">The index at which to add the image model.</param>
+    /// <param name="list">The list of image paths corresponding to the preload list.</param>
+    /// <param name="imageModel">The image model to preload.</param>
+    /// <returns>True if the image model was successfully added to the preload list; otherwise, false.</returns>
     public bool Add(int index, List<string> list, ImageModel imageModel)
     {
         if (list == null || index < 0 || index >= list.Count)
@@ -120,6 +113,12 @@ public class PreLoader(Func<FileInfo, MagickImage, Task<ImageModel>> imageModelL
 
     #region Refresh and resynchronize
 
+    /// <summary>
+    /// Updates the file information associated with a specific index in the preload list.
+    /// </summary>
+    /// <param name="index">The index of the item to update.</param>
+    /// <param name="fileInfo">The new file information to assign.</param>
+    /// <param name="list">The list of file paths.</param>
     public void RefreshFileInfo(int index, FileInfo fileInfo, List<string> list)
     {
         if (list == null)
@@ -236,7 +235,16 @@ public class PreLoader(Func<FileInfo, MagickImage, Task<ImageModel>> imageModelL
 
     #endregion
 
-    #region Get
+    #region Get and Contains
+
+    /// <summary>
+    ///     Checks if a specific key exists in the preload list.
+    /// </summary>
+    /// <param name="key">The key to check.</param>
+    /// <param name="list">The list of image paths.</param>
+    /// <returns>True if the key exists; otherwise, false.</returns>
+    public bool Contains(int key, List<string> list) =>
+        list != null && key >= 0 && key < list.Count && _preLoadList.ContainsKey(key);
 
     /// <summary>
     ///     Gets the preloaded value for a specific key.
@@ -513,7 +521,7 @@ public class PreLoader(Func<FileInfo, MagickImage, Task<ImageModel>> imageModelL
         var isPreloadListUnderMax = _preLoadList.Count < PreLoaderConfig.MaxCount;
         var options = new ParallelOptions
         {
-            MaxDegreeOfParallelism = _config.MaxParallelism,
+            MaxDegreeOfParallelism = PreLoaderConfig.MaxParallelism,
             CancellationToken = token
         };
 
@@ -539,15 +547,25 @@ public class PreLoader(Func<FileInfo, MagickImage, Task<ImageModel>> imageModelL
 
         async Task LoopAsync(ParallelOptions parallelOptions, bool positive)
         {
-            await Parallel.ForAsync(0, PreLoaderConfig.PositiveIterations, parallelOptions, async (i, _) =>
+            if (positive)
             {
-                token.ThrowIfCancellationRequested();
-                var index = positive ? (nextStartingIndex + i) % count : (prevStartingIndex - i + count) % count;
-                if (await AddAsync(index, list))
-                {
-                    additions.Add(index);
-                }
-            });
+                await Parallel.ForAsync(0, PreLoaderConfig.PositiveIterations, parallelOptions,
+                    async (i, _) => { await AddAddition((nextStartingIndex + i) % count); });
+            }
+            else
+            {
+                await Parallel.ForAsync(0, PreLoaderConfig.NegativeIterations, parallelOptions,
+                    async (i, _) => { await AddAddition((prevStartingIndex - i + count) % count); });
+            }
+        }
+
+        async Task AddAddition(int index)
+        {
+            token.ThrowIfCancellationRequested();
+            if (await AddAsync(index, list))
+            {
+                additions.Add(index);
+            }
         }
 
         void RemoveLoop()