Add attribute handling, rework atr and shape caches.

This commit is contained in:
Ottermandias 2025-05-21 15:44:26 +02:00
parent 3412786282
commit d7dee39fab
23 changed files with 1187 additions and 300 deletions

View file

@ -0,0 +1,56 @@
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using Penumbra.Meta;
using Penumbra.Meta.Manipulations;
namespace Penumbra.Collections.Cache;
public sealed class AtrCache(MetaFileManager manager, ModCollection collection) : MetaCacheBase<AtrIdentifier, AtrEntry>(manager, collection)
{
public bool ShouldBeDisabled(in ShapeAttributeString attribute, HumanSlot slot, PrimaryId id, GenderRace genderRace)
=> DisabledCount > 0 && _atrData.TryGetValue(attribute, out var value) && value.Contains(slot, id, genderRace);
public int DisabledCount { get; private set; }
internal IReadOnlyDictionary<ShapeAttributeString, ShapeAttributeHashSet> Data
=> _atrData;
private readonly Dictionary<ShapeAttributeString, ShapeAttributeHashSet> _atrData = [];
public void Reset()
{
Clear();
_atrData.Clear();
}
protected override void Dispose(bool _)
=> Clear();
protected override void ApplyModInternal(AtrIdentifier identifier, AtrEntry entry)
{
if (!_atrData.TryGetValue(identifier.Attribute, out var value))
{
if (entry.Value)
return;
value = [];
_atrData.Add(identifier.Attribute, value);
}
if (value.TrySet(identifier.Slot, identifier.Id, identifier.GenderRaceCondition, !entry.Value))
++DisabledCount;
}
protected override void RevertModInternal(AtrIdentifier identifier)
{
if (!_atrData.TryGetValue(identifier.Attribute, out var value))
return;
if (value.TrySet(identifier.Slot, identifier.Id, identifier.GenderRaceCondition, false))
{
--DisabledCount;
if (value.IsEmpty)
_atrData.Remove(identifier.Attribute);
}
}
}

View file

@ -247,6 +247,8 @@ public sealed class CollectionCache : IDisposable
AddManipulation(mod, identifier, entry);
foreach (var (identifier, entry) in files.Manipulations.Shp)
AddManipulation(mod, identifier, entry);
foreach (var (identifier, entry) in files.Manipulations.Atr)
AddManipulation(mod, identifier, entry);
foreach (var identifier in files.Manipulations.GlobalEqp)
AddManipulation(mod, identifier, null!);
}

View file

@ -17,11 +17,12 @@ public class MetaCache(MetaFileManager manager, ModCollection collection)
public readonly ImcCache Imc = new(manager, collection);
public readonly AtchCache Atch = new(manager, collection);
public readonly ShpCache Shp = new(manager, collection);
public readonly AtrCache Atr = new(manager, collection);
public readonly GlobalEqpCache GlobalEqp = new();
public bool IsDisposed { get; private set; }
public int Count
=> Eqp.Count + Eqdp.Count + Est.Count + Gmp.Count + Rsp.Count + Imc.Count + Atch.Count + Shp.Count + GlobalEqp.Count;
=> Eqp.Count + Eqdp.Count + Est.Count + Gmp.Count + Rsp.Count + Imc.Count + Atch.Count + Shp.Count + Atr.Count + GlobalEqp.Count;
public IEnumerable<(IMetaIdentifier, IMod)> IdentifierSources
=> Eqp.Select(kvp => ((IMetaIdentifier)kvp.Key, kvp.Value.Source))
@ -32,6 +33,7 @@ public class MetaCache(MetaFileManager manager, ModCollection collection)
.Concat(Imc.Select(kvp => ((IMetaIdentifier)kvp.Key, kvp.Value.Source)))
.Concat(Atch.Select(kvp => ((IMetaIdentifier)kvp.Key, kvp.Value.Source)))
.Concat(Shp.Select(kvp => ((IMetaIdentifier)kvp.Key, kvp.Value.Source)))
.Concat(Atr.Select(kvp => ((IMetaIdentifier)kvp.Key, kvp.Value.Source)))
.Concat(GlobalEqp.Select(kvp => ((IMetaIdentifier)kvp.Key, kvp.Value)));
public void Reset()
@ -44,6 +46,7 @@ public class MetaCache(MetaFileManager manager, ModCollection collection)
Imc.Reset();
Atch.Reset();
Shp.Reset();
Atr.Reset();
GlobalEqp.Clear();
}
@ -61,6 +64,7 @@ public class MetaCache(MetaFileManager manager, ModCollection collection)
Imc.Dispose();
Atch.Dispose();
Shp.Dispose();
Atr.Dispose();
}
public bool TryGetMod(IMetaIdentifier identifier, [NotNullWhen(true)] out IMod? mod)
@ -76,6 +80,7 @@ public class MetaCache(MetaFileManager manager, ModCollection collection)
RspIdentifier i => Rsp.TryGetValue(i, out var p) && Convert(p, out mod),
AtchIdentifier i => Atch.TryGetValue(i, out var p) && Convert(p, out mod),
ShpIdentifier i => Shp.TryGetValue(i, out var p) && Convert(p, out mod),
AtrIdentifier i => Atr.TryGetValue(i, out var p) && Convert(p, out mod),
GlobalEqpManipulation i => GlobalEqp.TryGetValue(i, out mod),
_ => false,
};
@ -98,6 +103,7 @@ public class MetaCache(MetaFileManager manager, ModCollection collection)
RspIdentifier i => Rsp.RevertMod(i, out mod),
AtchIdentifier i => Atch.RevertMod(i, out mod),
ShpIdentifier i => Shp.RevertMod(i, out mod),
AtrIdentifier i => Atr.RevertMod(i, out mod),
GlobalEqpManipulation i => GlobalEqp.RevertMod(i, out mod),
_ => (mod = null) != null,
};
@ -115,6 +121,7 @@ public class MetaCache(MetaFileManager manager, ModCollection collection)
RspIdentifier i when entry is RspEntry e => Rsp.ApplyMod(mod, i, e),
AtchIdentifier i when entry is AtchEntry e => Atch.ApplyMod(mod, i, e),
ShpIdentifier i when entry is ShpEntry e => Shp.ApplyMod(mod, i, e),
AtrIdentifier i when entry is AtrEntry e => Atr.ApplyMod(mod, i, e),
GlobalEqpManipulation i => GlobalEqp.ApplyMod(mod, i),
_ => false,
};

View file

@ -0,0 +1,123 @@
using System.Collections.Frozen;
using OtterGui.Extensions;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using Penumbra.Meta;
namespace Penumbra.Collections.Cache;
public sealed class ShapeAttributeHashSet : Dictionary<(HumanSlot Slot, PrimaryId Id), ulong>
{
public static readonly IReadOnlyList<GenderRace> GenderRaceValues =
[
GenderRace.Unknown, GenderRace.MidlanderMale, GenderRace.MidlanderFemale, GenderRace.HighlanderMale, GenderRace.HighlanderFemale,
GenderRace.ElezenMale, GenderRace.ElezenFemale, GenderRace.MiqoteMale, GenderRace.MiqoteFemale, GenderRace.RoegadynMale,
GenderRace.RoegadynFemale, GenderRace.LalafellMale, GenderRace.LalafellFemale, GenderRace.AuRaMale, GenderRace.AuRaFemale,
GenderRace.HrothgarMale, GenderRace.HrothgarFemale, GenderRace.VieraMale, GenderRace.VieraFemale,
];
public static readonly FrozenDictionary<GenderRace, int> GenderRaceIndices =
GenderRaceValues.WithIndex().ToFrozenDictionary(p => p.Value, p => p.Index);
private readonly BitArray _allIds = new((ShapeAttributeManager.ModelSlotSize + 1) * GenderRaceValues.Count);
public bool this[HumanSlot slot]
=> slot is HumanSlot.Unknown ? All : _allIds[(int)slot * GenderRaceIndices.Count];
public bool this[GenderRace genderRace]
=> GenderRaceIndices.TryGetValue(genderRace, out var index)
&& _allIds[ShapeAttributeManager.ModelSlotSize * GenderRaceIndices.Count + index];
public bool this[HumanSlot slot, GenderRace genderRace]
{
get
{
if (!GenderRaceIndices.TryGetValue(genderRace, out var index))
return false;
if (_allIds[ShapeAttributeManager.ModelSlotSize * GenderRaceIndices.Count + index])
return true;
return _allIds[(int)slot * GenderRaceIndices.Count + index];
}
set
{
if (!GenderRaceIndices.TryGetValue(genderRace, out var index))
return;
var genderRaceCount = GenderRaceValues.Count;
if (slot is HumanSlot.Unknown)
_allIds[ShapeAttributeManager.ModelSlotSize * genderRaceCount + index] = value;
else
_allIds[(int)slot * genderRaceCount + index] = value;
}
}
public bool All
=> _allIds[ShapeAttributeManager.ModelSlotSize * GenderRaceIndices.Count];
public bool Contains(HumanSlot slot, PrimaryId id, GenderRace genderRace)
=> All || this[slot, genderRace] || ContainsEntry(slot, id, genderRace);
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private bool ContainsEntry(HumanSlot slot, PrimaryId id, GenderRace genderRace)
=> GenderRaceIndices.TryGetValue(genderRace, out var index)
&& TryGetValue((slot, id), out var flags)
&& (flags & (1ul << index)) is not 0;
public bool TrySet(HumanSlot slot, PrimaryId? id, GenderRace genderRace, bool value)
{
if (!GenderRaceIndices.TryGetValue(genderRace, out var index))
return false;
if (!id.HasValue)
{
var slotIndex = slot is HumanSlot.Unknown ? ShapeAttributeManager.ModelSlotSize : (int)slot;
var old = _allIds[slotIndex * GenderRaceIndices.Count + index];
_allIds[slotIndex * GenderRaceIndices.Count + index] = value;
return old != value;
}
if (value)
{
if (TryGetValue((slot, id.Value), out var flags))
{
var newFlags = flags | (1ul << index);
if (newFlags == flags)
return false;
this[(slot, id.Value)] = newFlags;
return true;
}
this[(slot, id.Value)] = 1ul << index;
return true;
}
else if (TryGetValue((slot, id.Value), out var flags))
{
var newFlags = flags & ~(1ul << index);
if (newFlags == flags)
return false;
if (newFlags is 0)
{
Remove((slot, id.Value));
return true;
}
this[(slot, id.Value)] = newFlags;
return true;
}
return false;
}
public new void Clear()
{
base.Clear();
_allIds.SetAll(false);
}
public bool IsEmpty
=> !_allIds.HasAnySet() && Count is 0;
}

