Lots of collection progress.

This commit is contained in:
Ottermandias 2023-04-11 11:28:56 +02:00
parent d908f22a17
commit 3f33bab296
22 changed files with 666 additions and 636 deletions

View file

@ -6,6 +6,8 @@ using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Penumbra.Api;
using Penumbra.Api.Enums;
using Penumbra.Interop.Services;
using Penumbra.Mods;
using Penumbra.Mods.Manager;
using Penumbra.Services;
@ -16,6 +18,7 @@ public class CollectionCacheManager : IDisposable, IReadOnlyDictionary<ModCollec
{
private readonly ActiveCollections _active;
private readonly CommunicatorService _communicator;
private readonly CharacterUtility _characterUtility;
private readonly Dictionary<ModCollection, ModCollectionCache> _cache = new();
@ -46,17 +49,23 @@ public class CollectionCacheManager : IDisposable, IReadOnlyDictionary<ModCollec
public IEnumerable<ModCollection> Active
=> _cache.Keys.Where(c => c.Index > ModCollection.Empty.Index);
public CollectionCacheManager(ActiveCollections active, CommunicatorService communicator)
public CollectionCacheManager(ActiveCollections active, CommunicatorService communicator, CharacterUtility characterUtility)
{
_active = active;
_communicator = communicator;
_active = active;
_communicator = communicator;
_characterUtility = characterUtility;
_communicator.CollectionChange.Subscribe(OnCollectionChange);
_communicator.ModPathChanged.Subscribe(OnModChangeAddition, -100);
_communicator.ModPathChanged.Subscribe(OnModChangeRemoval, 100);
_communicator.TemporaryGlobalModChange.Subscribe(OnGlobalModChange);
_communicator.ModOptionChanged.Subscribe(OnModOptionChange, -100);
_communicator.ModSettingChanged.Subscribe(OnModSettingChange);
_communicator.CollectionInheritanceChanged.Subscribe(OnCollectionInheritanceChange);
CreateNecessaryCaches();
if (!_characterUtility.Ready)
_characterUtility.LoadingFinished += IncrementCounters;
}
public void Dispose()
@ -66,6 +75,9 @@ public class CollectionCacheManager : IDisposable, IReadOnlyDictionary<ModCollec
_communicator.ModPathChanged.Unsubscribe(OnModChangeRemoval);
_communicator.TemporaryGlobalModChange.Unsubscribe(OnGlobalModChange);
_communicator.ModOptionChanged.Unsubscribe(OnModOptionChange);
_communicator.ModSettingChanged.Unsubscribe(OnModSettingChange);
_communicator.CollectionInheritanceChanged.Unsubscribe(OnCollectionInheritanceChange);
_characterUtility.LoadingFinished -= IncrementCounters;
}
/// <summary>
@ -170,4 +182,57 @@ public class CollectionCacheManager : IDisposable, IReadOnlyDictionary<ModCollec
collection._cache!.AddMod(mod, true);
}
}
/// <summary> Increment the counter to ensure new files are loaded after applying meta changes. </summary>
private void IncrementCounters()
{
foreach (var (collection, _) in _cache)
++collection.ChangeCounter;
_characterUtility.LoadingFinished -= IncrementCounters;
}
private void OnModSettingChange(ModCollection collection, ModSettingChange type, Mod? mod, int oldValue, int groupIdx, bool _)
{
if (collection._cache == null)
return;
switch (type)
{
case ModSettingChange.Inheritance:
collection._cache.ReloadMod(mod!, true);
break;
case ModSettingChange.EnableState:
if (oldValue == 0)
collection._cache.AddMod(mod!, true);
else if (oldValue == 1)
collection._cache.RemoveMod(mod!, true);
else if (collection[mod!.Index].Settings?.Enabled == true)
collection._cache.ReloadMod(mod!, true);
else
collection._cache.RemoveMod(mod!, true);
break;
case ModSettingChange.Priority:
if (collection._cache.Conflicts(mod!).Count > 0)
collection._cache.ReloadMod(mod!, true);
break;
case ModSettingChange.Setting:
if (collection[mod!.Index].Settings?.Enabled == true)
collection._cache.ReloadMod(mod!, true);
break;
case ModSettingChange.MultiInheritance:
case ModSettingChange.MultiEnableState:
collection._cache.FullRecalculation(collection == _active.Default);
break;
}
}
/// <summary>
/// Inheritance changes are too big to check for relevance,
/// just recompute everything.
/// </summary>
private void OnCollectionInheritanceChange(ModCollection collection, bool _)
=> collection._cache?.FullRecalculation(collection == _active.Default);
}

View file

