From 85fb98b557280e513033be95f693e3e4d921a80b Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 14 Apr 2023 22:26:14 +0200 Subject: [PATCH] tmp --- ...dCollectionCache.cs => CollectionCache.cs} | 153 +++--------- .../Cache/CollectionCacheManager.cs | 234 ++++++++++++------ .../Cache/MetaCache.Cmp.cs} | 4 +- .../Cache/MetaCache.Eqdp.cs} | 4 +- .../Cache/MetaCache.Eqp.cs} | 4 +- .../Cache/MetaCache.Est.cs} | 4 +- .../Cache/MetaCache.Gmp.cs} | 4 +- .../Cache/MetaCache.Imc.cs} | 4 +- .../Cache/MetaCache.cs} | 19 +- .../Collections/Manager/ActiveCollections.cs | 4 +- .../Collections/Manager/CollectionManager.cs | 2 + .../Manager/TempCollectionManager.cs | 19 +- .../Collections/ModCollection.Cache.Access.cs | 50 +--- Penumbra/Collections/ModCollection.cs | 7 +- Penumbra/Penumbra.cs | 11 +- 15 files changed, 230 insertions(+), 293 deletions(-) rename Penumbra/Collections/Cache/{ModCollectionCache.cs => CollectionCache.cs} (79%) rename Penumbra/{Meta/Manager/MetaManager.Cmp.cs => Collections/Cache/MetaCache.Cmp.cs} (95%) rename Penumbra/{Meta/Manager/MetaManager.Eqdp.cs => Collections/Cache/MetaCache.Eqdp.cs} (97%) rename Penumbra/{Meta/Manager/MetaManager.Eqp.cs => Collections/Cache/MetaCache.Eqp.cs} (95%) rename Penumbra/{Meta/Manager/MetaManager.Est.cs => Collections/Cache/MetaCache.Est.cs} (98%) rename Penumbra/{Meta/Manager/MetaManager.Gmp.cs => Collections/Cache/MetaCache.Gmp.cs} (95%) rename Penumbra/{Meta/Manager/MetaManager.Imc.cs => Collections/Cache/MetaCache.Imc.cs} (97%) rename Penumbra/{Meta/Manager/MetaManager.cs => Collections/Cache/MetaCache.cs} (90%) diff --git a/Penumbra/Collections/Cache/ModCollectionCache.cs b/Penumbra/Collections/Cache/CollectionCache.cs similarity index 79% rename from Penumbra/Collections/Cache/ModCollectionCache.cs rename to Penumbra/Collections/Cache/CollectionCache.cs index 32cc9a8e..de95cae6 100644 --- a/Penumbra/Collections/Cache/ModCollectionCache.cs +++ b/Penumbra/Collections/Cache/CollectionCache.cs @@ -1,6 +1,5 @@ using OtterGui; using OtterGui.Classes; -using Penumbra.Meta.Manager; using Penumbra.Meta.Manipulations; using Penumbra.Mods; using System; @@ -9,24 +8,24 @@ using System.IO; using System.Linq; using Penumbra.Api.Enums; using Penumbra.String.Classes; -using Penumbra.Mods.Manager; - +using Penumbra.Mods.Manager; + namespace Penumbra.Collections.Cache; public record struct ModPath(IMod Mod, FullPath Path); public record ModConflicts(IMod Mod2, List Conflicts, bool HasPriority, bool Solved); -/// +/// /// The Cache contains all required temporary data to use a collection. -/// It will only be setup if a collection gets activated in any way. +/// It will only be setup if a collection gets activated in any way. /// public class ModCollectionCache : IDisposable { - private readonly ModCollection _collection; - private readonly SortedList, object?)> _changedItems = new(); - public readonly Dictionary ResolvedFiles = new(); - public readonly MetaManager MetaManipulations; - private readonly Dictionary> _conflicts = new(); + private readonly ModCollection _collection; + public readonly SortedList, object?)> _changedItems = new(); + public readonly Dictionary ResolvedFiles = new(); + public readonly MetaCache MetaManipulations; + public readonly Dictionary> _conflicts = new(); public IEnumerable> AllConflicts => _conflicts.Values; @@ -49,8 +48,8 @@ public class ModCollectionCache : IDisposable // The cache reacts through events on its collection changing. public ModCollectionCache(ModCollection collection) { - _collection = collection; - MetaManipulations = new MetaManager(_collection); + _collection = collection; + MetaManipulations = new MetaCache(_collection); } public void Dispose() @@ -62,15 +61,11 @@ public class ModCollectionCache : IDisposable public FullPath? ResolvePath(Utf8GamePath gameResourcePath) { if (!ResolvedFiles.TryGetValue(gameResourcePath, out var candidate)) - { return null; - } if (candidate.Path.InternalName.Length > Utf8GamePath.MaxGamePathLength - || candidate.Path.IsRooted && !candidate.Path.Exists) - { + || candidate.Path.IsRooted && !candidate.Path.Exists) return null; - } return candidate.Path; } @@ -80,9 +75,7 @@ public class ModCollectionCache : IDisposable { var needle = localFilePath.FullName.ToLower(); if (localFilePath.IsRooted) - { needle = needle.Replace('/', '\\'); - } var iterator = ResolvedFiles .Where(f => string.Equals(f.Value.Path.FullName, needle, StringComparison.OrdinalIgnoreCase)) @@ -90,9 +83,7 @@ public class ModCollectionCache : IDisposable // For files that are not rooted, try to add themselves. if (!localFilePath.IsRooted && Utf8GamePath.FromString(localFilePath.FullName, out var utf8)) - { iterator = iterator.Prepend(utf8); - } return iterator; } @@ -103,7 +94,7 @@ public class ModCollectionCache : IDisposable if (fullPaths.Count == 0) return Array.Empty>(); - var ret = new HashSet[fullPaths.Count]; + var ret = new HashSet[fullPaths.Count]; var dict = new Dictionary(fullPaths.Count); foreach (var (path, idx) in fullPaths.WithIndex()) { @@ -116,43 +107,12 @@ public class ModCollectionCache : IDisposable foreach (var (game, full) in ResolvedFiles) { if (dict.TryGetValue(full.Path, out var idx)) - { ret[idx].Add(game); - } } return ret; } - public void FullRecalculation(bool isDefault) - { - ResolvedFiles.Clear(); - MetaManipulations.Reset(); - _conflicts.Clear(); - - // Add all forced redirects. - foreach (var tempMod in Penumbra.TempMods.ModsForAllCollections.Concat( - Penumbra.TempMods.Mods.TryGetValue(_collection, out var list) ? list : Array.Empty())) - { - AddMod(tempMod, false); - } - - foreach (var mod in Penumbra.ModManager) - { - AddMod(mod, false); - } - - AddMetaFiles(); - - ++_collection.ChangeCounter; - - if (isDefault && Penumbra.CharacterUtility.Ready && Penumbra.Config.EnableMods) - { - Penumbra.ResidentResources.Reload(); - MetaManipulations.SetFiles(); - } - } - public void ReloadMod(IMod mod, bool addMetaChanges) { RemoveMod(mod, addMetaChanges); @@ -166,22 +126,16 @@ public class ModCollectionCache : IDisposable foreach (var (path, _) in mod.AllSubMods.SelectMany(s => s.Files.Concat(s.FileSwaps))) { if (!ResolvedFiles.TryGetValue(path, out var modPath)) - { continue; - } if (modPath.Mod == mod) - { ResolvedFiles.Remove(path); - } } foreach (var manipulation in mod.AllSubMods.SelectMany(s => s.Manipulations)) { if (MetaManipulations.TryGetValue(manipulation, out var registeredMod) && registeredMod == mod) - { MetaManipulations.RevertMod(manipulation); - } } _conflicts.Remove(mod); @@ -195,13 +149,9 @@ public class ModCollectionCache : IDisposable { var newConflicts = Conflicts(conflict.Mod2).Remove(c => c.Mod2 == mod); if (newConflicts.Count > 0) - { _conflicts[conflict.Mod2] = newConflicts; - } else - { _conflicts.Remove(conflict.Mod2); - } } } @@ -224,16 +174,12 @@ public class ModCollectionCache : IDisposable { var settings = _collection[mod.Index].Settings; if (settings is not { Enabled: true }) - { return; - } foreach (var (group, groupIndex) in mod.Groups.WithIndex().OrderByDescending(g => g.Item1.Priority)) { if (group.Count == 0) - { continue; - } var config = settings.Settings[groupIndex]; switch (group.Type) @@ -241,17 +187,15 @@ public class ModCollectionCache : IDisposable case GroupType.Single: AddSubMod(group[(int)config], mod); break; - case GroupType.Multi: - { - foreach (var (option, _) in group.WithIndex() - .Where(p => (1 << p.Item2 & config) != 0) - .OrderByDescending(p => group.OptionPriority(p.Item2))) - { - AddSubMod(option, mod); - } - - break; - } + case GroupType.Multi: + { + foreach (var (option, _) in group.WithIndex() + .Where(p => ((1 << p.Item2) & config) != 0) + .OrderByDescending(p => group.OptionPriority(p.Item2))) + AddSubMod(option, mod); + + break; + } } } } @@ -262,9 +206,7 @@ public class ModCollectionCache : IDisposable { ++_collection.ChangeCounter; if (Penumbra.ModCaches[mod.Index].TotalManipulations > 0) - { AddMetaFiles(); - } if (_collection == Penumbra.CollectionManager.Active.Default && Penumbra.CharacterUtility.Ready && Penumbra.Config.EnableMods) { @@ -278,14 +220,10 @@ public class ModCollectionCache : IDisposable private void AddSubMod(ISubMod subMod, IMod parentMod) { foreach (var (path, file) in subMod.Files.Concat(subMod.FileSwaps)) - { AddFile(path, file, parentMod); - } foreach (var manip in subMod.Manipulations) - { AddManipulation(manip, parentMod); - } } // Add a specific file redirection, handling potential conflicts. @@ -295,26 +233,18 @@ public class ModCollectionCache : IDisposable private void AddFile(Utf8GamePath path, FullPath file, IMod mod) { if (!ModCollection.CheckFullPath(path, file)) - { return; - } if (ResolvedFiles.TryAdd(path, new ModPath(mod, file))) - { return; - } var modPath = ResolvedFiles[path]; // Lower prioritized option in the same mod. if (mod == modPath.Mod) - { return; - } if (AddConflict(path, mod, modPath.Mod)) - { ResolvedFiles[path] = new ModPath(mod, file); - } } @@ -327,9 +257,7 @@ public class ModCollectionCache : IDisposable if (c.Conflicts.Count == 0) { if (transitive) - { RemoveEmptyConflicts(c.Mod2, Conflicts(c.Mod2), false); - } return true; } @@ -337,13 +265,9 @@ public class ModCollectionCache : IDisposable return false; }); if (changedConflicts.Count == 0) - { _conflicts.Remove(mod); - } else - { _conflicts[mod] = changedConflicts; - } } // Add a new conflict between the added mod and the existing mod. @@ -351,7 +275,7 @@ public class ModCollectionCache : IDisposable // Returns if the added mod takes priority before the existing mod. private bool AddConflict(object data, IMod addedMod, IMod existingMod) { - var addedPriority = addedMod.Index >= 0 ? _collection[addedMod.Index].Settings!.Priority : addedMod.Priority; + var addedPriority = addedMod.Index >= 0 ? _collection[addedMod.Index].Settings!.Priority : addedMod.Priority; var existingPriority = existingMod.Index >= 0 ? _collection[existingMod.Index].Settings!.Priority : existingMod.Priority; if (existingPriority < addedPriority) @@ -360,16 +284,14 @@ public class ModCollectionCache : IDisposable foreach (var conflict in tmpConflicts) { if (data is Utf8GamePath path && conflict.Conflicts.RemoveAll(p => p is Utf8GamePath x && x.Equals(path)) > 0 - || data is MetaManipulation meta && conflict.Conflicts.RemoveAll(m => m is MetaManipulation x && x.Equals(meta)) > 0) - { + || data is MetaManipulation meta && conflict.Conflicts.RemoveAll(m => m is MetaManipulation x && x.Equals(meta)) > 0) AddConflict(data, addedMod, conflict.Mod2); - } } RemoveEmptyConflicts(existingMod, tmpConflicts, true); } - var addedConflicts = Conflicts(addedMod); + var addedConflicts = Conflicts(addedMod); var existingConflicts = Conflicts(existingMod); if (addedConflicts.FindFirst(c => c.Mod2 == existingMod, out var oldConflicts)) { @@ -404,36 +326,23 @@ public class ModCollectionCache : IDisposable // Lower prioritized option in the same mod. if (mod == existingMod) - { return; - } if (AddConflict(manip, mod, existingMod)) - { MetaManipulations.ApplyMod(manip, mod); - } } // Add all necessary meta file redirects. - private void AddMetaFiles() + public void AddMetaFiles() => MetaManipulations.SetImcFiles(); - // Increment the counter to ensure new files are loaded after applying meta changes. - private void IncrementCounter() - { - ++_collection.ChangeCounter; - Penumbra.CharacterUtility.LoadingFinished -= IncrementCounter; - } - // Identify and record all manipulated objects for this entire collection. private void SetChangedItems() { if (_changedItemsSaveCounter == _collection.ChangeCounter) - { return; - } try { @@ -442,24 +351,18 @@ public class ModCollectionCache : IDisposable // Skip IMCs because they would result in far too many false-positive items, // since they are per set instead of per item-slot/item/variant. var identifier = Penumbra.Identifier; - var items = new SortedList(512); + var items = new SortedList(512); void AddItems(IMod mod) { foreach (var (name, obj) in items) { if (!_changedItems.TryGetValue(name, out var data)) - { _changedItems.Add(name, (new SingleArray(mod), obj)); - } else if (!data.Item1.Contains(mod)) - { _changedItems[name] = (data.Item1.Append(mod), obj is int x && data.Item2 is int y ? x + y : obj); - } else if (obj is int x && data.Item2 is int y) - { _changedItems[name] = (data.Item1, x + y); - } } items.Clear(); @@ -482,4 +385,4 @@ public class ModCollectionCache : IDisposable Penumbra.Log.Error($"Unknown Error:\n{e}"); } } -} \ No newline at end of file +} diff --git a/Penumbra/Collections/Cache/CollectionCacheManager.cs b/Penumbra/Collections/Cache/CollectionCacheManager.cs index d2604418..aa24d208 100644 --- a/Penumbra/Collections/Cache/CollectionCacheManager.cs +++ b/Penumbra/Collections/Cache/CollectionCacheManager.cs @@ -1,61 +1,53 @@ using System; -using System.Collections; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; +using System.Threading; using System.Threading.Tasks; +using OtterGui.Classes; using Penumbra.Api; using Penumbra.Api.Enums; -using Penumbra.Collections.Cache; +using Penumbra.Collections.Manager; using Penumbra.Interop.Services; using Penumbra.Mods; using Penumbra.Mods.Manager; using Penumbra.Services; -namespace Penumbra.Collections.Manager; +namespace Penumbra.Collections.Cache; -public class CollectionCacheManager : IDisposable, IReadOnlyDictionary +public class CollectionCacheManager : IDisposable { - private readonly ActiveCollections _active; - private readonly CommunicatorService _communicator; - private readonly CharacterUtility _characterUtility; + private readonly FrameworkManager _framework; + private readonly ActiveCollections _active; + private readonly CommunicatorService _communicator; + private readonly CharacterUtility _characterUtility; + private readonly TempModManager _tempMods; + private readonly ModStorage _modStorage; + private readonly ModCacheManager _modCaches; + private readonly Configuration _config; + private readonly ResidentResourceManager _resources; - private readonly List<(ModCollectionCache, int ChangeCounter)> - private readonly Dictionary _cache = new(); + private readonly Dictionary _caches = new(); public int Count - => _cache.Count; + => _caches.Count; - public IEnumerator> GetEnumerator() - => _cache.GetEnumerator(); + public IEnumerable<(ModCollection Collection, CollectionCache Cache)> Active + => _caches.Where(c => c.Key.Index > ModCollection.Empty.Index).Select(p => (p.Key, p.Value)); - IEnumerator IEnumerable.GetEnumerator() - => GetEnumerator(); - - public bool ContainsKey(ModCollection key) - => _cache.ContainsKey(key); - - public bool TryGetValue(ModCollection key, [NotNullWhen(true)] out ModCollectionCache? value) - => _cache.TryGetValue(key, out value); - - public ModCollectionCache this[ModCollection key] - => _cache[key]; - - public IEnumerable Keys - => _cache.Keys; - - public IEnumerable Values - => _cache.Values; - - public IEnumerable Active - => _cache.Keys.Where(c => c.Index > ModCollection.Empty.Index); - - public CollectionCacheManager(ActiveCollections active, CommunicatorService communicator, CharacterUtility characterUtility) + public CollectionCacheManager(FrameworkManager framework, ActiveCollections active, CommunicatorService communicator, + CharacterUtility characterUtility, TempModManager tempMods, ModStorage modStorage, Configuration config, + ResidentResourceManager resources, ModCacheManager modCaches) { + _framework = framework; _active = active; _communicator = communicator; _characterUtility = characterUtility; + _tempMods = tempMods; + _modStorage = modStorage; + _config = config; + _resources = resources; + _modCaches = modCaches; _communicator.CollectionChange.Subscribe(OnCollectionChange); _communicator.ModPathChanged.Subscribe(OnModChangeAddition, -100); @@ -82,50 +74,103 @@ public class CollectionCacheManager : IDisposable, IReadOnlyDictionary - /// Cache handling. Usually recreate caches on the next framework tick, - /// but at launch create all of them at once. - /// - public void CreateNecessaryCaches() + /// Only creates a new cache, does not update an existing one. + public bool CreateCache(ModCollection collection) { - var tasks = _active.SpecialAssignments.Select(p => p.Value) - .Concat(_active.Individuals.Select(p => p.Collection)) - .Prepend(_active.Current) - .Prepend(_active.Default) - .Prepend(_active.Interface) - .Distinct() - .Select(c => Task.Run(() => c.CalculateEffectiveFileListInternal(c == _active.Default))) - .ToArray(); + if (_caches.ContainsKey(collection) || collection.Index == ModCollection.Empty.Index) + return false; - Task.WaitAll(tasks); + var cache = new CollectionCache(collection); + _caches.Add(collection, cache); + collection._cache = cache; + Penumbra.Log.Verbose($"Created new cache for collection {collection.AnonymizedName}."); + return true; + } + + /// + /// Update the effective file list for the given cache. + /// Does not create caches. + /// + public void CalculateEffectiveFileList(ModCollection collection) + => _framework.RegisterImportant(nameof(CalculateEffectiveFileList) + collection.Name, + () => CalculateEffectiveFileListInternal(collection)); + + private void CalculateEffectiveFileListInternal(ModCollection collection) + { + // Skip the empty collection. + if (collection.Index == 0) + return; + + Penumbra.Log.Debug($"[{Thread.CurrentThread.ManagedThreadId}] Recalculating effective file list for {collection.AnonymizedName}"); + if (!_caches.TryGetValue(collection, out var cache)) + { + Penumbra.Log.Error( + $"[{Thread.CurrentThread.ManagedThreadId}] Recalculating effective file list for {collection.AnonymizedName} failed, no cache exists."); + return; + } + + FullRecalculation(collection, cache); + + Penumbra.Log.Debug( + $"[{Thread.CurrentThread.ManagedThreadId}] Recalculation of effective file list for {collection.AnonymizedName} finished."); + } + + private void FullRecalculation(ModCollection collection, CollectionCache cache) + { + cache.ResolvedFiles.Clear(); + cache.MetaManipulations.Reset(); + cache._conflicts.Clear(); + + // Add all forced redirects. + foreach (var tempMod in _tempMods.ModsForAllCollections.Concat( + _tempMods.Mods.TryGetValue(collection, out var list) ? list : Array.Empty())) + cache.AddMod(tempMod, false); + + foreach (var mod in _modStorage) + cache.AddMod(mod, false); + + cache.AddMetaFiles(); + + ++collection.ChangeCounter; + + if (_active.Default != collection || !_characterUtility.Ready || !_config.EnableMods) + return; + + _resources.Reload(); + cache.MetaManipulations.SetFiles(); } private void OnCollectionChange(CollectionType type, ModCollection? old, ModCollection? newCollection, string displayName) { - if (type is CollectionType.Inactive) - return; - - var isDefault = type is CollectionType.Default; - if (newCollection?.Index > ModCollection.Empty.Index) + if (type is CollectionType.Temporary) { - newCollection.CreateCache(isDefault); - _cache.TryAdd(newCollection, newCollection._cache!); - } + if (newCollection != null && CreateCache(newCollection)) + CalculateEffectiveFileList(newCollection); - RemoveCache(old); + if (old != null) + ClearCache(old); + } + else + { + RemoveCache(old); + + if (type is not CollectionType.Inactive && newCollection != null && newCollection.Index != 0 && CreateCache(newCollection)) + CalculateEffectiveFileList(newCollection); + } } + private void OnModChangeRemoval(ModPathChangeType type, Mod mod, DirectoryInfo? oldModPath, DirectoryInfo? newModPath) { switch (type) { case ModPathChangeType.Deleted: case ModPathChangeType.StartingReload: - foreach (var collection in _cache.Keys.Where(c => c[mod.Index].Settings?.Enabled == true)) + foreach (var collection in _caches.Keys.Where(c => c[mod.Index].Settings?.Enabled == true)) collection._cache!.RemoveMod(mod, true); break; case ModPathChangeType.Moved: - foreach (var collection in _cache.Keys.Where(c => c.HasCache && c[mod.Index].Settings?.Enabled == true)) + foreach (var collection in _caches.Keys.Where(c => c.HasCache && c[mod.Index].Settings?.Enabled == true)) collection._cache!.ReloadMod(mod, true); break; } @@ -136,13 +181,13 @@ public class CollectionCacheManager : IDisposable, IReadOnlyDictionary c[mod.Index].Settings?.Enabled == true)) + foreach (var collection in _caches.Keys.Where(c => c[mod.Index].Settings?.Enabled == true)) collection._cache!.AddMod(mod, true); } /// Apply a mod change to all collections with a cache. private void OnGlobalModChange(TemporaryMod mod, bool created, bool removed) - => TempModManager.OnGlobalModChange(_cache.Keys, mod, created, removed); + => TempModManager.OnGlobalModChange(_caches.Keys, mod, created, removed); /// Remove a cache from a collection if it is active. private void RemoveCache(ModCollection? collection) @@ -154,10 +199,7 @@ public class CollectionCacheManager : IDisposable, IReadOnlyDictionary c.Value.Index != collection.Index) && _active.Individuals.All(c => c.Collection.Index != collection.Index)) - { - _cache.Remove(collection); - collection.ClearCache(); - } + ClearCache(collection); } /// Prepare Changes by removing mods from caches with collections or add or reload mods. @@ -165,7 +207,7 @@ public class CollectionCacheManager : IDisposable, IReadOnlyDictionary collection[mod.Index].Settings is { Enabled: true })) + foreach (var collection in _caches.Keys.Where(collection => collection[mod.Index].Settings is { Enabled: true })) collection._cache!.RemoveMod(mod, false); return; @@ -176,7 +218,7 @@ public class CollectionCacheManager : IDisposable, IReadOnlyDictionary collection[mod.Index].Settings is { Enabled: true })) + foreach (var collection in _caches.Keys.Where(collection => collection[mod.Index].Settings is { Enabled: true })) { if (reload) collection._cache!.ReloadMod(mod, true); @@ -188,45 +230,45 @@ public class CollectionCacheManager : IDisposable, IReadOnlyDictionary Increment the counter to ensure new files are loaded after applying meta changes. private void IncrementCounters() { - foreach (var (collection, _) in _cache) + foreach (var (collection, _) in _caches) ++collection.ChangeCounter; _characterUtility.LoadingFinished -= IncrementCounters; } private void OnModSettingChange(ModCollection collection, ModSettingChange type, Mod? mod, int oldValue, int groupIdx, bool _) { - if (collection._cache == null) + if (!_caches.TryGetValue(collection, out var cache)) return; switch (type) { case ModSettingChange.Inheritance: - collection._cache.ReloadMod(mod!, true); + cache.ReloadMod(mod!, true); break; case ModSettingChange.EnableState: if (oldValue == 0) - collection._cache.AddMod(mod!, true); + cache.AddMod(mod!, true); else if (oldValue == 1) - collection._cache.RemoveMod(mod!, true); + cache.RemoveMod(mod!, true); else if (collection[mod!.Index].Settings?.Enabled == true) - collection._cache.ReloadMod(mod!, true); + cache.ReloadMod(mod!, true); else - collection._cache.RemoveMod(mod!, true); + cache.RemoveMod(mod!, true); break; case ModSettingChange.Priority: - if (collection._cache.Conflicts(mod!).Count > 0) - collection._cache.ReloadMod(mod!, true); + if (cache.Conflicts(mod!).Count > 0) + cache.ReloadMod(mod!, true); break; case ModSettingChange.Setting: if (collection[mod!.Index].Settings?.Enabled == true) - collection._cache.ReloadMod(mod!, true); + cache.ReloadMod(mod!, true); break; case ModSettingChange.MultiInheritance: case ModSettingChange.MultiEnableState: - collection._cache.FullRecalculation(collection == _active.Default); + FullRecalculation(collection, cache); break; } } @@ -236,5 +278,37 @@ public class CollectionCacheManager : IDisposable, IReadOnlyDictionary private void OnCollectionInheritanceChange(ModCollection collection, bool _) - => collection._cache?.FullRecalculation(collection == _active.Default); + { + if (_caches.TryGetValue(collection, out var cache)) + FullRecalculation(collection, cache); + } + + /// Clear the current cache of a collection. + private void ClearCache(ModCollection collection) + { + if (!_caches.Remove(collection, out var cache)) + return; + + cache.Dispose(); + collection._cache = null; + Penumbra.Log.Verbose($"Cleared cache of collection {collection.AnonymizedName}."); + } + + /// + /// Cache handling. Usually recreate caches on the next framework tick, + /// but at launch create all of them at once. + /// + private void CreateNecessaryCaches() + { + var tasks = _active.SpecialAssignments.Select(p => p.Value) + .Concat(_active.Individuals.Select(p => p.Collection)) + .Prepend(_active.Current) + .Prepend(_active.Default) + .Prepend(_active.Interface) + .Distinct() + .Select(c => CreateCache(c) ? Task.Run(() => CalculateEffectiveFileListInternal(c)) : Task.CompletedTask) + .ToArray(); + + Task.WaitAll(tasks); + } } diff --git a/Penumbra/Meta/Manager/MetaManager.Cmp.cs b/Penumbra/Collections/Cache/MetaCache.Cmp.cs similarity index 95% rename from Penumbra/Meta/Manager/MetaManager.Cmp.cs rename to Penumbra/Collections/Cache/MetaCache.Cmp.cs index f1267220..35d97eee 100644 --- a/Penumbra/Meta/Manager/MetaManager.Cmp.cs +++ b/Penumbra/Collections/Cache/MetaCache.Cmp.cs @@ -6,9 +6,9 @@ using Penumbra.Interop.Structs; using Penumbra.Meta.Files; using Penumbra.Meta.Manipulations; -namespace Penumbra.Meta.Manager; +namespace Penumbra.Collections.Cache; -public partial class MetaManager +public partial class MetaCache { private CmpFile? _cmpFile = null; private readonly List< RspManipulation > _cmpManipulations = new(); diff --git a/Penumbra/Meta/Manager/MetaManager.Eqdp.cs b/Penumbra/Collections/Cache/MetaCache.Eqdp.cs similarity index 97% rename from Penumbra/Meta/Manager/MetaManager.Eqdp.cs rename to Penumbra/Collections/Cache/MetaCache.Eqdp.cs index f5667f68..5bac9169 100644 --- a/Penumbra/Meta/Manager/MetaManager.Eqdp.cs +++ b/Penumbra/Collections/Cache/MetaCache.Eqdp.cs @@ -9,9 +9,9 @@ using Penumbra.Interop.Structs; using Penumbra.Meta.Files; using Penumbra.Meta.Manipulations; -namespace Penumbra.Meta.Manager; +namespace Penumbra.Collections.Cache; -public partial class MetaManager +public partial class MetaCache { private readonly ExpandedEqdpFile?[] _eqdpFiles = new ExpandedEqdpFile[CharacterUtilityData.EqdpIndices.Length]; // TODO: female Hrothgar diff --git a/Penumbra/Meta/Manager/MetaManager.Eqp.cs b/Penumbra/Collections/Cache/MetaCache.Eqp.cs similarity index 95% rename from Penumbra/Meta/Manager/MetaManager.Eqp.cs rename to Penumbra/Collections/Cache/MetaCache.Eqp.cs index 96194f9d..ce0829a0 100644 --- a/Penumbra/Meta/Manager/MetaManager.Eqp.cs +++ b/Penumbra/Collections/Cache/MetaCache.Eqp.cs @@ -6,9 +6,9 @@ using Penumbra.Interop.Structs; using Penumbra.Meta.Files; using Penumbra.Meta.Manipulations; -namespace Penumbra.Meta.Manager; +namespace Penumbra.Collections.Cache; -public partial class MetaManager +public partial class MetaCache { private ExpandedEqpFile? _eqpFile = null; private readonly List< EqpManipulation > _eqpManipulations = new(); diff --git a/Penumbra/Meta/Manager/MetaManager.Est.cs b/Penumbra/Collections/Cache/MetaCache.Est.cs similarity index 98% rename from Penumbra/Meta/Manager/MetaManager.Est.cs rename to Penumbra/Collections/Cache/MetaCache.Est.cs index 7e4a92db..6815be30 100644 --- a/Penumbra/Meta/Manager/MetaManager.Est.cs +++ b/Penumbra/Collections/Cache/MetaCache.Est.cs @@ -7,9 +7,9 @@ using Penumbra.Interop.Structs; using Penumbra.Meta.Files; using Penumbra.Meta.Manipulations; -namespace Penumbra.Meta.Manager; +namespace Penumbra.Collections.Cache; -public partial class MetaManager +public partial class MetaCache { private EstFile? _estFaceFile = null; private EstFile? _estHairFile = null; diff --git a/Penumbra/Meta/Manager/MetaManager.Gmp.cs b/Penumbra/Collections/Cache/MetaCache.Gmp.cs similarity index 95% rename from Penumbra/Meta/Manager/MetaManager.Gmp.cs rename to Penumbra/Collections/Cache/MetaCache.Gmp.cs index bb7e764b..f83efe77 100644 --- a/Penumbra/Meta/Manager/MetaManager.Gmp.cs +++ b/Penumbra/Collections/Cache/MetaCache.Gmp.cs @@ -6,9 +6,9 @@ using Penumbra.Interop.Structs; using Penumbra.Meta.Files; using Penumbra.Meta.Manipulations; -namespace Penumbra.Meta.Manager; +namespace Penumbra.Collections.Cache; -public partial class MetaManager +public partial class MetaCache { private ExpandedGmpFile? _gmpFile = null; private readonly List< GmpManipulation > _gmpManipulations = new(); diff --git a/Penumbra/Meta/Manager/MetaManager.Imc.cs b/Penumbra/Collections/Cache/MetaCache.Imc.cs similarity index 97% rename from Penumbra/Meta/Manager/MetaManager.Imc.cs rename to Penumbra/Collections/Cache/MetaCache.Imc.cs index cc09a13d..89e55b1b 100644 --- a/Penumbra/Meta/Manager/MetaManager.Imc.cs +++ b/Penumbra/Collections/Cache/MetaCache.Imc.cs @@ -6,9 +6,9 @@ using Penumbra.Meta.Files; using Penumbra.Meta.Manipulations; using Penumbra.String.Classes; -namespace Penumbra.Meta.Manager; +namespace Penumbra.Collections.Cache; -public partial class MetaManager +public partial class MetaCache { private readonly Dictionary< Utf8GamePath, ImcFile > _imcFiles = new(); private readonly List< ImcManipulation > _imcManipulations = new(); diff --git a/Penumbra/Meta/Manager/MetaManager.cs b/Penumbra/Collections/Cache/MetaCache.cs similarity index 90% rename from Penumbra/Meta/Manager/MetaManager.cs rename to Penumbra/Collections/Cache/MetaCache.cs index bd3a6086..0b6fa942 100644 --- a/Penumbra/Meta/Manager/MetaManager.cs +++ b/Penumbra/Collections/Cache/MetaCache.cs @@ -4,16 +4,15 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using OtterGui; -using Penumbra.Collections; using Penumbra.Interop.Services; using Penumbra.Interop.Structs; using Penumbra.Meta.Files; using Penumbra.Meta.Manipulations; using Penumbra.Mods; -namespace Penumbra.Meta.Manager; +namespace Penumbra.Collections.Cache; -public partial class MetaManager : IDisposable, IEnumerable< KeyValuePair< MetaManipulation, IMod > > +public partial class MetaCache : IDisposable, IEnumerable< KeyValuePair< MetaManipulation, IMod > > { private readonly Dictionary< MetaManipulation, IMod > _manipulations = new(); private readonly ModCollection _collection; @@ -33,7 +32,7 @@ public partial class MetaManager : IDisposable, IEnumerable< KeyValuePair< MetaM IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - public MetaManager( ModCollection collection ) + public MetaCache( ModCollection collection ) { _collection = collection; if( !Penumbra.CharacterUtility.Ready ) @@ -116,12 +115,12 @@ public partial class MetaManager : IDisposable, IEnumerable< KeyValuePair< MetaM // but they do require the file space to be ready. return manip.ManipulationType switch { - MetaManipulation.Type.Eqp => RevertMod( manip.Eqp ), - MetaManipulation.Type.Gmp => RevertMod( manip.Gmp ), - MetaManipulation.Type.Eqdp => RevertMod( manip.Eqdp ), - MetaManipulation.Type.Est => RevertMod( manip.Est ), - MetaManipulation.Type.Rsp => RevertMod( manip.Rsp ), - MetaManipulation.Type.Imc => RevertMod( manip.Imc ), + MetaManipulation.Type.Eqp => RevertMod( (MetaManipulation)manip.Eqp ), + MetaManipulation.Type.Gmp => RevertMod( (MetaManipulation)manip.Gmp ), + MetaManipulation.Type.Eqdp => RevertMod( (MetaManipulation)manip.Eqdp ), + MetaManipulation.Type.Est => RevertMod( (MetaManipulation)manip.Est ), + MetaManipulation.Type.Rsp => RevertMod( (MetaManipulation)manip.Rsp ), + MetaManipulation.Type.Imc => RevertMod( (MetaManipulation)manip.Imc ), MetaManipulation.Type.Unknown => false, _ => false, }; diff --git a/Penumbra/Collections/Manager/ActiveCollections.cs b/Penumbra/Collections/Manager/ActiveCollections.cs index 2ec3a33b..8fdeaa08 100644 --- a/Penumbra/Collections/Manager/ActiveCollections.cs +++ b/Penumbra/Collections/Manager/ActiveCollections.cs @@ -262,7 +262,7 @@ public class ActiveCollections : ISavable, IDisposable /// /// Load default, current, special, and character collections from config. - /// Then create caches. If a collection does not exist anymore, reset it to an appropriate default. + /// If a collection does not exist anymore, reset it to an appropriate default. /// private void LoadCollections() { @@ -338,7 +338,7 @@ public class ActiveCollections : ISavable, IDisposable configChanged |= ActiveCollectionMigration.MigrateIndividualCollections(_storage, Individuals, jObject); configChanged |= Individuals.ReadJObject(jObject[nameof(Individuals)] as JArray, _storage); - // Save any changes and create all required caches. + // Save any changes. if (configChanged) _saveService.ImmediateSave(this); } diff --git a/Penumbra/Collections/Manager/CollectionManager.cs b/Penumbra/Collections/Manager/CollectionManager.cs index 5e1c5781..16bf754c 100644 --- a/Penumbra/Collections/Manager/CollectionManager.cs +++ b/Penumbra/Collections/Manager/CollectionManager.cs @@ -1,3 +1,5 @@ +using Penumbra.Collections.Cache; + namespace Penumbra.Collections.Manager; public class CollectionManager diff --git a/Penumbra/Collections/Manager/TempCollectionManager.cs b/Penumbra/Collections/Manager/TempCollectionManager.cs index 42bbea19..8c64c8e0 100644 --- a/Penumbra/Collections/Manager/TempCollectionManager.cs +++ b/Penumbra/Collections/Manager/TempCollectionManager.cs @@ -54,9 +54,12 @@ public class TempCollectionManager : IDisposable GlobalChangeCounter = 0; var collection = ModCollection.CreateTemporary(name, ~Count, GlobalChangeCounter++); if (_customCollections.TryAdd(collection.Name.ToLowerInvariant(), collection)) + { + // Temporary collection created. + _communicator.CollectionChange.Invoke(CollectionType.Temporary, null, collection, string.Empty); return collection.Name; + } - collection.ClearCache(); return string.Empty; } @@ -66,12 +69,12 @@ public class TempCollectionManager : IDisposable return false; GlobalChangeCounter += Math.Max(collection.ChangeCounter + 1 - GlobalChangeCounter, 0); - collection.ClearCache(); for (var i = 0; i < Collections.Count; ++i) { if (Collections[i].Collection != collection) continue; + // Temporary collection assignment removed. _communicator.CollectionChange.Invoke(CollectionType.Temporary, collection, null, Collections[i].DisplayName); Collections.Delete(i--); } @@ -81,13 +84,13 @@ public class TempCollectionManager : IDisposable public bool AddIdentifier(ModCollection collection, params ActorIdentifier[] identifiers) { - if (Collections.Add(identifiers, collection)) - { - _communicator.CollectionChange.Invoke(CollectionType.Temporary, null, collection, Collections.Last().DisplayName); - return true; - } + if (!Collections.Add(identifiers, collection)) + return false; + + // Temporary collection assignment added. + _communicator.CollectionChange.Invoke(CollectionType.Temporary, null, collection, Collections.Last().DisplayName); + return true; - return false; } public bool AddIdentifier(string collectionName, params ActorIdentifier[] identifiers) diff --git a/Penumbra/Collections/ModCollection.Cache.Access.cs b/Penumbra/Collections/ModCollection.Cache.Access.cs index d85e3256..f0bb1f29 100644 --- a/Penumbra/Collections/ModCollection.Cache.Access.cs +++ b/Penumbra/Collections/ModCollection.Cache.Access.cs @@ -1,6 +1,5 @@ using OtterGui.Classes; using Penumbra.GameData.Enums; -using Penumbra.Meta.Manager; using Penumbra.Mods; using System; using System.Collections.Generic; @@ -25,25 +24,6 @@ public partial class ModCollection public bool HasCache => _cache != null; - /// - /// Count the number of changes of the effective file list. - /// This is used for material and imc changes. - /// - public int ChangeCounter { get; internal set; } - - // Only create, do not update. - internal void CreateCache(bool isDefault) - { - if (_cache != null) - return; - - CalculateEffectiveFileList(isDefault); - Penumbra.Log.Verbose($"Created new cache for collection {Name}."); - } - - // Force an update with metadata for this cache. - internal void ForceCacheUpdate() - => CalculateEffectiveFileList(this == Penumbra.CollectionManager.Active.Default); // Handle temporary mods for this collection. public void Apply(TemporaryMod tempMod, bool created) @@ -59,15 +39,6 @@ public partial class ModCollection _cache?.RemoveMod(tempMod, tempMod.TotalManipulations > 0); } - - // Clear the current cache. - internal void ClearCache() - { - _cache?.Dispose(); - _cache = null; - Penumbra.Log.Verbose($"Cleared cache of collection {Name}."); - } - public IEnumerable ReverseResolvePath(FullPath path) => _cache?.ReverseResolvePath(path) ?? Array.Empty(); @@ -99,7 +70,7 @@ public partial class ModCollection => _cache!.ResolvedFiles.Remove(path); // Obtain data from the cache. - internal MetaManager? MetaCache + internal MetaCache? MetaCache => _cache?.MetaManipulations; public bool GetImcFile(Utf8GamePath path, [NotNullWhen(true)] out ImcFile? file) @@ -123,25 +94,6 @@ public partial class ModCollection internal SingleArray Conflicts(Mod mod) => _cache?.Conflicts(mod) ?? new SingleArray(); - // Update the effective file list for the given cache. - // Creates a cache if necessary. - public void CalculateEffectiveFileList(bool isDefault) - => Penumbra.Framework.RegisterImportant(nameof(CalculateEffectiveFileList) + Name, () => - CalculateEffectiveFileListInternal(isDefault)); - - internal void CalculateEffectiveFileListInternal(bool isDefault) - { - // Skip the empty collection. - if (Index == 0) - return; - - Penumbra.Log.Debug($"[{Thread.CurrentThread.ManagedThreadId}] Recalculating effective file list for {AnonymizedName}"); - _cache ??= new ModCollectionCache(this); - _cache.FullRecalculation(isDefault); - - Penumbra.Log.Debug($"[{Thread.CurrentThread.ManagedThreadId}] Recalculation of effective file list for {AnonymizedName} finished."); - } - public void SetFiles() { if (_cache == null) diff --git a/Penumbra/Collections/ModCollection.cs b/Penumbra/Collections/ModCollection.cs index 026516cd..84e47897 100644 --- a/Penumbra/Collections/ModCollection.cs +++ b/Penumbra/Collections/ModCollection.cs @@ -43,6 +43,12 @@ public partial class ModCollection /// The index of the collection is set and kept up-to-date by the CollectionManager. public int Index { get; internal set; } + /// + /// Count the number of changes of the effective file list. + /// This is used for material and imc changes. + /// + public int ChangeCounter { get; internal set; } + /// /// If a ModSetting is null, it can be inherited from other collections. /// If no collection provides a setting for the mod, it is just disabled. @@ -123,7 +129,6 @@ public partial class ModCollection Debug.Assert(index < 0, "Temporary collection created with non-negative index."); var ret = new ModCollection(name, index, changeCounter, CurrentVersion, new List(), new List(), new Dictionary()); - ret.CreateCache(false); return ret; } diff --git a/Penumbra/Penumbra.cs b/Penumbra/Penumbra.cs index dafac640..24f13749 100644 --- a/Penumbra/Penumbra.cs +++ b/Penumbra/Penumbra.cs @@ -15,6 +15,7 @@ using Penumbra.Api.Enums; using Penumbra.UI; using Penumbra.Util; using Penumbra.Collections; +using Penumbra.Collections.Cache; using Penumbra.GameData; using Penumbra.GameData.Actors; using Penumbra.Interop.ResourceLoading; @@ -49,7 +50,6 @@ public class Penumbra : IDalamudPlugin public static CollectionManager CollectionManager { get; private set; } = null!; public static TempCollectionManager TempCollections { get; private set; } = null!; public static TempModManager TempMods { get; private set; } = null!; - public static FrameworkManager Framework { get; private set; } = null!; public static ActorManager Actors { get; private set; } = null!; public static IObjectIdentifier Identifier { get; private set; } = null!; public static IGamePathParser GamePathParser { get; private set; } = null!; @@ -81,7 +81,6 @@ public class Penumbra : IDalamudPlugin Config = _tmp.Services.GetRequiredService(); CharacterUtility = _tmp.Services.GetRequiredService(); MetaFileManager = _tmp.Services.GetRequiredService(); - Framework = _tmp.Services.GetRequiredService(); Actors = _tmp.Services.GetRequiredService().AwaitedService; Identifier = _tmp.Services.GetRequiredService().AwaitedService; GamePathParser = _tmp.Services.GetRequiredService(); @@ -251,7 +250,7 @@ public class Penumbra : IDalamudPlugin return name + ':'; } - void PrintCollection(ModCollection c) + void PrintCollection(ModCollection c, CollectionCache _) => sb.Append($"**Collection {c.AnonymizedName}**\n" + $"> **`Inheritances: `** {c.DirectlyInheritsFrom.Count}\n" + $"> **`Enabled Mods: `** {c.ActualSettings.Count(s => s is { Enabled: true })}\n" @@ -260,7 +259,7 @@ public class Penumbra : IDalamudPlugin sb.AppendLine("**Collections**"); sb.Append($"> **`#Collections: `** {CollectionManager.Storage.Count - 1}\n"); sb.Append($"> **`#Temp Collections: `** {TempCollections.Count}\n"); - sb.Append($"> **`Active Collections: `** {CollectionManager.Caches.Count}\n"); + sb.Append($"> **`Active Collections: `** {CollectionManager.Caches.Count - TempCollections.Count}\n"); sb.Append($"> **`Base Collection: `** {CollectionManager.Active.Default.AnonymizedName}\n"); sb.Append($"> **`Interface Collection: `** {CollectionManager.Active.Interface.AnonymizedName}\n"); sb.Append($"> **`Selected Collection: `** {CollectionManager.Active.Current.AnonymizedName}\n"); @@ -274,8 +273,8 @@ public class Penumbra : IDalamudPlugin foreach (var (name, id, collection) in CollectionManager.Active.Individuals.Assignments) sb.Append($"> **`{CharacterName(id[0], name),-30}`** {collection.AnonymizedName}\n"); - foreach (var collection in CollectionManager.Caches.Active) - PrintCollection(collection); + foreach (var (collection, cache) in CollectionManager.Caches.Active) + PrintCollection(collection, cache); return sb.ToString(); }