Optimize archive mounting startup

This commit is contained in:
Marlon 2026-07-16 17:07:48 +02:00
parent 990c38bbcb
commit 0b507d94f8
No known key found for this signature in database
GPG Key ID: 5C65CA190C38792F
6 changed files with 121 additions and 31 deletions

@ -1 +1 @@
Subproject commit cd9fa6d9c7f521370e40045d9515e982fc486bc0
Subproject commit 25d982ecf40620038525ebd7adbd07ed644df2fe

View File

@ -109,6 +109,14 @@ public partial class MainWindow
ApplicationViewModel.InitZlib()
);
var auxiliaryInitialization = Task.WhenAll(
_applicationView.CUE4Parse.InitMappings(),
ApplicationViewModel.InitDetex(),
ApplicationViewModel.InitVgmStream(),
ApplicationViewModel.InitImGuiSettings(newOrUpdated),
UserSettings.Default.DecompileLua ? ApplicationViewModel.InitUnluac() : Task.CompletedTask
);
await _applicationView.CUE4Parse.Initialize();
await _applicationView.AesManager.InitAes();
await _applicationView.UpdateProvider(true);
@ -119,16 +127,12 @@ public partial class MainWindow
await Task.WhenAll(
_applicationView.CUE4Parse.VerifyConsoleVariables(),
_applicationView.CUE4Parse.VerifyOnDemandArchives(),
_applicationView.CUE4Parse.InitMappings(),
ApplicationViewModel.InitDetex(),
ApplicationViewModel.InitVgmStream(),
ApplicationViewModel.InitImGuiSettings(newOrUpdated),
auxiliaryInitialization,
Task.Run(() =>
{
if (UserSettings.Default.DiscordRpc == EDiscordRpc.Always)
_discordHandler.Initialize(_applicationView.GameDisplayName);
}),
UserSettings.Default.DecompileLua ? ApplicationViewModel.InitUnluac() : Task.CompletedTask
})
).ConfigureAwait(false);
#if DEBUG

View File

@ -6,6 +6,7 @@ using System.IO.Compression;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;
using CUE4Parse_Conversion.Textures.BC;
using CUE4Parse.Compression;
using CUE4Parse.Encryption.Aes;
@ -27,6 +28,10 @@ namespace FModel.ViewModels;
public class ApplicationViewModel : ViewModel
{
private readonly object _providerStatusLock = new();
private (string Label, string Prefix)? _pendingProviderStatus;
private bool _providerStatusScheduled;
private EBuildKind _build;
public EBuildKind Build
{
@ -115,13 +120,13 @@ public class ApplicationViewModel : ViewModel
CUE4Parse.Provider.VfsRegistered += (sender, count) =>
{
if (sender is not IAesVfsReader reader) return;
Status.UpdateStatusLabel($"{count} Archives ({reader.Name})", "Registered");
QueueProviderStatus($"{count} Archives ({reader.Name})", "Registered");
CUE4Parse.GameDirectory.Add(reader);
};
CUE4Parse.Provider.VfsMounted += (sender, count) =>
{
if (sender is not IAesVfsReader reader) return;
Status.UpdateStatusLabel($"{count:N0} Packages ({reader.Name})", "Mounted");
QueueProviderStatus($"{count:N0} Packages ({reader.Name})", "Mounted");
CUE4Parse.GameDirectory.Verify(reader);
};
CUE4Parse.Provider.VfsUnmounted += (sender, _) =>
@ -137,6 +142,34 @@ public class ApplicationViewModel : ViewModel
Status.SetStatus(EStatusKind.Ready);
}
private void QueueProviderStatus(string label, string prefix)
{
lock (_providerStatusLock)
{
_pendingProviderStatus = (label, prefix);
if (_providerStatusScheduled)
return;
_providerStatusScheduled = true;
}
_ = Application.Current.Dispatcher.BeginInvoke(PublishProviderStatus, DispatcherPriority.Background);
}
private void PublishProviderStatus()
{
(string Label, string Prefix)? update;
lock (_providerStatusLock)
{
update = _pendingProviderStatus;
_pendingProviderStatus = null;
_providerStatusScheduled = false;
}
if (update is { } status)
Status.UpdateStatusLabel(status.Label, status.Prefix);
}
public DirectorySettings AvoidEmptyGameDirectory(bool bAlreadyLaunched)
{
var gameDirectory = UserSettings.Default.GameDirectory;

View File

@ -334,6 +334,7 @@ public class CUE4ParseViewModel : ViewModel
Provider.Initialize();
GameDirectory.AddLooseFiles(Provider.LooseFileCount);
GameDirectory.FlushPendingChanges();
_wwiseProviderLazy = new Lazy<WwiseProvider>(() => new WwiseProvider(Provider, UserSettings.Default.GameDirectory));
_fmodProviderLazy = new Lazy<FModProvider>(() => new FModProvider(Provider, UserSettings.Default.GameDirectory));
_criWareProviderLazy = new Lazy<CriWareProvider>(() => new CriWareProvider(Provider, UserSettings.Default.GameDirectory));
@ -379,6 +380,7 @@ public class CUE4ParseViewModel : ViewModel
{
Provider.SubmitKeys(aesKeys);
Provider.PostMount();
GameDirectory.FlushPendingChanges();
var aesMax = Provider.RequiredKeys.Count + Provider.Keys.Count;
var archiveMax = Provider.UnloadedVfs.Count + Provider.MountedVfs.Count;

View File

@ -1,10 +1,12 @@
using FModel.Framework;
using System.Collections.ObjectModel;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Windows;
using System.Windows.Data;
using System.Windows.Threading;
using CUE4Parse.Compression;
using CUE4Parse.UE4.IO;
using CUE4Parse.UE4.Objects.Core.Misc;
@ -123,11 +125,19 @@ public class FileItem : ViewModel
public partial class GameDirectoryViewModel : ViewModel
{
public readonly ObservableCollection<FileItem> DirectoryFiles;
private readonly record struct FileItemUpdate(FileItem File, bool IsEnabled, string MountPoint,
int FileCount, bool HasMountInfo);
public readonly RangeObservableCollection<FileItem> DirectoryFiles;
public ICollectionView DirectoryFilesView { get; }
private readonly Regex _hiddenArchives = ArchivesRegex();
private readonly ConcurrentDictionary<IAesVfsReader, FileItem> _filesByReader =
new(ReferenceEqualityComparer.Instance);
private readonly ConcurrentQueue<FileItem> _pendingAdditions = new();
private readonly ConcurrentQueue<FileItemUpdate> _pendingUpdates = new();
private int _publishScheduled;
public GameDirectoryViewModel()
{
@ -147,7 +157,11 @@ public partial class GameDirectoryViewModel : ViewModel
if (!_hiddenArchives.IsMatch(reader.Name)) return;
var fileItem = new FileItem(reader);
Application.Current.Dispatcher.Invoke(() => DirectoryFiles.Add(fileItem));
if (!_filesByReader.TryAdd(reader, fileItem))
return;
_pendingAdditions.Enqueue(fileItem);
SchedulePublish();
}
public void AddLooseFiles(int fileCount)
@ -155,33 +169,64 @@ public partial class GameDirectoryViewModel : ViewModel
if (fileCount < 1)
return;
Application.Current.Dispatcher.Invoke(() =>
{
var looseFilesContainer = DirectoryFiles.FirstOrDefault(x => x.IsLooseFilesContainer);
if (looseFilesContainer is not null)
{
looseFilesContainer.FileCount += fileCount;
}
else
{
DirectoryFiles.Add(new FileItem("Loose Files", fileCount, 0, true));
}
});
_pendingAdditions.Enqueue(new FileItem("Loose Files", fileCount, 0, true));
SchedulePublish();
}
public void Verify(IAesVfsReader reader)
{
if (DirectoryFiles.FirstOrDefault(x => x.Name == reader.Name) is not { } file) return;
if (!_filesByReader.TryGetValue(reader, out var file)) return;
file.IsEnabled = true;
file.MountPoint = reader.MountPoint;
file.FileCount = reader.FileCount;
_pendingUpdates.Enqueue(new FileItemUpdate(file, true, reader.MountPoint, reader.FileCount, true));
SchedulePublish();
}
public void Disable(IAesVfsReader reader)
{
if (DirectoryFiles.FirstOrDefault(x => x.Name == reader.Name) is not { } file) return;
file.IsEnabled = false;
if (!_filesByReader.TryGetValue(reader, out var file)) return;
_pendingUpdates.Enqueue(new FileItemUpdate(file, false, string.Empty, 0, false));
SchedulePublish();
}
public void FlushPendingChanges()
{
if (Application.Current.Dispatcher.CheckAccess())
PublishPendingChanges();
else
Application.Current.Dispatcher.Invoke(PublishPendingChanges);
}
private void SchedulePublish()
{
if (Interlocked.CompareExchange(ref _publishScheduled, 1, 0) != 0)
return;
_ = Application.Current.Dispatcher.BeginInvoke(PublishPendingChanges, DispatcherPriority.Background);
}
private void PublishPendingChanges()
{
var additions = new List<FileItem>();
while (_pendingAdditions.TryDequeue(out var file))
additions.Add(file);
if (additions.Count > 0)
DirectoryFiles.AddRange(additions);
while (_pendingUpdates.TryDequeue(out var update))
{
update.File.IsEnabled = update.IsEnabled;
if (!update.HasMountInfo)
continue;
update.File.MountPoint = update.MountPoint;
update.File.FileCount = update.FileCount;
}
Interlocked.Exchange(ref _publishScheduled, 0);
if (!_pendingAdditions.IsEmpty || !_pendingUpdates.IsEmpty)
SchedulePublish();
}
[GeneratedRegex(@"^(?!global|pakchunk.+(optional|ondemand)\-).+(pak|utoc)$", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.CultureInvariant)]

View File

@ -105,6 +105,12 @@
Value="{Binding CUE4Parse.GameDirectory.DirectoryFilesView, IsAsync=True}" />
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility"
Value="Disabled" />
<Setter Property="ScrollViewer.CanContentScroll"
Value="True" />
<Setter Property="VirtualizingPanel.IsVirtualizing"
Value="True" />
<Setter Property="VirtualizingPanel.VirtualizationMode"
Value="Recycling" />
<Setter Property="adonisExtensions:ScrollViewerExtension.VerticalScrollBarExpansionMode"
Value="NeverExpand" />
<Setter Property="adonisExtensions:ScrollViewerExtension.VerticalScrollBarPlacement"