diff --git a/OtterGui b/OtterGui index 1b61ce89..6a1e25a6 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 1b61ce894209ebabe6cf2d8b7c120f5a6edbe86a +Subproject commit 6a1e25a6c6aea6165c0a38771953e35550a1f9cf diff --git a/Penumbra/Interop/CharacterUtility.List.cs b/Penumbra/Interop/CharacterUtility.List.cs index 38f6e804..3cf137e8 100644 --- a/Penumbra/Interop/CharacterUtility.List.cs +++ b/Penumbra/Interop/CharacterUtility.List.cs @@ -77,13 +77,11 @@ public unsafe partial class CharacterUtility // Set the currently stored data of this resource to new values. private void SetResourceInternal( IntPtr data, int length ) { - TimingManager.StartTimer( TimingType.SetResource ); if( Ready ) { var resource = Penumbra.CharacterUtility.Address->Resource( GlobalIndex ); resource->SetData( data, length ); } - TimingManager.StopTimer( TimingType.SetResource ); } // Reset the currently stored data of this resource to its default values. @@ -135,7 +133,6 @@ public unsafe partial class CharacterUtility { if( !Disposed ) { - TimingManager.StartTimer( TimingType.SetResource ); var list = List._entries; var wasCurrent = ReferenceEquals( this, list.First?.Value ); list.Remove( this ); @@ -162,7 +159,6 @@ public unsafe partial class CharacterUtility } Disposed = true; - TimingManager.StopTimer( TimingType.SetResource ); } } } diff --git a/Penumbra/Interop/Loader/ResourceLoader.Debug.cs b/Penumbra/Interop/Loader/ResourceLoader.Debug.cs index a44ac58c..76eba25d 100644 --- a/Penumbra/Interop/Loader/ResourceLoader.Debug.cs +++ b/Penumbra/Interop/Loader/ResourceLoader.Debug.cs @@ -10,6 +10,7 @@ using Penumbra.Collections; using Penumbra.GameData.Enums; using Penumbra.String; using Penumbra.String.Classes; +using Penumbra.Util; namespace Penumbra.Interop.Loader; @@ -71,12 +72,13 @@ public unsafe partial class ResourceLoader private void AddModifiedDebugInfo( Structs.ResourceHandle* handle, Utf8GamePath originalPath, FullPath? manipulatedPath, ResolveData resolverInfo ) { + using var performance = Penumbra.Performance.Measure( PerformanceType.DebugTimes ); + if( manipulatedPath == null || manipulatedPath.Value.Crc64 == 0 ) { return; } - TimingManager.StartTimer( TimingType.DebugTimes ); // Got some incomprehensible null-dereference exceptions here when hot-reloading penumbra. try { @@ -97,7 +99,6 @@ public unsafe partial class ResourceLoader { Penumbra.Log.Error( e.ToString() ); } - TimingManager.StopTimer( TimingType.DebugTimes ); } // Find a key in a StdMap. @@ -204,10 +205,16 @@ public unsafe partial class ResourceLoader // Only used when the Replaced Resources Tab in the Debug tab is open. public void UpdateDebugInfo() { - TimingManager.StartTimer( TimingType.DebugTimes ); + using var performance = Penumbra.Performance.Measure( PerformanceType.DebugTimes ); for( var i = 0; i < _debugList.Count; ++i ) { - var data = _debugList.Values[ i ]; + var data = _debugList.Values[ i ]; + if( data.OriginalPath.Path == null ) + { + _debugList.RemoveAt( i-- ); + continue; + } + var regularResource = FindResource( data.Category, data.Extension, ( uint )data.OriginalPath.Path.Crc32 ); var modifiedResource = FindResource( data.Category, data.Extension, ( uint )data.ManipulatedPath.InternalName.Crc32 ); if( modifiedResource == null ) @@ -223,7 +230,6 @@ public unsafe partial class ResourceLoader }; } } - TimingManager.StopTimer( TimingType.DebugTimes ); } // Prevent resource management weirdness. diff --git a/Penumbra/Interop/Loader/ResourceLoader.Replacement.cs b/Penumbra/Interop/Loader/ResourceLoader.Replacement.cs index e2c83250..b4505ce7 100644 --- a/Penumbra/Interop/Loader/ResourceLoader.Replacement.cs +++ b/Penumbra/Interop/Loader/ResourceLoader.Replacement.cs @@ -11,6 +11,7 @@ using Penumbra.GameData.Enums; using Penumbra.Interop.Structs; using Penumbra.String; using Penumbra.String.Classes; +using Penumbra.Util; using FileMode = Penumbra.Interop.Structs.FileMode; using ResourceHandle = FFXIVClientStructs.FFXIV.Client.System.Resource.Handle.ResourceHandle; @@ -81,43 +82,36 @@ public unsafe partial class ResourceLoader internal ResourceHandle* GetResourceHandler( bool isSync, ResourceManager* resourceManager, ResourceCategory* categoryId, ResourceType* resourceType, int* resourceHash, byte* path, GetResourceParameters* pGetResParams, bool isUnk ) { - TimingManager.StartTimer( TimingType.GetResourceHandler ); - ResourceHandle* ret = null; + using var performance = Penumbra.Performance.Measure( PerformanceType.GetResourceHandler ); + + ResourceHandle* ret; if( !Utf8GamePath.FromPointer( path, out var gamePath ) ) { Penumbra.Log.Error( "Could not create GamePath from resource path." ); - ret = CallOriginalHandler( isSync, resourceManager, categoryId, resourceType, resourceHash, path, pGetResParams, isUnk ); + return CallOriginalHandler( isSync, resourceManager, categoryId, resourceType, resourceHash, path, pGetResParams, isUnk ); } - else + + CompareHash( ComputeHash( gamePath.Path, pGetResParams ), *resourceHash, gamePath ); + + ResourceRequested?.Invoke( gamePath, isSync ); + + // If no replacements are being made, we still want to be able to trigger the event. + var (resolvedPath, data) = ResolvePath( gamePath, *categoryId, *resourceType, *resourceHash ); + PathResolved?.Invoke( gamePath, *resourceType, resolvedPath ?? ( gamePath.IsRooted() ? new FullPath( gamePath ) : null ), data ); + if( resolvedPath == null ) { - - CompareHash( ComputeHash( gamePath.Path, pGetResParams ), *resourceHash, gamePath ); - - ResourceRequested?.Invoke( gamePath, isSync ); - - // If no replacements are being made, we still want to be able to trigger the event. - var (resolvedPath, data) = ResolvePath( gamePath, *categoryId, *resourceType, *resourceHash ); - PathResolved?.Invoke( gamePath, *resourceType, resolvedPath ?? ( gamePath.IsRooted() ? new FullPath( gamePath ) : null ), data ); - if( resolvedPath == null ) - { - var retUnmodified = - CallOriginalHandler( isSync, resourceManager, categoryId, resourceType, resourceHash, path, pGetResParams, isUnk ); - ResourceLoaded?.Invoke( ( Structs.ResourceHandle* )retUnmodified, gamePath, null, data ); - ret = retUnmodified; - } - else - { - - // Replace the hash and path with the correct one for the replacement. - *resourceHash = ComputeHash( resolvedPath.Value.InternalName, pGetResParams ); - - path = resolvedPath.Value.InternalName.Path; - ret = CallOriginalHandler( isSync, resourceManager, categoryId, resourceType, resourceHash, path, pGetResParams, isUnk ); - ResourceLoaded?.Invoke( ( Structs.ResourceHandle* )ret, gamePath, resolvedPath.Value, data ); - - } + ret = CallOriginalHandler( isSync, resourceManager, categoryId, resourceType, resourceHash, path, pGetResParams, isUnk ); + ResourceLoaded?.Invoke( ( Structs.ResourceHandle* )ret, gamePath, null, data ); + return ret; } - TimingManager.StopTimer( TimingType.GetResourceHandler ); + + // Replace the hash and path with the correct one for the replacement. + *resourceHash = ComputeHash( resolvedPath.Value.InternalName, pGetResParams ); + + path = resolvedPath.Value.InternalName.Path; + ret = CallOriginalHandler( isSync, resourceManager, categoryId, resourceType, resourceHash, path, pGetResParams, isUnk ); + ResourceLoaded?.Invoke( ( Structs.ResourceHandle* )ret, gamePath, resolvedPath.Value, data ); + return ret; } @@ -174,50 +168,51 @@ public unsafe partial class ResourceLoader private byte ReadSqPackDetour( ResourceManager* resourceManager, SeFileDescriptor* fileDescriptor, int priority, bool isSync ) { - TimingManager.StartTimer( TimingType.ReadSqPack ); - byte ret = 0; + using var performance = Penumbra.Performance.Measure( PerformanceType.ReadSqPack ); + if( !DoReplacements ) { - ret = ReadSqPackHook.Original( resourceManager, fileDescriptor, priority, isSync ); + return ReadSqPackHook.Original( resourceManager, fileDescriptor, priority, isSync ); } - else if( fileDescriptor == null || fileDescriptor->ResourceHandle == null ) + + if( fileDescriptor == null || fileDescriptor->ResourceHandle == null ) { Penumbra.Log.Error( "Failure to load file from SqPack: invalid File Descriptor." ); - ret = ReadSqPackHook.Original( resourceManager, fileDescriptor, priority, isSync ); + return ReadSqPackHook.Original( resourceManager, fileDescriptor, priority, isSync ); } - else if( !Utf8GamePath.FromSpan( fileDescriptor->ResourceHandle->FileNameSpan(), out var gamePath, false ) || gamePath.Length == 0 ) + + if( !Utf8GamePath.FromSpan( fileDescriptor->ResourceHandle->FileNameSpan(), out var gamePath, false ) || gamePath.Length == 0 ) { - ret = ReadSqPackHook.Original(resourceManager, fileDescriptor, priority, isSync); + return ReadSqPackHook.Original( resourceManager, fileDescriptor, priority, isSync ); } + // Paths starting with a '|' are handled separately to allow for special treatment. // They are expected to also have a closing '|'. - else if( ResourceLoadCustomization == null || gamePath.Path[ 0 ] != ( byte )'|' ) + if( ResourceLoadCustomization == null || gamePath.Path[ 0 ] != ( byte )'|' ) { - ret = DefaultLoadResource( gamePath.Path, resourceManager, fileDescriptor, priority, isSync ); - } - else - { - // Split the path into the special-treatment part (between the first and second '|') - // and the actual path. - var split = gamePath.Path.Split( ( byte )'|', 3, false ); - fileDescriptor->ResourceHandle->FileNameData = split[2].Path; - fileDescriptor->ResourceHandle->FileNameLength = split[2].Length; - var funcFound = fileDescriptor->ResourceHandle->Category != ResourceCategory.Ui - && ResourceLoadCustomization.GetInvocationList() - .Any( f => ( ( ResourceLoadCustomizationDelegate )f ) - .Invoke( split[1], split[2], resourceManager, fileDescriptor, priority, isSync, out ret ) ); - - if( !funcFound ) - { - ret = DefaultLoadResource( split[2], resourceManager, fileDescriptor, priority, isSync ); - } - - // Return original resource handle path so that they can be loaded separately. - fileDescriptor->ResourceHandle->FileNameData = gamePath.Path.Path; - fileDescriptor->ResourceHandle->FileNameLength = gamePath.Path.Length; + return DefaultLoadResource( gamePath.Path, resourceManager, fileDescriptor, priority, isSync ); } - TimingManager.StopTimer( TimingType.ReadSqPack ); + // Split the path into the special-treatment part (between the first and second '|') + // and the actual path. + byte ret = 0; + var split = gamePath.Path.Split( ( byte )'|', 3, false ); + fileDescriptor->ResourceHandle->FileNameData = split[ 2 ].Path; + fileDescriptor->ResourceHandle->FileNameLength = split[ 2 ].Length; + var funcFound = fileDescriptor->ResourceHandle->Category != ResourceCategory.Ui + && ResourceLoadCustomization.GetInvocationList() + .Any( f => ( ( ResourceLoadCustomizationDelegate )f ) + .Invoke( split[ 1 ], split[ 2 ], resourceManager, fileDescriptor, priority, isSync, out ret ) ); + + if( !funcFound ) + { + ret = DefaultLoadResource( split[ 2 ], resourceManager, fileDescriptor, priority, isSync ); + } + + // Return original resource handle path so that they can be loaded separately. + fileDescriptor->ResourceHandle->FileNameData = gamePath.Path.Path; + fileDescriptor->ResourceHandle->FileNameLength = gamePath.Path.Length; + return ret; } diff --git a/Penumbra/Interop/Resolver/PathResolver.AnimationState.cs b/Penumbra/Interop/Resolver/PathResolver.AnimationState.cs index 09e628fd..3a16cb30 100644 --- a/Penumbra/Interop/Resolver/PathResolver.AnimationState.cs +++ b/Penumbra/Interop/Resolver/PathResolver.AnimationState.cs @@ -6,6 +6,7 @@ using Penumbra.Collections; using Penumbra.GameData.Enums; using Penumbra.String; using Penumbra.String.Classes; +using Penumbra.Util; using GameObject = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject; namespace Penumbra.Interop.Resolver; @@ -110,7 +111,8 @@ public unsafe partial class PathResolver private IntPtr LoadCharacterSoundDetour( IntPtr character, int unk1, int unk2, IntPtr unk3, ulong unk4, int unk5, int unk6, ulong unk7 ) { - var last = _characterSoundData; + using var performance = Penumbra.Performance.Measure( PerformanceType.LoadSound ); + var last = _characterSoundData; _characterSoundData = IdentifyCollection( ( GameObject* )character, true ); var ret = _loadCharacterSoundHook.Original( character, unk1, unk2, unk3, unk4, unk5, unk6, unk7 ); _characterSoundData = last; @@ -126,9 +128,9 @@ public unsafe partial class PathResolver private ulong LoadTimelineResourcesDetour( IntPtr timeline ) { - TimingManager.StartTimer( TimingType.TimelineResources ); - ulong ret; - var old = _animationLoadData; + using var performance = Penumbra.Performance.Measure( PerformanceType.TimelineResources ); + ulong ret; + var old = _animationLoadData; try { if( timeline != IntPtr.Zero ) @@ -152,8 +154,6 @@ public unsafe partial class PathResolver } _animationLoadData = old; - - TimingManager.StopTimer( TimingType.TimelineResources ); return ret; } @@ -167,7 +167,8 @@ public unsafe partial class PathResolver private void CharacterBaseLoadAnimationDetour( IntPtr drawObject ) { - var last = _animationLoadData; + using var performance = Penumbra.Performance.Measure( PerformanceType.LoadCharacterBaseAnimation ); + var last = _animationLoadData; _animationLoadData = _drawObjectState.LastCreatedCollection.Valid ? _drawObjectState.LastCreatedCollection : FindParent( drawObject, out var collection ) != null @@ -186,8 +187,9 @@ public unsafe partial class PathResolver private void LoadSomePapDetour( IntPtr a1, int a2, IntPtr a3, int a4 ) { - var timelinePtr = a1 + 0x50; - var last = _animationLoadData; + using var performance = Penumbra.Performance.Measure( PerformanceType.LoadPap ); + var timelinePtr = a1 + 0x50; + var last = _animationLoadData; if( timelinePtr != IntPtr.Zero ) { var actorIdx = ( int )( *( *( ulong** )timelinePtr + 1 ) >> 3 ); @@ -207,7 +209,8 @@ public unsafe partial class PathResolver private void SomeActionLoadDetour( IntPtr gameObject ) { - var last = _animationLoadData; + using var performance = Penumbra.Performance.Measure( PerformanceType.LoadAction ); + var last = _animationLoadData; _animationLoadData = IdentifyCollection( ( GameObject* )gameObject, true ); _someActionLoadHook.Original( gameObject ); _animationLoadData = last; @@ -248,8 +251,8 @@ public unsafe partial class PathResolver private IntPtr LoadCharacterVfxDetour( byte* vfxPath, VfxParams* vfxParams, byte unk1, byte unk2, float unk3, int unk4 ) { - TimingManager.StartTimer( TimingType.LoadCharacterVfx ); - var last = _animationLoadData; + using var performance = Penumbra.Performance.Measure( PerformanceType.LoadCharacterVfx ); + var last = _animationLoadData; if( vfxParams != null && vfxParams->GameObjectId != unchecked( ( uint )-1 ) ) { var obj = vfxParams->GameObjectType switch @@ -267,6 +270,7 @@ public unsafe partial class PathResolver { _animationLoadData = ResolveData.Invalid; } + var ret = _loadCharacterVfxHook.Original( vfxPath, vfxParams, unk1, unk2, unk3, unk4 ); #if DEBUG var path = new ByteString( vfxPath ); @@ -274,7 +278,6 @@ public unsafe partial class PathResolver $"Load Character VFX: {path} {vfxParams->GameObjectId:X} {vfxParams->TargetCount} {unk1} {unk2} {unk3} {unk4} -> {ret:X} {_animationLoadData.ModCollection.Name} {_animationLoadData.AssociatedGameObject} {last.ModCollection.Name} {last.AssociatedGameObject}" ); #endif _animationLoadData = last; - TimingManager.StopTimer( TimingType.LoadCharacterVfx ); return ret; } @@ -285,8 +288,8 @@ public unsafe partial class PathResolver private IntPtr LoadAreaVfxDetour( uint vfxId, float* pos, GameObject* caster, float unk1, float unk2, byte unk3 ) { - TimingManager.StartTimer( TimingType.LoadAreaVfx ); - var last = _animationLoadData; + using var performance = Penumbra.Performance.Measure( PerformanceType.LoadAreaVfx ); + var last = _animationLoadData; if( caster != null ) { _animationLoadData = IdentifyCollection( caster, true ); @@ -302,7 +305,6 @@ public unsafe partial class PathResolver $"Load Area VFX: {vfxId}, {pos[ 0 ]} {pos[ 1 ]} {pos[ 2 ]} {( caster != null ? new ByteString( caster->GetName() ).ToString() : "Unknown" )} {unk1} {unk2} {unk3} -> {ret:X} {_animationLoadData.ModCollection.Name} {_animationLoadData.AssociatedGameObject} {last.ModCollection.Name} {last.AssociatedGameObject}" ); #endif _animationLoadData = last; - TimingManager.StopTimer( TimingType.LoadAreaVfx ); return ret; } diff --git a/Penumbra/Interop/Resolver/PathResolver.DrawObjectState.cs b/Penumbra/Interop/Resolver/PathResolver.DrawObjectState.cs index be17af9b..78a5ed0a 100644 --- a/Penumbra/Interop/Resolver/PathResolver.DrawObjectState.cs +++ b/Penumbra/Interop/Resolver/PathResolver.DrawObjectState.cs @@ -10,6 +10,7 @@ using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using OtterGui.Classes; using Penumbra.GameData.Enums; using Penumbra.String.Classes; +using Penumbra.Util; namespace Penumbra.Interop.Resolver; @@ -138,7 +139,8 @@ public unsafe partial class PathResolver private IntPtr CharacterBaseCreateDetour( uint a, IntPtr b, IntPtr c, byte d ) { - TimingManager.StartTimer( TimingType.CharacterBaseCreate ); + using var performance = Penumbra.Performance.Measure( PerformanceType.CharacterBaseCreate ); + var meta = DisposableContainer.Empty; if( LastGameObject != null ) { @@ -171,7 +173,7 @@ public unsafe partial class PathResolver { meta.Dispose(); } - TimingManager.StopTimer( TimingType.CharacterBaseCreate ); + return ret; } diff --git a/Penumbra/Interop/Resolver/PathResolver.Identification.cs b/Penumbra/Interop/Resolver/PathResolver.Identification.cs index 45322e14..8ee75678 100644 --- a/Penumbra/Interop/Resolver/PathResolver.Identification.cs +++ b/Penumbra/Interop/Resolver/PathResolver.Identification.cs @@ -8,6 +8,7 @@ using OtterGui; using Penumbra.Collections; using Penumbra.GameData.Actors; using Penumbra.GameData.Enums; +using Penumbra.Util; using Character = FFXIVClientStructs.FFXIV.Client.Game.Character.Character; using GameObject = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject; using ObjectKind = Dalamud.Game.ClientState.Objects.Enums.ObjectKind; @@ -19,12 +20,13 @@ public unsafe partial class PathResolver // Identify the correct collection for a GameObject by index and name. public static ResolveData IdentifyCollection( GameObject* gameObject, bool useCache ) { + using var performance = Penumbra.Performance.Measure( PerformanceType.IdentifyCollection ); + if( gameObject == null ) { return new ResolveData( Penumbra.CollectionManager.Default ); } - TimingManager.StartTimer( TimingType.IdentifyCollection ); try { if( useCache && IdentifiedCache.TryGetValue( gameObject, out var data ) ) @@ -69,35 +71,25 @@ public unsafe partial class PathResolver Penumbra.Log.Error( $"Error identifying collection:\n{e}" ); return Penumbra.CollectionManager.Default.ToResolveData( gameObject ); } - finally - { - TimingManager.StopTimer( TimingType.IdentifyCollection ); - } } // Get the collection applying to the current player character // or the default collection if no player exists. public static ModCollection PlayerCollection() { - TimingManager.StartTimer( TimingType.IdentifyCollection ); - var gameObject = ( GameObject* )Dalamud.Objects.GetObjectAddress( 0 ); - ModCollection ret; + using var performance = Penumbra.Performance.Measure( PerformanceType.IdentifyCollection ); + var gameObject = ( GameObject* )Dalamud.Objects.GetObjectAddress( 0 ); if( gameObject == null ) { - ret = Penumbra.CollectionManager.ByType( CollectionType.Yourself ) + return Penumbra.CollectionManager.ByType( CollectionType.Yourself ) ?? Penumbra.CollectionManager.Default; } - else - { - var player = Penumbra.Actors.GetCurrentPlayer(); - ret = CollectionByIdentifier( player ) - ?? CheckYourself( player, gameObject ) - ?? CollectionByAttributes( gameObject ) - ?? Penumbra.CollectionManager.Default; - } - TimingManager.StopTimer( TimingType.IdentifyCollection ); - return ret; + var player = Penumbra.Actors.GetCurrentPlayer(); + return CollectionByIdentifier( player ) + ?? CheckYourself( player, gameObject ) + ?? CollectionByAttributes( gameObject ) + ?? Penumbra.CollectionManager.Default; } // Check both temporary and permanent character collections. Temporary first. diff --git a/Penumbra/Interop/Resolver/PathResolver.Meta.cs b/Penumbra/Interop/Resolver/PathResolver.Meta.cs index 1faa3b76..a618e705 100644 --- a/Penumbra/Interop/Resolver/PathResolver.Meta.cs +++ b/Penumbra/Interop/Resolver/PathResolver.Meta.cs @@ -6,6 +6,7 @@ using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using OtterGui.Classes; using Penumbra.Collections; using Penumbra.GameData.Enums; +using Penumbra.Util; using ObjectType = FFXIVClientStructs.FFXIV.Client.Graphics.Scene.ObjectType; using static Penumbra.GameData.Enums.GenderRace; @@ -97,6 +98,7 @@ public unsafe partial class PathResolver { return; } + using var performance = Penumbra.Performance.Measure( PerformanceType.UpdateModels ); var collection = GetResolveData( drawObject ); using var eqp = collection.ModCollection.TemporarilySetEqpFile(); @@ -134,7 +136,7 @@ public unsafe partial class PathResolver { return; } - + using var performance = Penumbra.Performance.Measure( PerformanceType.GetEqp ); var resolveData = GetResolveData( drawObject ); using var eqp = resolveData.ModCollection.TemporarilySetEqpFile(); _getEqpIndirectHook.Original( drawObject ); @@ -150,6 +152,7 @@ public unsafe partial class PathResolver private byte SetupVisorDetour( IntPtr drawObject, ushort modelId, byte visorState ) { + using var performance = Penumbra.Performance.Measure( PerformanceType.SetupVisor ); var resolveData = GetResolveData( drawObject ); using var gmp = resolveData.ModCollection.TemporarilySetGmpFile(); return _setupVisorHook.Original( drawObject, modelId, visorState ); @@ -169,6 +172,7 @@ public unsafe partial class PathResolver } else { + using var performance = Penumbra.Performance.Measure( PerformanceType.SetupCharacter ); var resolveData = GetResolveData( drawObject ); using var cmp = resolveData.ModCollection.TemporarilySetCmpFile(); _rspSetupCharacterHook.Original( drawObject, unk2, unk3, unk4, unk5 ); @@ -184,6 +188,7 @@ public unsafe partial class PathResolver private bool ChangeCustomizeDetour( IntPtr human, IntPtr data, byte skipEquipment ) { + using var performance = Penumbra.Performance.Measure( PerformanceType.ChangeCustomize ); _inChangeCustomize = true; var resolveData = GetResolveData( human ); using var cmp = resolveData.ModCollection.TemporarilySetCmpFile(); diff --git a/Penumbra/Interop/Resolver/PathResolver.PathState.cs b/Penumbra/Interop/Resolver/PathResolver.PathState.cs index 2974dd03..2cf9cb4b 100644 --- a/Penumbra/Interop/Resolver/PathResolver.PathState.cs +++ b/Penumbra/Interop/Resolver/PathResolver.PathState.cs @@ -95,7 +95,6 @@ public unsafe partial class PathResolver // Special handling for paths so that we do not store non-owned temporary strings in the dictionary. public void SetCollection( IntPtr gameObject, ByteString path, ModCollection collection ) { - TimingManager.StartTimer( TimingType.SetPathCollection ); if( _pathCollections.ContainsKey( path ) || path.IsOwned ) { _pathCollections[ path ] = collection.ToResolveData( gameObject ); @@ -104,7 +103,6 @@ public unsafe partial class PathResolver { _pathCollections[ path.Clone() ] = collection.ToResolveData( gameObject ); } - TimingManager.StopTimer( TimingType.SetPathCollection ); } } } \ No newline at end of file diff --git a/Penumbra/Interop/Resolver/PathResolver.Subfiles.cs b/Penumbra/Interop/Resolver/PathResolver.Subfiles.cs index 091f6f62..cca76c70 100644 --- a/Penumbra/Interop/Resolver/PathResolver.Subfiles.cs +++ b/Penumbra/Interop/Resolver/PathResolver.Subfiles.cs @@ -11,6 +11,7 @@ using Penumbra.Interop.Loader; using Penumbra.Interop.Structs; using Penumbra.String; using Penumbra.String.Classes; +using Penumbra.Util; namespace Penumbra.Interop.Resolver; @@ -113,9 +114,7 @@ public unsafe partial class PathResolver case ResourceType.Avfx: if( handle->FileSize == 0 ) { - TimingManager.StartTimer( TimingType.AddSubfile ); _subFileCollection[ ( IntPtr )handle ] = resolveData; - TimingManager.StopTimer( TimingType.AddSubfile ); } break; @@ -128,9 +127,7 @@ public unsafe partial class PathResolver { case ResourceType.Mtrl: case ResourceType.Avfx: - TimingManager.StartTimer( TimingType.AddSubfile ); _subFileCollection.TryRemove( ( IntPtr )handle, out _ ); - TimingManager.StopTimer( TimingType.AddSubfile ); break; } } @@ -167,6 +164,7 @@ public unsafe partial class PathResolver private byte LoadMtrlTexDetour( IntPtr mtrlResourceHandle ) { + using var performance = Penumbra.Performance.Measure( PerformanceType.LoadTextures ); _mtrlData = LoadFileHelper( mtrlResourceHandle ); var ret = _loadMtrlTexHook.Original( mtrlResourceHandle ); _mtrlData = ResolveData.Invalid; @@ -179,6 +177,7 @@ public unsafe partial class PathResolver private byte LoadMtrlShpkDetour( IntPtr mtrlResourceHandle ) { + using var performance = Penumbra.Performance.Measure( PerformanceType.LoadShaders ); _mtrlData = LoadFileHelper( mtrlResourceHandle ); var ret = _loadMtrlShpkHook.Original( mtrlResourceHandle ); _mtrlData = ResolveData.Invalid; @@ -204,6 +203,7 @@ public unsafe partial class PathResolver private byte ApricotResourceLoadDetour( IntPtr handle, IntPtr unk1, byte unk2 ) { + using var performance = Penumbra.Performance.Measure( PerformanceType.LoadApricotResources ); _avfxData = LoadFileHelper( handle ); var ret = _apricotResourceLoadHook.Original( handle, unk1, unk2 ); _avfxData = ResolveData.Invalid; diff --git a/Penumbra/Interop/Resolver/PathResolver.cs b/Penumbra/Interop/Resolver/PathResolver.cs index b29432ba..2ece65f6 100644 --- a/Penumbra/Interop/Resolver/PathResolver.cs +++ b/Penumbra/Interop/Resolver/PathResolver.cs @@ -10,6 +10,7 @@ using Penumbra.GameData.Enums; using Penumbra.Interop.Loader; using Penumbra.String; using Penumbra.String.Classes; +using Penumbra.Util; namespace Penumbra.Interop.Resolver; @@ -48,7 +49,7 @@ public partial class PathResolver : IDisposable // The modified resolver that handles game path resolving. private bool CharacterResolver( Utf8GamePath gamePath, ResourceCategory _1, ResourceType type, int _2, out (FullPath?, ResolveData) data ) { - TimingManager.StartTimer( TimingType.CharacterResolver ); + using var performance = Penumbra.Performance.Measure( PerformanceType.CharacterResolver ); // Check if the path was marked for a specific collection, // or if it is a file loaded by a material, and if we are currently in a material load, // or if it is a face decal path and the current mod collection is set. @@ -72,7 +73,6 @@ public partial class PathResolver : IDisposable // We also need to handle defaulted materials against a non-default collection. var path = resolved == null ? gamePath.Path : resolved.Value.InternalName; SubfileHelper.HandleCollection( resolveData, path, nonDefault, type, resolved, out data ); - TimingManager.StopTimer( TimingType.CharacterResolver ); return true; } diff --git a/Penumbra/Penumbra.cs b/Penumbra/Penumbra.cs index b9cb7e80..79f6ba77 100644 --- a/Penumbra/Penumbra.cs +++ b/Penumbra/Penumbra.cs @@ -65,6 +65,7 @@ public class Penumbra : IDalamudPlugin public static IGamePathParser GamePathParser { get; private set; } = null!; public static StainManager StainManager { get; private set; } = null!; public static ItemData ItemData { get; private set; } = null!; + public static PerformanceTracker< PerformanceType > Performance { get; private set; } = null!; public static readonly List< Exception > ImcExceptions = new(); @@ -86,10 +87,8 @@ public class Penumbra : IDalamudPlugin { try { - TimingManager.StartTimer( TimingType.TotalTime ); - TimingManager.StartTimer( TimingType.LaunchTime ); - Dalamud.Initialize( pluginInterface ); + Performance = new PerformanceTracker< PerformanceType >( Dalamud.Framework ); Log = new Logger(); DevPenumbraExists = CheckDevPluginPenumbra(); IsNotInstalledPenumbra = CheckIsNotInstalled(); @@ -165,7 +164,6 @@ public class Penumbra : IDalamudPlugin { ResidentResources.Reload(); } - TimingManager.StopTimer( TimingType.LaunchTime ); } catch { @@ -308,7 +306,7 @@ public class Penumbra : IDalamudPlugin ResourceLogger?.Dispose(); ResourceLoader?.Dispose(); CharacterUtility?.Dispose(); - TimingManager.StopAllTimers(); + Performance?.Dispose(); } // Collect all relevant files for penumbra configuration. diff --git a/Penumbra/TimingManager.cs b/Penumbra/TimingManager.cs deleted file mode 100644 index fea4e00f..00000000 --- a/Penumbra/TimingManager.cs +++ /dev/null @@ -1,90 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Text; -using System.Threading; -using ImGuiNET; - -namespace Penumbra; - -public enum TimingType -{ - TotalTime, - LaunchTime, - DebugTimes, - UiMainWindow, - UiAdvancedWindow, - GetResourceHandler, - ReadSqPack, - CharacterResolver, - IdentifyCollection, - CharacterBaseCreate, - TimelineResources, - LoadCharacterVfx, - LoadAreaVfx, - AddSubfile, - SetResource, - SetPathCollection, -} - -public static class TimingManager -{ - public static readonly IReadOnlyList< ThreadLocal< Stopwatch > > StopWatches = -#if DEBUG - Enum.GetValues< TimingType >().Select( e => new ThreadLocal< Stopwatch >( () => new Stopwatch(), true ) ).ToArray(); -#else - Array.Empty>(); -#endif - - [Conditional( "DEBUG" )] - public static void StartTimer( TimingType timingType ) - { - var stopWatch = StopWatches[ ( int )timingType ].Value; - stopWatch!.Start(); - } - - [Conditional( "DEBUG" )] - public static void StopTimer( TimingType timingType ) - { - var stopWatch = StopWatches[ ( int )timingType ].Value; - stopWatch!.Stop(); - } - - [Conditional( "DEBUG" )] - public static void StopAllTimers() - { - foreach( var threadWatch in StopWatches ) - { - foreach( var stopWatch in threadWatch.Values ) - { - stopWatch.Stop(); - } - } - } - - [Conditional( "DEBUG" )] - public static void CreateTimingReport() - { - try - { - var sb = new StringBuilder( 1024 ); - sb.AppendLine( "```" ); - foreach( var type in Enum.GetValues< TimingType >() ) - { - var watches = StopWatches[ ( int )type ]; - var timeSum = watches.Values.Sum( w => w.ElapsedMilliseconds ); - - sb.AppendLine( $"{type,-20} - {timeSum,8} ms over {watches.Values.Count,2} Thread(s)" ); - } - - sb.AppendLine( "```" ); - - ImGui.SetClipboardText( sb.ToString() ); - } - catch( Exception ex ) - { - Penumbra.Log.Error( $"Could not create timing report:\n{ex}" ); - } - } -} \ No newline at end of file diff --git a/Penumbra/UI/Classes/ModEditWindow.cs b/Penumbra/UI/Classes/ModEditWindow.cs index 3b8aac19..fe6ba962 100644 --- a/Penumbra/UI/Classes/ModEditWindow.cs +++ b/Penumbra/UI/Classes/ModEditWindow.cs @@ -23,10 +23,10 @@ public partial class ModEditWindow : Window, IDisposable private const string WindowBaseLabel = "###SubModEdit"; internal readonly ItemSwapWindow _swapWindow = new(); - private Editor? _editor; - private Mod? _mod; - private Vector2 _iconSize = Vector2.Zero; - private bool _allowReduplicate = false; + private Editor? _editor; + private Mod? _mod; + private Vector2 _iconSize = Vector2.Zero; + private bool _allowReduplicate = false; public void ChangeMod( Mod mod ) { @@ -61,7 +61,8 @@ public partial class ModEditWindow : Window, IDisposable public override void PreDraw() { - TimingManager.StartTimer( TimingType.UiAdvancedWindow ); + using var performance = Penumbra.Performance.Measure( PerformanceType.UiAdvancedWindow ); + var sb = new StringBuilder( 256 ); var redirections = 0; @@ -126,7 +127,6 @@ public partial class ModEditWindow : Window, IDisposable _allowReduplicate = redirections != _editor.AvailableFiles.Count || _editor.MissingFiles.Count > 0; sb.Append( WindowBaseLabel ); WindowName = sb.ToString(); - TimingManager.StopTimer( TimingType.UiAdvancedWindow ); } public override void OnClose() @@ -137,7 +137,8 @@ public partial class ModEditWindow : Window, IDisposable public override void Draw() { - TimingManager.StartTimer( TimingType.UiAdvancedWindow ); + using var performance = Penumbra.Performance.Measure( PerformanceType.UiAdvancedWindow ); + using var tabBar = ImRaii.TabBar( "##tabs" ); if( !tabBar ) { @@ -155,7 +156,6 @@ public partial class ModEditWindow : Window, IDisposable _materialTab.Draw(); DrawTextureTab(); _swapWindow.DrawItemSwapPanel(); - TimingManager.StopTimer( TimingType.UiAdvancedWindow ); } // A row of three buttonSizes and a help marker that can be used for material suffix changing. diff --git a/Penumbra/UI/ConfigWindow.DebugTab.cs b/Penumbra/UI/ConfigWindow.DebugTab.cs index 8d57358a..0948231e 100644 --- a/Penumbra/UI/ConfigWindow.DebugTab.cs +++ b/Penumbra/UI/ConfigWindow.DebugTab.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.IO; using System.Linq; using System.Numerics; @@ -15,6 +16,7 @@ using Penumbra.Interop.Loader; using Penumbra.Interop.Resolver; using Penumbra.Interop.Structs; using Penumbra.String; +using Penumbra.Util; using CharacterUtility = Penumbra.Interop.CharacterUtility; using ObjectKind = Dalamud.Game.ClientState.Objects.Enums.ObjectKind; @@ -55,6 +57,7 @@ public partial class ConfigWindow } DrawDebugTabGeneral(); + DrawPerformanceTab(); ImGui.NewLine(); DrawDebugTabReplacedResources(); ImGui.NewLine(); @@ -109,6 +112,18 @@ public partial class ConfigWindow PrintValue( "Web Server Enabled", ( _window._penumbra.WebServer != null ).ToString() ); } + [Conditional("DEBUG")] + private static void DrawPerformanceTab() + { + ImGui.NewLine(); + if( !ImGui.CollapsingHeader( "Performance" ) ) + { + return; + } + + Penumbra.Performance.Draw( "##performance", "Enable Performance Tracking", PerformanceTypeExtensions.ToName ); + } + // Draw all resources currently replaced by Penumbra and (if existing) the resources they replace. // Resources are collected by iterating through the private static unsafe void DrawDebugTabReplacedResources() @@ -249,7 +264,7 @@ public partial class ConfigWindow ImGui.TableNextColumn(); ImGui.TextUnformatted( collection.ModCollection.Name ); ImGui.TableNextColumn(); - ImGui.TextUnformatted( collection.AssociatedGameObject.ToString("X") ); + ImGui.TextUnformatted( collection.AssociatedGameObject.ToString( "X" ) ); } } } diff --git a/Penumbra/UI/ConfigWindow.SettingsTab.cs b/Penumbra/UI/ConfigWindow.SettingsTab.cs index ba639180..3a251e23 100644 --- a/Penumbra/UI/ConfigWindow.SettingsTab.cs +++ b/Penumbra/UI/ConfigWindow.SettingsTab.cs @@ -370,14 +370,6 @@ public partial class ConfigWindow { _window._penumbra.ForceChangelogOpen(); } - -#if DEBUG - ImGui.SetCursorPos( new Vector2( xPos, 5 * ImGui.GetFrameHeightWithSpacing() ) ); - if( ImGui.Button( "Copy Timings", new Vector2( width, 0 ) ) ) - { - TimingManager.CreateTimingReport(); - } -#endif } } } \ No newline at end of file diff --git a/Penumbra/UI/ConfigWindow.cs b/Penumbra/UI/ConfigWindow.cs index 55b3a6cd..e861ccec 100644 --- a/Penumbra/UI/ConfigWindow.cs +++ b/Penumbra/UI/ConfigWindow.cs @@ -6,6 +6,7 @@ using ImGuiNET; using OtterGui; using OtterGui.Raii; using Penumbra.UI.Classes; +using Penumbra.Util; namespace Penumbra.UI; @@ -52,10 +53,10 @@ public sealed partial class ConfigWindow : Window, IDisposable public override void Draw() { + using var performance = Penumbra.Performance.Measure( PerformanceType.UiMainWindow ); + try { - TimingManager.StartTimer( TimingType.UiMainWindow ); - if( Penumbra.ImcExceptions.Count > 0 ) { DrawProblemWindow( $"There were {Penumbra.ImcExceptions.Count} errors while trying to load IMC files from the game data.\n" @@ -103,7 +104,6 @@ public sealed partial class ConfigWindow : Window, IDisposable { Penumbra.Log.Error( $"Exception thrown during UI Render:\n{e}" ); } - TimingManager.StopTimer( TimingType.UiMainWindow ); } private static void DrawProblemWindow( string text, bool withExceptions ) diff --git a/Penumbra/Util/PerformanceType.cs b/Penumbra/Util/PerformanceType.cs new file mode 100644 index 00000000..471f113a --- /dev/null +++ b/Penumbra/Util/PerformanceType.cs @@ -0,0 +1,60 @@ +namespace Penumbra.Util; + +public enum PerformanceType +{ + UiMainWindow, + UiAdvancedWindow, + CharacterResolver, + IdentifyCollection, + GetResourceHandler, + ReadSqPack, + CharacterBaseCreate, + TimelineResources, + LoadCharacterVfx, + LoadAreaVfx, + LoadSound, + LoadAction, + LoadCharacterBaseAnimation, + LoadPap, + LoadTextures, + LoadShaders, + LoadApricotResources, + UpdateModels, + GetEqp, + SetupVisor, + SetupCharacter, + ChangeCustomize, + DebugTimes, +} + +public static class PerformanceTypeExtensions +{ + public static string ToName( this PerformanceType type ) + => type switch + { + PerformanceType.UiMainWindow => "Main Interface Drawing", + PerformanceType.UiAdvancedWindow => "Advanced Window Drawing", + PerformanceType.GetResourceHandler => "GetResource Hook", + PerformanceType.ReadSqPack => "ReadSqPack Hook", + PerformanceType.CharacterResolver => "Resolving Characters", + PerformanceType.IdentifyCollection => "Identifying Collections", + PerformanceType.CharacterBaseCreate => "CharacterBaseCreate Hook", + PerformanceType.TimelineResources => "LoadTimelineResources Hook", + PerformanceType.LoadCharacterVfx => "LoadCharacterVfx Hook", + PerformanceType.LoadAreaVfx => "LoadAreaVfx Hook", + PerformanceType.LoadTextures => "LoadTextures Hook", + PerformanceType.LoadShaders => "LoadShaders Hook", + PerformanceType.LoadApricotResources => "LoadApricotFiles Hook", + PerformanceType.UpdateModels => "UpdateModels Hook", + PerformanceType.GetEqp => "GetEqp Hook", + PerformanceType.SetupVisor => "SetupVisor Hook", + PerformanceType.SetupCharacter => "SetupCharacter Hook", + PerformanceType.ChangeCustomize => "ChangeCustomize Hook", + PerformanceType.LoadSound => "LoadSound Hook", + PerformanceType.LoadCharacterBaseAnimation => "LoadCharacterAnimation Hook", + PerformanceType.LoadPap => "LoadPap Hook", + PerformanceType.LoadAction => "LoadAction Hook", + PerformanceType.DebugTimes => "Debug Tracking", + _ => $"Unknown {( int )type}", + }; +} \ No newline at end of file