View file

@ -7,10 +7,10 @@ namespace Penumbra.Collections.Cache;
public sealed class ShpCache(MetaFileManager manager, ModCollection collection) : MetaCacheBase<ShpIdentifier, ShpEntry>(manager, collection)
{
public bool ShouldBeEnabled(in ShapeString shape, HumanSlot slot, PrimaryId id)
=> EnabledCount > 0 && _shpData.TryGetValue(shape, out var value) && value.Contains(slot, id);
public bool ShouldBeEnabled(in ShapeAttributeString shape, HumanSlot slot, PrimaryId id, GenderRace genderRace)
=> EnabledCount > 0 && _shpData.TryGetValue(shape, out var value) && value.Contains(slot, id, genderRace);
internal IReadOnlyDictionary<ShapeString, ShpHashSet> State(ShapeConnectorCondition connector)
internal IReadOnlyDictionary<ShapeAttributeString, ShapeAttributeHashSet> State(ShapeConnectorCondition connector)
=> connector switch
{
ShapeConnectorCondition.None => _shpData,
@ -22,73 +22,10 @@ public sealed class ShpCache(MetaFileManager manager, ModCollection collection)
public int EnabledCount { get; private set; }
public sealed class ShpHashSet : HashSet<(HumanSlot Slot, PrimaryId Id)>
{
private readonly BitArray _allIds = new(ShapeManager.ModelSlotSize);
public bool All
{
get => _allIds[^1];
set => _allIds[^1] = value;
}
public bool this[HumanSlot slot]
{
get
{
if (slot is HumanSlot.Unknown)
return All;
return _allIds[(int)slot];
}
set
{
if (slot is HumanSlot.Unknown)
_allIds[^1] = value;
else
_allIds[(int)slot] = value;
}
}
public bool Contains(HumanSlot slot, PrimaryId id)
=> All || this[slot] || Contains((slot, id));
public bool TrySet(HumanSlot slot, PrimaryId? id, ShpEntry value)
{
if (slot is HumanSlot.Unknown)
{
var old = All;
All = value.Value;
return old != value.Value;
}
if (!id.HasValue)
{
var old = this[slot];
this[slot] = value.Value;
return old != value.Value;
}
if (value.Value)
return Add((slot, id.Value));
return Remove((slot, id.Value));
}
public new void Clear()
{
base.Clear();
_allIds.SetAll(false);
}
public bool IsEmpty
=> !_allIds.HasAnySet() && Count is 0;
}
private readonly Dictionary<ShapeString, ShpHashSet> _shpData = [];
private readonly Dictionary<ShapeString, ShpHashSet> _wristConnectors = [];
private readonly Dictionary<ShapeString, ShpHashSet> _waistConnectors = [];
private readonly Dictionary<ShapeString, ShpHashSet> _ankleConnectors = [];
private readonly Dictionary<ShapeAttributeString, ShapeAttributeHashSet> _shpData = [];
private readonly Dictionary<ShapeAttributeString, ShapeAttributeHashSet> _wristConnectors = [];
private readonly Dictionary<ShapeAttributeString, ShapeAttributeHashSet> _waistConnectors = [];
private readonly Dictionary<ShapeAttributeString, ShapeAttributeHashSet> _ankleConnectors = [];
public void Reset()
{
@ -114,7 +51,7 @@ public sealed class ShpCache(MetaFileManager manager, ModCollection collection)
return;
void Func(Dictionary<ShapeString, ShpHashSet> dict)
void Func(Dictionary<ShapeAttributeString, ShapeAttributeHashSet> dict)
{
if (!dict.TryGetValue(identifier.Shape, out var value))
{
@ -125,7 +62,7 @@ public sealed class ShpCache(MetaFileManager manager, ModCollection collection)
dict.Add(identifier.Shape, value);
}
if (value.TrySet(identifier.Slot, identifier.Id, entry))
if (value.TrySet(identifier.Slot, identifier.Id, identifier.GenderRaceCondition, entry.Value))
++EnabledCount;
}
}
@ -142,12 +79,12 @@ public sealed class ShpCache(MetaFileManager manager, ModCollection collection)
return;
void Func(Dictionary<ShapeString, ShpHashSet> dict)
void Func(Dictionary<ShapeAttributeString, ShapeAttributeHashSet> dict)
{
if (!dict.TryGetValue(identifier.Shape, out var value))
return;
if (value.TrySet(identifier.Slot, identifier.Id, ShpEntry.False))
if (value.TrySet(identifier.Slot, identifier.Id, identifier.GenderRaceCondition, false))
{
--EnabledCount;
if (value.IsEmpty)