This commit is contained in:
Ottermandias 2023-04-14 22:26:14 +02:00
parent 0108e51636
commit 85fb98b557
15 changed files with 230 additions and 293 deletions

View file

@ -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<object> Conflicts, bool HasPriority, bool Solved);
/// <summary>
/// <summary>
/// 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.
/// </summary>
public class ModCollectionCache : IDisposable
{
private readonly ModCollection _collection;
private readonly SortedList<string, (SingleArray<IMod>, object?)> _changedItems = new();
public readonly Dictionary<Utf8GamePath, ModPath> ResolvedFiles = new();
public readonly MetaManager MetaManipulations;
private readonly Dictionary<IMod, SingleArray<ModConflicts>> _conflicts = new();
private readonly ModCollection _collection;
public readonly SortedList<string, (SingleArray<IMod>, object?)> _changedItems = new();
public readonly Dictionary<Utf8GamePath, ModPath> ResolvedFiles = new();
public readonly MetaCache MetaManipulations;
public readonly Dictionary<IMod, SingleArray<ModConflicts>> _conflicts = new();
public IEnumerable<SingleArray<ModConflicts>> 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<HashSet<Utf8GamePath>>();
var ret = new HashSet<Utf8GamePath>[fullPaths.Count];
var ret = new HashSet<Utf8GamePath>[fullPaths.Count];
var dict = new Dictionary<FullPath, int>(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<TemporaryMod>()))
{
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<string, object?>(512);
var items = new SortedList<string, object?>(512);
void AddItems(IMod mod)
{
foreach (var (name, obj) in items)
{
if (!_changedItems.TryGetValue(name, out var data))
{
_changedItems.Add(name, (new SingleArray<IMod>(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}");
}
}
}
}

View file

@ -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<ModCollection, ModCollectionCache>
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<ModCollection, ModCollectionCache> _cache = new();
private readonly Dictionary<ModCollection, CollectionCache> _caches = new();
public int Count
=> _cache.Count;
=> _caches.Count;
public IEnumerator<KeyValuePair<ModCollection, ModCollectionCache>> 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<ModCollection> Keys
=> _cache.Keys;
public IEnumerable<ModCollectionCache> Values
=> _cache.Values;
public IEnumerable<ModCollection> 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<ModCollec
_characterUtility.LoadingFinished -= IncrementCounters;
}
/// <summary>
/// Cache handling. Usually recreate caches on the next framework tick,
/// but at launch create all of them at once.
/// </summary>
public void CreateNecessaryCaches()
/// <summary> Only creates a new cache, does not update an existing one. </summary>
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;
}
/// <summary>
/// Update the effective file list for the given cache.
/// Does not create caches.
/// </summary>
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<TemporaryMod>()))
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<ModCollec
if (type is not (ModPathChangeType.Added or ModPathChangeType.Reloaded))
return;
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!.AddMod(mod, true);
}
/// <summary> Apply a mod change to all collections with a cache. </summary>
private void OnGlobalModChange(TemporaryMod mod, bool created, bool removed)
=> TempModManager.OnGlobalModChange(_cache.Keys, mod, created, removed);
=> TempModManager.OnGlobalModChange(_caches.Keys, mod, created, removed);
/// <summary> Remove a cache from a collection if it is active. </summary>
private void RemoveCache(ModCollection? collection)
@ -154,10 +199,7 @@ public class CollectionCacheManager : IDisposable, IReadOnlyDictionary<ModCollec
&& collection.Index != _active.Current.Index
&& _active.SpecialAssignments.All(c => c.Value.Index != collection.Index)
&& _active.Individuals.All(c => c.Collection.Index != collection.Index))
{
_cache.Remove(collection);
collection.ClearCache();
}
ClearCache(collection);
}
/// <summary> Prepare Changes by removing mods from caches with collections or add or reload mods. </summary>
@ -165,7 +207,7 @@ public class CollectionCacheManager : IDisposable, IReadOnlyDictionary<ModCollec
{
if (type is ModOptionChangeType.PrepareChange)
{
foreach (var collection in _cache.Keys.Where(collection => 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<ModCollec
if (!recomputeList)
return;
foreach (var collection in _cache.Keys.Where(collection => 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<ModCollec
/// <summary> Increment the counter to ensure new files are loaded after applying meta changes. </summary>
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<ModCollec
/// just recompute everything.
/// </summary>
private void OnCollectionInheritanceChange(ModCollection collection, bool _)
=> collection._cache?.FullRecalculation(collection == _active.Default);
{
if (_caches.TryGetValue(collection, out var cache))
FullRecalculation(collection, cache);
}
/// <summary> Clear the current cache of a collection. </summary>
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}.");
}
/// <summary>
/// Cache handling. Usually recreate caches on the next framework tick,
/// but at launch create all of them at once.
/// </summary>
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);
}
}

View file

@ -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();

View file

@ -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

View file

@ -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();

View file

@ -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;

View file

@ -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();

View file

@ -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();

View file

@ -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,
};

View file

@ -262,7 +262,7 @@ public class ActiveCollections : ISavable, IDisposable
/// <summary>
/// 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.
/// </summary>
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);
}

View file

@ -1,3 +1,5 @@
using Penumbra.Collections.Cache;
namespace Penumbra.Collections.Manager;
public class CollectionManager

View file

@ -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)

View file

@ -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;
/// <summary>
/// Count the number of changes of the effective file list.
/// This is used for material and imc changes.
/// </summary>
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<Utf8GamePath> ReverseResolvePath(FullPath path)
=> _cache?.ReverseResolvePath(path) ?? Array.Empty<Utf8GamePath>();
@ -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<ModConflicts> Conflicts(Mod mod)
=> _cache?.Conflicts(mod) ?? new SingleArray<ModConflicts>();
// 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)

View file

@ -43,6 +43,12 @@ public partial class ModCollection
/// <summary> The index of the collection is set and kept up-to-date by the CollectionManager. </summary>
public int Index { get; internal set; }
/// <summary>
/// Count the number of changes of the effective file list.
/// This is used for material and imc changes.
/// </summary>
public int ChangeCounter { get; internal set; }
/// <summary>
/// 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<ModSettings?>(), new List<ModCollection>(),
new Dictionary<string, ModSettings.SavedSettings>());
ret.CreateCache(false);
return ret;
}

View file

@ -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<Configuration>();
CharacterUtility = _tmp.Services.GetRequiredService<CharacterUtility>();
MetaFileManager = _tmp.Services.GetRequiredService<MetaFileManager>();
Framework = _tmp.Services.GetRequiredService<FrameworkManager>();
Actors = _tmp.Services.GetRequiredService<ActorService>().AwaitedService;
Identifier = _tmp.Services.GetRequiredService<IdentifierService>().AwaitedService;
GamePathParser = _tmp.Services.GetRequiredService<IGamePathParser>();
@ -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();
}