Extract ModCollectionIdentity.

This commit is contained in:
Ottermandias 2024-12-27 11:36:30 +01:00
parent fbbfe5e00d
commit 67305d507a
43 changed files with 270 additions and 252 deletions

View file

@ -31,7 +31,7 @@ public sealed class CollectionCache : IDisposable
public int Calculating = -1;
public string AnonymizedName
=> _collection.AnonymizedName;
=> _collection.Identity.AnonymizedName;
public IEnumerable<SingleArray<ModConflicts>> AllConflicts
=> ConflictDict.Values;

View file

@ -114,16 +114,16 @@ public class CollectionCacheManager : IDisposable, IService
/// <summary> Only creates a new cache, does not update an existing one. </summary>
public bool CreateCache(ModCollection collection)
{
if (collection.Index == ModCollection.Empty.Index)
if (collection.Identity.Index == ModCollection.Empty.Identity.Index)
return false;
if (collection._cache != null)
return false;
collection._cache = new CollectionCache(this, collection);
if (collection.Index > 0)
if (collection.Identity.Index > 0)
Interlocked.Increment(ref _count);
Penumbra.Log.Verbose($"Created new cache for collection {collection.AnonymizedName}.");
Penumbra.Log.Verbose($"Created new cache for collection {collection.Identity.AnonymizedName}.");
return true;
}
@ -132,32 +132,32 @@ public class CollectionCacheManager : IDisposable, IService
/// Does not create caches.
/// </summary>
public void CalculateEffectiveFileList(ModCollection collection)
=> _framework.RegisterImportant(nameof(CalculateEffectiveFileList) + collection.Identifier,
=> _framework.RegisterImportant(nameof(CalculateEffectiveFileList) + collection.Identity.Identifier,
() => CalculateEffectiveFileListInternal(collection));
private void CalculateEffectiveFileListInternal(ModCollection collection)
{
// Skip the empty collection.
if (collection.Index == 0)
if (collection.Identity.Index == 0)
return;
Penumbra.Log.Debug($"[{Environment.CurrentManagedThreadId}] Recalculating effective file list for {collection.AnonymizedName}");
Penumbra.Log.Debug($"[{Environment.CurrentManagedThreadId}] Recalculating effective file list for {collection.Identity.AnonymizedName}");
if (!collection.HasCache)
{
Penumbra.Log.Error(
$"[{Environment.CurrentManagedThreadId}] Recalculating effective file list for {collection.AnonymizedName} failed, no cache exists.");
$"[{Environment.CurrentManagedThreadId}] Recalculating effective file list for {collection.Identity.AnonymizedName} failed, no cache exists.");
}
else if (collection._cache!.Calculating != -1)
{
Penumbra.Log.Error(
$"[{Environment.CurrentManagedThreadId}] Recalculating effective file list for {collection.AnonymizedName} failed, already in calculation on [{collection._cache!.Calculating}].");
$"[{Environment.CurrentManagedThreadId}] Recalculating effective file list for {collection.Identity.AnonymizedName} failed, already in calculation on [{collection._cache!.Calculating}].");
}
else
{
FullRecalculation(collection);
Penumbra.Log.Debug(
$"[{Environment.CurrentManagedThreadId}] Recalculation of effective file list for {collection.AnonymizedName} finished.");
$"[{Environment.CurrentManagedThreadId}] Recalculation of effective file list for {collection.Identity.AnonymizedName} finished.");
}
}
@ -213,7 +213,7 @@ public class CollectionCacheManager : IDisposable, IService
else
{
RemoveCache(old);
if (type is not CollectionType.Inactive && newCollection != null && newCollection.Index != 0 && CreateCache(newCollection))
if (type is not CollectionType.Inactive && newCollection != null && newCollection.Identity.Index != 0 && CreateCache(newCollection))
CalculateEffectiveFileList(newCollection);
if (type is CollectionType.Default)
@ -258,12 +258,12 @@ public class CollectionCacheManager : IDisposable, IService
private void RemoveCache(ModCollection? collection)
{
if (collection != null
&& collection.Index > ModCollection.Empty.Index
&& collection.Index != _active.Default.Index
&& collection.Index != _active.Interface.Index
&& collection.Index != _active.Current.Index
&& _active.SpecialAssignments.All(c => c.Value.Index != collection.Index)
&& _active.Individuals.All(c => c.Collection.Index != collection.Index))
&& collection.Identity.Index > ModCollection.Empty.Identity.Index
&& collection.Identity.Index != _active.Default.Identity.Index
&& collection.Identity.Index != _active.Interface.Identity.Index
&& collection.Identity.Index != _active.Current.Identity.Index
&& _active.SpecialAssignments.All(c => c.Value.Identity.Index != collection.Identity.Index)
&& _active.Individuals.All(c => c.Collection.Identity.Index != collection.Identity.Index))
ClearCache(collection);
}
@ -359,9 +359,9 @@ public class CollectionCacheManager : IDisposable, IService
collection._cache!.Dispose();
collection._cache = null;
if (collection.Index > 0)
if (collection.Identity.Index > 0)
Interlocked.Decrement(ref _count);
Penumbra.Log.Verbose($"Cleared cache of collection {collection.AnonymizedName}.");
Penumbra.Log.Verbose($"Cleared cache of collection {collection.Identity.AnonymizedName}.");
}
/// <summary>

