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;
using OtterGui.Classes; using OtterGui.Classes;
using Penumbra.Meta.Manager;
using Penumbra.Meta.Manipulations; using Penumbra.Meta.Manipulations;
using Penumbra.Mods; using Penumbra.Mods;
using System; using System;
@ -9,24 +8,24 @@ using System.IO;
using System.Linq; using System.Linq;
using Penumbra.Api.Enums; using Penumbra.Api.Enums;
using Penumbra.String.Classes; using Penumbra.String.Classes;
using Penumbra.Mods.Manager; using Penumbra.Mods.Manager;
namespace Penumbra.Collections.Cache; namespace Penumbra.Collections.Cache;
public record struct ModPath(IMod Mod, FullPath Path); public record struct ModPath(IMod Mod, FullPath Path);
public record ModConflicts(IMod Mod2, List<object> Conflicts, bool HasPriority, bool Solved); 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. /// 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> /// </summary>
public class ModCollectionCache : IDisposable public class ModCollectionCache : IDisposable
{ {
private readonly ModCollection _collection; private readonly ModCollection _collection;
private readonly SortedList<string, (SingleArray<IMod>, object?)> _changedItems = new(); public readonly SortedList<string, (SingleArray<IMod>, object?)> _changedItems = new();
public readonly Dictionary<Utf8GamePath, ModPath> ResolvedFiles = new(); public readonly Dictionary<Utf8GamePath, ModPath> ResolvedFiles = new();
public readonly MetaManager MetaManipulations; public readonly MetaCache MetaManipulations;
private readonly Dictionary<IMod, SingleArray<ModConflicts>> _conflicts = new(); public readonly Dictionary<IMod, SingleArray<ModConflicts>> _conflicts = new();
public IEnumerable<SingleArray<ModConflicts>> AllConflicts public IEnumerable<SingleArray<ModConflicts>> AllConflicts
=> _conflicts.Values; => _conflicts.Values;
@ -49,8 +48,8 @@ public class ModCollectionCache : IDisposable
// The cache reacts through events on its collection changing. // The cache reacts through events on its collection changing.
public ModCollectionCache(ModCollection collection) public ModCollectionCache(ModCollection collection)
{ {
_collection = collection; _collection = collection;
MetaManipulations = new MetaManager(_collection); MetaManipulations = new MetaCache(_collection);
} }
public void Dispose() public void Dispose()
@ -62,15 +61,11 @@ public class ModCollectionCache : IDisposable
public FullPath? ResolvePath(Utf8GamePath gameResourcePath) public FullPath? ResolvePath(Utf8GamePath gameResourcePath)
{ {
if (!ResolvedFiles.TryGetValue(gameResourcePath, out var candidate)) if (!ResolvedFiles.TryGetValue(gameResourcePath, out var candidate))
{
return null; return null;
}
if (candidate.Path.InternalName.Length > Utf8GamePath.MaxGamePathLength if (candidate.Path.InternalName.Length > Utf8GamePath.MaxGamePathLength
|| candidate.Path.IsRooted && !candidate.Path.Exists) || candidate.Path.IsRooted && !candidate.Path.Exists)
{
return null; return null;
}
return candidate.Path; return candidate.Path;
} }
@ -80,9 +75,7 @@ public class ModCollectionCache : IDisposable
{ {
var needle = localFilePath.FullName.ToLower(); var needle = localFilePath.FullName.ToLower();
if (localFilePath.IsRooted) if (localFilePath.IsRooted)
{
needle = needle.Replace('/', '\\'); needle = needle.Replace('/', '\\');
}
var iterator = ResolvedFiles var iterator = ResolvedFiles
.Where(f => string.Equals(f.Value.Path.FullName, needle, StringComparison.OrdinalIgnoreCase)) .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. // For files that are not rooted, try to add themselves.
if (!localFilePath.IsRooted && Utf8GamePath.FromString(localFilePath.FullName, out var utf8)) if (!localFilePath.IsRooted && Utf8GamePath.FromString(localFilePath.FullName, out var utf8))
{
iterator = iterator.Prepend(utf8); iterator = iterator.Prepend(utf8);
}
return iterator; return iterator;
} }
@ -103,7 +94,7 @@ public class ModCollectionCache : IDisposable
if (fullPaths.Count == 0) if (fullPaths.Count == 0)
return Array.Empty<HashSet<Utf8GamePath>>(); 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); var dict = new Dictionary<FullPath, int>(fullPaths.Count);
foreach (var (path, idx) in fullPaths.WithIndex()) foreach (var (path, idx) in fullPaths.WithIndex())
{ {
@ -116,43 +107,12 @@ public class ModCollectionCache : IDisposable
foreach (var (game, full) in ResolvedFiles) foreach (var (game, full) in ResolvedFiles)
{ {
if (dict.TryGetValue(full.Path, out var idx)) if (dict.TryGetValue(full.Path, out var idx))
{
ret[idx].Add(game); ret[idx].Add(game);
}
} }
return ret; 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) public void ReloadMod(IMod mod, bool addMetaChanges)
{ {
RemoveMod(mod, 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))) foreach (var (path, _) in mod.AllSubMods.SelectMany(s => s.Files.Concat(s.FileSwaps)))
{ {
if (!ResolvedFiles.TryGetValue(path, out var modPath)) if (!ResolvedFiles.TryGetValue(path, out var modPath))
{
continue; continue;
}
if (modPath.Mod == mod) if (modPath.Mod == mod)
{
ResolvedFiles.Remove(path); ResolvedFiles.Remove(path);
}
} }
foreach (var manipulation in mod.AllSubMods.SelectMany(s => s.Manipulations)) foreach (var manipulation in mod.AllSubMods.SelectMany(s => s.Manipulations))
{ {
if (MetaManipulations.TryGetValue(manipulation, out var registeredMod) && registeredMod == mod) if (MetaManipulations.TryGetValue(manipulation, out var registeredMod) && registeredMod == mod)
{
MetaManipulations.RevertMod(manipulation); MetaManipulations.RevertMod(manipulation);
}
} }
_conflicts.Remove(mod); _conflicts.Remove(mod);
@ -195,13 +149,9 @@ public class ModCollectionCache : IDisposable
{ {
var newConflicts = Conflicts(conflict.Mod2).Remove(c => c.Mod2 == mod); var newConflicts = Conflicts(conflict.Mod2).Remove(c => c.Mod2 == mod);
if (newConflicts.Count > 0) if (newConflicts.Count > 0)
{
_conflicts[conflict.Mod2] = newConflicts; _conflicts[conflict.Mod2] = newConflicts;
}
else else
{
_conflicts.Remove(conflict.Mod2); _conflicts.Remove(conflict.Mod2);
}
} }
} }
@ -224,16 +174,12 @@ public class ModCollectionCache : IDisposable
{ {
var settings = _collection[mod.Index].Settings; var settings = _collection[mod.Index].Settings;
if (settings is not { Enabled: true }) if (settings is not { Enabled: true })
{
return; return;
}
foreach (var (group, groupIndex) in mod.Groups.WithIndex().OrderByDescending(g => g.Item1.Priority)) foreach (var (group, groupIndex) in mod.Groups.WithIndex().OrderByDescending(g => g.Item1.Priority))
{ {
if (group.Count == 0) if (group.Count == 0)
{
continue; continue;
}
var config = settings.Settings[groupIndex]; var config = settings.Settings[groupIndex];
switch (group.Type) switch (group.Type)
@ -241,17 +187,15 @@ public class ModCollectionCache : IDisposable
case GroupType.Single: case GroupType.Single:
AddSubMod(group[(int)config], mod); AddSubMod(group[(int)config], mod);
break; break;
case GroupType.Multi: case GroupType.Multi:
{ {
foreach (var (option, _) in group.WithIndex() foreach (var (option, _) in group.WithIndex()
.Where(p => (1 << p.Item2 & config) != 0) .Where(p => ((1 << p.Item2) & config) != 0)
.OrderByDescending(p => group.OptionPriority(p.Item2))) .OrderByDescending(p => group.OptionPriority(p.Item2)))
{ AddSubMod(option, mod);
AddSubMod(option, mod);
} break;
}
break;
}
} }
} }
} }
@ -262,9 +206,7 @@ public class ModCollectionCache : IDisposable
{ {
++_collection.ChangeCounter; ++_collection.ChangeCounter;
if (Penumbra.ModCaches[mod.Index].TotalManipulations > 0) if (Penumbra.ModCaches[mod.Index].TotalManipulations > 0)
{
AddMetaFiles(); AddMetaFiles();
}
if (_collection == Penumbra.CollectionManager.Active.Default && Penumbra.CharacterUtility.Ready && Penumbra.Config.EnableMods) 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) private void AddSubMod(ISubMod subMod, IMod parentMod)
{ {
foreach (var (path, file) in subMod.Files.Concat(subMod.FileSwaps)) foreach (var (path, file) in subMod.Files.Concat(subMod.FileSwaps))
{
AddFile(path, file, parentMod); AddFile(path, file, parentMod);
}
foreach (var manip in subMod.Manipulations) foreach (var manip in subMod.Manipulations)
{
AddManipulation(manip, parentMod); AddManipulation(manip, parentMod);
}
} }
// Add a specific file redirection, handling potential conflicts. // 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) private void AddFile(Utf8GamePath path, FullPath file, IMod mod)
{ {
if (!ModCollection.CheckFullPath(path, file)) if (!ModCollection.CheckFullPath(path, file))
{
return; return;
}
if (ResolvedFiles.TryAdd(path, new ModPath(mod, file))) if (ResolvedFiles.TryAdd(path, new ModPath(mod, file)))
{
return; return;
}
var modPath = ResolvedFiles[path]; var modPath = ResolvedFiles[path];
// Lower prioritized option in the same mod. // Lower prioritized option in the same mod.
if (mod == modPath.Mod) if (mod == modPath.Mod)
{
return; return;
}
if (AddConflict(path, mod, modPath.Mod)) if (AddConflict(path, mod, modPath.Mod))
{
ResolvedFiles[path] = new ModPath(mod, file); ResolvedFiles[path] = new ModPath(mod, file);
}
} }
@ -327,9 +257,7 @@ public class ModCollectionCache : IDisposable
if (c.Conflicts.Count == 0) if (c.Conflicts.Count == 0)
{ {
if (transitive) if (transitive)
{
RemoveEmptyConflicts(c.Mod2, Conflicts(c.Mod2), false); RemoveEmptyConflicts(c.Mod2, Conflicts(c.Mod2), false);
}
return true; return true;
} }
@ -337,13 +265,9 @@ public class ModCollectionCache : IDisposable
return false; return false;
}); });
if (changedConflicts.Count == 0) if (changedConflicts.Count == 0)
{
_conflicts.Remove(mod); _conflicts.Remove(mod);
}
else else
{
_conflicts[mod] = changedConflicts; _conflicts[mod] = changedConflicts;
}
} }
// Add a new conflict between the added mod and the existing mod. // 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. // Returns if the added mod takes priority before the existing mod.
private bool AddConflict(object data, IMod addedMod, IMod existingMod) 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; var existingPriority = existingMod.Index >= 0 ? _collection[existingMod.Index].Settings!.Priority : existingMod.Priority;
if (existingPriority < addedPriority) if (existingPriority < addedPriority)
@ -360,16 +284,14 @@ public class ModCollectionCache : IDisposable
foreach (var conflict in tmpConflicts) foreach (var conflict in tmpConflicts)
{ {
if (data is Utf8GamePath path && conflict.Conflicts.RemoveAll(p => p is Utf8GamePath x && x.Equals(path)) > 0 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); AddConflict(data, addedMod, conflict.Mod2);
}
} }
RemoveEmptyConflicts(existingMod, tmpConflicts, true); RemoveEmptyConflicts(existingMod, tmpConflicts, true);
} }
var addedConflicts = Conflicts(addedMod); var addedConflicts = Conflicts(addedMod);
var existingConflicts = Conflicts(existingMod); var existingConflicts = Conflicts(existingMod);
if (addedConflicts.FindFirst(c => c.Mod2 == existingMod, out var oldConflicts)) 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. // Lower prioritized option in the same mod.
if (mod == existingMod) if (mod == existingMod)
{
return; return;
}
if (AddConflict(manip, mod, existingMod)) if (AddConflict(manip, mod, existingMod))
{
MetaManipulations.ApplyMod(manip, mod); MetaManipulations.ApplyMod(manip, mod);
}
} }
// Add all necessary meta file redirects. // Add all necessary meta file redirects.
private void AddMetaFiles() public void AddMetaFiles()
=> MetaManipulations.SetImcFiles(); => 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. // Identify and record all manipulated objects for this entire collection.
private void SetChangedItems() private void SetChangedItems()
{ {
if (_changedItemsSaveCounter == _collection.ChangeCounter) if (_changedItemsSaveCounter == _collection.ChangeCounter)
{
return; return;
}
try try
{ {
@ -442,24 +351,18 @@ public class ModCollectionCache : IDisposable
// Skip IMCs because they would result in far too many false-positive items, // 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. // since they are per set instead of per item-slot/item/variant.
var identifier = Penumbra.Identifier; var identifier = Penumbra.Identifier;
var items = new SortedList<string, object?>(512); var items = new SortedList<string, object?>(512);
void AddItems(IMod mod) void AddItems(IMod mod)
{ {
foreach (var (name, obj) in items) foreach (var (name, obj) in items)
{ {
if (!_changedItems.TryGetValue(name, out var data)) if (!_changedItems.TryGetValue(name, out var data))
{
_changedItems.Add(name, (new SingleArray<IMod>(mod), obj)); _changedItems.Add(name, (new SingleArray<IMod>(mod), obj));
}
else if (!data.Item1.Contains(mod)) else if (!data.Item1.Contains(mod))
{
_changedItems[name] = (data.Item1.Append(mod), obj is int x && data.Item2 is int y ? x + y : obj); _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) else if (obj is int x && data.Item2 is int y)
{
_changedItems[name] = (data.Item1, x + y); _changedItems[name] = (data.Item1, x + y);
}
} }
items.Clear(); items.Clear();
@ -482,4 +385,4 @@ public class ModCollectionCache : IDisposable
Penumbra.Log.Error($"Unknown Error:\n{e}"); Penumbra.Log.Error($"Unknown Error:\n{e}");
} }
} }
} }

