Timing test.

This commit is contained in:
Ottermandias 2023-01-03 21:55:39 +01:00
parent 6bc0b77ad3
commit f2997102c7
15 changed files with 220 additions and 59 deletions

View file

@ -77,11 +77,13 @@ public unsafe partial class CharacterUtility
// Set the currently stored data of this resource to new values. // Set the currently stored data of this resource to new values.
private void SetResourceInternal( IntPtr data, int length ) private void SetResourceInternal( IntPtr data, int length )
{ {
TimingManager.StartTimer( TimingType.SetResource );
if( Ready ) if( Ready )
{ {
var resource = Penumbra.CharacterUtility.Address->Resource( GlobalIndex ); var resource = Penumbra.CharacterUtility.Address->Resource( GlobalIndex );
resource->SetData( data, length ); resource->SetData( data, length );
} }
TimingManager.StopTimer( TimingType.SetResource );
} }
// Reset the currently stored data of this resource to its default values. // Reset the currently stored data of this resource to its default values.
@ -133,6 +135,7 @@ public unsafe partial class CharacterUtility
{ {
if( !Disposed ) if( !Disposed )
{ {
TimingManager.StartTimer( TimingType.SetResource );
var list = List._entries; var list = List._entries;
var wasCurrent = ReferenceEquals( this, list.First?.Value ); var wasCurrent = ReferenceEquals( this, list.First?.Value );
list.Remove( this ); list.Remove( this );
@ -159,6 +162,7 @@ public unsafe partial class CharacterUtility
} }
Disposed = true; Disposed = true;
TimingManager.StopTimer( TimingType.SetResource );
} }
} }
} }

View file

@ -76,6 +76,7 @@ public unsafe partial class ResourceLoader
return; return;
} }
TimingManager.StartTimer( TimingType.DebugTimes );
// Got some incomprehensible null-dereference exceptions here when hot-reloading penumbra. // Got some incomprehensible null-dereference exceptions here when hot-reloading penumbra.
try try
{ {
@ -96,6 +97,7 @@ public unsafe partial class ResourceLoader
{ {
Penumbra.Log.Error( e.ToString() ); Penumbra.Log.Error( e.ToString() );
} }
TimingManager.StopTimer( TimingType.DebugTimes );
} }
// Find a key in a StdMap. // Find a key in a StdMap.
@ -202,6 +204,7 @@ public unsafe partial class ResourceLoader
// Only used when the Replaced Resources Tab in the Debug tab is open. // Only used when the Replaced Resources Tab in the Debug tab is open.
public void UpdateDebugInfo() public void UpdateDebugInfo()
{ {
TimingManager.StartTimer( TimingType.DebugTimes );
for( var i = 0; i < _debugList.Count; ++i ) for( var i = 0; i < _debugList.Count; ++i )
{ {
var data = _debugList.Values[ i ]; var data = _debugList.Values[ i ];
@ -220,6 +223,7 @@ public unsafe partial class ResourceLoader
}; };
} }
} }
TimingManager.StopTimer( TimingType.DebugTimes );
} }
// Prevent resource management weirdness. // Prevent resource management weirdness.

View file

@ -81,11 +81,15 @@ public unsafe partial class ResourceLoader
internal ResourceHandle* GetResourceHandler( bool isSync, ResourceManager* resourceManager, ResourceCategory* categoryId, internal ResourceHandle* GetResourceHandler( bool isSync, ResourceManager* resourceManager, ResourceCategory* categoryId,
ResourceType* resourceType, int* resourceHash, byte* path, GetResourceParameters* pGetResParams, bool isUnk ) ResourceType* resourceType, int* resourceHash, byte* path, GetResourceParameters* pGetResParams, bool isUnk )
{ {
TimingManager.StartTimer( TimingType.GetResourceHandler );
ResourceHandle* ret = null;
if( !Utf8GamePath.FromPointer( path, out var gamePath ) ) if( !Utf8GamePath.FromPointer( path, out var gamePath ) )
{ {
Penumbra.Log.Error( "Could not create GamePath from resource path." ); Penumbra.Log.Error( "Could not create GamePath from resource path." );
return CallOriginalHandler( isSync, resourceManager, categoryId, resourceType, resourceHash, path, pGetResParams, isUnk ); ret = CallOriginalHandler( isSync, resourceManager, categoryId, resourceType, resourceHash, path, pGetResParams, isUnk );
} }
else
{
CompareHash( ComputeHash( gamePath.Path, pGetResParams ), *resourceHash, gamePath ); CompareHash( ComputeHash( gamePath.Path, pGetResParams ), *resourceHash, gamePath );
@ -99,16 +103,22 @@ public unsafe partial class ResourceLoader
var retUnmodified = var retUnmodified =
CallOriginalHandler( isSync, resourceManager, categoryId, resourceType, resourceHash, path, pGetResParams, isUnk ); CallOriginalHandler( isSync, resourceManager, categoryId, resourceType, resourceHash, path, pGetResParams, isUnk );
ResourceLoaded?.Invoke( ( Structs.ResourceHandle* )retUnmodified, gamePath, null, data ); ResourceLoaded?.Invoke( ( Structs.ResourceHandle* )retUnmodified, gamePath, null, data );
return retUnmodified; ret = retUnmodified;
} }
else
{
// Replace the hash and path with the correct one for the replacement. // Replace the hash and path with the correct one for the replacement.
*resourceHash = ComputeHash( resolvedPath.Value.InternalName, pGetResParams ); *resourceHash = ComputeHash( resolvedPath.Value.InternalName, pGetResParams );
path = resolvedPath.Value.InternalName.Path; path = resolvedPath.Value.InternalName.Path;
var retModified = CallOriginalHandler( isSync, resourceManager, categoryId, resourceType, resourceHash, path, pGetResParams, isUnk ); ret = CallOriginalHandler( isSync, resourceManager, categoryId, resourceType, resourceHash, path, pGetResParams, isUnk );
ResourceLoaded?.Invoke( ( Structs.ResourceHandle* )retModified, gamePath, resolvedPath.Value, data ); ResourceLoaded?.Invoke( ( Structs.ResourceHandle* )ret, gamePath, resolvedPath.Value, data );
return retModified;
}
}
TimingManager.StopTimer( TimingType.GetResourceHandler );
return ret;
} }
@ -164,48 +174,50 @@ public unsafe partial class ResourceLoader
private byte ReadSqPackDetour( ResourceManager* resourceManager, SeFileDescriptor* fileDescriptor, int priority, bool isSync ) private byte ReadSqPackDetour( ResourceManager* resourceManager, SeFileDescriptor* fileDescriptor, int priority, bool isSync )
{ {
TimingManager.StartTimer( TimingType.ReadSqPack );
byte ret = 0;
if( !DoReplacements ) if( !DoReplacements )
{ {
return ReadSqPackHook.Original( resourceManager, fileDescriptor, priority, isSync ); ret = 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." ); Penumbra.Log.Error( "Failure to load file from SqPack: invalid File Descriptor." );
return ReadSqPackHook.Original( resourceManager, fileDescriptor, priority, isSync ); ret = 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 )
{ {
return ReadSqPackHook.Original( resourceManager, fileDescriptor, priority, isSync ); ret = ReadSqPackHook.Original(resourceManager, fileDescriptor, priority, isSync);
} }
// Paths starting with a '|' are handled separately to allow for special treatment. // Paths starting with a '|' are handled separately to allow for special treatment.
// They are expected to also have a closing '|'. // They are expected to also have a closing '|'.
if( ResourceLoadCustomization == null || gamePath.Path[ 0 ] != ( byte )'|' ) else if( ResourceLoadCustomization == null || gamePath.Path[ 0 ] != ( byte )'|' )
{ {
return DefaultLoadResource( gamePath.Path, resourceManager, fileDescriptor, priority, isSync ); ret = DefaultLoadResource( gamePath.Path, resourceManager, fileDescriptor, priority, isSync );
} }
else
{
// Split the path into the special-treatment part (between the first and second '|') // Split the path into the special-treatment part (between the first and second '|')
// and the actual path. // and the actual path.
byte ret = 0;
var split = gamePath.Path.Split( ( byte )'|', 3, false ); var split = gamePath.Path.Split( ( byte )'|', 3, false );
fileDescriptor->ResourceHandle->FileNameData = split[ 2 ].Path; fileDescriptor->ResourceHandle->FileNameData = split[2].Path;
fileDescriptor->ResourceHandle->FileNameLength = split[ 2 ].Length; fileDescriptor->ResourceHandle->FileNameLength = split[2].Length;
var funcFound = fileDescriptor->ResourceHandle->Category != ResourceCategory.Ui var funcFound = fileDescriptor->ResourceHandle->Category != ResourceCategory.Ui
&& ResourceLoadCustomization.GetInvocationList() && ResourceLoadCustomization.GetInvocationList()
.Any( f => ( ( ResourceLoadCustomizationDelegate )f ) .Any( f => ( ( ResourceLoadCustomizationDelegate )f )
.Invoke( split[ 1 ], split[ 2 ], resourceManager, fileDescriptor, priority, isSync, out ret ) ); .Invoke( split[1], split[2], resourceManager, fileDescriptor, priority, isSync, out ret ) );
if( !funcFound ) if( !funcFound )
{ {
ret = DefaultLoadResource( split[ 2 ], resourceManager, fileDescriptor, priority, isSync ); ret = DefaultLoadResource( split[2], resourceManager, fileDescriptor, priority, isSync );
} }
// Return original resource handle path so that they can be loaded separately. // Return original resource handle path so that they can be loaded separately.
fileDescriptor->ResourceHandle->FileNameData = gamePath.Path.Path; fileDescriptor->ResourceHandle->FileNameData = gamePath.Path.Path;
fileDescriptor->ResourceHandle->FileNameLength = gamePath.Path.Length; fileDescriptor->ResourceHandle->FileNameLength = gamePath.Path.Length;
}
TimingManager.StopTimer( TimingType.ReadSqPack );
return ret; return ret;
} }

View file

@ -126,6 +126,7 @@ public unsafe partial class PathResolver
private ulong LoadTimelineResourcesDetour( IntPtr timeline ) private ulong LoadTimelineResourcesDetour( IntPtr timeline )
{ {
TimingManager.StartTimer( TimingType.TimelineResources );
ulong ret; ulong ret;
var old = _animationLoadData; var old = _animationLoadData;
try try
@ -152,6 +153,7 @@ public unsafe partial class PathResolver
_animationLoadData = old; _animationLoadData = old;
TimingManager.StopTimer( TimingType.TimelineResources );
return ret; return ret;
} }
@ -246,6 +248,7 @@ public unsafe partial class PathResolver
private IntPtr LoadCharacterVfxDetour( byte* vfxPath, VfxParams* vfxParams, byte unk1, byte unk2, float unk3, int unk4 ) private IntPtr LoadCharacterVfxDetour( byte* vfxPath, VfxParams* vfxParams, byte unk1, byte unk2, float unk3, int unk4 )
{ {
TimingManager.StartTimer( TimingType.LoadCharacterVfx );
var last = _animationLoadData; var last = _animationLoadData;
if( vfxParams != null && vfxParams->GameObjectId != unchecked( ( uint )-1 ) ) if( vfxParams != null && vfxParams->GameObjectId != unchecked( ( uint )-1 ) )
{ {
@ -264,7 +267,6 @@ public unsafe partial class PathResolver
{ {
_animationLoadData = ResolveData.Invalid; _animationLoadData = ResolveData.Invalid;
} }
var ret = _loadCharacterVfxHook.Original( vfxPath, vfxParams, unk1, unk2, unk3, unk4 ); var ret = _loadCharacterVfxHook.Original( vfxPath, vfxParams, unk1, unk2, unk3, unk4 );
#if DEBUG #if DEBUG
var path = new ByteString( vfxPath ); var path = new ByteString( vfxPath );
@ -272,6 +274,7 @@ 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}" ); $"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 #endif
_animationLoadData = last; _animationLoadData = last;
TimingManager.StopTimer( TimingType.LoadCharacterVfx );
return ret; return ret;
} }
@ -282,6 +285,7 @@ public unsafe partial class PathResolver
private IntPtr LoadAreaVfxDetour( uint vfxId, float* pos, GameObject* caster, float unk1, float unk2, byte unk3 ) private IntPtr LoadAreaVfxDetour( uint vfxId, float* pos, GameObject* caster, float unk1, float unk2, byte unk3 )
{ {
TimingManager.StartTimer( TimingType.LoadAreaVfx );
var last = _animationLoadData; var last = _animationLoadData;
if( caster != null ) if( caster != null )
{ {
@ -298,6 +302,7 @@ 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}" ); $"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 #endif
_animationLoadData = last; _animationLoadData = last;
TimingManager.StopTimer( TimingType.LoadAreaVfx );
return ret; return ret;
} }

View file

@ -138,6 +138,7 @@ public unsafe partial class PathResolver
private IntPtr CharacterBaseCreateDetour( uint a, IntPtr b, IntPtr c, byte d ) private IntPtr CharacterBaseCreateDetour( uint a, IntPtr b, IntPtr c, byte d )
{ {
TimingManager.StartTimer( TimingType.CharacterBaseCreate );
var meta = DisposableContainer.Empty; var meta = DisposableContainer.Empty;
if( LastGameObject != null ) if( LastGameObject != null )
{ {
@ -170,6 +171,7 @@ public unsafe partial class PathResolver
{ {
meta.Dispose(); meta.Dispose();
} }
TimingManager.StopTimer( TimingType.CharacterBaseCreate );
return ret; return ret;
} }

View file

@ -24,6 +24,7 @@ public unsafe partial class PathResolver
return new ResolveData( Penumbra.CollectionManager.Default ); return new ResolveData( Penumbra.CollectionManager.Default );
} }
TimingManager.StartTimer( TimingType.IdentifyCollection );
try try
{ {
if( useCache && IdentifiedCache.TryGetValue( gameObject, out var data ) ) if( useCache && IdentifiedCache.TryGetValue( gameObject, out var data ) )
@ -60,6 +61,7 @@ public unsafe partial class PathResolver
?? CollectionByAttributes( gameObject ) ?? CollectionByAttributes( gameObject )
?? CheckOwnedCollection( identifier, owner ) ?? CheckOwnedCollection( identifier, owner )
?? Penumbra.CollectionManager.Default; ?? Penumbra.CollectionManager.Default;
return IdentifiedCache.Set( collection, identifier, gameObject ); return IdentifiedCache.Set( collection, identifier, gameObject );
} }
catch( Exception e ) catch( Exception e )
@ -67,25 +69,36 @@ public unsafe partial class PathResolver
Penumbra.Log.Error( $"Error identifying collection:\n{e}" ); Penumbra.Log.Error( $"Error identifying collection:\n{e}" );
return Penumbra.CollectionManager.Default.ToResolveData( gameObject ); return Penumbra.CollectionManager.Default.ToResolveData( gameObject );
} }
finally
{
TimingManager.StopTimer( TimingType.IdentifyCollection );
}
} }
// Get the collection applying to the current player character // Get the collection applying to the current player character
// or the default collection if no player exists. // or the default collection if no player exists.
public static ModCollection PlayerCollection() public static ModCollection PlayerCollection()
{ {
TimingManager.StartTimer( TimingType.IdentifyCollection );
var gameObject = ( GameObject* )Dalamud.Objects.GetObjectAddress( 0 ); var gameObject = ( GameObject* )Dalamud.Objects.GetObjectAddress( 0 );
ModCollection ret;
if( gameObject == null ) if( gameObject == null )
{ {
return Penumbra.CollectionManager.ByType( CollectionType.Yourself ) ret = Penumbra.CollectionManager.ByType( CollectionType.Yourself )
?? Penumbra.CollectionManager.Default; ?? Penumbra.CollectionManager.Default;
} }
else
{
var player = Penumbra.Actors.GetCurrentPlayer(); var player = Penumbra.Actors.GetCurrentPlayer();
return CollectionByIdentifier( player ) ret = CollectionByIdentifier( player )
?? CheckYourself( player, gameObject ) ?? CheckYourself( player, gameObject )
?? CollectionByAttributes( gameObject ) ?? CollectionByAttributes( gameObject )
?? Penumbra.CollectionManager.Default; ?? Penumbra.CollectionManager.Default;
} }
TimingManager.StopTimer( TimingType.IdentifyCollection );
return ret;
}
// Check both temporary and permanent character collections. Temporary first. // Check both temporary and permanent character collections. Temporary first.
private static ModCollection? CollectionByIdentifier( ActorIdentifier identifier ) private static ModCollection? CollectionByIdentifier( ActorIdentifier identifier )