View file

@ -59,7 +59,7 @@ public sealed class CollectionAutoSelector : IService, IDisposable
return;
var collection = _resolver.PlayerCollection();
Penumbra.Log.Debug($"Setting current collection to {collection.Identifier} through automatic collection selection.");
Penumbra.Log.Debug($"Setting current collection to {collection.Identity.Identifier} through automatic collection selection.");
_collections.SetCollection(collection, CollectionType.Current);
}

View file

@ -48,7 +48,7 @@ public static class ActiveCollectionMigration
if (!storage.ByName(collectionName, out var collection))
{
Penumbra.Messager.NotificationMessage(
$"Last choice of <{player}>'s Collection {collectionName} is not available, reset to {ModCollection.Empty.Name}.", NotificationType.Warning);
$"Last choice of <{player}>'s Collection {collectionName} is not available, reset to {ModCollection.Empty.Identity.Name}.", NotificationType.Warning);
dict.Add(player, ModCollection.Empty);
}
else

View file

@ -219,7 +219,7 @@ public class ActiveCollections : ISavable, IDisposable, IService
_ => null,
};
if (oldCollection == null || collection == oldCollection || collection.Index >= _storage.Count)
if (oldCollection == null || collection == oldCollection || collection.Identity.Index >= _storage.Count)
return;
switch (collectionType)
@ -262,13 +262,13 @@ public class ActiveCollections : ISavable, IDisposable, IService
var jObj = new JObject
{
{ nameof(Version), Version },
{ nameof(Default), Default.Id },
{ nameof(Interface), Interface.Id },
{ nameof(Current), Current.Id },
{ nameof(Default), Default.Identity.Id },
{ nameof(Interface), Interface.Identity.Id },
{ nameof(Current), Current.Identity.Id },
};
foreach (var (type, collection) in SpecialCollections.WithIndex().Where(p => p.Value != null)
.Select(p => ((CollectionType)p.Index, p.Value!)))
jObj.Add(type.ToString(), collection.Id);
jObj.Add(type.ToString(), collection.Identity.Id);
jObj.Add(nameof(Individuals), Individuals.ToJObject());
using var j = new JsonTextWriter(writer);
@ -300,7 +300,7 @@ public class ActiveCollections : ISavable, IDisposable, IService
if (oldCollection == Interface)
SetCollection(ModCollection.Empty, CollectionType.Interface);
if (oldCollection == Current)
SetCollection(Default.Index > ModCollection.Empty.Index ? Default : _storage.DefaultNamed, CollectionType.Current);
SetCollection(Default.Identity.Index > ModCollection.Empty.Identity.Index ? Default : _storage.DefaultNamed, CollectionType.Current);
for (var i = 0; i < SpecialCollections.Length; ++i)
{
@ -325,11 +325,11 @@ public class ActiveCollections : ISavable, IDisposable, IService
{
var configChanged = false;
// Load the default collection. If the name does not exist take the empty collection.
var defaultName = jObject[nameof(Default)]?.ToObject<string>() ?? ModCollection.Empty.Name;
var defaultName = jObject[nameof(Default)]?.ToObject<string>() ?? ModCollection.Empty.Identity.Name;
if (!_storage.ByName(defaultName, out var defaultCollection))
{
Penumbra.Messager.NotificationMessage(
$"Last choice of {TutorialService.DefaultCollection} {defaultName} is not available, reset to {ModCollection.Empty.Name}.",
$"Last choice of {TutorialService.DefaultCollection} {defaultName} is not available, reset to {ModCollection.Empty.Identity.Name}.",
NotificationType.Warning);
Default = ModCollection.Empty;
configChanged = true;
@ -340,11 +340,11 @@ public class ActiveCollections : ISavable, IDisposable, IService
}
// Load the interface collection. If no string is set, use the name of whatever was set as Default.
var interfaceName = jObject[nameof(Interface)]?.ToObject<string>() ?? Default.Name;
var interfaceName = jObject[nameof(Interface)]?.ToObject<string>() ?? Default.Identity.Name;
if (!_storage.ByName(interfaceName, out var interfaceCollection))
{
Penumbra.Messager.NotificationMessage(
$"Last choice of {TutorialService.InterfaceCollection} {interfaceName} is not available, reset to {ModCollection.Empty.Name}.",
$"Last choice of {TutorialService.InterfaceCollection} {interfaceName} is not available, reset to {ModCollection.Empty.Identity.Name}.",
NotificationType.Warning);
Interface = ModCollection.Empty;
configChanged = true;
@ -355,11 +355,11 @@ public class ActiveCollections : ISavable, IDisposable, IService
}
// Load the current collection.
var currentName = jObject[nameof(Current)]?.ToObject<string>() ?? Default.Name;
var currentName = jObject[nameof(Current)]?.ToObject<string>() ?? Default.Identity.Name;
if (!_storage.ByName(currentName, out var currentCollection))
{
Penumbra.Messager.NotificationMessage(
$"Last choice of {TutorialService.SelectedCollection} {currentName} is not available, reset to {ModCollection.DefaultCollectionName}.",
$"Last choice of {TutorialService.SelectedCollection} {currentName} is not available, reset to {ModCollectionIdentity.DefaultCollectionName}.",
NotificationType.Warning);
Current = _storage.DefaultNamed;
configChanged = true;
@ -404,7 +404,7 @@ public class ActiveCollections : ISavable, IDisposable, IService
if (!_storage.ById(defaultId, out var defaultCollection))
{
Penumbra.Messager.NotificationMessage(
$"Last choice of {TutorialService.DefaultCollection} {defaultId} is not available, reset to {ModCollection.Empty.Name}.",
$"Last choice of {TutorialService.DefaultCollection} {defaultId} is not available, reset to {ModCollection.Empty.Identity.Name}.",
NotificationType.Warning);
Default = ModCollection.Empty;
configChanged = true;
@ -415,11 +415,11 @@ public class ActiveCollections : ISavable, IDisposable, IService
}
// Load the interface collection. If no string is set, use the name of whatever was set as Default.
var interfaceId = jObject[nameof(Interface)]?.ToObject<Guid>() ?? Default.Id;
var interfaceId = jObject[nameof(Interface)]?.ToObject<Guid>() ?? Default.Identity.Id;
if (!_storage.ById(interfaceId, out var interfaceCollection))
{
Penumbra.Messager.NotificationMessage(
$"Last choice of {TutorialService.InterfaceCollection} {interfaceId} is not available, reset to {ModCollection.Empty.Name}.",
$"Last choice of {TutorialService.InterfaceCollection} {interfaceId} is not available, reset to {ModCollection.Empty.Identity.Name}.",
NotificationType.Warning);
Interface = ModCollection.Empty;
configChanged = true;
@ -430,11 +430,11 @@ public class ActiveCollections : ISavable, IDisposable, IService
}
// Load the current collection.
var currentId = jObject[nameof(Current)]?.ToObject<Guid>() ?? _storage.DefaultNamed.Id;
var currentId = jObject[nameof(Current)]?.ToObject<Guid>() ?? _storage.DefaultNamed.Identity.Id;
if (!_storage.ById(currentId, out var currentCollection))
{
Penumbra.Messager.NotificationMessage(
$"Last choice of {TutorialService.SelectedCollection} {currentId} is not available, reset to {ModCollection.DefaultCollectionName}.",
$"Last choice of {TutorialService.SelectedCollection} {currentId} is not available, reset to {ModCollectionIdentity.DefaultCollectionName}.",
NotificationType.Warning);
Current = _storage.DefaultNamed;
configChanged = true;
@ -587,7 +587,7 @@ public class ActiveCollections : ISavable, IDisposable, IService
case IdentifierType.Player when id.HomeWorld != ushort.MaxValue:
{
var global = ByType(CollectionType.Individual, _actors.CreatePlayer(id.PlayerName, ushort.MaxValue));
return global?.Index == checkAssignment.Index
return (global != null ? global.Identity.Index : null) == checkAssignment.Identity.Index
? "Assignment is redundant due to an identical Any-World assignment existing.\nYou can remove it."
: string.Empty;
}
@ -596,12 +596,12 @@ public class ActiveCollections : ISavable, IDisposable, IService
{
var global = ByType(CollectionType.Individual,
_actors.CreateOwned(id.PlayerName, ushort.MaxValue, id.Kind, id.DataId));
if (global?.Index == checkAssignment.Index)
if ((global != null ? global.Identity.Index : null) == checkAssignment.Identity.Index)
return "Assignment is redundant due to an identical Any-World assignment existing.\nYou can remove it.";
}
var unowned = ByType(CollectionType.Individual, _actors.CreateNpc(id.Kind, id.DataId));
return unowned?.Index == checkAssignment.Index
return (unowned != null ? unowned.Identity.Index : null) == checkAssignment.Identity.Index
? "Assignment is redundant due to an identical unowned NPC assignment existing.\nYou can remove it."
: string.Empty;
}
@ -617,7 +617,7 @@ public class ActiveCollections : ISavable, IDisposable, IService
if (maleNpc == null)
{
maleNpc = Default;
if (maleNpc.Index != checkAssignment.Index)
if (maleNpc.Identity.Index != checkAssignment.Identity.Index)
return string.Empty;
collection1 = CollectionType.Default;
@ -626,7 +626,7 @@ public class ActiveCollections : ISavable, IDisposable, IService
if (femaleNpc == null)
{
femaleNpc = Default;
if (femaleNpc.Index != checkAssignment.Index)
if (femaleNpc.Identity.Index != checkAssignment.Identity.Index)
return string.Empty;
collection2 = CollectionType.Default;
@ -646,7 +646,7 @@ public class ActiveCollections : ISavable, IDisposable, IService
if (assignment == null)
continue;
if (assignment.Index == checkAssignment.Index)
if (assignment.Identity.Index == checkAssignment.Identity.Index)
return
$"Assignment is currently redundant due to overwriting {parentType.ToName()} with an identical collection.\nYou can remove it.";
}

View file

@ -41,7 +41,7 @@ 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, id, name, CurrentCollectionId, version, Count, allSettings,
var newCollection = ModCollection.CreateFromData(_saveService, _modStorage, new ModCollectionIdentity(id, CurrentCollectionId, name, Count), version, allSettings,
inheritances);
_collectionsByLocal[CurrentCollectionId] = newCollection;
CurrentCollectionId += 1;
@ -57,7 +57,7 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable, ISer
}
public void Delete(ModCollection collection)
=> _collectionsByLocal.Remove(collection.LocalId);
=> _collectionsByLocal.Remove(collection.Identity.LocalId);
/// <remarks> The empty collection is always available at Index 0. </remarks>
private readonly List<ModCollection> _collections =
@ -92,7 +92,7 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable, ISer
public bool ByName(string name, [NotNullWhen(true)] out ModCollection? collection)
{
if (name.Length != 0)
return _collections.FindFirst(c => string.Equals(c.Name, name, StringComparison.OrdinalIgnoreCase), out collection);
return _collections.FindFirst(c => string.Equals(c.Identity.Name, name, StringComparison.OrdinalIgnoreCase), out collection);
collection = ModCollection.Empty;
return true;
@ -102,7 +102,7 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable, ISer
public bool ById(Guid id, [NotNullWhen(true)] out ModCollection? collection)
{
if (id != Guid.Empty)
return _collections.FindFirst(c => c.Id == id, out collection);
return _collections.FindFirst(c => c.Identity.Id == id, out collection);
collection = ModCollection.Empty;
return true;
@ -158,7 +158,7 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable, ISer
var newCollection = Create(name, _collections.Count, duplicate);
_collections.Add(newCollection);
_saveService.ImmediateSave(new ModCollectionSave(_modStorage, newCollection));
Penumbra.Messager.NotificationMessage($"Created new collection {newCollection.AnonymizedName}.", NotificationType.Success, false);
Penumbra.Messager.NotificationMessage($"Created new collection {newCollection.Identity.AnonymizedName}.", NotificationType.Success, false);
_communicator.CollectionChange.Invoke(CollectionType.Inactive, null, newCollection, string.Empty);
return true;
}
@ -168,13 +168,13 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable, ISer
/// </summary>
public bool RemoveCollection(ModCollection collection)
{
if (collection.Index <= ModCollection.Empty.Index || collection.Index >= _collections.Count)
if (collection.Identity.Index <= ModCollection.Empty.Identity.Index || collection.Identity.Index >= _collections.Count)
{
Penumbra.Messager.NotificationMessage("Can not remove the empty collection.", NotificationType.Error, false);
return false;
}
if (collection.Index == DefaultNamed.Index)
if (collection.Identity.Index == DefaultNamed.Identity.Index)
{
Penumbra.Messager.NotificationMessage("Can not remove the default collection.", NotificationType.Error, false);
return false;
@ -182,13 +182,13 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable, ISer
Delete(collection);
_saveService.ImmediateDelete(new ModCollectionSave(_modStorage, collection));
_collections.RemoveAt(collection.Index);
_collections.RemoveAt(collection.Identity.Index);
// Update indices.
for (var i = collection.Index; i < Count; ++i)
_collections[i].Index = i;
_collectionsByLocal.Remove(collection.LocalId);
for (var i = collection.Identity.Index; i < Count; ++i)
_collections[i].Identity.Index = i;
_collectionsByLocal.Remove(collection.Identity.LocalId);
Penumbra.Messager.NotificationMessage($"Deleted collection {collection.AnonymizedName}.", NotificationType.Success, false);
Penumbra.Messager.NotificationMessage($"Deleted collection {collection.Identity.AnonymizedName}.", NotificationType.Success, false);
_communicator.CollectionChange.Invoke(CollectionType.Inactive, collection, null, string.Empty);
return true;
}
@ -246,13 +246,13 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable, ISer
{
File.Move(file.FullName, correctName, false);
Penumbra.Messager.NotificationMessage(
$"Collection {file.Name} does not correspond to {collection.Identifier}, renamed.",
$"Collection {file.Name} does not correspond to {collection.Identity.Identifier}, renamed.",
NotificationType.Warning);
}
catch (Exception ex)
{
Penumbra.Messager.NotificationMessage(
$"Collection {file.Name} does not correspond to {collection.Identifier}, rename failed:\n{ex}",
$"Collection {file.Name} does not correspond to {collection.Identity.Identifier}, rename failed:\n{ex}",
NotificationType.Warning);
}
}
@ -273,7 +273,7 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable, ISer
catch (Exception e)
{
Penumbra.Messager.NotificationMessage(e,
$"Collection {file.Name} does not correspond to {collection.Identifier}, but could not rename.",
$"Collection {file.Name} does not correspond to {collection.Identity.Identifier}, but could not rename.",
NotificationType.Error);
}
@ -291,14 +291,14 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable, ISer
/// </summary>
private ModCollection SetDefaultNamedCollection()
{
if (ByName(ModCollection.DefaultCollectionName, out var collection))
if (ByName(ModCollectionIdentity.DefaultCollectionName, out var collection))
return collection;
if (AddCollection(ModCollection.DefaultCollectionName, null))
if (AddCollection(ModCollectionIdentity.DefaultCollectionName, null))
return _collections[^1];
Penumbra.Messager.NotificationMessage(
$"Unknown problem creating a collection with the name {ModCollection.DefaultCollectionName}, which is required to exist.",
$"Unknown problem creating a collection with the name {ModCollectionIdentity.DefaultCollectionName}, which is required to exist.",
NotificationType.Error);
return Count > 1 ? _collections[1] : _collections[0];
}

View file

@ -18,7 +18,7 @@ public partial class IndividualCollections
foreach (var (name, identifiers, collection) in Assignments)
{
var tmp = identifiers[0].ToJson();
tmp.Add("Collection", collection.Id);
tmp.Add("Collection", collection.Identity.Id);
tmp.Add("Display", name);
ret.Add(tmp);
}
@ -182,7 +182,7 @@ public partial class IndividualCollections
Penumbra.Log.Information($"Migrated {name} ({kind.ToName()}) to NPC Identifiers [{ids}].");
else
Penumbra.Messager.NotificationMessage(
$"Could not migrate {name} ({collection.AnonymizedName}) which was assumed to be a {kind.ToName()} with IDs [{ids}], please look through your individual collections.",
$"Could not migrate {name} ({collection.Identity.AnonymizedName}) which was assumed to be a {kind.ToName()} with IDs [{ids}], please look through your individual collections.",
NotificationType.Error);
}
// If it is not a valid NPC name, check if it can be a player name.
@ -192,16 +192,16 @@ public partial class IndividualCollections
var shortName = string.Join(" ", name.Split().Select(n => $"{n[0]}."));
// Try to migrate the player name without logging full names.
if (Add($"{name} ({_actors.Data.ToWorldName(identifier.HomeWorld)})", [identifier], collection))
Penumbra.Log.Information($"Migrated {shortName} ({collection.AnonymizedName}) to Player Identifier.");
Penumbra.Log.Information($"Migrated {shortName} ({collection.Identity.AnonymizedName}) to Player Identifier.");
else
Penumbra.Messager.NotificationMessage(
$"Could not migrate {shortName} ({collection.AnonymizedName}), please look through your individual collections.",
$"Could not migrate {shortName} ({collection.Identity.AnonymizedName}), please look through your individual collections.",
NotificationType.Error);
}
else
{
Penumbra.Messager.NotificationMessage(
$"Could not migrate {name} ({collection.AnonymizedName}), which can not be a player name nor is it a known NPC name, please look through your individual collections.",
$"Could not migrate {name} ({collection.Identity.AnonymizedName}), which can not be a player name nor is it a known NPC name, please look through your individual collections.",
NotificationType.Error);
}
}

View file

@ -89,7 +89,7 @@ public class InheritanceManager : IDisposable, IService
_saveService.QueueSave(new ModCollectionSave(_modStorage, inheritor));
_communicator.CollectionInheritanceChanged.Invoke(inheritor, false);
RecurseInheritanceChanges(inheritor);
Penumbra.Log.Debug($"Removed {parent.AnonymizedName} from {inheritor.AnonymizedName} inheritances.");
Penumbra.Log.Debug($"Removed {parent.Identity.AnonymizedName} from {inheritor.Identity.AnonymizedName} inheritances.");
}
/// <summary> Order in the inheritance list is relevant. </summary>
@ -101,7 +101,7 @@ public class InheritanceManager : IDisposable, IService
_saveService.QueueSave(new ModCollectionSave(_modStorage, inheritor));
_communicator.CollectionInheritanceChanged.Invoke(inheritor, false);
RecurseInheritanceChanges(inheritor);
Penumbra.Log.Debug($"Moved {inheritor.AnonymizedName}s inheritance {from} to {to}.");
Penumbra.Log.Debug($"Moved {inheritor.Identity.AnonymizedName}s inheritance {from} to {to}.");
}
/// <inheritdoc cref="AddInheritance(ModCollection, ModCollection)"/>
@ -119,7 +119,7 @@ public class InheritanceManager : IDisposable, IService
RecurseInheritanceChanges(inheritor);
}
Penumbra.Log.Debug($"Added {parent.AnonymizedName} to {inheritor.AnonymizedName} inheritances.");
Penumbra.Log.Debug($"Added {parent.Identity.AnonymizedName} to {inheritor.Identity.AnonymizedName} inheritances.");
return true;
}
@ -143,23 +143,23 @@ public class InheritanceManager : IDisposable, IService
continue;
changes = true;
Penumbra.Messager.NotificationMessage($"{collection.Name} can not inherit from {subCollection.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))
{
changes = true;
Penumbra.Log.Information($"Migrating inheritance for {collection.AnonymizedName} from name to GUID.");
Penumbra.Log.Information($"Migrating inheritance for {collection.Identity.AnonymizedName} from name to GUID.");
if (AddInheritance(collection, subCollection, false))
continue;
Penumbra.Messager.NotificationMessage($"{collection.Name} can not inherit from {subCollection.Name}, removed.",
Penumbra.Messager.NotificationMessage($"{collection.Identity.Name} can not inherit from {subCollection.Identity.Name}, removed.",
NotificationType.Warning);
}
else
{
Penumbra.Messager.NotificationMessage(
$"Inherited collection {subCollectionName} for {collection.AnonymizedName} does not exist, it was removed.",
$"Inherited collection {subCollectionName} for {collection.Identity.AnonymizedName} does not exist, it was removed.",
NotificationType.Warning);
changes = true;
}

View file

@ -44,7 +44,7 @@ public class TempCollectionManager : IDisposable, IService
=> _customCollections.Values;
public bool CollectionByName(string name, [NotNullWhen(true)] out ModCollection? collection)
=> _customCollections.Values.FindFirst(c => string.Equals(name, c.Name, StringComparison.OrdinalIgnoreCase), out collection);
=> _customCollections.Values.FindFirst(c => string.Equals(name, c.Identity.Name, StringComparison.OrdinalIgnoreCase), out collection);
public bool CollectionById(Guid id, [NotNullWhen(true)] out ModCollection? collection)
=> _customCollections.TryGetValue(id, out collection);
@ -54,12 +54,12 @@ public class TempCollectionManager : IDisposable, IService
if (GlobalChangeCounter == int.MaxValue)
GlobalChangeCounter = 0;
var collection = _storage.CreateTemporary(name, ~Count, GlobalChangeCounter++);
Penumbra.Log.Debug($"Creating temporary collection {collection.Name} with {collection.Id}.");
if (_customCollections.TryAdd(collection.Id, collection))
Penumbra.Log.Debug($"Creating temporary collection {collection.Identity.Name} with {collection.Identity.Id}.");
if (_customCollections.TryAdd(collection.Identity.Id, collection))
{
// Temporary collection created.
_communicator.CollectionChange.Invoke(CollectionType.Temporary, null, collection, string.Empty);
return collection.Id;
return collection.Identity.Id;
}
return Guid.Empty;
@ -74,7 +74,7 @@ public class TempCollectionManager : IDisposable, IService
}
_storage.Delete(collection);
Penumbra.Log.Debug($"Deleted temporary collection {collection.Id}.");
Penumbra.Log.Debug($"Deleted temporary collection {collection.Identity.Id}.");
GlobalChangeCounter += Math.Max(collection.Counters.Change + 1 - GlobalChangeCounter, 0);
for (var i = 0; i < Collections.Count; ++i)
{
@ -83,7 +83,7 @@ public class TempCollectionManager : IDisposable, IService
// Temporary collection assignment removed.
_communicator.CollectionChange.Invoke(CollectionType.Temporary, collection, null, Collections[i].DisplayName);
Penumbra.Log.Verbose($"Unassigned temporary collection {collection.Id} from {Collections[i].DisplayName}.");
Penumbra.Log.Verbose($"Unassigned temporary collection {collection.Identity.Id} from {Collections[i].DisplayName}.");
Collections.Delete(i--);
}
@ -96,7 +96,7 @@ public class TempCollectionManager : IDisposable, IService
return false;
// Temporary collection assignment added.
Penumbra.Log.Verbose($"Assigned temporary collection {collection.AnonymizedName} to {Collections.Last().DisplayName}.");
Penumbra.Log.Verbose($"Assigned temporary collection {collection.Identity.AnonymizedName} to {Collections.Last().DisplayName}.");
_communicator.CollectionChange.Invoke(CollectionType.Temporary, null, collection, Collections.Last().DisplayName);
return true;
}
@ -127,6 +127,6 @@ public class TempCollectionManager : IDisposable, IService
return false;
var identifier = _actors.CreatePlayer(byteString, worldId);
return Collections.TryGetValue(identifier, out var collection) && RemoveTemporaryCollection(collection.Id);
return Collections.TryGetValue(identifier, out var collection) && RemoveTemporaryCollection(collection.Identity.Id);
}
}

