diff --git a/Penumbra/Collections/Cache/CollectionCache.cs b/Penumbra/Collections/Cache/CollectionCache.cs index 1196a110..6daa78f3 100644 --- a/Penumbra/Collections/Cache/CollectionCache.cs +++ b/Penumbra/Collections/Cache/CollectionCache.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.CompilerServices; +using System.Threading; using Penumbra.Api.Enums; using Penumbra.String.Classes; using Penumbra.Mods.Manager; @@ -30,7 +31,10 @@ public class CollectionCache : IDisposable public readonly MetaCache Meta; public readonly Dictionary> _conflicts = new(); - public bool Calculating; + public int Calculating = -1; + + public string AnonymizedName + => _collection.AnonymizedName; public IEnumerable> AllConflicts => _conflicts.Values; @@ -138,7 +142,7 @@ public class CollectionCache : IDisposable => _manager.AddChange(ChangeData.ModRemoval(this, mod, addMetaChanges)); /// Force a file to be resolved to a specific path regardless of conflicts. - private void ForceFileSync(Utf8GamePath path, FullPath fullPath) + internal void ForceFileSync(Utf8GamePath path, FullPath fullPath) { if (!CheckFullPath(path, fullPath)) return; @@ -149,7 +153,7 @@ public class CollectionCache : IDisposable ResolvedFiles.Add(path, new ModPath(Mod.ForcedFiles, fullPath)); } - internal void ReloadModSync(IMod mod, bool addMetaChanges) + private void ReloadModSync(IMod mod, bool addMetaChanges) { RemoveModSync(mod, addMetaChanges); AddModSync(mod, addMetaChanges); @@ -238,7 +242,7 @@ public class CollectionCache : IDisposable { ++_collection.ChangeCounter; if (mod.TotalManipulations > 0) - AddMetaFiles(); + AddMetaFiles(false); _manager.MetaFileManager.ApplyDefaultFiles(_collection); } @@ -263,22 +267,29 @@ public class CollectionCache : IDisposable if (!CheckFullPath(path, file)) return; - if (ResolvedFiles.TryAdd(path, new ModPath(mod, file))) + try { - ModData.AddPath(mod, path); - return; + if (ResolvedFiles.TryAdd(path, new ModPath(mod, file))) + { + ModData.AddPath(mod, path); + return; + } + + var modPath = ResolvedFiles[path]; + // Lower prioritized option in the same mod. + if (mod == modPath.Mod) + return; + + if (AddConflict(path, mod, modPath.Mod)) + { + ModData.RemovePath(modPath.Mod, path); + ResolvedFiles[path] = new ModPath(mod, file); + ModData.AddPath(mod, path); + } } - - var modPath = ResolvedFiles[path]; - // Lower prioritized option in the same mod. - if (mod == modPath.Mod) - return; - - if (AddConflict(path, mod, modPath.Mod)) + catch (Exception ex) { - ModData.RemovePath(modPath.Mod, path); - ResolvedFiles[path] = new ModPath(mod, file); - ModData.AddPath(mod, path); + Penumbra.Log.Error($"[{Thread.CurrentThread.ManagedThreadId}] Error adding redirection {file} -> {path} for mod {mod.Name} to collection cache {AnonymizedName}:\n{ex}"); } } @@ -374,8 +385,8 @@ public class CollectionCache : IDisposable // Add all necessary meta file redirects. - public void AddMetaFiles() - => Meta.SetImcFiles(); + public void AddMetaFiles(bool fromFullCompute) + => Meta.SetImcFiles(fromFullCompute); // Identify and record all manipulated objects for this entire collection. diff --git a/Penumbra/Collections/Cache/CollectionCacheManager.cs b/Penumbra/Collections/Cache/CollectionCacheManager.cs index 0c084c5c..5ac1ab7f 100644 --- a/Penumbra/Collections/Cache/CollectionCacheManager.cs +++ b/Penumbra/Collections/Cache/CollectionCacheManager.cs @@ -82,16 +82,30 @@ public class CollectionCacheManager : IDisposable public void AddChange(CollectionCache.ChangeData data) { - if (_framework.Framework.IsInFrameworkUpdateThread) + if (data.Cache.Calculating == -1) + { + if (_framework.Framework.IsInFrameworkUpdateThread) + data.Apply(); + else + _changeQueue.Enqueue(data); + } + else if (data.Cache.Calculating == Environment.CurrentManagedThreadId) + { data.Apply(); + } else + { _changeQueue.Enqueue(data); + } } /// Only creates a new cache, does not update an existing one. public bool CreateCache(ModCollection collection) { - if (collection.HasCache || collection.Index == ModCollection.Empty.Index) + if (collection.Index == ModCollection.Empty.Index) + return false; + + if (collection._cache != null) return false; collection._cache = new CollectionCache(this, collection); @@ -115,27 +129,33 @@ public class CollectionCacheManager : IDisposable if (collection.Index == 0) return; - Penumbra.Log.Debug($"[{Thread.CurrentThread.ManagedThreadId}] Recalculating effective file list for {collection.AnonymizedName}"); + Penumbra.Log.Debug($"[{Environment.CurrentManagedThreadId}] Recalculating effective file list for {collection.AnonymizedName}"); if (!collection.HasCache) { Penumbra.Log.Error( - $"[{Thread.CurrentThread.ManagedThreadId}] Recalculating effective file list for {collection.AnonymizedName} failed, no cache exists."); - return; + $"[{Environment.CurrentManagedThreadId}] Recalculating effective file list for {collection.AnonymizedName} failed, no cache exists."); } + else if (collection._cache!.Calculating != -1) + { + Penumbra.Log.Error( + $"[{Environment.CurrentManagedThreadId}] Recalculating effective file list for {collection.AnonymizedName} failed, already in calculation on [{collection._cache!.Calculating}]."); + } + else + { + FullRecalculation(collection); - FullRecalculation(collection); - - Penumbra.Log.Debug( - $"[{Thread.CurrentThread.ManagedThreadId}] Recalculation of effective file list for {collection.AnonymizedName} finished."); + Penumbra.Log.Debug( + $"[{Environment.CurrentManagedThreadId}] Recalculation of effective file list for {collection.AnonymizedName} finished."); + } } private void FullRecalculation(ModCollection collection) { var cache = collection._cache; - if (cache == null || cache.Calculating) + if (cache is not { Calculating: -1 }) return; - cache.Calculating = true; + cache.Calculating = Environment.CurrentManagedThreadId; try { cache.ResolvedFiles.Clear(); @@ -152,7 +172,7 @@ public class CollectionCacheManager : IDisposable foreach (var mod in _modStorage) cache.AddModSync(mod, false); - cache.AddMetaFiles(); + cache.AddMetaFiles(true); ++collection.ChangeCounter; @@ -160,7 +180,7 @@ public class CollectionCacheManager : IDisposable } finally { - cache.Calculating = false; + cache.Calculating = -1; } } @@ -329,12 +349,19 @@ public class CollectionCacheManager : IDisposable /// public void CreateNecessaryCaches() { - Parallel.ForEach(_active.SpecialAssignments.Select(p => p.Value) - .Concat(_active.Individuals.Select(p => p.Collection)) - .Prepend(_active.Current) - .Prepend(_active.Default) - .Prepend(_active.Interface) - .Where(CreateCache), CalculateEffectiveFileListInternal); + ModCollection[] caches; + // Lock to make sure no race conditions during CreateCache happen. + lock (this) + { + caches = _active.SpecialAssignments.Select(p => p.Value) + .Concat(_active.Individuals.Select(p => p.Collection)) + .Prepend(_active.Current) + .Prepend(_active.Default) + .Prepend(_active.Interface) + .Where(CreateCache).ToArray(); + } + + Parallel.ForEach(caches, CalculateEffectiveFileListInternal); } private void OnModDiscoveryStarted() diff --git a/Penumbra/Collections/Cache/ImcCache.cs b/Penumbra/Collections/Cache/ImcCache.cs index 97f2aa8a..896e3078 100644 --- a/Penumbra/Collections/Cache/ImcCache.cs +++ b/Penumbra/Collections/Cache/ImcCache.cs @@ -17,10 +17,18 @@ public readonly struct ImcCache : IDisposable public ImcCache() { } - public void SetFiles(ModCollection collection) + public void SetFiles(ModCollection collection, bool fromFullCompute) { - foreach( var path in _imcFiles.Keys ) - collection._cache!.ForceFile( path, CreateImcPath( collection, path ) ); + if (fromFullCompute) + { + foreach (var path in _imcFiles.Keys) + collection._cache!.ForceFileSync(path, CreateImcPath(collection, path)); + } + else + { + foreach (var path in _imcFiles.Keys) + collection._cache!.ForceFile(path, CreateImcPath(collection, path)); + } } public void Reset(ModCollection collection) diff --git a/Penumbra/Collections/Cache/MetaCache.cs b/Penumbra/Collections/Cache/MetaCache.cs index 6906f6dc..ee589d1b 100644 --- a/Penumbra/Collections/Cache/MetaCache.cs +++ b/Penumbra/Collections/Cache/MetaCache.cs @@ -59,7 +59,7 @@ public class MetaCache : IDisposable, IEnumerable Set the currently relevant IMC files for the collection cache. - public void SetImcFiles() - => _imcCache.SetFiles(_collection); + public void SetImcFiles(bool fromFullCompute) + => _imcCache.SetFiles(_collection, fromFullCompute); public MetaList.MetaReverter TemporarilySetEqpFile() => _eqpCache.TemporarilySetFiles(_manager); diff --git a/Penumbra/Collections/Manager/IndividualCollections.Files.cs b/Penumbra/Collections/Manager/IndividualCollections.Files.cs index 4e238722..0225927e 100644 --- a/Penumbra/Collections/Manager/IndividualCollections.Files.cs +++ b/Penumbra/Collections/Manager/IndividualCollections.Files.cs @@ -32,14 +32,11 @@ public partial class IndividualCollections return ReadJObjectInternal(obj, storage); void Func() { - saver.DalamudFramework.RunOnFrameworkThread(() => - { - if (ReadJObjectInternal(obj, storage)) - saver.ImmediateSave(parent); - IsLoaded = true; - Loaded.Invoke(); - _actorService.FinishedCreation -= Func; - }); + if (ReadJObjectInternal(obj, storage)) + saver.ImmediateSave(parent); + IsLoaded = true; + Loaded.Invoke(); + _actorService.FinishedCreation -= Func; } _actorService.FinishedCreation += Func; return false; diff --git a/Penumbra/Mods/Manager/ModManager.cs b/Penumbra/Mods/Manager/ModManager.cs index 6c1ff9ab..38eff57b 100644 --- a/Penumbra/Mods/Manager/ModManager.cs +++ b/Penumbra/Mods/Manager/ModManager.cs @@ -77,7 +77,7 @@ public sealed class ModManager : ModStorage, IDisposable ScanMods(); _communicator.ModDiscoveryFinished.Invoke(); - Penumbra.Log.Information("Rediscovered mods."); + Penumbra.Log.Information($"Rediscovered {Mods.Count} mods."); if (ModBackup.MigrateModBackups) ModBackup.MigrateZipToPmp(this);