mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-13 12:14:17 +01:00
Make saving files and recalculating effective files threaded/once per frame.
This commit is contained in:
parent
b8210e094b
commit
67de0ccf45
14 changed files with 147 additions and 42 deletions
|
|
@ -177,7 +177,10 @@ public partial class ModCollection
|
|||
|
||||
|
||||
public void SaveActiveCollections()
|
||||
=> SaveActiveCollections( Default.Name, Current.Name, Characters.Select( kvp => ( kvp.Key, kvp.Value.Name ) ) );
|
||||
{
|
||||
Penumbra.Framework.RegisterDelayed( nameof( SaveActiveCollections ),
|
||||
() => SaveActiveCollections( Default.Name, Current.Name, Characters.Select( kvp => ( kvp.Key, kvp.Value.Name ) ) ) );
|
||||
}
|
||||
|
||||
internal static void SaveActiveCollections( string def, string current, IEnumerable< (string, string) > characters )
|
||||
{
|
||||
|
|
@ -203,7 +206,7 @@ public partial class ModCollection
|
|||
|
||||
j.WriteEndObject();
|
||||
j.WriteEndObject();
|
||||
PluginLog.Verbose( "Active Collections saved." );
|
||||
PluginLog.Verbose( "Active Collections saved." );
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using Dalamud.Logging;
|
||||
using OtterGui.Classes;
|
||||
using Penumbra.GameData.ByteString;
|
||||
|
|
@ -61,9 +62,6 @@ public partial class ModCollection
|
|||
internal IReadOnlyDictionary< Utf8GamePath, FullPath > ResolvedFiles
|
||||
=> _cache?.ResolvedFiles ?? new Dictionary< Utf8GamePath, FullPath >();
|
||||
|
||||
internal IReadOnlySet< FullPath > MissingFiles
|
||||
=> _cache?.MissingFiles ?? new HashSet< FullPath >();
|
||||
|
||||
internal IReadOnlyDictionary< string, object? > ChangedItems
|
||||
=> _cache?.ChangedItems ?? new Dictionary< string, object? >();
|
||||
|
||||
|
|
@ -76,6 +74,10 @@ public partial class ModCollection
|
|||
// Update the effective file list for the given cache.
|
||||
// Creates a cache if necessary.
|
||||
public void CalculateEffectiveFileList( bool withMetaManipulations, bool reloadDefault )
|
||||
=> Penumbra.Framework.RegisterImportant( nameof( CalculateEffectiveFileList ) + Name,
|
||||
() => CalculateEffectiveFileListInternal( withMetaManipulations, reloadDefault ) );
|
||||
|
||||
private void CalculateEffectiveFileListInternal( bool withMetaManipulations, bool reloadDefault )
|
||||
{
|
||||
// Skip the empty collection.
|
||||
if( Index == 0 )
|
||||
|
|
@ -83,7 +85,7 @@ public partial class ModCollection
|
|||
return;
|
||||
}
|
||||
|
||||
PluginLog.Debug( "Recalculating effective file list for {CollectionName:l} [{WithMetaManipulations}] [{ReloadDefault}]", Name,
|
||||
PluginLog.Debug( "[{Thread}] Recalculating effective file list for {CollectionName:l} [{WithMetaManipulations}] [{ReloadDefault}]", Thread.CurrentThread.ManagedThreadId, Name,
|
||||
withMetaManipulations, reloadDefault );
|
||||
_cache ??= new Cache( this );
|
||||
_cache.CalculateEffectiveFileList( withMetaManipulations );
|
||||
|
|
@ -92,6 +94,7 @@ public partial class ModCollection
|
|||
SetFiles();
|
||||
Penumbra.ResidentResources.Reload();
|
||||
}
|
||||
PluginLog.Debug( "[{Thread}] Recalculation of effective file list for {CollectionName:l} finished.", Thread.CurrentThread.ManagedThreadId, Name);
|
||||
}
|
||||
|
||||
// Set Metadata files.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Dalamud.Logging;
|
||||
using Dalamud.Utility;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.Meta.Manager;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
|
|
@ -15,15 +18,12 @@ public partial class ModCollection
|
|||
// It will only be setup if a collection gets activated in any way.
|
||||
private class Cache : IDisposable
|
||||
{
|
||||
// Shared caches to avoid allocations.
|
||||
private static readonly Dictionary< Utf8GamePath, FileRegister > RegisteredFiles = new(1024);
|
||||
private static readonly Dictionary< MetaManipulation, FileRegister > RegisteredManipulations = new(1024);
|
||||
private static readonly List< ModSettings? > ResolvedSettings = new(128);
|
||||
private readonly Dictionary< Utf8GamePath, FileRegister > _registeredFiles = new();
|
||||
private readonly Dictionary< MetaManipulation, FileRegister > _registeredManipulations = new();
|
||||
|
||||
private readonly ModCollection _collection;
|
||||
private readonly SortedList< string, object? > _changedItems = new();
|
||||
public readonly Dictionary< Utf8GamePath, FullPath > ResolvedFiles = new();
|
||||
public readonly HashSet< FullPath > MissingFiles = new();
|
||||
public readonly MetaManager MetaManipulations;
|
||||
public ConflictCache Conflicts = new();
|
||||
|
||||
|
|
@ -96,14 +96,10 @@ public partial class ModCollection
|
|||
// Clear all local and global caches to prepare for recomputation.
|
||||
private void ClearStorageAndPrepare()
|
||||
{
|
||||
ResolvedFiles.Clear();
|
||||
MissingFiles.Clear();
|
||||
RegisteredFiles.Clear();
|
||||
_changedItems.Clear();
|
||||
ResolvedSettings.Clear();
|
||||
_registeredFiles.EnsureCapacity( 2 * ResolvedFiles.Count );
|
||||
ResolvedFiles.Clear();
|
||||
Conflicts.ClearFileConflicts();
|
||||
// Obtains actual settings for this collection with all inheritances.
|
||||
ResolvedSettings.AddRange( _collection.ActualSettings );
|
||||
}
|
||||
|
||||
// Recalculate all file changes from current settings. Include all fixed custom redirects.
|
||||
|
|
@ -113,7 +109,7 @@ public partial class ModCollection
|
|||
ClearStorageAndPrepare();
|
||||
if( withManipulations )
|
||||
{
|
||||
RegisteredManipulations.Clear();
|
||||
_registeredManipulations.EnsureCapacity( 2 * MetaManipulations.Count );
|
||||
MetaManipulations.Reset();
|
||||
}
|
||||
|
||||
|
|
@ -125,6 +121,10 @@ public partial class ModCollection
|
|||
|
||||
AddMetaFiles();
|
||||
++RecomputeCounter;
|
||||
_registeredFiles.Clear();
|
||||
_registeredFiles.TrimExcess();
|
||||
_registeredManipulations.Clear();
|
||||
_registeredManipulations.TrimExcess();
|
||||
}
|
||||
|
||||
// Identify and record all manipulated objects for this entire collection.
|
||||
|
|
@ -158,15 +158,15 @@ public partial class ModCollection
|
|||
// Inside the same mod, conflicts are not recorded.
|
||||
private void AddFile( Utf8GamePath path, FullPath file, FileRegister priority )
|
||||
{
|
||||
if( RegisteredFiles.TryGetValue( path, out var register ) )
|
||||
if( _registeredFiles.TryGetValue( path, out var register ) )
|
||||
{
|
||||
if( register.SameMod( priority, out var less ) )
|
||||
{
|
||||
Conflicts.AddConflict( register.ModIdx, priority.ModIdx, register.ModPriority, priority.ModPriority, path );
|
||||
if( less )
|
||||
{
|
||||
RegisteredFiles[ path ] = priority;
|
||||
ResolvedFiles[ path ] = file;
|
||||
_registeredFiles[ path ] = priority;
|
||||
ResolvedFiles[ path ] = file;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
@ -176,14 +176,14 @@ public partial class ModCollection
|
|||
// Do not add conflicts.
|
||||
if( less )
|
||||
{
|
||||
RegisteredFiles[ path ] = priority;
|
||||
ResolvedFiles[ path ] = file;
|
||||
_registeredFiles[ path ] = priority;
|
||||
ResolvedFiles[ path ] = file;
|
||||
}
|
||||
}
|
||||
}
|
||||
else // File not seen before, just add it.
|
||||
{
|
||||
RegisteredFiles.Add( path, priority );
|
||||
_registeredFiles.Add( path, priority );
|
||||
ResolvedFiles.Add( path, file );
|
||||
}
|
||||
}
|
||||
|
|
@ -194,14 +194,14 @@ public partial class ModCollection
|
|||
// Inside the same mod, conflicts are not recorded.
|
||||
private void AddManipulation( MetaManipulation manip, FileRegister priority )
|
||||
{
|
||||
if( RegisteredManipulations.TryGetValue( manip, out var register ) )
|
||||
if( _registeredManipulations.TryGetValue( manip, out var register ) )
|
||||
{
|
||||
if( register.SameMod( priority, out var less ) )
|
||||
{
|
||||
Conflicts.AddConflict( register.ModIdx, priority.ModIdx, register.ModPriority, priority.ModPriority, manip );
|
||||
if( less )
|
||||
{
|
||||
RegisteredManipulations[ manip ] = priority;
|
||||
_registeredManipulations[ manip ] = priority;
|
||||
MetaManipulations.ApplyMod( manip, priority.ModIdx );
|
||||
}
|
||||
}
|
||||
|
|
@ -212,14 +212,14 @@ public partial class ModCollection
|
|||
// Do not add conflicts.
|
||||
if( less )
|
||||
{
|
||||
RegisteredManipulations[ manip ] = priority;
|
||||
_registeredManipulations[ manip ] = priority;
|
||||
MetaManipulations.ApplyMod( manip, priority.ModIdx );
|
||||
}
|
||||
}
|
||||
}
|
||||
else // Manipulation not seen before, just add it.
|
||||
{
|
||||
RegisteredManipulations[ manip ] = priority;
|
||||
_registeredManipulations[ manip ] = priority;
|
||||
MetaManipulations.ApplyMod( manip, priority.ModIdx );
|
||||
}
|
||||
}
|
||||
|
|
@ -250,7 +250,7 @@ public partial class ModCollection
|
|||
// Add all files and possibly manipulations of a given mod according to its settings in this collection.
|
||||
private void AddMod( int modIdx, bool withManipulations )
|
||||
{
|
||||
var settings = ResolvedSettings[ modIdx ];
|
||||
var settings = _collection[ modIdx ].Settings;
|
||||
if( settings is not { Enabled: true } )
|
||||
{
|
||||
return;
|
||||
|
|
@ -300,7 +300,7 @@ public partial class ModCollection
|
|||
Penumbra.Redirects.Apply( ResolvedFiles );
|
||||
foreach( var gamePath in ResolvedFiles.Keys )
|
||||
{
|
||||
RegisteredFiles.Add( gamePath, new FileRegister( -1, int.MaxValue, 0, 0 ) );
|
||||
_registeredFiles.Add( gamePath, new FileRegister( -1, int.MaxValue, 0, 0 ) );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ public partial class ModCollection
|
|||
=> new(Path.Combine( CollectionDirectory, $"{Name.RemoveInvalidPathSymbols()}.json" ));
|
||||
|
||||
// Custom serialization due to shared mod information across managers.
|
||||
public void Save()
|
||||
private void SaveCollection()
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
@ -71,6 +71,9 @@ public partial class ModCollection
|
|||
}
|
||||
}
|
||||
|
||||
public void Save()
|
||||
=> Penumbra.Framework.RegisterDelayed( nameof( SaveCollection ) + Name, SaveCollection );
|
||||
|
||||
public void Delete()
|
||||
{
|
||||
if( Index == 0 )
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ public partial class Configuration : IPluginConfiguration
|
|||
}
|
||||
|
||||
// Save the current configuration.
|
||||
public void Save()
|
||||
private void SaveConfiguration()
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
@ -76,6 +76,9 @@ public partial class Configuration : IPluginConfiguration
|
|||
}
|
||||
}
|
||||
|
||||
public void Save()
|
||||
=> Penumbra.Framework.RegisterDelayed( nameof( SaveConfiguration ), SaveConfiguration );
|
||||
|
||||
// Add missing colors to the dictionary if necessary.
|
||||
private void AddColors( bool forceSave )
|
||||
{
|
||||
|
|
|
|||
|
|
@ -426,7 +426,7 @@ public sealed partial class Mod
|
|||
}
|
||||
else
|
||||
{
|
||||
IModGroup.SaveModGroup( mod._groups[ groupIdx ], mod.ModPath, groupIdx );
|
||||
IModGroup.SaveDelayed( mod._groups[ groupIdx ], mod.ModPath, groupIdx );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ public partial class Mod
|
|||
Priority = priority,
|
||||
};
|
||||
group.PrioritizedOptions.AddRange( subMods.OfType< SubMod >().Select( ( s, idx ) => ( s, idx ) ) );
|
||||
IModGroup.SaveModGroup( group, baseFolder, index );
|
||||
IModGroup.Save( group, baseFolder, index );
|
||||
break;
|
||||
}
|
||||
case SelectType.Single:
|
||||
|
|
@ -84,7 +84,7 @@ public partial class Mod
|
|||
Priority = priority,
|
||||
};
|
||||
group.OptionData.AddRange( subMods.OfType< SubMod >() );
|
||||
IModGroup.SaveModGroup( group, baseFolder, index );
|
||||
IModGroup.Save( group, baseFolder, index );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ public partial class Mod
|
|||
|
||||
foreach( var (group, index) in _groups.WithIndex() )
|
||||
{
|
||||
IModGroup.SaveModGroup( group, ModPath, index );
|
||||
IModGroup.SaveDelayed( group, ModPath, index );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -83,7 +83,7 @@ public sealed partial class Mod
|
|||
mod._default.IncorporateMetaChanges( mod.ModPath, true );
|
||||
foreach( var (group, index) in mod.Groups.WithIndex() )
|
||||
{
|
||||
IModGroup.SaveModGroup( group, mod.ModPath, index );
|
||||
IModGroup.Save( group, mod.ModPath, index );
|
||||
}
|
||||
|
||||
// Delete meta files.
|
||||
|
|
|
|||
|
|
@ -114,6 +114,9 @@ public sealed partial class Mod
|
|||
}
|
||||
|
||||
private void SaveMeta()
|
||||
=> Penumbra.Framework.RegisterDelayed( nameof( SaveMetaFile ) + ModPath.Name, SaveMetaFile );
|
||||
|
||||
private void SaveMetaFile()
|
||||
{
|
||||
var metaFile = MetaFile;
|
||||
try
|
||||
|
|
|
|||
|
|
@ -15,12 +15,15 @@ public sealed class ModFileSystem : FileSystem< Mod >, IDisposable
|
|||
// Save the current sort order.
|
||||
// Does not save or copy the backup in the current mod directory,
|
||||
// as this is done on mod directory changes only.
|
||||
private void Save()
|
||||
{
|
||||
SaveToFile( new FileInfo( ModFileSystemFile ), SaveMod, true );
|
||||
PluginLog.Verbose( "Saved mod filesystem." );
|
||||
private void SaveFilesystem()
|
||||
{
|
||||
SaveToFile( new FileInfo( ModFileSystemFile ), SaveMod, true );
|
||||
PluginLog.Verbose( "Saved mod filesystem." );
|
||||
}
|
||||
|
||||
private void Save()
|
||||
=> Penumbra.Framework.RegisterDelayed( nameof( SaveFilesystem ), SaveFilesystem );
|
||||
|
||||
// Create a new ModFileSystem from the currently loaded mods and the current sort order file.
|
||||
public static ModFileSystem Load()
|
||||
{
|
||||
|
|
@ -50,6 +53,7 @@ public sealed class ModFileSystem : FileSystem< Mod >, IDisposable
|
|||
{
|
||||
Save();
|
||||
}
|
||||
|
||||
PluginLog.Debug( "Reloaded mod filesystem." );
|
||||
}
|
||||
|
||||
|
|
@ -98,6 +102,7 @@ public sealed class ModFileSystem : FileSystem< Mod >, IDisposable
|
|||
{
|
||||
Delete( leaf );
|
||||
}
|
||||
|
||||
break;
|
||||
case ModPathChangeType.Moved:
|
||||
Save();
|
||||
|
|
|
|||
|
|
@ -57,7 +57,15 @@ public interface IModGroup : IEnumerable< ISubMod >
|
|||
}
|
||||
}
|
||||
|
||||
public static void SaveModGroup( IModGroup group, DirectoryInfo basePath, int groupIdx )
|
||||
public static void SaveDelayed( IModGroup group, DirectoryInfo basePath, int groupIdx )
|
||||
{
|
||||
Penumbra.Framework.RegisterDelayed( $"{nameof( SaveModGroup )}_{basePath.Name}_{group.Name}", () => SaveModGroup( group, basePath, groupIdx ) );
|
||||
}
|
||||
|
||||
public static void Save( IModGroup group, DirectoryInfo basePath, int groupIdx )
|
||||
=> SaveModGroup( group, basePath, groupIdx );
|
||||
|
||||
private static void SaveModGroup( IModGroup group, DirectoryInfo basePath, int groupIdx )
|
||||
{
|
||||
var file = group.FileName( basePath, groupIdx );
|
||||
using var s = File.Exists( file ) ? File.Open( file, FileMode.Truncate ) : File.Open( file, FileMode.CreateNew );
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ public class Penumbra : IDalamudPlugin
|
|||
public static ModCollection.Manager CollectionManager { get; private set; } = null!;
|
||||
public static SimpleRedirectManager Redirects { get; private set; } = null!;
|
||||
public static ResourceLoader ResourceLoader { get; private set; } = null!;
|
||||
public static FrameworkManager Framework { get; private set; } = null!;
|
||||
|
||||
|
||||
public readonly ResourceLogger ResourceLogger;
|
||||
|
|
@ -62,6 +63,7 @@ public class Penumbra : IDalamudPlugin
|
|||
public Penumbra( DalamudPluginInterface pluginInterface )
|
||||
{
|
||||
Dalamud.Initialize( pluginInterface );
|
||||
Framework = new FrameworkManager();
|
||||
GameData.GameData.GetIdentifier( Dalamud.GameData, Dalamud.ClientState.ClientLanguage );
|
||||
Backup.CreateBackup( PenumbraBackupFiles() );
|
||||
Config = Configuration.Load();
|
||||
|
|
|
|||
75
Penumbra/Util/FrameworkManager.cs
Normal file
75
Penumbra/Util/FrameworkManager.cs
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Dalamud.Game;
|
||||
|
||||
namespace Penumbra.Util;
|
||||
|
||||
// Manage certain actions to only occur on framework updates.
|
||||
public class FrameworkManager : IDisposable
|
||||
{
|
||||
private readonly Dictionary< string, Action > _important = new();
|
||||
private readonly Dictionary< string, Action > _delayed = new();
|
||||
|
||||
public FrameworkManager()
|
||||
=> Dalamud.Framework.Update += OnUpdate;
|
||||
|
||||
// Register an action that is not time critical.
|
||||
// One action per frame will be executed.
|
||||
// On dispose, any remaining actions will be executed.
|
||||
public void RegisterDelayed( string tag, Action action )
|
||||
=> _delayed[ tag ] = action;
|
||||
|
||||
// Register an action that should be executed on the next frame.
|
||||
// All of those actions will be executed in the next frame.
|
||||
// If there are more than one, they will be launched in separated tasks, but waited for.
|
||||
public void RegisterImportant( string tag, Action action )
|
||||
=> _important[ tag ] = action;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dalamud.Framework.Update -= OnUpdate;
|
||||
HandleAll( _delayed );
|
||||
}
|
||||
|
||||
private void OnUpdate( Framework _ )
|
||||
{
|
||||
HandleOne();
|
||||
HandleAllTasks( _important );
|
||||
}
|
||||
|
||||
private void HandleOne()
|
||||
{
|
||||
if( _delayed.Count > 0 )
|
||||
{
|
||||
var (key, action) = _delayed.First();
|
||||
action();
|
||||
_delayed.Remove( key );
|
||||
}
|
||||
}
|
||||
|
||||
private static void HandleAll( IDictionary< string, Action > dict )
|
||||
{
|
||||
foreach( var (_, action) in dict )
|
||||
{
|
||||
action();
|
||||
}
|
||||
|
||||
dict.Clear();
|
||||
}
|
||||
|
||||
private static void HandleAllTasks( IDictionary< string, Action > dict )
|
||||
{
|
||||
if( dict.Count < 2 )
|
||||
{
|
||||
HandleAll( dict );
|
||||
}
|
||||
else
|
||||
{
|
||||
var tasks = dict.Values.Select( Task.Run ).ToArray();
|
||||
Task.WaitAll( tasks );
|
||||
dict.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue