Skip to content

Commit

Permalink
FileCleanup Improvements #633
Browse files Browse the repository at this point in the history
# Main Goal
Improve FileCleanup logics for items > 1000 as before it froze the UI completely
1. Use SelectAllSafe and UnselectAll to do as described
> Improved speed when selecting all items by (from ~140000 ms to  ~170ms) when selecting 25000 files.
2. Use .NET's built-in SIMD calculation for summing all the asset sizes
3. Use batching when injecting files to the ListViewTable source

## PR Status :
- Overall Status : Done
- Commits : Done
- Synced to base (Collapse:main) : Yes
- Build status : OK
- Crashing : No
- Bug found caused by PR : 0

Co-authored-by: Kemal Setya Adhi <[email protected]>
  • Loading branch information
bagusnl and neon-nyan committed Dec 21, 2024
2 parents 4c0f1d7 + afc9dd8 commit 019685f
Show file tree
Hide file tree
Showing 11 changed files with 517 additions and 191 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace CollapseLauncher.Extension
{
/// <summary>
/// Provides extension methods for <see cref="ObservableCollection{T}"/>.
/// </summary>
/// <typeparam name="T">The type of elements in the collection.</typeparam>
internal static class ObservableCollectionExtension<T>
{
/// <summary>
/// Gets the backing list of the specified collection.
/// </summary>
/// <param name="source">The collection to get the backing list from.</param>
/// <returns>A reference to the backing list of the collection.</returns>
[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "items")]
internal static extern ref IList<T> GetBackedCollectionList(Collection<T> source);

/// <summary>
/// Invokes the OnCountPropertyChanged method on the specified observable collection.
/// </summary>
/// <param name="source">The observable collection to invoke the method on.</param>
[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "OnCountPropertyChanged")]
internal static extern void OnCountPropertyChanged(ObservableCollection<T> source);

/// <summary>
/// Invokes the OnIndexerPropertyChanged method on the specified observable collection.
/// </summary>
/// <param name="source">The observable collection to invoke the method on.</param>
[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "OnIndexerPropertyChanged")]
internal static extern void OnIndexerPropertyChanged(ObservableCollection<T> source);

/// <summary>
/// Invokes the OnCollectionChanged method on the specified observable collection.
/// </summary>
/// <param name="source">The observable collection to invoke the method on.</param>
/// <param name="e">The event arguments for the collection changed event.</param>
[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "OnCollectionChanged")]
internal static extern void OnCollectionChanged(ObservableCollection<T> source, NotifyCollectionChangedEventArgs e);