View file

@ -95,6 +95,7 @@ public unsafe partial class PathResolver
// Special handling for paths so that we do not store non-owned temporary strings in the dictionary. // 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 ) public void SetCollection( IntPtr gameObject, ByteString path, ModCollection collection )
{ {
TimingManager.StartTimer( TimingType.SetPathCollection );
if( _pathCollections.ContainsKey( path ) || path.IsOwned ) if( _pathCollections.ContainsKey( path ) || path.IsOwned )
{ {
_pathCollections[ path ] = collection.ToResolveData( gameObject ); _pathCollections[ path ] = collection.ToResolveData( gameObject );
@ -103,6 +104,7 @@ public unsafe partial class PathResolver
{ {
_pathCollections[ path.Clone() ] = collection.ToResolveData( gameObject ); _pathCollections[ path.Clone() ] = collection.ToResolveData( gameObject );
} }
TimingManager.StopTimer( TimingType.SetPathCollection );
} }
} }
} }

View file

@ -113,7 +113,9 @@ public unsafe partial class PathResolver
case ResourceType.Avfx: case ResourceType.Avfx:
if( handle->FileSize == 0 ) if( handle->FileSize == 0 )
{ {
TimingManager.StartTimer( TimingType.AddSubfile );
_subFileCollection[ ( IntPtr )handle ] = resolveData; _subFileCollection[ ( IntPtr )handle ] = resolveData;
TimingManager.StopTimer( TimingType.AddSubfile );
} }
break; break;
@ -126,7 +128,9 @@ public unsafe partial class PathResolver
{ {
case ResourceType.Mtrl: case ResourceType.Mtrl:
case ResourceType.Avfx: case ResourceType.Avfx:
TimingManager.StartTimer( TimingType.AddSubfile );
_subFileCollection.TryRemove( ( IntPtr )handle, out _ ); _subFileCollection.TryRemove( ( IntPtr )handle, out _ );
TimingManager.StopTimer( TimingType.AddSubfile );
break; break;
} }
} }

