diff --git a/Penumbra/Collections/Cache/CollectionCache.cs b/Penumbra/Collections/Cache/CollectionCache.cs index af037ce5..97205990 100644 --- a/Penumbra/Collections/Cache/CollectionCache.cs +++ b/Penumbra/Collections/Cache/CollectionCache.cs @@ -24,6 +24,7 @@ public class CollectionCache : IDisposable { private readonly CollectionCacheManager _manager; private readonly ModCollection _collection; + public readonly CollectionModData ModData = new(); public readonly SortedList, object?)> _changedItems = new(); public readonly Dictionary ResolvedFiles = new(); public readonly MetaCache Meta; @@ -124,13 +125,21 @@ public class CollectionCache : IDisposable /// Force a file to be resolved to a specific path regardless of conflicts. internal void ForceFile(Utf8GamePath path, FullPath fullPath) { - if (CheckFullPath(path, fullPath)) - ResolvedFiles[path] = new ModPath(Mod.ForcedFiles, fullPath); + if (!CheckFullPath(path, fullPath)) + return; + + if (ResolvedFiles.Remove(path, out var modPath)) + ModData.RemovePath(modPath.Mod, path); + ResolvedFiles.Add(path, new ModPath(Mod.ForcedFiles, fullPath)); + ModData.AddPath(Mod.ForcedFiles, path); } /// Force a file resolve to be removed. - internal void RemoveFile(Utf8GamePath path) - => ResolvedFiles.Remove(path); + internal void RemovePath(Utf8GamePath path) + { + if (ResolvedFiles.Remove(path, out var modPath)) + ModData.RemovePath(modPath.Mod, path); + } public void ReloadMod(IMod mod, bool addMetaChanges) { @@ -141,20 +150,19 @@ public class CollectionCache : IDisposable public void RemoveMod(IMod mod, bool addMetaChanges) { var conflicts = Conflicts(mod); - - foreach (var (path, _) in mod.AllSubMods.SelectMany(s => s.Files.Concat(s.FileSwaps))) + var (paths, manipulations) = ModData.RemoveMod(mod); + foreach (var path in paths) { - if (!ResolvedFiles.TryGetValue(path, out var modPath)) - continue; - - if (modPath.Mod == mod) - ResolvedFiles.Remove(path); + if (ResolvedFiles.Remove(path, out var mp) && mp.Mod != mod) + Penumbra.Log.Warning( + $"Invalid mod state, removing {mod.Name} and associated file {path} returned current mod {mp.Mod.Name}."); } - foreach (var manipulation in mod.AllSubMods.SelectMany(s => s.Manipulations)) + foreach (var manipulation in manipulations) { - if (Meta.TryGetValue(manipulation, out var registeredMod) && registeredMod == mod) - Meta.RevertMod(manipulation); + if (Meta.RevertMod(manipulation, out var mp) && mp != mod) + Penumbra.Log.Warning( + $"Invalid mod state, removing {mod.Name} and associated manipulation {manipulation} returned current mod {mp.Name}."); } _conflicts.Remove(mod); @@ -247,7 +255,10 @@ public class CollectionCache : IDisposable 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. @@ -255,7 +266,11 @@ public class CollectionCache : IDisposable return; if (AddConflict(path, mod, modPath.Mod)) + { + ModData.RemovePath(modPath.Mod, path); ResolvedFiles[path] = new ModPath(mod, file); + ModData.AddPath(mod, path); + } } @@ -332,6 +347,7 @@ public class CollectionCache : IDisposable if (!Meta.TryGetValue(manip, out var existingMod)) { Meta.ApplyMod(manip, mod); + ModData.AddManip(mod, manip); return; } @@ -340,7 +356,11 @@ public class CollectionCache : IDisposable return; if (AddConflict(manip, mod, existingMod)) + { + ModData.RemoveManip(existingMod, manip); Meta.ApplyMod(manip, mod); + ModData.AddManip(mod, manip); + } } diff --git a/Penumbra/Collections/Cache/CollectionModData.cs b/Penumbra/Collections/Cache/CollectionModData.cs new file mode 100644 index 00000000..24f8f297 --- /dev/null +++ b/Penumbra/Collections/Cache/CollectionModData.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Penumbra.Meta.Manipulations; +using Penumbra.Mods; +using Penumbra.String.Classes; + +namespace Penumbra.Collections.Cache; + +public class CollectionModData +{ + private readonly Dictionary, HashSet)> _data = new(); + + public IEnumerable<(IMod, IReadOnlySet, IReadOnlySet)> Data + => _data.Select(kvp => (kvp.Key, (IReadOnlySet)kvp.Value.Item1, (IReadOnlySet)kvp.Value.Item2)); + + public (IReadOnlyCollection Paths, IReadOnlyCollection Manipulations) RemoveMod(IMod mod) + { + if (_data.Remove(mod, out var data)) + return data; + + return (Array.Empty(), Array.Empty()); + } + + public void AddPath(IMod mod, Utf8GamePath path) + { + if (_data.TryGetValue(mod, out var data)) + { + data.Item1.Add(path); + } + else + { + data = (new HashSet { path }, new HashSet()); + _data.Add(mod, data); + } + } + + public void AddManip(IMod mod, MetaManipulation manipulation) + { + if (_data.TryGetValue(mod, out var data)) + { + data.Item2.Add(manipulation); + } + else + { + data = (new HashSet(), new HashSet { manipulation }); + _data.Add(mod, data); + } + } + + public void RemovePath(IMod mod, Utf8GamePath path) + { + if (_data.TryGetValue(mod, out var data) && data.Item1.Remove(path) && data.Item1.Count == 0 && data.Item2.Count == 0) + _data.Remove(mod); + } + + public void RemoveManip(IMod mod, MetaManipulation manip) + { + if (_data.TryGetValue(mod, out var data) && data.Item2.Remove(manip) && data.Item1.Count == 0 && data.Item2.Count == 0) + _data.Remove(mod); + } +} diff --git a/Penumbra/Collections/Cache/ImcCache.cs b/Penumbra/Collections/Cache/ImcCache.cs index 2335fadd..7689e5b2 100644 --- a/Penumbra/Collections/Cache/ImcCache.cs +++ b/Penumbra/Collections/Cache/ImcCache.cs @@ -27,7 +27,7 @@ public readonly struct ImcCache : IDisposable { foreach( var (path, file) in _imcFiles ) { - collection._cache!.RemoveFile( path ); + collection._cache!.RemovePath( path ); file.Reset(); } diff --git a/Penumbra/Collections/Cache/MetaCache.cs b/Penumbra/Collections/Cache/MetaCache.cs index 559fb3cd..e2bff762 100644 --- a/Penumbra/Collections/Cache/MetaCache.cs +++ b/Penumbra/Collections/Cache/MetaCache.cs @@ -108,9 +108,9 @@ public class MetaCache : IDisposable, IEnumerable t.Item1.Name)) + { + using var id = mod is TemporaryMod t ? PushId(t.Priority) : PushId(((Mod)mod).ModPath.Name); + using var node2 = TreeNode(mod.Name.Text); + if (!node2) + continue; + + foreach (var path in paths) + + TreeNode(path.ToString(), ImGuiTreeNodeFlags.Bullet | ImGuiTreeNodeFlags.Leaf).Dispose(); + + foreach (var manip in manips) + TreeNode(manip.ToString(), ImGuiTreeNodeFlags.Bullet | ImGuiTreeNodeFlags.Leaf).Dispose(); + } + } + else + { + using var color = ImRaii.PushColor(ImGuiCol.Text, ColorId.UndefinedMod.Value()); + TreeNode(collection.AnonymizedName, ImGuiTreeNodeFlags.Bullet | ImGuiTreeNodeFlags.Leaf).Dispose(); + } + } + } + /// Draw general information about mod and collection state. private void DrawDebugTabGeneral() { @@ -171,17 +215,6 @@ public class DebugTab : Window, ITab } } - using (var tree = TreeNode($"Collections ({_collectionManager.Caches.Count}/{_collectionManager.Storage.Count - 1})###Collections")) - { - if (tree) - { - using var table = Table("##DebugCollectionsTable", 2, ImGuiTableFlags.SizingFixedFit); - if (table) - foreach (var collection in _collectionManager.Storage) - PrintValue(collection.Name, collection.HasCache.ToString()); - } - } - var issues = _modManager.WithIndex().Count(p => p.Index != p.Value.Index); using (var tree = TreeNode($"Mods ({issues} Issues)###Mods")) { @@ -245,7 +278,7 @@ public class DebugTab : Window, ITab using var table = Table("##DebugFramework", 2, ImGuiTableFlags.SizingFixedFit); if (table) { - foreach(var important in _framework.Important) + foreach (var important in _framework.Important) PrintValue(important, "Immediate"); foreach (var (onTick, idx) in _framework.OnTick.WithIndex())