mirror of
https://github.com/xivdev/Penumbra.git
synced 2026-02-20 14:57:50 +01:00
tmp
This commit is contained in:
parent
2bf80dfa6b
commit
e86899c943
11 changed files with 362 additions and 353 deletions
|
|
@ -1,168 +1,73 @@
|
|||
using OtterGui.Filesystem;
|
||||
using Penumbra.Mods;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using OtterGui;
|
||||
using Penumbra.Mods.Manager;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.Collections;
|
||||
|
||||
// A ModCollection is a named set of ModSettings to all of the users' installed mods.
|
||||
// Settings to mods that are not installed anymore are kept as long as no call to CleanUnavailableSettings is made.
|
||||
// Invariants:
|
||||
// - Index is the collections index in the ModCollection.Manager
|
||||
// - Settings has the same size as ModManager.Mods.
|
||||
// - any change in settings or inheritance of the collection causes a Save.
|
||||
/// <summary>
|
||||
/// A ModCollection is a named set of ModSettings to all of the users' installed mods.
|
||||
/// Settings to mods that are not installed anymore are kept as long as no call to CleanUnavailableSettings is made.
|
||||
/// Invariants:
|
||||
/// - Index is the collections index in the ModCollection.Manager
|
||||
/// - Settings has the same size as ModManager.Mods.
|
||||
/// - any change in settings or inheritance of the collection causes a Save.
|
||||
/// - the name can not contain invalid path characters and has to be unique when lower-cased.
|
||||
/// </summary>
|
||||
public partial class ModCollection
|
||||
{
|
||||
public const int CurrentVersion = 1;
|
||||
public const string DefaultCollectionName = "Default";
|
||||
public const string EmptyCollection = "None";
|
||||
public const string EmptyCollectionName = "None";
|
||||
|
||||
public static readonly ModCollection Empty = CreateEmpty();
|
||||
/// <summary>
|
||||
/// 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 = CreateEmpty(EmptyCollectionName, 0);
|
||||
|
||||
// The collection name can contain invalid path characters,
|
||||
// but after removing those and going to lower case it has to be unique.
|
||||
/// <summary> The name of a collection can not contain characters invalid in a path. </summary>
|
||||
public string Name { get; internal init; }
|
||||
|
||||
// Get the first two letters of a collection name and its Index (or None if it is the empty collection).
|
||||
public string AnonymizedName
|
||||
=> this == Empty ? Empty.Name : Name.Length > 2 ? $"{Name[..2]}... ({Index})" : $"{Name} ({Index})";
|
||||
|
||||
public int Version { get; internal set; }
|
||||
public int Index { get; internal set; } = -1;
|
||||
|
||||
// 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.
|
||||
internal readonly List<ModSettings?> _settings;
|
||||
|
||||
public IReadOnlyList<ModSettings?> Settings
|
||||
=> _settings;
|
||||
|
||||
// Returns whether there are settings not in use by any current mod.
|
||||
public bool HasUnusedSettings
|
||||
=> _unusedSettings.Count > 0;
|
||||
|
||||
public int NumUnusedSettings
|
||||
=> _unusedSettings.Count;
|
||||
|
||||
// Evaluates the settings along the whole inheritance tree.
|
||||
public IEnumerable<ModSettings?> ActualSettings
|
||||
=> Enumerable.Range(0, _settings.Count).Select(i => this[i].Settings);
|
||||
|
||||
// Settings for deleted mods will be kept via directory name.
|
||||
internal readonly Dictionary<string, ModSettings.SavedSettings> _unusedSettings;
|
||||
|
||||
// Constructor for duplication.
|
||||
private ModCollection(string name, ModCollection duplicate)
|
||||
{
|
||||
Name = name;
|
||||
Version = duplicate.Version;
|
||||
_settings = duplicate._settings.ConvertAll(s => s?.DeepCopy());
|
||||
_unusedSettings = duplicate._unusedSettings.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.DeepCopy());
|
||||
DirectlyInheritsFrom = duplicate.DirectlyInheritsFrom.ToList();
|
||||
foreach (var c in DirectlyInheritsFrom)
|
||||
((List<ModCollection>)c.DirectParentOf).Add(this);
|
||||
}
|
||||
|
||||
// Constructor for reading from files.
|
||||
private ModCollection(string name, int version, Dictionary<string, ModSettings.SavedSettings> allSettings)
|
||||
{
|
||||
Name = name;
|
||||
Version = version;
|
||||
_unusedSettings = allSettings;
|
||||
|
||||
_settings = new List<ModSettings?>();
|
||||
ApplyModSettings();
|
||||
|
||||
Migration.Migrate(Penumbra.SaveService, this);
|
||||
}
|
||||
|
||||
// Create a new, unique empty collection of a given name.
|
||||
public static ModCollection CreateNewEmpty(string name)
|
||||
=> new(name, CurrentVersion, new Dictionary<string, ModSettings.SavedSettings>());
|
||||
|
||||
// Create a new temporary collection that does not save and has a negative index.
|
||||
public static ModCollection CreateNewTemporary(string name, int changeCounter)
|
||||
{
|
||||
var collection = new ModCollection(name, Empty)
|
||||
{
|
||||
Index = ~Penumbra.TempCollections.Count,
|
||||
ChangeCounter = changeCounter,
|
||||
};
|
||||
collection.CreateCache(false);
|
||||
return collection;
|
||||
}
|
||||
|
||||
// Duplicate the calling collection to a new, unique collection of a given name.
|
||||
public ModCollection Duplicate(string name)
|
||||
=> new(name, this);
|
||||
|
||||
// Remove all settings for not currently-installed mods.
|
||||
public void CleanUnavailableSettings()
|
||||
{
|
||||
var any = _unusedSettings.Count > 0;
|
||||
_unusedSettings.Clear();
|
||||
if (any)
|
||||
Penumbra.SaveService.QueueSave(this);
|
||||
}
|
||||
|
||||
// Add settings for a new appended mod, by checking if the mod had settings from a previous deletion.
|
||||
internal bool AddMod(Mod mod)
|
||||
{
|
||||
if (_unusedSettings.TryGetValue(mod.ModPath.Name, out var save))
|
||||
{
|
||||
var ret = save.ToSettings(mod, out var settings);
|
||||
_settings.Add(settings);
|
||||
_unusedSettings.Remove(mod.ModPath.Name);
|
||||
return ret;
|
||||
}
|
||||
|
||||
_settings.Add(null);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Move settings from the current mod list to the unused mod settings.
|
||||
internal void RemoveMod(Mod mod, int idx)
|
||||
{
|
||||
var settings = _settings[idx];
|
||||
if (settings != null)
|
||||
_unusedSettings[mod.ModPath.Name] = new ModSettings.SavedSettings(settings, mod);
|
||||
|
||||
_settings.RemoveAt(idx);
|
||||
}
|
||||
|
||||
// Create the always available Empty Collection that will always sit at index 0,
|
||||
// can not be deleted and does never create a cache.
|
||||
private static ModCollection CreateEmpty()
|
||||
{
|
||||
var collection = CreateNewEmpty(EmptyCollection);
|
||||
collection.Index = 0;
|
||||
collection._settings.Clear();
|
||||
return collection;
|
||||
}
|
||||
|
||||
// Move all settings to unused settings for rediscovery.
|
||||
internal void PrepareModDiscovery()
|
||||
{
|
||||
foreach (var (mod, setting) in Penumbra.ModManager.Zip(_settings).Where(s => s.Second != null))
|
||||
_unusedSettings[mod.ModPath.Name] = new ModSettings.SavedSettings(setting!, mod);
|
||||
|
||||
_settings.Clear();
|
||||
}
|
||||
|
||||
// Apply all mod settings from unused settings to the current set of mods.
|
||||
// Also fixes invalid settings.
|
||||
internal void ApplyModSettings()
|
||||
{
|
||||
_settings.Capacity = Math.Max(_settings.Capacity, Penumbra.ModManager.Count);
|
||||
if (Penumbra.ModManager.Aggregate(false, (current, mod) => current | AddMod(mod)))
|
||||
Penumbra.SaveService.ImmediateSave(this);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> Name;
|
||||
|
||||
/// <summary> Get the first two letters of a collection name and its Index (or None if it is the empty collection). </summary>
|
||||
public string AnonymizedName
|
||||
=> this == Empty ? Empty.Name : Name.Length > 2 ? $"{Name[..2]}... ({Index})" : $"{Name} ({Index})";
|
||||
|
||||
/// <summary> The index of the collection is set and kept up-to-date by the CollectionManager. </summary>
|
||||
public int Index { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// If a ModSetting is null, it can be inherited from other collections.
|
||||
/// If no collection provides a setting for the mod, it is just disabled.
|
||||
/// </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> 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.
|
||||
|
|
@ -177,7 +82,7 @@ public partial class ModCollection
|
|||
|
||||
foreach (var collection in GetFlattenedInheritance())
|
||||
{
|
||||
var settings = collection._settings[idx];
|
||||
var settings = collection.Settings[idx];
|
||||
if (settings != null)
|
||||
return (settings, collection);
|
||||
}
|
||||
|
|
@ -186,17 +91,104 @@ public partial class ModCollection
|
|||
}
|
||||
}
|
||||
|
||||
public readonly IReadOnlyList<ModCollection> DirectlyInheritsFrom = new List<ModCollection>();
|
||||
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> Evaluates all settings along the whole inheritance tree. </summary>
|
||||
public IEnumerable<ModSettings?> ActualSettings
|
||||
=> Enumerable.Range(0, Settings.Count).Select(i => this[i].Settings);
|
||||
|
||||
/// <summary>
|
||||
/// Iterate over all collections inherited from in depth-first order.
|
||||
/// Skip already visited collections to avoid circular dependencies.
|
||||
/// Constructor for duplication. Deep copies all settings and parent collections and adds the new collection to their children lists.
|
||||
/// </summary>
|
||||
public IEnumerable<ModCollection> GetFlattenedInheritance()
|
||||
=> InheritedCollections(this).Distinct();
|
||||
public ModCollection Duplicate(string name, int index)
|
||||
{
|
||||
Debug.Assert(index > 0, "Collection duplicated with non-positive index.");
|
||||
return new ModCollection(name, index, 0, CurrentVersion, Settings.Select(s => s?.DeepCopy()).ToList(),
|
||||
DirectlyInheritsFrom.ToList(), UnusedSettings.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.DeepCopy()));
|
||||
}
|
||||
|
||||
/// <summary> Constructor for reading from files. </summary>
|
||||
public static ModCollection CreateFromData(SaveService saver, ModStorage mods, string name, int version, int index,
|
||||
Dictionary<string, ModSettings.SavedSettings> allSettings)
|
||||
{
|
||||
Debug.Assert(index > 0, "Collection read with non-positive index.");
|
||||
var ret = new ModCollection(name, index, 0, version, new List<ModSettings?>(), new List<ModCollection>(), allSettings);
|
||||
ret.ApplyModSettings(saver, mods);
|
||||
ModCollectionMigration.Migrate(saver, mods, version, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary> Constructor for temporary collections. </summary>
|
||||
public static ModCollection CreateTemporary(string name, int index, int changeCounter)
|
||||
{
|
||||
Debug.Assert(index < 0, "Temporary collection created with non-negative index.");
|
||||
var ret = new ModCollection(name, index, changeCounter, CurrentVersion, new List<ModSettings?>(), new List<ModCollection>(),
|
||||
new Dictionary<string, ModSettings.SavedSettings>());
|
||||
ret.CreateCache(false);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary> Constructor for empty collections. </summary>
|
||||
public static ModCollection CreateEmpty(string name, int index)
|
||||
{
|
||||
Debug.Assert(index >= 0, "Empty collection created with negative index.");
|
||||
return new ModCollection(name, index, 0, CurrentVersion, new List<ModSettings?>(), new List<ModCollection>(),
|
||||
new Dictionary<string, ModSettings.SavedSettings>());
|
||||
}
|
||||
|
||||
/// <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 (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(string name, int index, int changeCounter, int version, List<ModSettings?> appliedSettings,
|
||||
List<ModCollection> inheritsFrom, Dictionary<string, ModSettings.SavedSettings> settings)
|
||||
{
|
||||
Name = name;
|
||||
Index = index;
|
||||
ChangeCounter = changeCounter;
|
||||
Settings = appliedSettings;
|
||||
UnusedSettings = settings;
|
||||
DirectlyInheritsFrom = inheritsFrom;
|
||||
foreach (var c in DirectlyInheritsFrom)
|
||||
((List<ModCollection>)c.DirectParentOf).Add(this);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue