diff --git a/OtterGui b/OtterGui index 8c7a309d..86ec4d72 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 8c7a309d039fdf008c85cf51923b4eac51b32428 +Subproject commit 86ec4d72c9c9ed57aa7be4a7d0c81069c5b94ad7 diff --git a/Penumbra/Interop/PathResolving/SubfileHelper.cs b/Penumbra/Interop/PathResolving/SubfileHelper.cs index ffa3ac24..c0b8c5e3 100644 --- a/Penumbra/Interop/PathResolving/SubfileHelper.cs +++ b/Penumbra/Interop/PathResolving/SubfileHelper.cs @@ -11,7 +11,7 @@ using Penumbra.GameData.Enums; using Penumbra.Interop.ResourceLoading; using Penumbra.Interop.Services; using Penumbra.Interop.Structs; -using Penumbra.Services; +using Penumbra.Services; using Penumbra.String; using Penumbra.String.Classes; using Penumbra.Util; @@ -27,7 +27,7 @@ public unsafe class SubfileHelper : IDisposable, IReadOnlyCollection _mtrlData = new(() => ResolveData.Invalid); @@ -41,7 +41,7 @@ public unsafe class SubfileHelper : IDisposable, IReadOnlyCollectionFileType) { case ResourceType.Mtrl: - case ResourceType.Avfx: + case ResourceType.Avfx: if (handle->FileSize == 0) _subFileCollection[(nint)handle] = resolveData; @@ -153,11 +153,11 @@ public unsafe class SubfileHelper : IDisposable, IReadOnlyCollection SkinShpkName + public static ReadOnlySpan SkinShpkName => "skin.shpk"u8; [Signature(Sigs.HumanVTable, ScanType = ScanType.StaticAddress)] @@ -37,90 +35,85 @@ public sealed unsafe class SkinFixer : IDisposable private readonly GameEventManager _gameEvents; private readonly CommunicatorService _communicator; - private readonly ResourceLoader _resources; private readonly CharacterUtility _utility; - - // MaterialResourceHandle set - private readonly ConcurrentDictionary _moddedSkinShpkMaterials = new(); + + // MaterialResourceHandle set + private readonly ConcurrentSet _moddedSkinShpkMaterials = new(); private readonly object _lock = new(); - + // ConcurrentDictionary.Count uses a lock in its current implementation. - private int _moddedSkinShpkCount = 0; - private ulong _slowPathCallDelta = 0; + private int _moddedSkinShpkCount; + private ulong _slowPathCallDelta; public bool Enabled { get; internal set; } = true; public int ModdedSkinShpkCount => _moddedSkinShpkCount; - public SkinFixer(GameEventManager gameEvents, ResourceLoader resources, CharacterUtility utility, CommunicatorService communicator) + public SkinFixer(GameEventManager gameEvents, CharacterUtility utility, CommunicatorService communicator) { SignatureHelper.Initialise(this); _gameEvents = gameEvents; - _resources = resources; _utility = utility; - _communicator = communicator; - _onRenderMaterialHook = Hook.FromAddress(_humanVTable[62], OnRenderHumanMaterial); + _communicator = communicator; + _onRenderMaterialHook = Hook.FromAddress(_humanVTable[62], OnRenderHumanMaterial); _communicator.MtrlShpkLoaded.Subscribe(OnMtrlShpkLoaded, MtrlShpkLoaded.Priority.SkinFixer); _gameEvents.ResourceHandleDestructor += OnResourceHandleDestructor; _onRenderMaterialHook.Enable(); - } - + } + public void Dispose() { - _onRenderMaterialHook.Dispose(); - _communicator.MtrlShpkLoaded.Unsubscribe(OnMtrlShpkLoaded); + _onRenderMaterialHook.Dispose(); + _communicator.MtrlShpkLoaded.Unsubscribe(OnMtrlShpkLoaded); _gameEvents.ResourceHandleDestructor -= OnResourceHandleDestructor; _moddedSkinShpkMaterials.Clear(); _moddedSkinShpkCount = 0; - } - + } + public ulong GetAndResetSlowPathCallDelta() - => Interlocked.Exchange(ref _slowPathCallDelta, 0); - - private static bool IsSkinMaterial(Structs.MtrlResource* mtrlResource) - { - if (mtrlResource == null) - return false; - - var shpkName = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(mtrlResource->ShpkString); - return SkinShpkName.SequenceEqual(shpkName); - } - - private void OnMtrlShpkLoaded(nint mtrlResourceHandle, nint gameObject) - { - var mtrl = (Structs.MtrlResource*)mtrlResourceHandle; - var shpk = mtrl->ShpkResourceHandle; - if (shpk == null) - return; - - if (!IsSkinMaterial(mtrl)) - return; - - if ((nint)shpk != _utility.DefaultSkinShpkResource) - { - if (_moddedSkinShpkMaterials.TryAdd(mtrlResourceHandle, Unit.Instance)) - Interlocked.Increment(ref _moddedSkinShpkCount); - } - } - - private void OnResourceHandleDestructor(Structs.ResourceHandle* handle) - { - if (_moddedSkinShpkMaterials.TryRemove((nint)handle, out _)) - Interlocked.Decrement(ref _moddedSkinShpkCount); + => Interlocked.Exchange(ref _slowPathCallDelta, 0); + + private static bool IsSkinMaterial(Structs.MtrlResource* mtrlResource) + { + if (mtrlResource == null) + return false; + + var shpkName = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(mtrlResource->ShpkString); + return SkinShpkName.SequenceEqual(shpkName); + } + + private void OnMtrlShpkLoaded(nint mtrlResourceHandle, nint gameObject) + { + var mtrl = (Structs.MtrlResource*)mtrlResourceHandle; + var shpk = mtrl->ShpkResourceHandle; + if (shpk == null) + return; + + if (!IsSkinMaterial(mtrl) || (nint)shpk == _utility.DefaultSkinShpkResource) + return; + + if (_moddedSkinShpkMaterials.TryAdd(mtrlResourceHandle)) + Interlocked.Increment(ref _moddedSkinShpkCount); + } + + private void OnResourceHandleDestructor(Structs.ResourceHandle* handle) + { + if (_moddedSkinShpkMaterials.TryRemove((nint)handle)) + Interlocked.Decrement(ref _moddedSkinShpkCount); } private nint OnRenderHumanMaterial(nint human, OnRenderMaterialParams* param) { // If we don't have any on-screen instances of modded skin.shpk, we don't need the slow path at all. if (!Enabled || _moddedSkinShpkCount == 0) - return _onRenderMaterialHook!.Original(human, param); + return _onRenderMaterialHook.Original(human, param); - var material = param->Model->Materials[param->MaterialIndex]; - var mtrlResource = (Structs.MtrlResource*)material->MaterialResourceHandle; - if (!IsSkinMaterial(mtrlResource)) - return _onRenderMaterialHook!.Original(human, param); + var material = param->Model->Materials[param->MaterialIndex]; + var mtrlResource = (Structs.MtrlResource*)material->MaterialResourceHandle; + if (!IsSkinMaterial(mtrlResource)) + return _onRenderMaterialHook.Original(human, param); Interlocked.Increment(ref _slowPathCallDelta); @@ -134,7 +127,7 @@ public sealed unsafe class SkinFixer : IDisposable try { _utility.Address->SkinShpkResource = (Structs.ResourceHandle*)mtrlResource->ShpkResourceHandle; - return _onRenderMaterialHook!.Original(human, param); + return _onRenderMaterialHook.Original(human, param); } finally { diff --git a/Penumbra/Util/Unit.cs b/Penumbra/Util/Unit.cs deleted file mode 100644 index 9b8f4b1e..00000000 --- a/Penumbra/Util/Unit.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; - -namespace Penumbra.Util; - -/// -/// An empty structure. Can be used as value of a concurrent dictionary, to use it as a set. -/// -public readonly struct Unit : IEquatable -{ - public static readonly Unit Instance = new(); - - public bool Equals(Unit other) - => true; - - public override bool Equals(object? obj) - => obj is Unit; - - public override int GetHashCode() - => 0; - - public static bool operator ==(Unit left, Unit right) - => true; - - public static bool operator !=(Unit left, Unit right) - => false; -}