Current state.

This commit is contained in:
Ottermandias 2024-12-27 16:02:50 +01:00
parent 67305d507a
commit 98a89bb2b4
28 changed files with 606 additions and 265 deletions

View file

@ -260,7 +260,7 @@ public sealed class CollectionCache : IDisposable
if (mod.Index < 0)
return mod.GetData();
var settings = _collection[mod.Index].Settings;
var settings = _collection.GetActualSettings(mod.Index).Settings;
return settings is not { Enabled: true }
? AppliedModData.Empty
: mod.GetData(settings);
@ -342,8 +342,8 @@ public sealed class CollectionCache : 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 existingPriority = existingMod.Index >= 0 ? _collection[existingMod.Index].Settings!.Priority : existingMod.Priority;
var addedPriority = addedMod.Index >= 0 ? _collection.GetActualSettings(addedMod.Index).Settings!.Priority : addedMod.Priority;
var existingPriority = existingMod.Index >= 0 ? _collection.GetActualSettings(existingMod.Index).Settings!.Priority : existingMod.Priority;
if (existingPriority < addedPriority)
{

View file

@ -231,11 +231,11 @@ public class CollectionCacheManager : IDisposable, IService
{
case ModPathChangeType.Deleted:
case ModPathChangeType.StartingReload:
foreach (var collection in _storage.Where(c => c.HasCache && c[mod.Index].Settings?.Enabled == true))
foreach (var collection in _storage.Where(c => c.HasCache && c.GetActualSettings(mod.Index).Settings?.Enabled == true))
collection._cache!.RemoveMod(mod, true);
break;
case ModPathChangeType.Moved:
foreach (var collection in _storage.Where(c => c.HasCache && c[mod.Index].Settings?.Enabled == true))
foreach (var collection in _storage.Where(c => c.HasCache && c.GetActualSettings(mod.Index).Settings?.Enabled == true))
collection._cache!.ReloadMod(mod, true);
break;
}
@ -246,7 +246,7 @@ public class CollectionCacheManager : IDisposable, IService
if (type is not (ModPathChangeType.Added or ModPathChangeType.Reloaded))
return;
foreach (var collection in _storage.Where(c => c.HasCache && c[mod.Index].Settings?.Enabled == true))
foreach (var collection in _storage.Where(c => c.HasCache && c.GetActualSettings(mod.Index).Settings?.Enabled == true))
collection._cache!.AddMod(mod, true);
}
@ -273,7 +273,7 @@ public class CollectionCacheManager : IDisposable, IService
{
if (type is ModOptionChangeType.PrepareChange)
{
foreach (var collection in _storage.Where(collection => collection.HasCache && collection[mod.Index].Settings is { Enabled: true }))
foreach (var collection in _storage.Where(collection => collection.HasCache && collection.GetActualSettings(mod.Index).Settings is { Enabled: true }))
collection._cache!.RemoveMod(mod, false);
return;
@ -284,7 +284,7 @@ public class CollectionCacheManager : IDisposable, IService
if (!recomputeList)
return;
foreach (var collection in _storage.Where(collection => collection.HasCache && collection[mod.Index].Settings is { Enabled: true }))
foreach (var collection in _storage.Where(collection => collection.HasCache && collection.GetActualSettings(mod.Index).Settings is { Enabled: true }))
{
if (justAdd)
collection._cache!.AddMod(mod, true);
@ -317,7 +317,7 @@ public class CollectionCacheManager : IDisposable, IService
cache.AddMod(mod!, true);
else if (oldValue == Setting.True)
cache.RemoveMod(mod!, true);
else if (collection[mod!.Index].Settings?.Enabled == true)
else if (collection.GetActualSettings(mod!.Index).Settings?.Enabled == true)
cache.ReloadMod(mod!, true);
else
cache.RemoveMod(mod!, true);
@ -329,8 +329,8 @@ public class CollectionCacheManager : IDisposable, IService
break;
case ModSettingChange.Setting:
if (collection[mod!.Index].Settings?.Enabled == true)
cache.ReloadMod(mod!, true);
if (collection.GetActualSettings(mod!.Index).Settings?.Enabled == true)
cache.ReloadMod(mod, true);
break;
case ModSettingChange.MultiInheritance:

View file

@ -282,7 +282,7 @@ public class ActiveCollections : ISavable, IDisposable, IService
.Prepend(Interface)
.Prepend(Default)
.Concat(Individuals.Assignments.Select(kvp => kvp.Collection))
.SelectMany(c => c.GetFlattenedInheritance()).Contains(Current);
.SelectMany(c => c.Inheritance.FlatHierarchy).Contains(Current);
/// <summary> Save if any of the active collections is changed and set new collections to Current. </summary>
private void OnCollectionChange(CollectionType collectionType, ModCollection? oldCollection, ModCollection? newCollection, string _3)

View file

@ -26,12 +26,12 @@ public class CollectionEditor(SaveService saveService, CommunicatorService commu
/// </summary>
public bool SetModState(ModCollection collection, Mod mod, bool newValue)
{
var oldValue = collection.Settings[mod.Index]?.Enabled ?? collection[mod.Index].Settings?.Enabled ?? false;
var oldValue = collection.GetInheritedSettings(mod.Index).Settings?.Enabled ?? false;
if (newValue == oldValue)
return false;
var inheritance = FixInheritance(collection, mod, false);
((List<ModSettings?>)collection.Settings)[mod.Index]!.Enabled = newValue;
collection.GetOwnSettings(mod.Index)!.Enabled = newValue;
InvokeChange(collection, ModSettingChange.EnableState, mod, inheritance ? Setting.Indefinite : newValue ? Setting.False : Setting.True,
0);
return true;
@ -55,13 +55,13 @@ public class CollectionEditor(SaveService saveService, CommunicatorService commu
var changes = false;
foreach (var mod in mods)
{
var oldValue = collection.Settings[mod.Index]?.Enabled;
var oldValue = collection.GetOwnSettings(mod.Index)?.Enabled;
if (newValue == oldValue)
continue;
FixInheritance(collection, mod, false);
((List<ModSettings?>)collection.Settings)[mod.Index]!.Enabled = newValue;
changes = true;
collection.GetOwnSettings(mod.Index)!.Enabled = newValue;
changes = true;
}
if (!changes)
@ -76,12 +76,12 @@ public class CollectionEditor(SaveService saveService, CommunicatorService commu
/// </summary>
public bool SetModPriority(ModCollection collection, Mod mod, ModPriority newValue)
{
var oldValue = collection.Settings[mod.Index]?.Priority ?? collection[mod.Index].Settings?.Priority ?? ModPriority.Default;
var oldValue = collection.GetInheritedSettings(mod.Index).Settings?.Priority ?? ModPriority.Default;
if (newValue == oldValue)
return false;
var inheritance = FixInheritance(collection, mod, false);
((List<ModSettings?>)collection.Settings)[mod.Index]!.Priority = newValue;
collection.GetOwnSettings(mod.Index)!.Priority = newValue;
InvokeChange(collection, ModSettingChange.Priority, mod, inheritance ? Setting.Indefinite : oldValue.AsSetting, 0);
return true;
}
@ -92,15 +92,13 @@ public class CollectionEditor(SaveService saveService, CommunicatorService commu
/// </summary>
public bool SetModSetting(ModCollection collection, Mod mod, int groupIdx, Setting newValue)
{
var settings = collection.Settings[mod.Index] != null
? collection.Settings[mod.Index]!.Settings
: collection[mod.Index].Settings?.Settings;
var settings = collection.GetInheritedSettings(mod.Index).Settings?.Settings;
var oldValue = settings?[groupIdx] ?? mod.Groups[groupIdx].DefaultSettings;
if (oldValue == newValue)
return false;
var inheritance = FixInheritance(collection, mod, false);
((List<ModSettings?>)collection.Settings)[mod.Index]!.SetValue(mod, groupIdx, newValue);
collection.GetOwnSettings(mod.Index)!.SetValue(mod, groupIdx, newValue);
InvokeChange(collection, ModSettingChange.Setting, mod, inheritance ? Setting.Indefinite : oldValue, groupIdx);
return true;
}
@ -115,10 +113,10 @@ public class CollectionEditor(SaveService saveService, CommunicatorService commu
// If it does not exist, check unused settings.
// If it does not exist and has no unused settings, also use null.
ModSettings.SavedSettings? savedSettings = sourceMod != null
? collection.Settings[sourceMod.Index] != null
? new ModSettings.SavedSettings(collection.Settings[sourceMod.Index]!, sourceMod)
? collection.GetOwnSettings(sourceMod.Index) is { } ownSettings
? new ModSettings.SavedSettings(ownSettings, sourceMod)
: null
: collection.UnusedSettings.TryGetValue(sourceName, out var s)
: collection.Settings.Unused.TryGetValue(sourceName, out var s)
? s
: null;
@ -148,10 +146,10 @@ public class CollectionEditor(SaveService saveService, CommunicatorService commu
// or remove any unused settings for the target if they are inheriting.
if (savedSettings != null)
{
((Dictionary<string, ModSettings.SavedSettings>)collection.UnusedSettings)[targetName] = savedSettings.Value;
((Dictionary<string, ModSettings.SavedSettings>)collection.Settings.Unused)[targetName] = savedSettings.Value;
saveService.QueueSave(new ModCollectionSave(modStorage, collection));
}
else if (((Dictionary<string, ModSettings.SavedSettings>)collection.UnusedSettings).Remove(targetName))
else if (((Dictionary<string, ModSettings.SavedSettings>)collection.Settings.Unused).Remove(targetName))
{
saveService.QueueSave(new ModCollectionSave(modStorage, collection));
}
@ -166,12 +164,12 @@ public class CollectionEditor(SaveService saveService, CommunicatorService commu
/// </summary>
private static bool FixInheritance(ModCollection collection, Mod mod, bool inherit)
{
var settings = collection.Settings[mod.Index];
var settings = collection.GetOwnSettings(mod.Index);
if (inherit == (settings == null))
return false;
((List<ModSettings?>)collection.Settings)[mod.Index] =
inherit ? null : collection[mod.Index].Settings?.DeepCopy() ?? ModSettings.DefaultSettings(mod);
ModSettings? settings1 = inherit ? null : collection.GetInheritedSettings(mod.Index).Settings?.DeepCopy() ?? ModSettings.DefaultSettings(mod);
collection.Settings.Set(mod.Index, settings1);
return true;
}
@ -188,7 +186,7 @@ public class CollectionEditor(SaveService saveService, CommunicatorService commu
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private void RecurseInheritors(ModCollection directParent, ModSettingChange type, Mod? mod, Setting oldValue, int groupIdx)
{
foreach (var directInheritor in directParent.DirectParentOf)
foreach (var directInheritor in directParent.Inheritance.DirectlyInheritedBy)
{
switch (type)
{
@ -197,7 +195,7 @@ public class CollectionEditor(SaveService saveService, CommunicatorService commu
communicator.ModSettingChanged.Invoke(directInheritor, type, null, oldValue, groupIdx, true);
break;
default:
if (directInheritor.Settings[mod!.Index] == null)
if (directInheritor.GetOwnSettings(mod!.Index) == null)
communicator.ModSettingChanged.Invoke(directInheritor, type, mod, oldValue, groupIdx, true);
break;
}

View file

@ -41,8 +41,8 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable, ISer
public ModCollection CreateFromData(Guid id, string name, int version, Dictionary<string, ModSettings.SavedSettings> allSettings,
IReadOnlyList<string> inheritances)
{
var newCollection = ModCollection.CreateFromData(_saveService, _modStorage, new ModCollectionIdentity(id, CurrentCollectionId, name, Count), version, allSettings,
inheritances);
var newCollection = ModCollection.CreateFromData(_saveService, _modStorage,
new ModCollectionIdentity(id, CurrentCollectionId, name, Count), version, allSettings, inheritances);
_collectionsByLocal[CurrentCollectionId] = newCollection;
CurrentCollectionId += 1;
return newCollection;
@ -196,8 +196,8 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable, ISer
/// <summary> Remove all settings for not currently-installed mods from the given collection. </summary>
public void CleanUnavailableSettings(ModCollection collection)
{
var any = collection.UnusedSettings.Count > 0;
((Dictionary<string, ModSettings.SavedSettings>)collection.UnusedSettings).Clear();
var any = collection.Settings.Unused.Count > 0;
((Dictionary<string, ModSettings.SavedSettings>)collection.Settings.Unused).Clear();
if (any)
_saveService.QueueSave(new ModCollectionSave(_modStorage, collection));
}
@ -205,7 +205,7 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable, ISer
/// <summary> Remove a specific setting for not currently-installed mods from the given collection. </summary>
public void CleanUnavailableSetting(ModCollection collection, string? setting)
{
if (setting != null && ((Dictionary<string, ModSettings.SavedSettings>)collection.UnusedSettings).Remove(setting))
if (setting != null && ((Dictionary<string, ModSettings.SavedSettings>)collection.Settings.Unused).Remove(setting))
_saveService.QueueSave(new ModCollectionSave(_modStorage, collection));
}
@ -307,7 +307,7 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable, ISer
private void OnModDiscoveryStarted()
{
foreach (var collection in this)
collection.PrepareModDiscovery(_modStorage);
collection.Settings.PrepareModDiscovery(_modStorage);
}
/// <summary> Restore all settings in all collections to mods. </summary>
@ -315,7 +315,7 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable, ISer
{
// Re-apply all mod settings.
foreach (var collection in this)
collection.ApplyModSettings(_saveService, _modStorage);
collection.Settings.ApplyModSettings(collection, _saveService, _modStorage);
}
/// <summary> Add or remove a mod from all collections, or re-save all collections where the mod has settings. </summary>
@ -326,21 +326,22 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable, ISer
{
case ModPathChangeType.Added:
foreach (var collection in this)
collection.AddMod(mod);
collection.Settings.AddMod(mod);
break;
case ModPathChangeType.Deleted:
foreach (var collection in this)
collection.RemoveMod(mod);
collection.Settings.RemoveMod(mod);
break;
case ModPathChangeType.Moved:
foreach (var collection in this.Where(collection => collection.Settings[mod.Index] != null))
foreach (var collection in this.Where(collection => collection.GetOwnSettings(mod.Index) != null))
_saveService.QueueSave(new ModCollectionSave(_modStorage, collection));
break;
case ModPathChangeType.Reloaded:
foreach (var collection in this)
{
if (collection.Settings[mod.Index]?.Settings.FixAll(mod) ?? false)
if (collection.GetOwnSettings(mod.Index)?.Settings.FixAll(mod) ?? false)
_saveService.QueueSave(new ModCollectionSave(_modStorage, collection));
collection.Settings.SetTemporary(mod.Index, null);
}
break;
@ -357,8 +358,9 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable, ISer
foreach (var collection in this)
{
if (collection.Settings[mod.Index]?.HandleChanges(type, mod, group, option, movedToIdx) ?? false)
if (collection.GetOwnSettings(mod.Index)?.HandleChanges(type, mod, group, option, movedToIdx) ?? false)
_saveService.QueueSave(new ModCollectionSave(_modStorage, collection));
collection.Settings.SetTemporary(mod.Index, null);
}
}
@ -370,7 +372,7 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable, ISer
foreach (var collection in this)
{
var (settings, _) = collection[mod.Index];
var (settings, _) = collection.GetActualSettings(mod.Index);
if (settings is { Enabled: true })
collection.Counters.IncrementChange();
}

View file

@ -1,7 +1,6 @@
using Dalamud.Interface.ImGuiNotification;
using OtterGui;
using OtterGui.Classes;
using OtterGui.Filesystem;
using OtterGui.Services;
using Penumbra.Communication;
using Penumbra.Mods.Manager;
@ -63,10 +62,10 @@ public class InheritanceManager : IDisposable, IService
if (ReferenceEquals(potentialParent, potentialInheritor))
return ValidInheritance.Self;
if (potentialInheritor.DirectlyInheritsFrom.Contains(potentialParent))
if (potentialInheritor.Inheritance.DirectlyInheritsFrom.Contains(potentialParent))
return ValidInheritance.Contained;
if (ModCollection.InheritedCollections(potentialParent).Any(c => ReferenceEquals(c, potentialInheritor)))
if (potentialParent.Inheritance.FlatHierarchy.Any(c => ReferenceEquals(c, potentialInheritor)))
return ValidInheritance.Circle;
return ValidInheritance.Valid;
@ -83,24 +82,22 @@ public class InheritanceManager : IDisposable, IService
/// <summary> Remove an existing inheritance from a collection. </summary>
public void RemoveInheritance(ModCollection inheritor, int idx)
{
var parent = inheritor.DirectlyInheritsFrom[idx];
((List<ModCollection>)inheritor.DirectlyInheritsFrom).RemoveAt(idx);
((List<ModCollection>)parent.DirectParentOf).Remove(inheritor);
var parent = inheritor.Inheritance.RemoveInheritanceAt(inheritor, idx);
_saveService.QueueSave(new ModCollectionSave(_modStorage, inheritor));
_communicator.CollectionInheritanceChanged.Invoke(inheritor, false);
RecurseInheritanceChanges(inheritor);
RecurseInheritanceChanges(inheritor, true);
Penumbra.Log.Debug($"Removed {parent.Identity.AnonymizedName} from {inheritor.Identity.AnonymizedName} inheritances.");
}
/// <summary> Order in the inheritance list is relevant. </summary>
public void MoveInheritance(ModCollection inheritor, int from, int to)
{
if (!((List<ModCollection>)inheritor.DirectlyInheritsFrom).Move(from, to))
if (!inheritor.Inheritance.MoveInheritance(inheritor, from, to))
return;
_saveService.QueueSave(new ModCollectionSave(_modStorage, inheritor));
_communicator.CollectionInheritanceChanged.Invoke(inheritor, false);
RecurseInheritanceChanges(inheritor);
RecurseInheritanceChanges(inheritor, true);
Penumbra.Log.Debug($"Moved {inheritor.Identity.AnonymizedName}s inheritance {from} to {to}.");
}
@ -110,15 +107,15 @@ public class InheritanceManager : IDisposable, IService
if (CheckValidInheritance(inheritor, parent) != ValidInheritance.Valid)
return false;
((List<ModCollection>)inheritor.DirectlyInheritsFrom).Add(parent);
((List<ModCollection>)parent.DirectParentOf).Add(inheritor);
inheritor.Inheritance.AddInheritance(inheritor, parent);
if (invokeEvent)
{
_saveService.QueueSave(new ModCollectionSave(_modStorage, inheritor));
_communicator.CollectionInheritanceChanged.Invoke(inheritor, false);
RecurseInheritanceChanges(inheritor);
}
RecurseInheritanceChanges(inheritor, invokeEvent);
Penumbra.Log.Debug($"Added {parent.Identity.AnonymizedName} to {inheritor.Identity.AnonymizedName} inheritances.");
return true;
}
@ -131,11 +128,11 @@ public class InheritanceManager : IDisposable, IService
{
foreach (var collection in _storage)
{
if (collection.InheritanceByName == null)
if (collection.Inheritance.ConsumeNames() is not { } byName)
continue;
var changes = false;
foreach (var subCollectionName in collection.InheritanceByName)
foreach (var subCollectionName in byName)
{
if (Guid.TryParse(subCollectionName, out var guid) && _storage.ById(guid, out var subCollection))
{
@ -143,7 +140,8 @@ public class InheritanceManager : IDisposable, IService
continue;
changes = true;
Penumbra.Messager.NotificationMessage($"{collection.Identity.Name} can not inherit from {subCollection.Identity.Name}, removed.",
Penumbra.Messager.NotificationMessage(
$"{collection.Identity.Name} can not inherit from {subCollection.Identity.Name}, removed.",
NotificationType.Warning);
}
else if (_storage.ByName(subCollectionName, out subCollection))
@ -153,7 +151,8 @@ public class InheritanceManager : IDisposable, IService
if (AddInheritance(collection, subCollection, false))
continue;
Penumbra.Messager.NotificationMessage($"{collection.Identity.Name} can not inherit from {subCollection.Identity.Name}, removed.",
Penumbra.Messager.NotificationMessage(
$"{collection.Identity.Name} can not inherit from {subCollection.Identity.Name}, removed.",
NotificationType.Warning);
}
else
@ -165,7 +164,6 @@ public class InheritanceManager : IDisposable, IService
}
}
collection.InheritanceByName = null;
if (changes)
_saveService.ImmediateSave(new ModCollectionSave(_modStorage, collection));
}
@ -178,20 +176,22 @@ public class InheritanceManager : IDisposable, IService
foreach (var c in _storage)
{
var inheritedIdx = c.DirectlyInheritsFrom.IndexOf(old);
var inheritedIdx = c.Inheritance.DirectlyInheritsFrom.IndexOf(old);
if (inheritedIdx >= 0)
RemoveInheritance(c, inheritedIdx);
((List<ModCollection>)c.DirectParentOf).Remove(old);
c.Inheritance.RemoveChild(old);
}
}
private void RecurseInheritanceChanges(ModCollection newInheritor)
private void RecurseInheritanceChanges(ModCollection newInheritor, bool invokeEvent)
{
foreach (var inheritor in newInheritor.DirectParentOf)
foreach (var inheritor in newInheritor.Inheritance.DirectlyInheritedBy)
{
_communicator.CollectionInheritanceChanged.Invoke(inheritor, true);
RecurseInheritanceChanges(inheritor);
ModCollectionInheritance.UpdateFlattenedInheritance(inheritor);
RecurseInheritanceChanges(inheritor, invokeEvent);
if (invokeEvent)
_communicator.CollectionInheritanceChanged.Invoke(inheritor, true);
}
}
}

View file

@ -26,12 +26,12 @@ internal static class ModCollectionMigration
// Remove all completely defaulted settings from active and inactive mods.
for (var i = 0; i < collection.Settings.Count; ++i)
{
if (SettingIsDefaultV0(collection.Settings[i]))
((List<ModSettings?>)collection.Settings)[i] = null;
if (SettingIsDefaultV0(collection.GetOwnSettings(i)))
collection.Settings.SetAll(i, FullModSettings.Empty);
}
foreach (var (key, _) in collection.UnusedSettings.Where(kvp => SettingIsDefaultV0(kvp.Value)).ToList())
((Dictionary<string, ModSettings.SavedSettings>)collection.UnusedSettings).Remove(key);
foreach (var (key, _) in collection.Settings.Unused.Where(kvp => SettingIsDefaultV0(kvp.Value)).ToList())
collection.Settings.RemoveUnused(key);
return true;
}

View file

@ -1,4 +1,3 @@
using Penumbra.Mods;
using Penumbra.Mods.Manager;
using Penumbra.Collections.Manager;
using Penumbra.Mods.Settings;
@ -22,70 +21,74 @@ public partial class ModCollection
/// Create the always available Empty Collection that will always sit at index 0,
/// can not be deleted and does never create a cache.
/// </summary>
public static readonly ModCollection Empty = new(ModCollectionIdentity.Empty, 0, CurrentVersion, [], [], []);
public static readonly ModCollection Empty = new(ModCollectionIdentity.Empty, 0, CurrentVersion, new ModSettingProvider(),
new ModCollectionInheritance());
public ModCollectionIdentity Identity;
public override string ToString()
=> Identity.ToString();
public CollectionCounters Counters;
public readonly ModSettingProvider Settings;
public ModCollectionInheritance Inheritance;
public CollectionCounters Counters;
/// <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.
/// </summary>
public readonly IReadOnlyList<ModSettings?> Settings;
/// <summary> Settings for deleted mods will be kept via the mods identifier (directory name). </summary>
public readonly IReadOnlyDictionary<string, ModSettings.SavedSettings> UnusedSettings;
/// <summary> Inheritances stored before they can be applied. </summary>
public IReadOnlyList<string>? InheritanceByName;
/// <summary> Contains all direct parent collections this collection inherits settings from. </summary>
public readonly IReadOnlyList<ModCollection> DirectlyInheritsFrom;
/// <summary> Contains all direct child collections that inherit from this collection. </summary>
public readonly IReadOnlyList<ModCollection> DirectParentOf = new List<ModCollection>();
/// <summary> All inherited collections in application order without filtering for duplicates. </summary>
public static IEnumerable<ModCollection> InheritedCollections(ModCollection collection)
=> collection.DirectlyInheritsFrom.SelectMany(InheritedCollections).Prepend(collection);
/// <summary>
/// Iterate over all collections inherited from in depth-first order.
/// Skip already visited collections to avoid circular dependencies.
/// </summary>
public IEnumerable<ModCollection> GetFlattenedInheritance()
=> InheritedCollections(this).Distinct();
/// <summary>
/// Obtain the actual settings for a given mod via index.
/// Also returns the collection the settings are taken from.
/// If no collection provides settings for this mod, this collection is returned together with null.
/// </summary>
public (ModSettings? Settings, ModCollection Collection) this[Index idx]
public ModSettings? GetOwnSettings(Index idx)
{
get
if (Identity.Index <= 0)
return ModSettings.Empty;
return Settings.Settings[idx].Settings;
}
public TemporaryModSettings? GetTempSettings(Index idx)
{
if (Identity.Index <= 0)
return null;
return Settings.Settings[idx].TempSettings;
}
public (ModSettings? Settings, ModCollection Collection) GetInheritedSettings(Index idx)
{
if (Identity.Index <= 0)
return (ModSettings.Empty, this);
foreach (var collection in Inheritance.FlatHierarchy)
{
if (Identity.Index <= 0)
return (ModSettings.Empty, this);
foreach (var collection in GetFlattenedInheritance())
{
var settings = collection.Settings[idx];
if (settings != null)
return (settings, collection);
}
return (null, this);
var settings = collection.Settings.Settings[idx].Settings;
if (settings != null)
return (settings, collection);
}
return (null, this);
}
public (ModSettings? Settings, ModCollection Collection) GetActualSettings(Index idx)
{
if (Identity.Index <= 0)
return (ModSettings.Empty, this);
// Check temp settings.
var ownTempSettings = Settings.Settings[idx].Resolve();
if (ownTempSettings != null)
return (ownTempSettings, this);
// Ignore temp settings for inherited collections.
foreach (var collection in Inheritance.FlatHierarchy.Skip(1))
{
var settings = collection.Settings.Settings[idx].Settings;
if (settings != null)
return (settings, collection);
}
return (null, this);
}
/// <summary> Evaluates all settings along the whole inheritance tree. </summary>
public IEnumerable<ModSettings?> ActualSettings
=> Enumerable.Range(0, Settings.Count).Select(i => this[i].Settings);
=> Enumerable.Range(0, Settings.Count).Select(i => GetActualSettings(i).Settings);
/// <summary>
/// Constructor for duplication. Deep copies all settings and parent collections and adds the new collection to their children lists.
@ -93,9 +96,7 @@ public partial class ModCollection
public ModCollection Duplicate(string name, LocalCollectionId localId, int index)
{
Debug.Assert(index > 0, "Collection duplicated with non-positive index.");
return new ModCollection(ModCollectionIdentity.New(name, localId, index), 0, CurrentVersion,
Settings.Select(s => s?.DeepCopy()).ToList(), [.. DirectlyInheritsFrom],
UnusedSettings.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.DeepCopy()));
return new ModCollection(ModCollectionIdentity.New(name, localId, index), 0, CurrentVersion, Settings.Clone(), Inheritance.Clone());
}
/// <summary> Constructor for reading from files. </summary>
@ -103,11 +104,8 @@ public partial class ModCollection
Dictionary<string, ModSettings.SavedSettings> allSettings, IReadOnlyList<string> inheritances)
{
Debug.Assert(identity.Index > 0, "Collection read with non-positive index.");
var ret = new ModCollection(identity, 0, version, [], [], allSettings)
{
InheritanceByName = inheritances,
};
ret.ApplyModSettings(saver, mods);
var ret = new ModCollection(identity, 0, version, new ModSettingProvider(allSettings), new ModCollectionInheritance(inheritances));
ret.Settings.ApplyModSettings(ret, saver, mods);
ModCollectionMigration.Migrate(saver, mods, version, ret);
return ret;
}
@ -116,7 +114,8 @@ public partial class ModCollection
public static ModCollection CreateTemporary(string name, LocalCollectionId localId, int index, int changeCounter)
{
Debug.Assert(index < 0, "Temporary collection created with non-negative index.");
var ret = new ModCollection(ModCollectionIdentity.New(name, localId, index), changeCounter, CurrentVersion, [], [], []);
var ret = new ModCollection(ModCollectionIdentity.New(name, localId, index), changeCounter, CurrentVersion, new ModSettingProvider(),
new ModCollectionInheritance());
return ret;
}
@ -124,64 +123,18 @@ public partial class ModCollection
public static ModCollection CreateEmpty(string name, LocalCollectionId localId, int index, int modCount)
{
Debug.Assert(index >= 0, "Empty collection created with negative index.");
return new ModCollection(ModCollectionIdentity.New(name, localId, index), 0, CurrentVersion,
Enumerable.Repeat((ModSettings?)null, modCount).ToList(), [], []);
return new ModCollection(ModCollectionIdentity.New(name, localId, index), 0, CurrentVersion, ModSettingProvider.Empty(modCount),
new ModCollectionInheritance());
}
/// <summary> Add settings for a new appended mod, by checking if the mod had settings from a previous deletion. </summary>
internal bool AddMod(Mod mod)
private ModCollection(ModCollectionIdentity identity, int changeCounter, int version, ModSettingProvider settings,
ModCollectionInheritance inheritance)
{
if (UnusedSettings.TryGetValue(mod.ModPath.Name, out var save))
{
var ret = save.ToSettings(mod, out var settings);
((List<ModSettings?>)Settings).Add(settings);
((Dictionary<string, ModSettings.SavedSettings>)UnusedSettings).Remove(mod.ModPath.Name);
return ret;
}
((List<ModSettings?>)Settings).Add(null);
return false;
}
/// <summary> Move settings from the current mod list to the unused mod settings. </summary>
internal void RemoveMod(Mod mod)
{
var settings = Settings[mod.Index];
if (settings != null)
((Dictionary<string, ModSettings.SavedSettings>)UnusedSettings)[mod.ModPath.Name] = new ModSettings.SavedSettings(settings, mod);
((List<ModSettings?>)Settings).RemoveAt(mod.Index);
}
/// <summary> Move all settings to unused settings for rediscovery. </summary>
internal void PrepareModDiscovery(ModStorage mods)
{
foreach (var (mod, setting) in mods.Zip(Settings).Where(s => s.Second != null))
((Dictionary<string, ModSettings.SavedSettings>)UnusedSettings)[mod.ModPath.Name] = new ModSettings.SavedSettings(setting!, mod);
((List<ModSettings?>)Settings).Clear();
}
/// <summary>
/// Apply all mod settings from unused settings to the current set of mods.
/// Also fixes invalid settings.
/// </summary>
internal void ApplyModSettings(SaveService saver, ModStorage mods)
{
((List<ModSettings?>)Settings).Capacity = Math.Max(((List<ModSettings?>)Settings).Capacity, mods.Count);
if (mods.Aggregate(false, (current, mod) => current | AddMod(mod)))
saver.ImmediateSave(new ModCollectionSave(mods, this));
}
private ModCollection(ModCollectionIdentity identity, int changeCounter, int version, List<ModSettings?> appliedSettings,
List<ModCollection> inheritsFrom, Dictionary<string, ModSettings.SavedSettings> settings)
{
Identity = identity;
Counters = new CollectionCounters(changeCounter);
Settings = appliedSettings;
UnusedSettings = settings;
DirectlyInheritsFrom = inheritsFrom;
foreach (var c in DirectlyInheritsFrom)
((List<ModCollection>)c.DirectParentOf).Add(this);
Identity = identity;
Counters = new CollectionCounters(changeCounter);
Settings = settings;
Inheritance = inheritance;
ModCollectionInheritance.UpdateChildren(this);
ModCollectionInheritance.UpdateFlattenedInheritance(this);
}
}

View file

@ -0,0 +1,92 @@
using OtterGui.Filesystem;
namespace Penumbra.Collections;
public struct ModCollectionInheritance
{
public IReadOnlyList<string>? InheritanceByName { get; private set; }
private readonly List<ModCollection> _directlyInheritsFrom = [];
private readonly List<ModCollection> _directlyInheritedBy = [];
private readonly List<ModCollection> _flatHierarchy = [];
public ModCollectionInheritance()
{ }
private ModCollectionInheritance(List<ModCollection> inheritsFrom)
=> _directlyInheritsFrom = [.. inheritsFrom];
public ModCollectionInheritance(IReadOnlyList<string> byName)
=> InheritanceByName = byName;
public ModCollectionInheritance Clone()
=> new(_directlyInheritsFrom);
public IEnumerable<string> Identifiers
=> InheritanceByName ?? _directlyInheritsFrom.Select(c => c.Identity.Identifier);
public IReadOnlyList<string>? ConsumeNames()
{
var ret = InheritanceByName;
InheritanceByName = null;
return ret;
}
public static void UpdateChildren(ModCollection parent)
{
foreach (var inheritance in parent.Inheritance.DirectlyInheritsFrom)
inheritance.Inheritance._directlyInheritedBy.Add(parent);
}
public void AddInheritance(ModCollection inheritor, ModCollection newParent)
{
_directlyInheritsFrom.Add(newParent);
newParent.Inheritance._directlyInheritedBy.Add(inheritor);
UpdateFlattenedInheritance(inheritor);
}
public ModCollection RemoveInheritanceAt(ModCollection inheritor, int idx)
{
var parent = DirectlyInheritsFrom[idx];
_directlyInheritsFrom.RemoveAt(idx);
parent.Inheritance._directlyInheritedBy.Remove(parent);
UpdateFlattenedInheritance(inheritor);
return parent;
}
public bool MoveInheritance(ModCollection inheritor, int from, int to)
{
if (!_directlyInheritsFrom.Move(from, to))
return false;
UpdateFlattenedInheritance(inheritor);
return true;
}
public void RemoveChild(ModCollection child)
=> _directlyInheritedBy.Remove(child);
/// <summary> Contains all direct parent collections this collection inherits settings from. </summary>
public readonly IReadOnlyList<ModCollection> DirectlyInheritsFrom
=> _directlyInheritsFrom;
/// <summary> Contains all direct child collections that inherit from this collection. </summary>
public readonly IReadOnlyList<ModCollection> DirectlyInheritedBy
=> _directlyInheritedBy;
/// <summary>
/// Iterate over all collections inherited from in depth-first order.
/// Skip already visited collections to avoid circular dependencies.
/// </summary>
public readonly IReadOnlyList<ModCollection> FlatHierarchy
=> _flatHierarchy;
public static void UpdateFlattenedInheritance(ModCollection parent)
{
parent.Inheritance._flatHierarchy.Clear();
parent.Inheritance._flatHierarchy.AddRange(InheritedCollections(parent).Distinct());
}
/// <summary> All inherited collections in application order without filtering for duplicates. </summary>
private static IEnumerable<ModCollection> InheritedCollections(ModCollection parent)
=> parent.Inheritance.DirectlyInheritsFrom.SelectMany(InheritedCollections).Prepend(parent);
}

View file

@ -32,19 +32,19 @@ internal readonly struct ModCollectionSave(ModStorage modStorage, ModCollection
j.WriteValue(modCollection.Identity.Identifier);
j.WritePropertyName(nameof(ModCollectionIdentity.Name));
j.WriteValue(modCollection.Identity.Name);
j.WritePropertyName(nameof(ModCollection.Settings));
j.WritePropertyName("Settings");
// Write all used and unused settings by mod directory name.
j.WriteStartObject();
var list = new List<(string, ModSettings.SavedSettings)>(modCollection.Settings.Count + modCollection.UnusedSettings.Count);
var list = new List<(string, ModSettings.SavedSettings)>(modCollection.Settings.Count + modCollection.Settings.Unused.Count);
for (var i = 0; i < modCollection.Settings.Count; ++i)
{
var settings = modCollection.Settings[i];
var settings = modCollection.GetOwnSettings(i);
if (settings != null)
list.Add((modStorage[i].ModPath.Name, new ModSettings.SavedSettings(settings, modStorage[i])));
}
list.AddRange(modCollection.UnusedSettings.Select(kvp => (kvp.Key, kvp.Value)));
list.AddRange(modCollection.Settings.Unused.Select(kvp => (kvp.Key, kvp.Value)));
list.Sort((a, b) => string.Compare(a.Item1, b.Item1, StringComparison.OrdinalIgnoreCase));
foreach (var (modDir, settings) in list)
@ -57,7 +57,7 @@ internal readonly struct ModCollectionSave(ModStorage modStorage, ModCollection
// Inherit by collection name.
j.WritePropertyName("Inheritance");
x.Serialize(j, modCollection.InheritanceByName ?? modCollection.DirectlyInheritsFrom.Select(c => c.Identity.Identifier));
x.Serialize(j, modCollection.Inheritance.Identifiers);
j.WriteEndObject();
}
@ -82,7 +82,7 @@ internal readonly struct ModCollectionSave(ModStorage modStorage, ModCollection
name = obj[nameof(ModCollectionIdentity.Name)]?.ToObject<string>() ?? string.Empty;
id = obj[nameof(ModCollectionIdentity.Id)]?.ToObject<Guid>() ?? (version == 1 ? Guid.NewGuid() : Guid.Empty);
// Custom deserialization that is converted with the constructor.
settings = obj[nameof(ModCollection.Settings)]?.ToObject<Dictionary<string, ModSettings.SavedSettings>>() ?? settings;
settings = obj["Settings"]?.ToObject<Dictionary<string, ModSettings.SavedSettings>>() ?? settings;
inheritance = obj["Inheritance"]?.ToObject<List<string>>() ?? inheritance;
return true;
}

View file

@ -0,0 +1,98 @@
using Penumbra.Mods;
using Penumbra.Mods.Manager;
using Penumbra.Mods.Settings;
using Penumbra.Services;
namespace Penumbra.Collections;
public readonly struct ModSettingProvider
{
private ModSettingProvider(IEnumerable<FullModSettings> settings, Dictionary<string, ModSettings.SavedSettings> unusedSettings)
{
_settings = settings.Select(s => s.DeepCopy()).ToList();
_unused = unusedSettings.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.DeepCopy());
}
public ModSettingProvider()
{ }
public static ModSettingProvider Empty(int count)
=> new(Enumerable.Repeat(FullModSettings.Empty, count), []);
public ModSettingProvider(Dictionary<string, ModSettings.SavedSettings> allSettings)
=> _unused = allSettings;
private readonly List<FullModSettings> _settings = [];
/// <summary> Settings for deleted mods will be kept via the mods identifier (directory name). </summary>
private readonly Dictionary<string, ModSettings.SavedSettings> _unused = [];
public int Count
=> _settings.Count;
public bool RemoveUnused(string key)
=> _unused.Remove(key);
internal void Set(Index index, ModSettings? settings)
=> _settings[index] = _settings[index] with { Settings = settings };
internal void SetTemporary(Index index, TemporaryModSettings? settings)
=> _settings[index] = _settings[index] with { TempSettings = settings };
internal void SetAll(Index index, FullModSettings settings)
=> _settings[index] = settings;
public IReadOnlyList<FullModSettings> Settings
=> _settings;
public IReadOnlyDictionary<string, ModSettings.SavedSettings> Unused
=> _unused;
public ModSettingProvider Clone()
=> new(_settings, _unused);
/// <summary> Add settings for a new appended mod, by checking if the mod had settings from a previous deletion. </summary>
internal bool AddMod(Mod mod)
{
if (_unused.Remove(mod.ModPath.Name, out var save))
{
var ret = save.ToSettings(mod, out var settings);
_settings.Add(new FullModSettings(settings));
return ret;
}
_settings.Add(FullModSettings.Empty);
return false;
}
/// <summary> Move settings from the current mod list to the unused mod settings. </summary>
internal void RemoveMod(Mod mod)
{
var settings = _settings[mod.Index];
if (settings.Settings != null)
_unused[mod.ModPath.Name] = new ModSettings.SavedSettings(settings.Settings, mod);
_settings.RemoveAt(mod.Index);
}
/// <summary> Move all settings to unused settings for rediscovery. </summary>
internal void PrepareModDiscovery(ModStorage mods)
{
foreach (var (mod, setting) in mods.Zip(_settings).Where(s => s.Second.Settings != null))
_unused[mod.ModPath.Name] = new ModSettings.SavedSettings(setting.Settings!, mod);
_settings.Clear();
}
/// <summary>
/// Apply all mod settings from unused settings to the current set of mods.
/// Also fixes invalid settings.
/// </summary>
internal void ApplyModSettings(ModCollection parent, SaveService saver, ModStorage mods)
{
_settings.Capacity = Math.Max(_settings.Capacity, mods.Count);
var settings = this;
if (mods.Aggregate(false, (current, mod) => current | settings.AddMod(mod)))
saver.ImmediateSave(new ModCollectionSave(mods, parent));
}
}