View file

@ -1,61 +1,53 @@
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using OtterGui.Classes;
using Penumbra.Api; using Penumbra.Api;
using Penumbra.Api.Enums; using Penumbra.Api.Enums;
using Penumbra.Collections.Cache; using Penumbra.Collections.Manager;
using Penumbra.Interop.Services; using Penumbra.Interop.Services;
using Penumbra.Mods; using Penumbra.Mods;
using Penumbra.Mods.Manager; using Penumbra.Mods.Manager;
using Penumbra.Services; 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 FrameworkManager _framework;
private readonly CommunicatorService _communicator; private readonly ActiveCollections _active;
private readonly CharacterUtility _characterUtility; 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, CollectionCache> _caches = new();
private readonly Dictionary<ModCollection, ModCollectionCache> _cache = new();
public int Count public int Count
=> _cache.Count; => _caches.Count;
public IEnumerator<KeyValuePair<ModCollection, ModCollectionCache>> GetEnumerator() public IEnumerable<(ModCollection Collection, CollectionCache Cache)> Active
=> _cache.GetEnumerator(); => _caches.Where(c => c.Key.Index > ModCollection.Empty.Index).Select(p => (p.Key, p.Value));
IEnumerator IEnumerable.GetEnumerator() public CollectionCacheManager(FrameworkManager framework, ActiveCollections active, CommunicatorService communicator,
=> GetEnumerator(); CharacterUtility characterUtility, TempModManager tempMods, ModStorage modStorage, Configuration config,
ResidentResourceManager resources, ModCacheManager modCaches)
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)
{ {
_framework = framework;
_active = active; _active = active;
_communicator = communicator; _communicator = communicator;
_characterUtility = characterUtility; _characterUtility = characterUtility;
_tempMods = tempMods;
_modStorage = modStorage;
_config = config;
_resources = resources;
_modCaches = modCaches;
_communicator.CollectionChange.Subscribe(OnCollectionChange); _communicator.CollectionChange.Subscribe(OnCollectionChange);
_communicator.ModPathChanged.Subscribe(OnModChangeAddition, -100); _communicator.ModPathChanged.Subscribe(OnModChangeAddition, -100);
@ -82,50 +74,103 @@ public class CollectionCacheManager : IDisposable, IReadOnlyDictionary<ModCollec
_characterUtility.LoadingFinished -= IncrementCounters; _characterUtility.LoadingFinished -= IncrementCounters;
} }
/// <summary> /// <summary> Only creates a new cache, does not update an existing one. </summary>
/// Cache handling. Usually recreate caches on the next framework tick, public bool CreateCache(ModCollection collection)
/// but at launch create all of them at once.
/// </summary>
public void CreateNecessaryCaches()
{ {
var tasks = _active.SpecialAssignments.Select(p => p.Value) if (_caches.ContainsKey(collection) || collection.Index == ModCollection.Empty.Index)
.Concat(_active.Individuals.Select(p => p.Collection)) return false;
.Prepend(_active.Current)
.Prepend(_active.Default)
.Prepend(_active.Interface)
.Distinct()
.Select(c => Task.Run(() => c.CalculateEffectiveFileListInternal(c == _active.Default)))
.ToArray();
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) private void OnCollectionChange(CollectionType type, ModCollection? old, ModCollection? newCollection, string displayName)
{ {
if (type is CollectionType.Inactive) if (type is CollectionType.Temporary)
return;
var isDefault = type is CollectionType.Default;
if (newCollection?.Index > ModCollection.Empty.Index)
{ {
newCollection.CreateCache(isDefault); if (newCollection != null && CreateCache(newCollection))
_cache.TryAdd(newCollection, newCollection._cache!); 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) private void OnModChangeRemoval(ModPathChangeType type, Mod mod, DirectoryInfo? oldModPath, DirectoryInfo? newModPath)
{ {
switch (type) switch (type)
{ {
case ModPathChangeType.Deleted: case ModPathChangeType.Deleted:
case ModPathChangeType.StartingReload: 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); collection._cache!.RemoveMod(mod, true);
break; break;
case ModPathChangeType.Moved: 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); collection._cache!.ReloadMod(mod, true);
break; break;
} }
@ -136,13 +181,13 @@ public class CollectionCacheManager : IDisposable, IReadOnlyDictionary<ModCollec
if (type is not (ModPathChangeType.Added or ModPathChangeType.Reloaded)) if (type is not (ModPathChangeType.Added or ModPathChangeType.Reloaded))
return; 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); collection._cache!.AddMod(mod, true);
} }
/// <summary> Apply a mod change to all collections with a cache. </summary> /// <summary> Apply a mod change to all collections with a cache. </summary>
private void OnGlobalModChange(TemporaryMod mod, bool created, bool removed) 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> /// <summary> Remove a cache from a collection if it is active. </summary>
private void RemoveCache(ModCollection? collection) private void RemoveCache(ModCollection? collection)
@ -154,10 +199,7 @@ public class CollectionCacheManager : IDisposable, IReadOnlyDictionary<ModCollec
&& collection.Index != _active.Current.Index && collection.Index != _active.Current.Index
&& _active.SpecialAssignments.All(c => c.Value.Index != collection.Index) && _active.SpecialAssignments.All(c => c.Value.Index != collection.Index)
&& _active.Individuals.All(c => c.Collection.Index != collection.Index)) && _active.Individuals.All(c => c.Collection.Index != collection.Index))
{ ClearCache(collection);
_cache.Remove(collection);
collection.ClearCache();
}
} }
/// <summary> Prepare Changes by removing mods from caches with collections or add or reload mods. </summary> /// <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) 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); collection._cache!.RemoveMod(mod, false);
return; return;
@ -176,7 +218,7 @@ public class CollectionCacheManager : IDisposable, IReadOnlyDictionary<ModCollec
if (!recomputeList) if (!recomputeList)
return; 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) if (reload)
collection._cache!.ReloadMod(mod, true); 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> /// <summary> Increment the counter to ensure new files are loaded after applying meta changes. </summary>
private void IncrementCounters() private void IncrementCounters()
{ {
foreach (var (collection, _) in _cache) foreach (var (collection, _) in _caches)
++collection.ChangeCounter; ++collection.ChangeCounter;
_characterUtility.LoadingFinished -= IncrementCounters; _characterUtility.LoadingFinished -= IncrementCounters;
} }
private void OnModSettingChange(ModCollection collection, ModSettingChange type, Mod? mod, int oldValue, int groupIdx, bool _) 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; return;
switch (type) switch (type)
{ {
case ModSettingChange.Inheritance: case ModSettingChange.Inheritance:
collection._cache.ReloadMod(mod!, true); cache.ReloadMod(mod!, true);
break; break;
case ModSettingChange.EnableState: case ModSettingChange.EnableState:
if (oldValue == 0) if (oldValue == 0)
collection._cache.AddMod(mod!, true); cache.AddMod(mod!, true);
else if (oldValue == 1) else if (oldValue == 1)
collection._cache.RemoveMod(mod!, true); cache.RemoveMod(mod!, true);
else if (collection[mod!.Index].Settings?.Enabled == true) else if (collection[mod!.Index].Settings?.Enabled == true)
collection._cache.ReloadMod(mod!, true); cache.ReloadMod(mod!, true);
else else
collection._cache.RemoveMod(mod!, true); cache.RemoveMod(mod!, true);
break; break;
case ModSettingChange.Priority: case ModSettingChange.Priority:
if (collection._cache.Conflicts(mod!).Count > 0) if (cache.Conflicts(mod!).Count > 0)
collection._cache.ReloadMod(mod!, true); cache.ReloadMod(mod!, true);
break; break;
case ModSettingChange.Setting: case ModSettingChange.Setting:
if (collection[mod!.Index].Settings?.Enabled == true) if (collection[mod!.Index].Settings?.Enabled == true)
collection._cache.ReloadMod(mod!, true); cache.ReloadMod(mod!, true);
break; break;
case ModSettingChange.MultiInheritance: case ModSettingChange.MultiInheritance:
case ModSettingChange.MultiEnableState: case ModSettingChange.MultiEnableState:
collection._cache.FullRecalculation(collection == _active.Default); FullRecalculation(collection, cache);
break; break;
} }
} }
@ -236,5 +278,37 @@ public class CollectionCacheManager : IDisposable, IReadOnlyDictionary<ModCollec
/// just recompute everything. /// just recompute everything.
/// </summary> /// </summary>
private void OnCollectionInheritanceChange(ModCollection collection, bool _) 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.Files;
using Penumbra.Meta.Manipulations; 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 CmpFile? _cmpFile = null;
private readonly List< RspManipulation > _cmpManipulations = new(); private readonly List< RspManipulation > _cmpManipulations = new();