/// <summary>
/// Refreshes all events for the specified observable collection.
/// </summary>
/// <param name="source">The observable collection to invoke the method on.</param>
internal static void RefreshAllEvents(ObservableCollection<T> source)
{
OnCountPropertyChanged(source);
OnIndexerPropertyChanged(source);
OnCollectionChanged(source, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}

/// <summary>
/// Removes a range of items from the specified observable collection quickly.
/// </summary>
/// <param name="sourceRange">The list of items to remove from the target collection.</param>
/// <param name="target">The observable collection from which the items will be removed.</param>
/// <exception cref="InvalidCastException">Thrown when the backing list of the target collection cannot be cast to a List{T}.</exception>
/// <remarks>
/// This method directly manipulates the backing list of the observable collection to remove the specified items,
/// and then fires the necessary property changed and collection changed events to update any bindings.
/// </remarks>
internal static void RemoveItemsFast(List<T> sourceRange, ObservableCollection<T> target)
{
// Get the backed list instance of the collection
List<T> targetBackedList = GetBackedCollectionList(target) as List<T> ?? throw new InvalidCastException();

// Get the count and iterate the reference of the T from the source range
ReadOnlySpan<T> sourceRangeSpan = CollectionsMarshal.AsSpan(sourceRange);
int len = sourceRangeSpan.Length - 1;
for (; len >= 0; len--)
{
// Remove the reference of the item T from the target backed list
_ = targetBackedList.Remove(sourceRangeSpan[len]);
}

// Fire the changes event
RefreshAllEvents(target);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using Microsoft.UI.Input;
using Microsoft.UI.Xaml;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Runtime.CompilerServices;

#nullable enable
namespace CollapseLauncher.Extension
{
internal static partial class UIElementExtensions
{
/// <summary>
/// Set the cursor for the element.
/// </summary>
/// <param name="element">The <seealso cref="UIElement"/> member of an element</param>
/// <param name="inputCursor">The cursor you want to set. Use <see cref="InputSystemCursor.Create"/> to choose the cursor you want to set.</param>
[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "set_ProtectedCursor")]
internal static extern void SetCursor(this UIElement element, InputCursor inputCursor);

/// <summary>
/// Set the cursor for the element.
/// </summary>
/// <param name="element">The <seealso cref="UIElement"/> member of an element</param>
/// <param name="inputCursor">The cursor you want to set. Use <see cref="InputSystemCursor.Create"/> to choose the cursor you want to set.</param>
internal static ref T WithCursor<T>(this T element, InputCursor inputCursor) where T : UIElement
{
element.SetCursor(inputCursor);
return ref Unsafe.AsRef(ref element);
}
}
}
22 changes: 2 additions & 20 deletions CollapseLauncher/Classes/Extension/UIElementExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
using Windows.UI;
using Windows.UI.Text;
using Hi3Helper.SentryHelper;
using System.Collections.ObjectModel;

namespace CollapseLauncher.Extension
{
Expand All @@ -28,27 +29,8 @@ internal class NavigationViewItemLocaleTextProperty
public string LocalePropertyName { get; set; }
}

internal static class UIElementExtensions
internal static partial class UIElementExtensions
{
/// <summary>
/// Set the cursor for the element.
/// </summary>
/// <param name="element">The <seealso cref="UIElement"/> member of an element</param>
/// <param name="inputCursor">The cursor you want to set. Use <see cref="InputSystemCursor.Create"/> to choose the cursor you want to set.</param>
[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "set_ProtectedCursor")]
internal static extern void SetCursor(this UIElement element, InputCursor inputCursor);

/// <summary>
/// Set the cursor for the element.
/// </summary>
/// <param name="element">The <seealso cref="UIElement"/> member of an element</param>
/// <param name="inputCursor">The cursor you want to set. Use <see cref="InputSystemCursor.Create"/> to choose the cursor you want to set.</param>
internal static ref T WithCursor<T>(this T element, InputCursor inputCursor) where T : UIElement
{
element.SetCursor(inputCursor);
return ref Unsafe.AsRef(ref element);
}

#nullable enable
/// <summary>
/// Set the initial navigation view item's locale binding before getting set with <seealso cref="ApplyNavigationViewItemLocaleTextBindings"/>
Expand Down
55 changes: 39 additions & 16 deletions CollapseLauncher/Classes/Helper/Loading/LoadingMessageHelper.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using CollapseLauncher.Extension;
using CollapseLauncher.Helper.Animation;
using CommunityToolkit.WinUI;
using CommunityToolkit.WinUI.Animations;
using Microsoft.UI.Text;
using Microsoft.UI.Xaml;
Expand Down Expand Up @@ -64,43 +65,65 @@ internal static void SetProgressBarState(double maxValue = 100d, bool isProgress
/// Show the loading frame.
/// </summary>
internal static async void ShowLoadingFrame()
{
if (currentMainWindow.LoadingStatusBackgroundGrid.DispatcherQueue.HasThreadAccess)
{
await ShowLoadingFrameInner();
return;
}

await currentMainWindow.LoadingStatusBackgroundGrid.DispatcherQueue.EnqueueAsync(ShowLoadingFrameInner);
}

private static async Task ShowLoadingFrameInner()
{
if (isCurrentlyShow) return;

isCurrentlyShow = true;
currentMainWindow!.LoadingStatusGrid!.Visibility = Visibility.Visible;
isCurrentlyShow = true;
currentMainWindow!.LoadingStatusGrid!.Visibility = Visibility.Visible;
currentMainWindow!.LoadingStatusBackgroundGrid!.Visibility = Visibility.Visible;

TimeSpan duration = TimeSpan.FromSeconds(0.25);

await Task.WhenAll(
AnimationHelper.StartAnimation(currentMainWindow.LoadingStatusBackgroundGrid, duration,
currentMainWindow.LoadingStatusBackgroundGrid.GetElementCompositor()!.CreateScalarKeyFrameAnimation("Opacity", 1, 0)),
AnimationHelper.StartAnimation(currentMainWindow.LoadingStatusGrid, duration,
currentMainWindow.LoadingStatusGrid.GetElementCompositor()!.CreateVector3KeyFrameAnimation("Translation", new Vector3(0,0,currentMainWindow.LoadingStatusGrid.Translation.Z), new Vector3(0, (float)(currentMainWindow.LoadingStatusGrid.ActualHeight + 16), currentMainWindow.LoadingStatusGrid.Translation.Z)),
currentMainWindow.LoadingStatusGrid.GetElementCompositor()!.CreateScalarKeyFrameAnimation("Opacity", 1, 0))
);
AnimationHelper.StartAnimation(currentMainWindow.LoadingStatusBackgroundGrid, duration,
currentMainWindow.LoadingStatusBackgroundGrid.GetElementCompositor()!.CreateScalarKeyFrameAnimation("Opacity", 1, 0)),
AnimationHelper.StartAnimation(currentMainWindow.LoadingStatusGrid, duration,
currentMainWindow.LoadingStatusGrid.GetElementCompositor()!.CreateVector3KeyFrameAnimation("Translation", new Vector3(0, 0, currentMainWindow.LoadingStatusGrid.Translation.Z), new Vector3(0, (float)(currentMainWindow.LoadingStatusGrid.ActualHeight + 16), currentMainWindow.LoadingStatusGrid.Translation.Z)),
currentMainWindow.LoadingStatusGrid.GetElementCompositor()!.CreateScalarKeyFrameAnimation("Opacity", 1, 0))
);
}

/// <summary>
/// Hide the loading frame (also hide the action button).
/// </summary>
internal static async void HideLoadingFrame()
{
if (currentMainWindow.LoadingStatusBackgroundGrid.DispatcherQueue.HasThreadAccess)
{
await HideLoadingFrameInner();
return;
}

await currentMainWindow.LoadingStatusBackgroundGrid.DispatcherQueue.EnqueueAsync(HideLoadingFrameInner);
}

private static async Task HideLoadingFrameInner()
{
if (!isCurrentlyShow) return;

isCurrentlyShow = false;

TimeSpan duration = TimeSpan.FromSeconds(0.25);
await Task.WhenAll(
AnimationHelper.StartAnimation(currentMainWindow.LoadingStatusBackgroundGrid, duration,
currentMainWindow.LoadingStatusBackgroundGrid.GetElementCompositor()!.CreateScalarKeyFrameAnimation("Opacity", 0, 1)),
AnimationHelper.StartAnimation(currentMainWindow.LoadingStatusGrid, duration,
currentMainWindow.LoadingStatusGrid.GetElementCompositor()!.CreateVector3KeyFrameAnimation("Translation", new Vector3(0, (float)(currentMainWindow.LoadingStatusGrid.ActualHeight + 16), currentMainWindow.LoadingStatusGrid.Translation.Z), new Vector3(0,0,currentMainWindow.LoadingStatusGrid.Translation.Z)),
currentMainWindow.LoadingStatusGrid.GetElementCompositor()!.CreateScalarKeyFrameAnimation("Opacity", 0, 1))
);

currentMainWindow.LoadingStatusGrid.Visibility = Visibility.Collapsed;
AnimationHelper.StartAnimation(currentMainWindow.LoadingStatusBackgroundGrid, duration,
currentMainWindow.LoadingStatusBackgroundGrid.GetElementCompositor()!.CreateScalarKeyFrameAnimation("Opacity", 0, 1)),
AnimationHelper.StartAnimation(currentMainWindow.LoadingStatusGrid, duration,
currentMainWindow.LoadingStatusGrid.GetElementCompositor()!.CreateVector3KeyFrameAnimation("Translation", new Vector3(0, (float)(currentMainWindow.LoadingStatusGrid.ActualHeight + 16), currentMainWindow.LoadingStatusGrid.Translation.Z), new Vector3(0, 0, currentMainWindow.LoadingStatusGrid.Translation.Z)),
currentMainWindow.LoadingStatusGrid.GetElementCompositor()!.CreateScalarKeyFrameAnimation("Opacity", 0, 1))
);

currentMainWindow.LoadingStatusGrid.Visibility = Visibility.Collapsed;
currentMainWindow.LoadingStatusBackgroundGrid!.Visibility = Visibility.Collapsed;
HideActionButton();
}
Expand Down
6 changes: 4 additions & 2 deletions CollapseLauncher/Classes/Helper/StreamUtility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,14 @@ private static void EnsureFilePathExist([NotNull] string? path)
*/

internal static FileInfo EnsureNoReadOnly(this FileInfo fileInfo)
=> fileInfo.EnsureNoReadOnly(out _);

internal static FileInfo EnsureNoReadOnly(this FileInfo fileInfo, out bool isFileExist)
{
if (!fileInfo.Exists)
if (!(isFileExist = fileInfo.Exists))
return fileInfo;

fileInfo.IsReadOnly = false;
fileInfo.Refresh();

return fileInfo;
}
Expand Down
Loading

0 comments on commit 019685f

Please sign in to comment.