View file

@ -48,6 +48,7 @@ public partial class PathResolver : IDisposable
// The modified resolver that handles game path resolving. // The modified resolver that handles game path resolving.
private bool CharacterResolver( Utf8GamePath gamePath, ResourceCategory _1, ResourceType type, int _2, out (FullPath?, ResolveData) data ) private bool CharacterResolver( Utf8GamePath gamePath, ResourceCategory _1, ResourceType type, int _2, out (FullPath?, ResolveData) data )
{ {
TimingManager.StartTimer( TimingType.CharacterResolver );
// Check if the path was marked for a specific collection, // 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 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. // or if it is a face decal path and the current mod collection is set.
@ -71,6 +72,7 @@ public partial class PathResolver : IDisposable
// We also need to handle defaulted materials against a non-default collection. // We also need to handle defaulted materials against a non-default collection.
var path = resolved == null ? gamePath.Path : resolved.Value.InternalName; var path = resolved == null ? gamePath.Path : resolved.Value.InternalName;
SubfileHelper.HandleCollection( resolveData, path, nonDefault, type, resolved, out data ); SubfileHelper.HandleCollection( resolveData, path, nonDefault, type, resolved, out data );
TimingManager.StopTimer( TimingType.CharacterResolver );
return true; return true;
} }

View file

@ -86,6 +86,9 @@ public class Penumbra : IDalamudPlugin
{ {
try try
{ {
TimingManager.StartTimer( TimingType.TotalTime );
TimingManager.StartTimer( TimingType.LaunchTime );
Dalamud.Initialize( pluginInterface ); Dalamud.Initialize( pluginInterface );
Log = new Logger(); Log = new Logger();
DevPenumbraExists = CheckDevPluginPenumbra(); DevPenumbraExists = CheckDevPluginPenumbra();
@ -162,6 +165,7 @@ public class Penumbra : IDalamudPlugin
{ {
ResidentResources.Reload(); ResidentResources.Reload();
} }
TimingManager.StopTimer( TimingType.LaunchTime );
} }
catch catch
{ {
@ -304,6 +308,7 @@ public class Penumbra : IDalamudPlugin
ResourceLogger?.Dispose(); ResourceLogger?.Dispose();
ResourceLoader?.Dispose(); ResourceLoader?.Dispose();
CharacterUtility?.Dispose(); CharacterUtility?.Dispose();
TimingManager.StopAllTimers();
} }
// Collect all relevant files for penumbra configuration. // Collect all relevant files for penumbra configuration.

90
Penumbra/TimingManager.cs Normal file
View file

@ -0,0 +1,90 @@
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<ThreadLocal<Stopwatch>>();
#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}" );
}
}
}