@ -0,0 +1,230 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using OtterGui;
using Penumbra.Api.Enums;
using Penumbra.Mods;
using Penumbra.Services;
using Penumbra.Util;
namespace Penumbra.Collections.Manager;
public class CollectionEditor
{
private readonly CommunicatorService _communicator;
private readonly SaveService _saveService;
public CollectionEditor(SaveService saveService, CommunicatorService communicator)
{
_saveService = saveService;
_communicator = communicator;
}
/// <summary> Enable or disable the mod inheritance of mod idx. </summary>
public bool SetModInheritance(ModCollection collection, Mod mod, bool inherit)
{
if (!FixInheritance(collection, mod, inherit))
return false;
InvokeChange(collection, ModSettingChange.Inheritance, mod, inherit ? 0 : 1, 0);
return true;
}
/// <summary>
/// Set the enabled state mod idx to newValue if it differs from the current enabled state.
/// If the mod is currently inherited, stop the inheritance.
/// </summary>
public bool SetModState(ModCollection collection, Mod mod, bool newValue)
{
var oldValue = collection._settings[mod.Index]?.Enabled ?? collection[mod.Index].Settings?.Enabled ?? false;
if (newValue == oldValue)
return false;
var inheritance = FixInheritance(collection, mod, false);
collection._settings[mod.Index]!.Enabled = newValue;
InvokeChange(collection, ModSettingChange.EnableState, mod, inheritance ? -1 : newValue ? 0 : 1, 0);
return true;
}
/// <summary> Enable or disable the mod inheritance of every mod in mods. </summary>
public void SetMultipleModInheritances(ModCollection collection, IEnumerable<Mod> mods, bool inherit)
{
if (!mods.Aggregate(false, (current, mod) => current | FixInheritance(collection, mod, inherit)))
return;
InvokeChange(collection, ModSettingChange.MultiInheritance, null, -1, 0);
}
/// <summary>
/// Set the enabled state of every mod in mods to the new value.
/// If the mod is currently inherited, stop the inheritance.
/// </summary>
public void SetMultipleModStates(ModCollection collection, IEnumerable<Mod> mods, bool newValue)
{
var changes = false;
foreach (var mod in mods)
{
var oldValue = collection._settings[mod.Index]?.Enabled;
if (newValue == oldValue)
continue;
FixInheritance(collection, mod, false);
collection._settings[mod.Index]!.Enabled = newValue;
changes = true;
}
if (!changes)
return;
InvokeChange(collection, ModSettingChange.MultiEnableState, null, -1, 0);
}
/// <summary>
/// Set the priority of mod idx to newValue if it differs from the current priority.
/// If the mod is currently inherited, stop the inheritance.
/// </summary>
public bool SetModPriority(ModCollection collection, Mod mod, int newValue)
{
var oldValue = collection._settings[mod.Index]?.Priority ?? collection[mod.Index].Settings?.Priority ?? 0;
if (newValue == oldValue)
return false;
var inheritance = FixInheritance(collection, mod, false);
collection._settings[mod.Index]!.Priority = newValue;
InvokeChange(collection, ModSettingChange.Priority, mod, inheritance ? -1 : oldValue, 0);
return true;
}
/// <summary>
/// Set a given setting group settingName of mod idx to newValue if it differs from the current value and fix it if necessary.
/// /// If the mod is currently inherited, stop the inheritance.
/// </summary>
public bool SetModSetting(ModCollection collection, Mod mod, int groupIdx, uint newValue)
{
var settings = collection._settings[mod.Index] != null
? collection._settings[mod.Index]!.Settings
: collection[mod.Index].Settings?.Settings;
var oldValue = settings?[groupIdx] ?? mod.Groups[groupIdx].DefaultSettings;
if (oldValue == newValue)
return false;
var inheritance = FixInheritance(collection, mod, false);
collection._settings[mod.Index]!.SetValue(mod, groupIdx, newValue);
InvokeChange(collection, ModSettingChange.Setting, mod, inheritance ? -1 : (int)oldValue, groupIdx);
return true;
}
/// <summary> Copy the settings of an existing (sourceMod != null) or stored (sourceName) mod to another mod, if they exist. </summary>
public bool CopyModSettings(ModCollection collection, Mod? sourceMod, string sourceName, Mod? targetMod, string targetName)
{
if (targetName.Length == 0 && targetMod == null || sourceName.Length == 0)
return false;
// If the source mod exists, convert its settings to saved settings or null if its inheriting.
// 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)
: null
: collection._unusedSettings.TryGetValue(sourceName, out var s)
? s
: null;
if (targetMod != null)
{
if (savedSettings != null)
{
// The target mod exists and the source settings are not inheriting, convert and fix the settings and copy them.
// This triggers multiple events.
savedSettings.Value.ToSettings(targetMod, out var settings);
SetModState(collection, targetMod, settings.Enabled);
SetModPriority(collection, targetMod, settings.Priority);
foreach (var (value, index) in settings.Settings.WithIndex())
SetModSetting(collection, targetMod, index, value);
}
else
{
// The target mod exists, but the source is inheriting, set the target to inheriting.
// This triggers events.
SetModInheritance(collection, targetMod, true);
}
}
else
{
// The target mod does not exist.
// Either copy the unused source settings directly if they are not inheriting,
// or remove any unused settings for the target if they are inheriting.
if (savedSettings != null)
collection._unusedSettings[targetName] = savedSettings.Value;
else
collection._unusedSettings.Remove(targetName);
}
return true;
}
/// <summary>
/// Change one of the available mod settings for mod idx discerned by type.
/// If type == Setting, settingName should be a valid setting for that mod, otherwise it will be ignored.
/// The setting will also be automatically fixed if it is invalid for that setting group.
/// For boolean parameters, newValue == 0 will be treated as false and != 0 as true.
/// </summary>
public bool ChangeModSetting(ModCollection collection, ModSettingChange type, Mod mod, int newValue, int groupIdx)
{
return type switch
{
ModSettingChange.Inheritance => SetModInheritance(collection, mod, newValue != 0),
ModSettingChange.EnableState => SetModState(collection, mod, newValue != 0),
ModSettingChange.Priority => SetModPriority(collection, mod, newValue),
ModSettingChange.Setting => SetModSetting(collection, mod, groupIdx, (uint)newValue),
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null),
};
}
/// <summary>
/// Set inheritance of a mod without saving,
/// to be used as an intermediary.
/// </summary>
private static bool FixInheritance(ModCollection collection, Mod mod, bool inherit)
{
var settings = collection._settings[mod.Index];
if (inherit == (settings == null))
return false;
collection._settings[mod.Index] = inherit ? null : collection[mod.Index].Settings?.DeepCopy() ?? ModSettings.DefaultSettings(mod);
return true;
}
/// <summary> Queue saves and trigger changes for any non-inherited change in a collection, then trigger changes for all inheritors. </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private void InvokeChange(ModCollection changedCollection, ModSettingChange type, Mod? mod, int oldValue, int groupIdx)
{
_saveService.QueueSave(changedCollection);
_communicator.ModSettingChanged.Invoke(changedCollection, type, mod, oldValue, groupIdx, false);
RecurseInheritors(changedCollection, type, mod, oldValue, groupIdx);
}
/// <summary> Trigger changes in all inherited collections. </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private void RecurseInheritors(ModCollection directParent, ModSettingChange type, Mod? mod, int oldValue, int groupIdx)
{
foreach (var directInheritor in directParent.DirectParentOf)
{
switch (type)
{
case ModSettingChange.MultiInheritance:
case ModSettingChange.MultiEnableState:
_communicator.ModSettingChanged.Invoke(directInheritor, type, null, oldValue, groupIdx, true);
break;
default:
if (directInheritor._settings[mod!.Index] == null)
_communicator.ModSettingChanged.Invoke(directInheritor, type, mod, oldValue, groupIdx, true);
break;
}
RecurseInheritors(directInheritor, type, mod, oldValue, groupIdx);
}
}
}