View file

@ -9,9 +9,9 @@ using Penumbra.Interop.Structs;
using Penumbra.Meta.Files; using Penumbra.Meta.Files;
using Penumbra.Meta.Manipulations; 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 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.Files;
using Penumbra.Meta.Manipulations; 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 ExpandedEqpFile? _eqpFile = null;
private readonly List< EqpManipulation > _eqpManipulations = new(); private readonly List< EqpManipulation > _eqpManipulations = new();

View file

@ -7,9 +7,9 @@ using Penumbra.Interop.Structs;
using Penumbra.Meta.Files; using Penumbra.Meta.Files;
using Penumbra.Meta.Manipulations; 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? _estFaceFile = null;
private EstFile? _estHairFile = null; private EstFile? _estHairFile = null;

View file

@ -6,9 +6,9 @@ using Penumbra.Interop.Structs;
using Penumbra.Meta.Files; using Penumbra.Meta.Files;
using Penumbra.Meta.Manipulations; 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 ExpandedGmpFile? _gmpFile = null;
private readonly List< GmpManipulation > _gmpManipulations = new(); private readonly List< GmpManipulation > _gmpManipulations = new();

View file

@ -6,9 +6,9 @@ using Penumbra.Meta.Files;
using Penumbra.Meta.Manipulations; using Penumbra.Meta.Manipulations;
using Penumbra.String.Classes; 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 Dictionary< Utf8GamePath, ImcFile > _imcFiles = new();
private readonly List< ImcManipulation > _imcManipulations = new(); private readonly List< ImcManipulation > _imcManipulations = new();