View file

@ -61,6 +61,7 @@ public partial class ModEditWindow : Window, IDisposable
public override void PreDraw() public override void PreDraw()
{ {
TimingManager.StartTimer( TimingType.UiAdvancedWindow );
var sb = new StringBuilder( 256 ); var sb = new StringBuilder( 256 );
var redirections = 0; var redirections = 0;
@ -125,6 +126,7 @@ public partial class ModEditWindow : Window, IDisposable
_allowReduplicate = redirections != _editor.AvailableFiles.Count || _editor.MissingFiles.Count > 0; _allowReduplicate = redirections != _editor.AvailableFiles.Count || _editor.MissingFiles.Count > 0;
sb.Append( WindowBaseLabel ); sb.Append( WindowBaseLabel );
WindowName = sb.ToString(); WindowName = sb.ToString();
TimingManager.StopTimer( TimingType.UiAdvancedWindow );
} }
public override void OnClose() public override void OnClose()
@ -135,6 +137,7 @@ public partial class ModEditWindow : Window, IDisposable
public override void Draw() public override void Draw()
{ {
TimingManager.StartTimer( TimingType.UiAdvancedWindow );
using var tabBar = ImRaii.TabBar( "##tabs" ); using var tabBar = ImRaii.TabBar( "##tabs" );
if( !tabBar ) if( !tabBar )
{ {
@ -152,6 +155,7 @@ public partial class ModEditWindow : Window, IDisposable
_materialTab.Draw(); _materialTab.Draw();
DrawTextureTab(); DrawTextureTab();
_swapWindow.DrawItemSwapPanel(); _swapWindow.DrawItemSwapPanel();
TimingManager.StopTimer( TimingType.UiAdvancedWindow );
} }
// A row of three buttonSizes and a help marker that can be used for material suffix changing. // A row of three buttonSizes and a help marker that can be used for material suffix changing.

View file

@ -29,6 +29,9 @@ public partial class ConfigWindow
private void DrawModSelectorSettings() private void DrawModSelectorSettings()
{ {
#if DEBUG
ImGui.NewLine(); // Due to the timing button.
#endif
if( !ImGui.CollapsingHeader( "General" ) ) if( !ImGui.CollapsingHeader( "General" ) )
{ {
OpenTutorial( BasicTutorialSteps.GeneralSettings ); OpenTutorial( BasicTutorialSteps.GeneralSettings );

View file

@ -370,6 +370,14 @@ public partial class ConfigWindow
{ {
_window._penumbra.ForceChangelogOpen(); _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
} }
} }
} }

View file

@ -54,6 +54,8 @@ public sealed partial class ConfigWindow : Window, IDisposable
{ {
try try
{ {
TimingManager.StartTimer( TimingType.UiMainWindow );
if( Penumbra.ImcExceptions.Count > 0 ) if( Penumbra.ImcExceptions.Count > 0 )
{ {
DrawProblemWindow( $"There were {Penumbra.ImcExceptions.Count} errors while trying to load IMC files from the game data.\n" DrawProblemWindow( $"There were {Penumbra.ImcExceptions.Count} errors while trying to load IMC files from the game data.\n"
@ -101,6 +103,7 @@ public sealed partial class ConfigWindow : Window, IDisposable
{ {
Penumbra.Log.Error( $"Exception thrown during UI Render:\n{e}" ); Penumbra.Log.Error( $"Exception thrown during UI Render:\n{e}" );
} }
TimingManager.StopTimer( TimingType.UiMainWindow );
} }
private static void DrawProblemWindow( string text, bool withExceptions ) private static void DrawProblemWindow( string text, bool withExceptions )