View file

@ -7,14 +7,16 @@ public class CollectionManager
public readonly InheritanceManager Inheritances;
public readonly CollectionCacheManager Caches;
public readonly TempCollectionManager Temp;
public readonly CollectionEditor Editor;
public CollectionManager(CollectionStorage storage, ActiveCollections active, InheritanceManager inheritances,
CollectionCacheManager caches, TempCollectionManager temp)
CollectionCacheManager caches, TempCollectionManager temp, CollectionEditor editor)
{
Storage = storage;
Active = active;
Inheritances = inheritances;
Caches = caches;
Temp = temp;
Editor = editor;
}
}

View file

@ -1,12 +1,34 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Dalamud.Interface.Internal.Notifications;
using OtterGui;
using OtterGui.Filesystem;
using Penumbra.Services;
using Penumbra.Util;
namespace Penumbra.Collections.Manager;
/// <summary>
/// ModCollections can inherit from an arbitrary number of other collections.
/// This is transitive, so a collection A inheriting from B also inherits from everything B inherits.
/// Circular dependencies are resolved by distinctness.
/// </summary>
public class InheritanceManager : IDisposable
{
public enum ValidInheritance
{
Valid,
/// <summary> Can not inherit from self </summary>
Self,
/// <summary> Can not inherit from the empty collection </summary>
Empty,
/// <summary> Already inherited from </summary>
Contained,
/// <summary> Inheritance would lead to a circle. </summary>
Circle,
}
private readonly CollectionStorage _storage;
private readonly CommunicatorService _communicator;
private readonly SaveService _saveService;
@ -26,22 +48,88 @@ public class InheritanceManager : IDisposable
_communicator.CollectionChange.Unsubscribe(OnCollectionChange);
}
/// <summary> Check whether a collection can be inherited from. </summary>
public static ValidInheritance CheckValidInheritance(ModCollection potentialInheritor, ModCollection? potentialParent)
{
if (potentialParent == null || ReferenceEquals(potentialParent, ModCollection.Empty))
return ValidInheritance.Empty;
if (ReferenceEquals(potentialParent, potentialInheritor))
return ValidInheritance.Self;
if (potentialInheritor.DirectlyInheritsFrom.Contains(potentialParent))
return ValidInheritance.Contained;
if (ModCollection.InheritedCollections(potentialParent).Any(c => ReferenceEquals(c, potentialInheritor)))
return ValidInheritance.Circle;
return ValidInheritance.Valid;
}
/// <summary>
/// Add a new collection to the inheritance list.
/// We do not check if this collection would be visited before,
/// only that it is unique in the list itself.
/// </summary>
public bool AddInheritance(ModCollection inheritor, ModCollection parent)
=> AddInheritance(inheritor, parent, true);
/// <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);
_communicator.CollectionInheritanceChanged.Invoke(inheritor, false);
RecurseInheritanceChanges(inheritor);
Penumbra.Log.Debug($"Removed {parent.AnonymizedName} from {inheritor.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))
return;
_communicator.CollectionInheritanceChanged.Invoke(inheritor, false);
RecurseInheritanceChanges(inheritor);
Penumbra.Log.Debug($"Moved {inheritor.AnonymizedName}s inheritance {from} to {to}.");
}
/// <inheritdoc cref="AddInheritance(ModCollection, ModCollection)"/>
private bool AddInheritance(ModCollection inheritor, ModCollection parent, bool invokeEvent)
{
if (CheckValidInheritance(inheritor, parent) != ValidInheritance.Valid)
return false;
((List<ModCollection>)inheritor.DirectlyInheritsFrom).Add(parent);
((List<ModCollection>)parent.DirectParentOf).Add(inheritor);
if (invokeEvent)
{
_communicator.CollectionInheritanceChanged.Invoke(inheritor, false);
RecurseInheritanceChanges(inheritor);
}
Penumbra.Log.Debug($"Added {parent.AnonymizedName} to {inheritor.AnonymizedName} inheritances.");
return true;
}
/// <summary>
/// Inheritances can not be setup before all collections are read,
/// so this happens after reading the collections in the constructor, consuming the stored strings.
/// </summary>
private void ApplyInheritances()
{
foreach (var (collection, inheritances, changes) in _storage.ConsumeInheritanceNames())
foreach (var (collection, directParents, changes) in _storage.ConsumeInheritanceNames())
{
var localChanges = changes;
foreach (var subCollection in inheritances)
foreach (var parent in directParents)
{
if (collection.AddInheritance(subCollection, false))
if (AddInheritance(collection, parent, false))
continue;
localChanges = true;
Penumbra.ChatService.NotificationMessage($"{collection.Name} can not inherit from {subCollection.Name}, removed.", "Warning",
Penumbra.ChatService.NotificationMessage($"{collection.Name} can not inherit from {parent.Name}, removed.", "Warning",
NotificationType.Warning);
}
@ -55,14 +143,22 @@ public class InheritanceManager : IDisposable
if (collectionType is not CollectionType.Inactive || old == null)
return;
foreach (var inheritance in old.Inheritance)
old.ClearSubscriptions(inheritance);
foreach (var c in _storage)
{
var inheritedIdx = c._inheritance.IndexOf(old);
var inheritedIdx = c.DirectlyInheritsFrom.IndexOf(old);
if (inheritedIdx >= 0)
c.RemoveInheritance(inheritedIdx);
RemoveInheritance(c, inheritedIdx);
((List<ModCollection>)c.DirectParentOf).Remove(old);
}
}
private void RecurseInheritanceChanges(ModCollection newInheritor)
{
foreach (var inheritor in newInheritor.DirectParentOf)
{
_communicator.CollectionInheritanceChanged.Invoke(inheritor, true);
RecurseInheritanceChanges(inheritor);
}
}
}