View file

@ -4,16 +4,15 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using OtterGui; using OtterGui;
using Penumbra.Collections;
using Penumbra.Interop.Services; using Penumbra.Interop.Services;
using Penumbra.Interop.Structs; using Penumbra.Interop.Structs;
using Penumbra.Meta.Files; using Penumbra.Meta.Files;
using Penumbra.Meta.Manipulations; using Penumbra.Meta.Manipulations;
using Penumbra.Mods; 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 Dictionary< MetaManipulation, IMod > _manipulations = new();
private readonly ModCollection _collection; private readonly ModCollection _collection;
@ -33,7 +32,7 @@ public partial class MetaManager : IDisposable, IEnumerable< KeyValuePair< MetaM
IEnumerator IEnumerable.GetEnumerator() IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator(); => GetEnumerator();
public MetaManager( ModCollection collection ) public MetaCache( ModCollection collection )
{ {
_collection = collection; _collection = collection;
if( !Penumbra.CharacterUtility.Ready ) 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. // but they do require the file space to be ready.
return manip.ManipulationType switch return manip.ManipulationType switch
{ {
MetaManipulation.Type.Eqp => RevertMod( manip.Eqp ), MetaManipulation.Type.Eqp => RevertMod( (MetaManipulation)manip.Eqp ),
MetaManipulation.Type.Gmp => RevertMod( manip.Gmp ), MetaManipulation.Type.Gmp => RevertMod( (MetaManipulation)manip.Gmp ),
MetaManipulation.Type.Eqdp => RevertMod( manip.Eqdp ), MetaManipulation.Type.Eqdp => RevertMod( (MetaManipulation)manip.Eqdp ),
MetaManipulation.Type.Est => RevertMod( manip.Est ), MetaManipulation.Type.Est => RevertMod( (MetaManipulation)manip.Est ),
MetaManipulation.Type.Rsp => RevertMod( manip.Rsp ), MetaManipulation.Type.Rsp => RevertMod( (MetaManipulation)manip.Rsp ),
MetaManipulation.Type.Imc => RevertMod( manip.Imc ), MetaManipulation.Type.Imc => RevertMod( (MetaManipulation)manip.Imc ),
MetaManipulation.Type.Unknown => false, MetaManipulation.Type.Unknown => false,
_ => false, _ => false,
}; };

View file

@ -262,7 +262,7 @@ public class ActiveCollections : ISavable, IDisposable
/// <summary> /// <summary>
/// Load default, current, special, and character collections from config. /// 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> /// </summary>
private void LoadCollections() private void LoadCollections()
{ {
@ -338,7 +338,7 @@ public class ActiveCollections : ISavable, IDisposable
configChanged |= ActiveCollectionMigration.MigrateIndividualCollections(_storage, Individuals, jObject); configChanged |= ActiveCollectionMigration.MigrateIndividualCollections(_storage, Individuals, jObject);
configChanged |= Individuals.ReadJObject(jObject[nameof(Individuals)] as JArray, _storage); configChanged |= Individuals.ReadJObject(jObject[nameof(Individuals)] as JArray, _storage);
// Save any changes and create all required caches. // Save any changes.
if (configChanged) if (configChanged)
_saveService.ImmediateSave(this); _saveService.ImmediateSave(this);
} }

View file

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

View file

@ -54,9 +54,12 @@ public class TempCollectionManager : IDisposable
GlobalChangeCounter = 0; GlobalChangeCounter = 0;
var collection = ModCollection.CreateTemporary(name, ~Count, GlobalChangeCounter++); var collection = ModCollection.CreateTemporary(name, ~Count, GlobalChangeCounter++);
if (_customCollections.TryAdd(collection.Name.ToLowerInvariant(), collection)) if (_customCollections.TryAdd(collection.Name.ToLowerInvariant(), collection))
{
// Temporary collection created.
_communicator.CollectionChange.Invoke(CollectionType.Temporary, null, collection, string.Empty);
return collection.Name; return collection.Name;
}
collection.ClearCache();
return string.Empty; return string.Empty;
} }
@ -66,12 +69,12 @@ public class TempCollectionManager : IDisposable
return false; return false;
GlobalChangeCounter += Math.Max(collection.ChangeCounter + 1 - GlobalChangeCounter, 0); GlobalChangeCounter += Math.Max(collection.ChangeCounter + 1 - GlobalChangeCounter, 0);
collection.ClearCache();
for (var i = 0; i < Collections.Count; ++i) for (var i = 0; i < Collections.Count; ++i)
{ {
if (Collections[i].Collection != collection) if (Collections[i].Collection != collection)
continue; continue;
// Temporary collection assignment removed.
_communicator.CollectionChange.Invoke(CollectionType.Temporary, collection, null, Collections[i].DisplayName); _communicator.CollectionChange.Invoke(CollectionType.Temporary, collection, null, Collections[i].DisplayName);
Collections.Delete(i--); Collections.Delete(i--);
} }
@ -81,13 +84,13 @@ public class TempCollectionManager : IDisposable
public bool AddIdentifier(ModCollection collection, params ActorIdentifier[] identifiers) public bool AddIdentifier(ModCollection collection, params ActorIdentifier[] identifiers)
{ {
if (Collections.Add(identifiers, collection)) if (!Collections.Add(identifiers, collection))
{ return false;
_communicator.CollectionChange.Invoke(CollectionType.Temporary, null, collection, Collections.Last().DisplayName);
return true; // 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) public bool AddIdentifier(string collectionName, params ActorIdentifier[] identifiers)

View file

@ -1,6 +1,5 @@
using OtterGui.Classes; using OtterGui.Classes;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.Meta.Manager;
using Penumbra.Mods; using Penumbra.Mods;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -25,25 +24,6 @@ public partial class ModCollection
public bool HasCache public bool HasCache
=> _cache != null; => _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. // Handle temporary mods for this collection.
public void Apply(TemporaryMod tempMod, bool created) public void Apply(TemporaryMod tempMod, bool created)
@ -59,15 +39,6 @@ public partial class ModCollection
_cache?.RemoveMod(tempMod, tempMod.TotalManipulations > 0); _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) public IEnumerable<Utf8GamePath> ReverseResolvePath(FullPath path)
=> _cache?.ReverseResolvePath(path) ?? Array.Empty<Utf8GamePath>(); => _cache?.ReverseResolvePath(path) ?? Array.Empty<Utf8GamePath>();
@ -99,7 +70,7 @@ public partial class ModCollection
=> _cache!.ResolvedFiles.Remove(path); => _cache!.ResolvedFiles.Remove(path);
// Obtain data from the cache. // Obtain data from the cache.
internal MetaManager? MetaCache internal MetaCache? MetaCache
=> _cache?.MetaManipulations; => _cache?.MetaManipulations;
public bool GetImcFile(Utf8GamePath path, [NotNullWhen(true)] out ImcFile? file) public bool GetImcFile(Utf8GamePath path, [NotNullWhen(true)] out ImcFile? file)
@ -123,25 +94,6 @@ public partial class ModCollection
internal SingleArray<ModConflicts> Conflicts(Mod mod) internal SingleArray<ModConflicts> Conflicts(Mod mod)
=> _cache?.Conflicts(mod) ?? new SingleArray<ModConflicts>(); => _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() public void SetFiles()
{ {
if (_cache == null) 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> /// <summary> The index of the collection is set and kept up-to-date by the CollectionManager. </summary>
public int Index { get; internal set; } 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> /// <summary>
/// If a ModSetting is null, it can be inherited from other collections. /// 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. /// 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."); 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>(), var ret = new ModCollection(name, index, changeCounter, CurrentVersion, new List<ModSettings?>(), new List<ModCollection>(),
new Dictionary<string, ModSettings.SavedSettings>()); new Dictionary<string, ModSettings.SavedSettings>());
ret.CreateCache(false);
return ret; return ret;
} }