View file

@ -13,42 +13,21 @@ namespace Penumbra.Collections;
/// - 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 = 2;
public const string DefaultCollectionName = "Default";
public const string EmptyCollectionName = "None";
public const int CurrentVersion = 2;
/// <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 = new(Guid.Empty, EmptyCollectionName, LocalCollectionId.Zero, 0, 0, CurrentVersion, [], [], []);
public static readonly ModCollection Empty = new(ModCollectionIdentity.Empty, 0, CurrentVersion, [], [], []);
/// <summary> The name of a collection. </summary>
public string Name { get; set; }
public Guid Id { get; }
public LocalCollectionId LocalId { get; }
public string Identifier
=> Id.ToString();
public string ShortIdentifier
=> Identifier[..8];
public ModCollectionIdentity Identity;
public override string ToString()
=> Name.Length > 0 ? Name : ShortIdentifier;
/// <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 == DefaultCollectionName ? Name : ShortIdentifier;
/// <summary> The index of the collection is set and kept up-to-date by the CollectionManager. </summary>
public int Index { get; internal set; }
=> Identity.ToString();
public CollectionCounters Counters;
@ -90,7 +69,7 @@ public partial class ModCollection
{
get
{
if (Index <= 0)
if (Identity.Index <= 0)
return (ModSettings.Empty, this);
foreach (var collection in GetFlattenedInheritance())
@ -114,17 +93,17 @@ 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(Guid.NewGuid(), 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.Select(s => s?.DeepCopy()).ToList(), [.. DirectlyInheritsFrom],
UnusedSettings.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.DeepCopy()));
}
/// <summary> Constructor for reading from files. </summary>
public static ModCollection CreateFromData(SaveService saver, ModStorage mods, Guid id, string name, LocalCollectionId localId, int version,
int index,
public static ModCollection CreateFromData(SaveService saver, ModStorage mods, ModCollectionIdentity identity, int version,
Dictionary<string, ModSettings.SavedSettings> allSettings, IReadOnlyList<string> inheritances)
{
Debug.Assert(index > 0, "Collection read with non-positive index.");
var ret = new ModCollection(id, name, localId, index, 0, version, [], [], allSettings)
Debug.Assert(identity.Index > 0, "Collection read with non-positive index.");
var ret = new ModCollection(identity, 0, version, [], [], allSettings)
{
InheritanceByName = inheritances,
};
@ -137,7 +116,7 @@ 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(Guid.NewGuid(), name, localId, index, changeCounter, CurrentVersion, [], [], []);
var ret = new ModCollection(ModCollectionIdentity.New(name, localId, index), changeCounter, CurrentVersion, [], [], []);
return ret;
}
@ -145,9 +124,8 @@ 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(Guid.NewGuid(), name, localId, index, 0, CurrentVersion,
Enumerable.Repeat((ModSettings?)null, modCount).ToList(), [],
[]);
return new ModCollection(ModCollectionIdentity.New(name, localId, index), 0, CurrentVersion,
Enumerable.Repeat((ModSettings?)null, modCount).ToList(), [], []);
}
/// <summary> Add settings for a new appended mod, by checking if the mod had settings from a previous deletion. </summary>
@ -195,16 +173,13 @@ public partial class ModCollection
saver.ImmediateSave(new ModCollectionSave(mods, this));
}
private ModCollection(Guid id, string name, LocalCollectionId localId, int index, int changeCounter, int version,
List<ModSettings?> appliedSettings, List<ModCollection> inheritsFrom, Dictionary<string, ModSettings.SavedSettings> settings)
private ModCollection(ModCollectionIdentity identity, int changeCounter, int version, List<ModSettings?> appliedSettings,
List<ModCollection> inheritsFrom, Dictionary<string, ModSettings.SavedSettings> settings)
{
Name = name;
Id = id;
LocalId = localId;
Index = index;
Identity = identity;
Counters = new CollectionCounters(changeCounter);
Settings = appliedSettings;
UnusedSettings = settings;
Settings = appliedSettings;
UnusedSettings = settings;
DirectlyInheritsFrom = inheritsFrom;
foreach (var c in DirectlyInheritsFrom)
((List<ModCollection>)c.DirectParentOf).Add(this);

View file

@ -0,0 +1,42 @@
using OtterGui;
using Penumbra.Collections.Manager;
namespace Penumbra.Collections;
public struct ModCollectionIdentity(Guid id, LocalCollectionId localId)
{
public const string DefaultCollectionName = "Default";
public const string EmptyCollectionName = "None";
public static readonly ModCollectionIdentity Empty = new(Guid.Empty, LocalCollectionId.Zero, EmptyCollectionName, 0);
public string Name { get; set; }
public Guid Id { get; } = id;
public LocalCollectionId LocalId { get; } = localId;
/// <summary> The index of the collection is set and kept up-to-date by the CollectionManager. </summary>
public int Index { get; internal set; }
public string Identifier
=> Id.ToString();
public string ShortIdentifier
=> Id.ShortGuid();
/// <summary> Get the short identifier of a collection unless it is a well-known collection name. </summary>
public string AnonymizedName
=> Id == Guid.Empty ? EmptyCollectionName : Name == DefaultCollectionName ? Name : ShortIdentifier;
public override string ToString()
=> Name.Length > 0 ? Name : ShortIdentifier;
public ModCollectionIdentity(Guid id, LocalCollectionId localId, string name, int index)
: this(id, localId)
{
Name = name;
Index = index;
}
public static ModCollectionIdentity New(string name, LocalCollectionId id, int index)
=> new(Guid.NewGuid(), id, name, index);
}

View file

@ -15,7 +15,7 @@ internal readonly struct ModCollectionSave(ModStorage modStorage, ModCollection
=> fileNames.CollectionFile(modCollection);
public string LogName(string _)
=> modCollection.AnonymizedName;
=> modCollection.Identity.AnonymizedName;
public string TypeName
=> "Collection";
@ -28,10 +28,10 @@ internal readonly struct ModCollectionSave(ModStorage modStorage, ModCollection
j.WriteStartObject();
j.WritePropertyName("Version");
j.WriteValue(ModCollection.CurrentVersion);
j.WritePropertyName(nameof(ModCollection.Id));
j.WriteValue(modCollection.Identifier);
j.WritePropertyName(nameof(ModCollection.Name));
j.WriteValue(modCollection.Name);
j.WritePropertyName(nameof(ModCollectionIdentity.Id));
j.WriteValue(modCollection.Identity.Identifier);
j.WritePropertyName(nameof(ModCollectionIdentity.Name));
j.WriteValue(modCollection.Identity.Name);
j.WritePropertyName(nameof(ModCollection.Settings));
// Write all used and unused settings by mod directory name.
@ -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.Identifier));
x.Serialize(j, modCollection.InheritanceByName ?? modCollection.DirectlyInheritsFrom.Select(c => c.Identity.Identifier));
j.WriteEndObject();
}
@ -79,8 +79,8 @@ internal readonly struct ModCollectionSave(ModStorage modStorage, ModCollection
{
var obj = JObject.Parse(File.ReadAllText(file.FullName));
version = obj["Version"]?.ToObject<int>() ?? 0;
name = obj[nameof(ModCollection.Name)]?.ToObject<string>() ?? string.Empty;
id = obj[nameof(ModCollection.Id)]?.ToObject<Guid>() ?? (version == 1 ? Guid.NewGuid() : Guid.Empty);
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;
inheritance = obj["Inheritance"]?.ToObject<List<string>>() ?? inheritance;

View file

@ -23,7 +23,7 @@ public readonly struct ResolveData(ModCollection collection, nint gameObject)
{ }
public override string ToString()
=> ModCollection.Name;
=> ModCollection.Identity.Name;
}
public static class ResolveDataExtensions