View file

@ -15,6 +15,7 @@ using Penumbra.Api.Enums;
using Penumbra.UI; using Penumbra.UI;
using Penumbra.Util; using Penumbra.Util;
using Penumbra.Collections; using Penumbra.Collections;
using Penumbra.Collections.Cache;
using Penumbra.GameData; using Penumbra.GameData;
using Penumbra.GameData.Actors; using Penumbra.GameData.Actors;
using Penumbra.Interop.ResourceLoading; using Penumbra.Interop.ResourceLoading;
@ -49,7 +50,6 @@ public class Penumbra : IDalamudPlugin
public static CollectionManager CollectionManager { get; private set; } = null!; public static CollectionManager CollectionManager { get; private set; } = null!;
public static TempCollectionManager TempCollections { get; private set; } = null!; public static TempCollectionManager TempCollections { get; private set; } = null!;
public static TempModManager TempMods { 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 ActorManager Actors { get; private set; } = null!;
public static IObjectIdentifier Identifier { get; private set; } = null!; public static IObjectIdentifier Identifier { get; private set; } = null!;
public static IGamePathParser GamePathParser { 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>(); Config = _tmp.Services.GetRequiredService<Configuration>();
CharacterUtility = _tmp.Services.GetRequiredService<CharacterUtility>(); CharacterUtility = _tmp.Services.GetRequiredService<CharacterUtility>();
MetaFileManager = _tmp.Services.GetRequiredService<MetaFileManager>(); MetaFileManager = _tmp.Services.GetRequiredService<MetaFileManager>();
Framework = _tmp.Services.GetRequiredService<FrameworkManager>();
Actors = _tmp.Services.GetRequiredService<ActorService>().AwaitedService; Actors = _tmp.Services.GetRequiredService<ActorService>().AwaitedService;
Identifier = _tmp.Services.GetRequiredService<IdentifierService>().AwaitedService; Identifier = _tmp.Services.GetRequiredService<IdentifierService>().AwaitedService;
GamePathParser = _tmp.Services.GetRequiredService<IGamePathParser>(); GamePathParser = _tmp.Services.GetRequiredService<IGamePathParser>();
@ -251,7 +250,7 @@ public class Penumbra : IDalamudPlugin
return name + ':'; return name + ':';
} }
void PrintCollection(ModCollection c) void PrintCollection(ModCollection c, CollectionCache _)
=> sb.Append($"**Collection {c.AnonymizedName}**\n" => sb.Append($"**Collection {c.AnonymizedName}**\n"
+ $"> **`Inheritances: `** {c.DirectlyInheritsFrom.Count}\n" + $"> **`Inheritances: `** {c.DirectlyInheritsFrom.Count}\n"
+ $"> **`Enabled Mods: `** {c.ActualSettings.Count(s => s is { Enabled: true })}\n" + $"> **`Enabled Mods: `** {c.ActualSettings.Count(s => s is { Enabled: true })}\n"
@ -260,7 +259,7 @@ public class Penumbra : IDalamudPlugin
sb.AppendLine("**Collections**"); sb.AppendLine("**Collections**");
sb.Append($"> **`#Collections: `** {CollectionManager.Storage.Count - 1}\n"); sb.Append($"> **`#Collections: `** {CollectionManager.Storage.Count - 1}\n");
sb.Append($"> **`#Temp Collections: `** {TempCollections.Count}\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($"> **`Base Collection: `** {CollectionManager.Active.Default.AnonymizedName}\n");
sb.Append($"> **`Interface Collection: `** {CollectionManager.Active.Interface.AnonymizedName}\n"); sb.Append($"> **`Interface Collection: `** {CollectionManager.Active.Interface.AnonymizedName}\n");
sb.Append($"> **`Selected Collection: `** {CollectionManager.Active.Current.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) foreach (var (name, id, collection) in CollectionManager.Active.Individuals.Assignments)
sb.Append($"> **`{CharacterName(id[0], name),-30}`** {collection.AnonymizedName}\n"); sb.Append($"> **`{CharacterName(id[0], name),-30}`** {collection.AnonymizedName}\n");
foreach (var collection in CollectionManager.Caches.Active) foreach (var (collection, cache) in CollectionManager.Caches.Active)
PrintCollection(collection); PrintCollection(collection, cache);
return sb.ToString(); return sb.ToString();
} }