mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-13 20:24:17 +01:00
Revamp resource logging.
This commit is contained in:
parent
08519396a0
commit
9098b5b3b3
11 changed files with 1051 additions and 148 deletions
2
OtterGui
2
OtterGui
|
|
@ -1 +1 @@
|
||||||
Subproject commit 9a574c4a50b86a5ff84544d989608d2339b713b2
|
Subproject commit fb6526d0c034e97ee079ee88ca931e536f99c5a7
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
||||||
using Penumbra.String;
|
using Penumbra.String;
|
||||||
using Penumbra.String.Functions;
|
using Penumbra.String.Functions;
|
||||||
|
|
||||||
|
|
@ -7,6 +9,7 @@ namespace Penumbra.GameData.Enums;
|
||||||
|
|
||||||
public enum ResourceType : uint
|
public enum ResourceType : uint
|
||||||
{
|
{
|
||||||
|
Unknown = 0,
|
||||||
Aet = 0x00616574,
|
Aet = 0x00616574,
|
||||||
Amb = 0x00616D62,
|
Amb = 0x00616D62,
|
||||||
Atch = 0x61746368,
|
Atch = 0x61746368,
|
||||||
|
|
@ -21,6 +24,7 @@ public enum ResourceType : uint
|
||||||
Eqp = 0x00657170,
|
Eqp = 0x00657170,
|
||||||
Essb = 0x65737362,
|
Essb = 0x65737362,
|
||||||
Est = 0x00657374,
|
Est = 0x00657374,
|
||||||
|
Evp = 0x00657670,
|
||||||
Exd = 0x00657864,
|
Exd = 0x00657864,
|
||||||
Exh = 0x00657868,
|
Exh = 0x00657868,
|
||||||
Exl = 0x0065786C,
|
Exl = 0x0065786C,
|
||||||
|
|
@ -60,8 +64,171 @@ public enum ResourceType : uint
|
||||||
Wtd = 0x00777464,
|
Wtd = 0x00777464,
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ResourceTypeExtensions
|
[Flags]
|
||||||
|
public enum ResourceTypeFlag : ulong
|
||||||
{
|
{
|
||||||
|
Aet = 0x0000_0000_0000_0001,
|
||||||
|
Amb = 0x0000_0000_0000_0002,
|
||||||
|
Atch = 0x0000_0000_0000_0004,
|
||||||
|
Atex = 0x0000_0000_0000_0008,
|
||||||
|
Avfx = 0x0000_0000_0000_0010,
|
||||||
|
Awt = 0x0000_0000_0000_0020,
|
||||||
|
Cmp = 0x0000_0000_0000_0040,
|
||||||
|
Dic = 0x0000_0000_0000_0080,
|
||||||
|
Eid = 0x0000_0000_0000_0100,
|
||||||
|
Envb = 0x0000_0000_0000_0200,
|
||||||
|
Eqdp = 0x0000_0000_0000_0400,
|
||||||
|
Eqp = 0x0000_0000_0000_0800,
|
||||||
|
Essb = 0x0000_0000_0000_1000,
|
||||||
|
Est = 0x0000_0000_0000_2000,
|
||||||
|
Evp = 0x0000_0000_0000_4000,
|
||||||
|
Exd = 0x0000_0000_0000_8000,
|
||||||
|
Exh = 0x0000_0000_0001_0000,
|
||||||
|
Exl = 0x0000_0000_0002_0000,
|
||||||
|
Fdt = 0x0000_0000_0004_0000,
|
||||||
|
Gfd = 0x0000_0000_0008_0000,
|
||||||
|
Ggd = 0x0000_0000_0010_0000,
|
||||||
|
Gmp = 0x0000_0000_0020_0000,
|
||||||
|
Gzd = 0x0000_0000_0040_0000,
|
||||||
|
Imc = 0x0000_0000_0080_0000,
|
||||||
|
Lcb = 0x0000_0000_0100_0000,
|
||||||
|
Lgb = 0x0000_0000_0200_0000,
|
||||||
|
Luab = 0x0000_0000_0400_0000,
|
||||||
|
Lvb = 0x0000_0000_0800_0000,
|
||||||
|
Mdl = 0x0000_0000_1000_0000,
|
||||||
|
Mlt = 0x0000_0000_2000_0000,
|
||||||
|
Mtrl = 0x0000_0000_4000_0000,
|
||||||
|
Obsb = 0x0000_0000_8000_0000,
|
||||||
|
Pap = 0x0000_0001_0000_0000,
|
||||||
|
Pbd = 0x0000_0002_0000_0000,
|
||||||
|
Pcb = 0x0000_0004_0000_0000,
|
||||||
|
Phyb = 0x0000_0008_0000_0000,
|
||||||
|
Plt = 0x0000_0010_0000_0000,
|
||||||
|
Scd = 0x0000_0020_0000_0000,
|
||||||
|
Sgb = 0x0000_0040_0000_0000,
|
||||||
|
Shcd = 0x0000_0080_0000_0000,
|
||||||
|
Shpk = 0x0000_0100_0000_0000,
|
||||||
|
Sklb = 0x0000_0200_0000_0000,
|
||||||
|
Skp = 0x0000_0400_0000_0000,
|
||||||
|
Stm = 0x0000_0800_0000_0000,
|
||||||
|
Svb = 0x0000_1000_0000_0000,
|
||||||
|
Tera = 0x0000_2000_0000_0000,
|
||||||
|
Tex = 0x0000_4000_0000_0000,
|
||||||
|
Tmb = 0x0000_8000_0000_0000,
|
||||||
|
Ugd = 0x0001_0000_0000_0000,
|
||||||
|
Uld = 0x0002_0000_0000_0000,
|
||||||
|
Waoe = 0x0004_0000_0000_0000,
|
||||||
|
Wtd = 0x0008_0000_0000_0000,
|
||||||
|
}
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum ResourceCategoryFlag : ushort
|
||||||
|
{
|
||||||
|
Common = 0x0001,
|
||||||
|
BgCommon = 0x0002,
|
||||||
|
Bg = 0x0004,
|
||||||
|
Cut = 0x0008,
|
||||||
|
Chara = 0x0010,
|
||||||
|
Shader = 0x0020,
|
||||||
|
Ui = 0x0040,
|
||||||
|
Sound = 0x0080,
|
||||||
|
Vfx = 0x0100,
|
||||||
|
UiScript = 0x0200,
|
||||||
|
Exd = 0x0400,
|
||||||
|
GameScript = 0x0800,
|
||||||
|
Music = 0x1000,
|
||||||
|
SqpackTest = 0x2000,
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ResourceExtensions
|
||||||
|
{
|
||||||
|
public static readonly ResourceTypeFlag AllResourceTypes = Enum.GetValues<ResourceTypeFlag>().Aggregate((v, f) => v | f);
|
||||||
|
public static readonly ResourceCategoryFlag AllResourceCategories = Enum.GetValues<ResourceCategoryFlag>().Aggregate((v, f) => v | f);
|
||||||
|
|
||||||
|
public static ResourceTypeFlag ToFlag(this ResourceType type)
|
||||||
|
=> type switch
|
||||||
|
{
|
||||||
|
ResourceType.Aet => ResourceTypeFlag.Aet,
|
||||||
|
ResourceType.Amb => ResourceTypeFlag.Amb,
|
||||||
|
ResourceType.Atch => ResourceTypeFlag.Atch,
|
||||||
|
ResourceType.Atex => ResourceTypeFlag.Atex,
|
||||||
|
ResourceType.Avfx => ResourceTypeFlag.Avfx,
|
||||||
|
ResourceType.Awt => ResourceTypeFlag.Awt,
|
||||||
|
ResourceType.Cmp => ResourceTypeFlag.Cmp,
|
||||||
|
ResourceType.Dic => ResourceTypeFlag.Dic,
|
||||||
|
ResourceType.Eid => ResourceTypeFlag.Eid,
|
||||||
|
ResourceType.Envb => ResourceTypeFlag.Envb,
|
||||||
|
ResourceType.Eqdp => ResourceTypeFlag.Eqdp,
|
||||||
|
ResourceType.Eqp => ResourceTypeFlag.Eqp,
|
||||||
|
ResourceType.Essb => ResourceTypeFlag.Essb,
|
||||||
|
ResourceType.Est => ResourceTypeFlag.Est,
|
||||||
|
ResourceType.Evp => ResourceTypeFlag.Evp,
|
||||||
|
ResourceType.Exd => ResourceTypeFlag.Exd,
|
||||||
|
ResourceType.Exh => ResourceTypeFlag.Exh,
|
||||||
|
ResourceType.Exl => ResourceTypeFlag.Exl,
|
||||||
|
ResourceType.Fdt => ResourceTypeFlag.Fdt,
|
||||||
|
ResourceType.Gfd => ResourceTypeFlag.Gfd,
|
||||||
|
ResourceType.Ggd => ResourceTypeFlag.Ggd,
|
||||||
|
ResourceType.Gmp => ResourceTypeFlag.Gmp,
|
||||||
|
ResourceType.Gzd => ResourceTypeFlag.Gzd,
|
||||||
|
ResourceType.Imc => ResourceTypeFlag.Imc,
|
||||||
|
ResourceType.Lcb => ResourceTypeFlag.Lcb,
|
||||||
|
ResourceType.Lgb => ResourceTypeFlag.Lgb,
|
||||||
|
ResourceType.Luab => ResourceTypeFlag.Luab,
|
||||||
|
ResourceType.Lvb => ResourceTypeFlag.Lvb,
|
||||||
|
ResourceType.Mdl => ResourceTypeFlag.Mdl,
|
||||||
|
ResourceType.Mlt => ResourceTypeFlag.Mlt,
|
||||||
|
ResourceType.Mtrl => ResourceTypeFlag.Mtrl,
|
||||||
|
ResourceType.Obsb => ResourceTypeFlag.Obsb,
|
||||||
|
ResourceType.Pap => ResourceTypeFlag.Pap,
|
||||||
|
ResourceType.Pbd => ResourceTypeFlag.Pbd,
|
||||||
|
ResourceType.Pcb => ResourceTypeFlag.Pcb,
|
||||||
|
ResourceType.Phyb => ResourceTypeFlag.Phyb,
|
||||||
|
ResourceType.Plt => ResourceTypeFlag.Plt,
|
||||||
|
ResourceType.Scd => ResourceTypeFlag.Scd,
|
||||||
|
ResourceType.Sgb => ResourceTypeFlag.Sgb,
|
||||||
|
ResourceType.Shcd => ResourceTypeFlag.Shcd,
|
||||||
|
ResourceType.Shpk => ResourceTypeFlag.Shpk,
|
||||||
|
ResourceType.Sklb => ResourceTypeFlag.Sklb,
|
||||||
|
ResourceType.Skp => ResourceTypeFlag.Skp,
|
||||||
|
ResourceType.Stm => ResourceTypeFlag.Stm,
|
||||||
|
ResourceType.Svb => ResourceTypeFlag.Svb,
|
||||||
|
ResourceType.Tera => ResourceTypeFlag.Tera,
|
||||||
|
ResourceType.Tex => ResourceTypeFlag.Tex,
|
||||||
|
ResourceType.Tmb => ResourceTypeFlag.Tmb,
|
||||||
|
ResourceType.Ugd => ResourceTypeFlag.Ugd,
|
||||||
|
ResourceType.Uld => ResourceTypeFlag.Uld,
|
||||||
|
ResourceType.Waoe => ResourceTypeFlag.Waoe,
|
||||||
|
ResourceType.Wtd => ResourceTypeFlag.Wtd,
|
||||||
|
_ => 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
public static bool FitsFlag(this ResourceType type, ResourceTypeFlag flags)
|
||||||
|
=> (type.ToFlag() & flags) != 0;
|
||||||
|
|
||||||
|
public static ResourceCategoryFlag ToFlag(this ResourceCategory type)
|
||||||
|
=> type switch
|
||||||
|
{
|
||||||
|
ResourceCategory.Common => ResourceCategoryFlag.Common,
|
||||||
|
ResourceCategory.BgCommon => ResourceCategoryFlag.BgCommon,
|
||||||
|
ResourceCategory.Bg => ResourceCategoryFlag.Bg,
|
||||||
|
ResourceCategory.Cut => ResourceCategoryFlag.Cut,
|
||||||
|
ResourceCategory.Chara => ResourceCategoryFlag.Chara,
|
||||||
|
ResourceCategory.Shader => ResourceCategoryFlag.Shader,
|
||||||
|
ResourceCategory.Ui => ResourceCategoryFlag.Ui,
|
||||||
|
ResourceCategory.Sound => ResourceCategoryFlag.Sound,
|
||||||
|
ResourceCategory.Vfx => ResourceCategoryFlag.Vfx,
|
||||||
|
ResourceCategory.UiScript => ResourceCategoryFlag.UiScript,
|
||||||
|
ResourceCategory.Exd => ResourceCategoryFlag.Exd,
|
||||||
|
ResourceCategory.GameScript => ResourceCategoryFlag.GameScript,
|
||||||
|
ResourceCategory.Music => ResourceCategoryFlag.Music,
|
||||||
|
ResourceCategory.SqpackTest => ResourceCategoryFlag.SqpackTest,
|
||||||
|
_ => 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
public static bool FitsFlag(this ResourceCategory type, ResourceCategoryFlag flags)
|
||||||
|
=> (type.ToFlag() & flags) != 0;
|
||||||
|
|
||||||
public static ResourceType FromBytes(byte a1, byte a2, byte a3)
|
public static ResourceType FromBytes(byte a1, byte a2, byte a3)
|
||||||
=> (ResourceType)(((uint)ByteStringFunctions.AsciiToLower(a1) << 16)
|
=> (ResourceType)(((uint)ByteStringFunctions.AsciiToLower(a1) << 16)
|
||||||
| ((uint)ByteStringFunctions.AsciiToLower(a2) << 8)
|
| ((uint)ByteStringFunctions.AsciiToLower(a2) << 8)
|
||||||
|
|
@ -79,7 +246,7 @@ public static class ResourceTypeExtensions
|
||||||
public static ResourceType FromBytes(char a1, char a2, char a3, char a4)
|
public static ResourceType FromBytes(char a1, char a2, char a3, char a4)
|
||||||
=> FromBytes((byte)a1, (byte)a2, (byte)a3, (byte)a4);
|
=> FromBytes((byte)a1, (byte)a2, (byte)a3, (byte)a4);
|
||||||
|
|
||||||
public static ResourceType FromString( string path )
|
public static ResourceType Type(string path)
|
||||||
{
|
{
|
||||||
var ext = Path.GetExtension(path.AsSpan());
|
var ext = Path.GetExtension(path.AsSpan());
|
||||||
ext = ext.Length == 0 ? path.AsSpan() : ext[1..];
|
ext = ext.Length == 0 ? path.AsSpan() : ext[1..];
|
||||||
|
|
@ -94,7 +261,7 @@ public static class ResourceTypeExtensions
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ResourceType FromString( ByteString path )
|
public static ResourceType Type(ByteString path)
|
||||||
{
|
{
|
||||||
var extIdx = path.LastIndexOf((byte)'.');
|
var extIdx = path.LastIndexOf((byte)'.');
|
||||||
var ext = extIdx == -1 ? path : extIdx == path.Length - 1 ? ByteString.Empty : path.Substring(extIdx + 1);
|
var ext = extIdx == -1 ? path : extIdx == path.Length - 1 ? ByteString.Empty : path.Substring(extIdx + 1);
|
||||||
|
|
@ -108,4 +275,45 @@ public static class ResourceTypeExtensions
|
||||||
_ => FromBytes(ext[^4], ext[^3], ext[^2], ext[^1]),
|
_ => FromBytes(ext[^4], ext[^3], ext[^2], ext[^1]),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static ResourceCategory Category(ByteString path)
|
||||||
|
{
|
||||||
|
if (path.Length < 3)
|
||||||
|
return ResourceCategory.Debug;
|
||||||
|
|
||||||
|
return ByteStringFunctions.AsciiToUpper(path[0]) switch
|
||||||
|
{
|
||||||
|
(byte)'C' => ByteStringFunctions.AsciiToUpper(path[1]) switch
|
||||||
|
{
|
||||||
|
(byte)'O' => ResourceCategory.Common,
|
||||||
|
(byte)'U' => ResourceCategory.Cut,
|
||||||
|
(byte)'H' => ResourceCategory.Chara,
|
||||||
|
_ => ResourceCategory.Debug,
|
||||||
|
},
|
||||||
|
(byte)'B' => ByteStringFunctions.AsciiToUpper(path[2]) switch
|
||||||
|
{
|
||||||
|
(byte)'C' => ResourceCategory.BgCommon,
|
||||||
|
(byte)'/' => ResourceCategory.Bg,
|
||||||
|
_ => ResourceCategory.Debug,
|
||||||
|
},
|
||||||
|
(byte)'S' => ByteStringFunctions.AsciiToUpper(path[1]) switch
|
||||||
|
{
|
||||||
|
(byte)'H' => ResourceCategory.Shader,
|
||||||
|
(byte)'O' => ResourceCategory.Sound,
|
||||||
|
(byte)'Q' => ResourceCategory.SqpackTest,
|
||||||
|
_ => ResourceCategory.Debug,
|
||||||
|
},
|
||||||
|
(byte)'U' => ByteStringFunctions.AsciiToUpper(path[2]) switch
|
||||||
|
{
|
||||||
|
(byte)'/' => ResourceCategory.Ui,
|
||||||
|
(byte)'S' => ResourceCategory.UiScript,
|
||||||
|
_ => ResourceCategory.Debug,
|
||||||
|
},
|
||||||
|
(byte)'V' => ResourceCategory.Vfx,
|
||||||
|
(byte)'E' => ResourceCategory.Exd,
|
||||||
|
(byte)'G' => ResourceCategory.GameScript,
|
||||||
|
(byte)'M' => ResourceCategory.Music,
|
||||||
|
_ => ResourceCategory.Debug,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -8,6 +8,7 @@ using OtterGui;
|
||||||
using OtterGui.Classes;
|
using OtterGui.Classes;
|
||||||
using OtterGui.Filesystem;
|
using OtterGui.Filesystem;
|
||||||
using OtterGui.Widgets;
|
using OtterGui.Widgets;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.Import;
|
using Penumbra.Import;
|
||||||
using Penumbra.Mods;
|
using Penumbra.Mods;
|
||||||
using Penumbra.UI;
|
using Penumbra.UI;
|
||||||
|
|
@ -49,9 +50,15 @@ public partial class Configuration : IPluginConfiguration
|
||||||
|
|
||||||
public int TutorialStep { get; set; } = 0;
|
public int TutorialStep { get; set; } = 0;
|
||||||
|
|
||||||
public bool EnableFullResourceLogging { get; set; } = false;
|
|
||||||
public bool EnableResourceLogging { get; set; } = false;
|
public bool EnableResourceLogging { get; set; } = false;
|
||||||
public string ResourceLoggingFilter { get; set; } = string.Empty;
|
public string ResourceLoggingFilter { get; set; } = string.Empty;
|
||||||
|
public bool EnableResourceWatcher { get; set; } = false;
|
||||||
|
public int MaxResourceWatcherRecords { get; set; } = ResourceWatcher.DefaultMaxEntries;
|
||||||
|
|
||||||
|
public ResourceTypeFlag ResourceWatcherResourceTypes { get; set; } = ResourceExtensions.AllResourceTypes;
|
||||||
|
public ResourceCategoryFlag ResourceWatcherResourceCategories { get; set; } = ResourceExtensions.AllResourceCategories;
|
||||||
|
public ResourceWatcher.RecordType ResourceWatcherRecordTypes { get; set; } = ResourceWatcher.AllRecords;
|
||||||
|
|
||||||
|
|
||||||
[JsonConverter( typeof( SortModeConverter ) )]
|
[JsonConverter( typeof( SortModeConverter ) )]
|
||||||
[JsonProperty( Order = int.MaxValue )]
|
[JsonProperty( Order = int.MaxValue )]
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,7 @@ public class Penumbra : IDalamudPlugin
|
||||||
private readonly WindowSystem _windowSystem;
|
private readonly WindowSystem _windowSystem;
|
||||||
private readonly Changelog _changelog;
|
private readonly Changelog _changelog;
|
||||||
private readonly CommandHandler _commandHandler;
|
private readonly CommandHandler _commandHandler;
|
||||||
|
private readonly ResourceWatcher _resourceWatcher;
|
||||||
|
|
||||||
internal WebServer? WebServer;
|
internal WebServer? WebServer;
|
||||||
|
|
||||||
|
|
@ -129,6 +130,7 @@ public class Penumbra : IDalamudPlugin
|
||||||
MetaFileManager = new MetaFileManager();
|
MetaFileManager = new MetaFileManager();
|
||||||
ResourceLoader = new ResourceLoader( this );
|
ResourceLoader = new ResourceLoader( this );
|
||||||
ResourceLoader.EnableHooks();
|
ResourceLoader.EnableHooks();
|
||||||
|
_resourceWatcher = new ResourceWatcher( ResourceLoader );
|
||||||
ResourceLogger = new ResourceLogger( ResourceLoader );
|
ResourceLogger = new ResourceLogger( ResourceLoader );
|
||||||
ResidentResources = new ResidentResourceManager();
|
ResidentResources = new ResidentResourceManager();
|
||||||
StartTimer.Measure( StartTimeType.Mods, () =>
|
StartTimer.Measure( StartTimeType.Mods, () =>
|
||||||
|
|
@ -167,11 +169,6 @@ public class Penumbra : IDalamudPlugin
|
||||||
_configWindow.IsOpen = true;
|
_configWindow.IsOpen = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if( Config.EnableFullResourceLogging )
|
|
||||||
{
|
|
||||||
ResourceLoader.EnableFullLogging();
|
|
||||||
}
|
|
||||||
|
|
||||||
using( var tAPI = StartTimer.Measure( StartTimeType.Api ) )
|
using( var tAPI = StartTimer.Measure( StartTimeType.Api ) )
|
||||||
{
|
{
|
||||||
Api = new PenumbraApi( this );
|
Api = new PenumbraApi( this );
|
||||||
|
|
@ -208,7 +205,7 @@ public class Penumbra : IDalamudPlugin
|
||||||
private void SetupInterface( out ConfigWindow cfg, out LaunchButton btn, out WindowSystem system, out Changelog changelog )
|
private void SetupInterface( out ConfigWindow cfg, out LaunchButton btn, out WindowSystem system, out Changelog changelog )
|
||||||
{
|
{
|
||||||
using var tInterface = StartTimer.Measure( StartTimeType.Interface );
|
using var tInterface = StartTimer.Measure( StartTimeType.Interface );
|
||||||
cfg = new ConfigWindow( this );
|
cfg = new ConfigWindow( this, _resourceWatcher );
|
||||||
btn = new LaunchButton( _configWindow );
|
btn = new LaunchButton( _configWindow );
|
||||||
system = new WindowSystem( Name );
|
system = new WindowSystem( Name );
|
||||||
changelog = ConfigWindow.CreateChangelog();
|
changelog = ConfigWindow.CreateChangelog();
|
||||||
|
|
@ -338,6 +335,7 @@ public class Penumbra : IDalamudPlugin
|
||||||
CollectionManager?.Dispose();
|
CollectionManager?.Dispose();
|
||||||
PathResolver?.Dispose();
|
PathResolver?.Dispose();
|
||||||
ResourceLogger?.Dispose();
|
ResourceLogger?.Dispose();
|
||||||
|
_resourceWatcher?.Dispose();
|
||||||
ResourceLoader?.Dispose();
|
ResourceLoader?.Dispose();
|
||||||
GameEvents?.Dispose();
|
GameEvents?.Dispose();
|
||||||
CharacterUtility?.Dispose();
|
CharacterUtility?.Dispose();
|
||||||
|
|
@ -374,7 +372,7 @@ public class Penumbra : IDalamudPlugin
|
||||||
sb.Append( $"> **`Auto-Deduplication: `** {Config.AutoDeduplicateOnImport}\n" );
|
sb.Append( $"> **`Auto-Deduplication: `** {Config.AutoDeduplicateOnImport}\n" );
|
||||||
sb.Append( $"> **`Debug Mode: `** {Config.DebugMode}\n" );
|
sb.Append( $"> **`Debug Mode: `** {Config.DebugMode}\n" );
|
||||||
sb.Append( $"> **`Synchronous Load (Dalamud): `** {( Dalamud.GetDalamudConfig( Dalamud.WaitingForPluginsOption, out bool v ) ? v.ToString() : "Unknown" )}\n" );
|
sb.Append( $"> **`Synchronous Load (Dalamud): `** {( Dalamud.GetDalamudConfig( Dalamud.WaitingForPluginsOption, out bool v ) ? v.ToString() : "Unknown" )}\n" );
|
||||||
sb.Append( $"> **`Logging: `** Full: {Config.EnableFullResourceLogging}, Resource: {Config.EnableResourceLogging}\n" );
|
sb.Append( $"> **`Logging: `** Log: {Config.EnableResourceLogging}, Watcher: {Config.EnableResourceWatcher} ({Config.MaxResourceWatcherRecords})\n" );
|
||||||
sb.Append( $"> **`Use Ownership: `** {Config.UseOwnerNameForCharacterCollection}\n" );
|
sb.Append( $"> **`Use Ownership: `** {Config.UseOwnerNameForCharacterCollection}\n" );
|
||||||
sb.AppendLine( "**Mods**" );
|
sb.AppendLine( "**Mods**" );
|
||||||
sb.Append( $"> **`Installed Mods: `** {ModManager.Count}\n" );
|
sb.Append( $"> **`Installed Mods: `** {ModManager.Count}\n" );
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=mods_005Ceditor/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=mods_005Ceditor/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=mods_005Cmanager/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=mods_005Cmanager/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=mods_005Csubclasses/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=mods_005Csubclasses/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=ui_005Cresourcewatcher/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||||
|
|
@ -31,44 +31,13 @@ public partial class ConfigWindow
|
||||||
+ "Toggle this to keep them, for example if an option in a mod is supposed to disable a metadata change from a prior option.",
|
+ "Toggle this to keep them, for example if an option in a mod is supposed to disable a metadata change from a prior option.",
|
||||||
Penumbra.Config.KeepDefaultMetaChanges, v => Penumbra.Config.KeepDefaultMetaChanges = v );
|
Penumbra.Config.KeepDefaultMetaChanges, v => Penumbra.Config.KeepDefaultMetaChanges = v );
|
||||||
DrawWaitForPluginsReflection();
|
DrawWaitForPluginsReflection();
|
||||||
DrawRequestedResourceLogging();
|
|
||||||
DrawEnableHttpApiBox();
|
DrawEnableHttpApiBox();
|
||||||
DrawEnableDebugModeBox();
|
DrawEnableDebugModeBox();
|
||||||
DrawEnableFullResourceLoggingBox();
|
|
||||||
DrawReloadResourceButton();
|
DrawReloadResourceButton();
|
||||||
DrawReloadFontsButton();
|
DrawReloadFontsButton();
|
||||||
ImGui.NewLine();
|
ImGui.NewLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sets the resource logger state when toggled,
|
|
||||||
// and the filter when entered.
|
|
||||||
private void DrawRequestedResourceLogging()
|
|
||||||
{
|
|
||||||
var tmp = Penumbra.Config.EnableResourceLogging;
|
|
||||||
if( ImGui.Checkbox( "##resourceLogging", ref tmp ) )
|
|
||||||
{
|
|
||||||
_window._penumbra.ResourceLogger.SetState( tmp );
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
ImGuiUtil.LabeledHelpMarker( "Enable Requested Resource Logging", "Log all game paths FFXIV requests to the plugin log.\n"
|
|
||||||
+ "You can filter the logged paths for those containing the entered string or matching the regex, if the entered string compiles to a valid regex.\n"
|
|
||||||
+ "Red boundary indicates invalid regex." );
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
|
|
||||||
// Red borders if the string is not a valid regex.
|
|
||||||
var tmpString = Penumbra.Config.ResourceLoggingFilter;
|
|
||||||
using var color = ImRaii.PushColor( ImGuiCol.Border, Colors.RegexWarningBorder, !_window._penumbra.ResourceLogger.ValidRegex );
|
|
||||||
using var style = ImRaii.PushStyle( ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale,
|
|
||||||
!_window._penumbra.ResourceLogger.ValidRegex );
|
|
||||||
ImGui.SetNextItemWidth( -1 );
|
|
||||||
if( ImGui.InputTextWithHint( "##ResourceLogFilter", "Filter...", ref tmpString, Utf8GamePath.MaxGamePathLength ) )
|
|
||||||
{
|
|
||||||
_window._penumbra.ResourceLogger.SetFilter( tmpString );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creates and destroys the web server when toggled.
|
// Creates and destroys the web server when toggled.
|
||||||
private void DrawEnableHttpApiBox()
|
private void DrawEnableHttpApiBox()
|
||||||
{
|
{
|
||||||
|
|
@ -93,30 +62,6 @@ public partial class ConfigWindow
|
||||||
"Enables other applications, e.g. Anamnesis, to use some Penumbra functions, like requesting redraws." );
|
"Enables other applications, e.g. Anamnesis, to use some Penumbra functions, like requesting redraws." );
|
||||||
}
|
}
|
||||||
|
|
||||||
// Should only be used for debugging.
|
|
||||||
private static void DrawEnableFullResourceLoggingBox()
|
|
||||||
{
|
|
||||||
var tmp = Penumbra.Config.EnableFullResourceLogging;
|
|
||||||
if( ImGui.Checkbox( "##fullLogging", ref tmp ) && tmp != Penumbra.Config.EnableFullResourceLogging )
|
|
||||||
{
|
|
||||||
if( tmp )
|
|
||||||
{
|
|
||||||
Penumbra.ResourceLoader.EnableFullLogging();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Penumbra.ResourceLoader.DisableFullLogging();
|
|
||||||
}
|
|
||||||
|
|
||||||
Penumbra.Config.EnableFullResourceLogging = tmp;
|
|
||||||
Penumbra.Config.Save();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
ImGuiUtil.LabeledHelpMarker( "Enable Full Resource Logging",
|
|
||||||
"[DEBUG] Enable the logging of all ResourceLoader events indiscriminately." );
|
|
||||||
}
|
|
||||||
|
|
||||||
// Should only be used for debugging.
|
// Should only be used for debugging.
|
||||||
private static void DrawEnableDebugModeBox()
|
private static void DrawEnableDebugModeBox()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -20,14 +20,16 @@ public sealed partial class ConfigWindow : Window, IDisposable
|
||||||
private readonly EffectiveTab _effectiveTab;
|
private readonly EffectiveTab _effectiveTab;
|
||||||
private readonly DebugTab _debugTab;
|
private readonly DebugTab _debugTab;
|
||||||
private readonly ResourceTab _resourceTab;
|
private readonly ResourceTab _resourceTab;
|
||||||
|
private readonly ResourceWatcher _resourceWatcher;
|
||||||
public readonly ModEditWindow ModEditPopup = new();
|
public readonly ModEditWindow ModEditPopup = new();
|
||||||
|
|
||||||
public ConfigWindow( Penumbra penumbra )
|
public ConfigWindow( Penumbra penumbra, ResourceWatcher watcher )
|
||||||
: base( GetLabel() )
|
: base( GetLabel() )
|
||||||
{
|
{
|
||||||
_penumbra = penumbra;
|
_penumbra = penumbra;
|
||||||
_settingsTab = new SettingsTab( this );
|
_resourceWatcher = watcher;
|
||||||
|
|
||||||
|
_settingsTab = new SettingsTab( this );
|
||||||
_selector = new ModFileSystemSelector( _penumbra.ModFileSystem );
|
_selector = new ModFileSystemSelector( _penumbra.ModFileSystem );
|
||||||
_modPanel = new ModPanel( this );
|
_modPanel = new ModPanel( this );
|
||||||
_selector.SelectionChanged += _modPanel.OnSelectionChange;
|
_selector.SelectionChanged += _modPanel.OnSelectionChange;
|
||||||
|
|
@ -99,6 +101,7 @@ public sealed partial class ConfigWindow : Window, IDisposable
|
||||||
_effectiveTab.Draw();
|
_effectiveTab.Draw();
|
||||||
_debugTab.Draw();
|
_debugTab.Draw();
|
||||||
_resourceTab.Draw();
|
_resourceTab.Draw();
|
||||||
|
DrawResourceWatcher();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch( Exception e )
|
catch( Exception e )
|
||||||
|
|
@ -160,4 +163,13 @@ public sealed partial class ConfigWindow : Window, IDisposable
|
||||||
_inputTextWidth = new Vector2( 350f * ImGuiHelpers.GlobalScale, 0 );
|
_inputTextWidth = new Vector2( 350f * ImGuiHelpers.GlobalScale, 0 );
|
||||||
_iconButtonSize = new Vector2( ImGui.GetFrameHeight() );
|
_iconButtonSize = new Vector2( ImGui.GetFrameHeight() );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void DrawResourceWatcher()
|
||||||
|
{
|
||||||
|
using var tab = ImRaii.TabItem( "Resource Logger" );
|
||||||
|
if (tab)
|
||||||
|
{
|
||||||
|
_resourceWatcher.Draw();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
118
Penumbra/UI/ResourceWatcher/ResourceWatcher.Record.cs
Normal file
118
Penumbra/UI/ResourceWatcher/ResourceWatcher.Record.cs
Normal file
|
|
@ -0,0 +1,118 @@
|
||||||
|
using System;
|
||||||
|
using OtterGui.Classes;
|
||||||
|
using Penumbra.Collections;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.Interop.Structs;
|
||||||
|
using Penumbra.String;
|
||||||
|
|
||||||
|
namespace Penumbra.UI;
|
||||||
|
|
||||||
|
public partial class ResourceWatcher
|
||||||
|
{
|
||||||
|
private unsafe struct Record
|
||||||
|
{
|
||||||
|
public DateTime Time;
|
||||||
|
public ByteString Path;
|
||||||
|
public ByteString OriginalPath;
|
||||||
|
public ModCollection? Collection;
|
||||||
|
public ResourceHandle* Handle;
|
||||||
|
public ResourceTypeFlag ResourceType;
|
||||||
|
public ResourceCategoryFlag Category;
|
||||||
|
public uint RefCount;
|
||||||
|
public RecordType RecordType;
|
||||||
|
public OptionalBool Synchronously;
|
||||||
|
public OptionalBool ReturnValue;
|
||||||
|
public OptionalBool CustomLoad;
|
||||||
|
|
||||||
|
public static Record CreateRequest( ByteString path, bool sync )
|
||||||
|
=> new()
|
||||||
|
{
|
||||||
|
Time = DateTime.UtcNow,
|
||||||
|
Path = path.IsOwned ? path : path.Clone(),
|
||||||
|
OriginalPath = ByteString.Empty,
|
||||||
|
Collection = null,
|
||||||
|
Handle = null,
|
||||||
|
ResourceType = ResourceExtensions.Type( path ).ToFlag(),
|
||||||
|
Category = ResourceExtensions.Category( path ).ToFlag(),
|
||||||
|
RefCount = 0,
|
||||||
|
RecordType = RecordType.Request,
|
||||||
|
Synchronously = sync,
|
||||||
|
ReturnValue = OptionalBool.Null,
|
||||||
|
CustomLoad = OptionalBool.Null,
|
||||||
|
};
|
||||||
|
|
||||||
|
public static Record CreateDefaultLoad( ByteString path, ResourceHandle* handle, ModCollection collection )
|
||||||
|
{
|
||||||
|
path = path.IsOwned ? path : path.Clone();
|
||||||
|
return new Record
|
||||||
|
{
|
||||||
|
Time = DateTime.UtcNow,
|
||||||
|
Path = path,
|
||||||
|
OriginalPath = path,
|
||||||
|
Collection = collection,
|
||||||
|
Handle = handle,
|
||||||
|
ResourceType = handle->FileType.ToFlag(),
|
||||||
|
Category = handle->Category.ToFlag(),
|
||||||
|
RefCount = handle->RefCount,
|
||||||
|
RecordType = RecordType.ResourceLoad,
|
||||||
|
Synchronously = OptionalBool.Null,
|
||||||
|
ReturnValue = OptionalBool.Null,
|
||||||
|
CustomLoad = false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Record CreateLoad( ByteString path, ByteString originalPath, ResourceHandle* handle, ModCollection collection )
|
||||||
|
=> new()
|
||||||
|
{
|
||||||
|
Time = DateTime.UtcNow,
|
||||||
|
Path = path.IsOwned ? path : path.Clone(),
|
||||||
|
OriginalPath = originalPath.IsOwned ? originalPath : originalPath.Clone(),
|
||||||
|
Collection = collection,
|
||||||
|
Handle = handle,
|
||||||
|
ResourceType = handle->FileType.ToFlag(),
|
||||||
|
Category = handle->Category.ToFlag(),
|
||||||
|
RefCount = handle->RefCount,
|
||||||
|
RecordType = RecordType.ResourceLoad,
|
||||||
|
Synchronously = OptionalBool.Null,
|
||||||
|
ReturnValue = OptionalBool.Null,
|
||||||
|
CustomLoad = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
public static Record CreateDestruction( ResourceHandle* handle )
|
||||||
|
{
|
||||||
|
var path = handle->FileName().Clone();
|
||||||
|
return new Record
|
||||||
|
{
|
||||||
|
Time = DateTime.UtcNow,
|
||||||
|
Path = path,
|
||||||
|
OriginalPath = ByteString.Empty,
|
||||||
|
Collection = null,
|
||||||
|
Handle = handle,
|
||||||
|
ResourceType = handle->FileType.ToFlag(),
|
||||||
|
Category = handle->Category.ToFlag(),
|
||||||
|
RefCount = handle->RefCount,
|
||||||
|
RecordType = RecordType.Destruction,
|
||||||
|
Synchronously = OptionalBool.Null,
|
||||||
|
ReturnValue = OptionalBool.Null,
|
||||||
|
CustomLoad = OptionalBool.Null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Record CreateFileLoad( ByteString path, ResourceHandle* handle, bool ret, bool custom )
|
||||||
|
=> new()
|
||||||
|
{
|
||||||
|
Time = DateTime.UtcNow,
|
||||||
|
Path = path.IsOwned ? path : path.Clone(),
|
||||||
|
OriginalPath = ByteString.Empty,
|
||||||
|
Collection = null,
|
||||||
|
Handle = handle,
|
||||||
|
ResourceType = handle->FileType.ToFlag(),
|
||||||
|
Category = handle->Category.ToFlag(),
|
||||||
|
RefCount = handle->RefCount,
|
||||||
|
RecordType = RecordType.FileLoad,
|
||||||
|
Synchronously = OptionalBool.Null,
|
||||||
|
ReturnValue = ret,
|
||||||
|
CustomLoad = custom,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
17
Penumbra/UI/ResourceWatcher/ResourceWatcher.RecordType.cs
Normal file
17
Penumbra/UI/ResourceWatcher/ResourceWatcher.RecordType.cs
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Penumbra.UI;
|
||||||
|
|
||||||
|
public partial class ResourceWatcher
|
||||||
|
{
|
||||||
|
[Flags]
|
||||||
|
public enum RecordType : byte
|
||||||
|
{
|
||||||
|
Request = 0x01,
|
||||||
|
ResourceLoad = 0x02,
|
||||||
|
FileLoad = 0x04,
|
||||||
|
Destruction = 0x08,
|
||||||
|
}
|
||||||
|
|
||||||
|
public const RecordType AllRecords = RecordType.Request | RecordType.ResourceLoad | RecordType.FileLoad | RecordType.Destruction;
|
||||||
|
}
|
||||||
352
Penumbra/UI/ResourceWatcher/ResourceWatcher.Table.cs
Normal file
352
Penumbra/UI/ResourceWatcher/ResourceWatcher.Table.cs
Normal file
|
|
@ -0,0 +1,352 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
using Dalamud.Interface;
|
||||||
|
using ImGuiNET;
|
||||||
|
using OtterGui;
|
||||||
|
using OtterGui.Classes;
|
||||||
|
using OtterGui.Raii;
|
||||||
|
using OtterGui.Table;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.String;
|
||||||
|
|
||||||
|
namespace Penumbra.UI;
|
||||||
|
|
||||||
|
public partial class ResourceWatcher
|
||||||
|
{
|
||||||
|
private sealed class Table : Table< Record >
|
||||||
|
{
|
||||||
|
private static readonly PathColumn Path = new() { Label = "Path" };
|
||||||
|
private static readonly RecordTypeColumn RecordType = new() { Label = "Record" };
|
||||||
|
private static readonly DateColumn Date = new() { Label = "Time" };
|
||||||
|
|
||||||
|
private static readonly CollectionColumn Coll = new() { Label = "Collection" };
|
||||||
|
private static readonly CustomLoadColumn Custom = new() { Label = "Custom" };
|
||||||
|
private static readonly SynchronousLoadColumn Sync = new() { Label = "Sync" };
|
||||||
|
|
||||||
|
private static readonly OriginalPathColumn Orig = new() { Label = "Original Path" };
|
||||||
|
private static readonly ResourceCategoryColumn Cat = new() { Label = "Category" };
|
||||||
|
private static readonly ResourceTypeColumn Type = new() { Label = "Type" };
|
||||||
|
|
||||||
|
private static readonly HandleColumn Handle = new() { Label = "Resource" };
|
||||||
|
private static readonly RefCountColumn Ref = new() { Label = "#Ref" };
|
||||||
|
|
||||||
|
public Table( ICollection< Record > records )
|
||||||
|
: base( "##records", records, Path, RecordType, Coll, Custom, Sync, Orig, Cat, Type, Handle, Ref, Date )
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
=> FilterDirty = true;
|
||||||
|
|
||||||
|
private sealed class PathColumn : ColumnString< Record >
|
||||||
|
{
|
||||||
|
public override float Width
|
||||||
|
=> 300 * ImGuiHelpers.GlobalScale;
|
||||||
|
|
||||||
|
public override string ToName( Record item )
|
||||||
|
=> item.Path.ToString();
|
||||||
|
|
||||||
|
public override int Compare( Record lhs, Record rhs )
|
||||||
|
=> lhs.Path.CompareTo( rhs.Path );
|
||||||
|
|
||||||
|
public override void DrawColumn( Record item, int _ )
|
||||||
|
=> DrawByteString( item.Path, 290 * ImGuiHelpers.GlobalScale );
|
||||||
|
}
|
||||||
|
|
||||||
|
private static unsafe void DrawByteString( ByteString path, float length )
|
||||||
|
{
|
||||||
|
Vector2 vec;
|
||||||
|
ImGuiNative.igCalcTextSize( &vec, path.Path, path.Path + path.Length, 0, 0 );
|
||||||
|
if( vec.X <= length )
|
||||||
|
{
|
||||||
|
ImGuiNative.igTextUnformatted( path.Path, path.Path + path.Length );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var fileName = path.LastIndexOf( ( byte )'/' );
|
||||||
|
ByteString shortPath;
|
||||||
|
if( fileName != -1 )
|
||||||
|
{
|
||||||
|
using var style = ImRaii.PushStyle( ImGuiStyleVar.ItemSpacing, new Vector2( 2 * ImGuiHelpers.GlobalScale ) );
|
||||||
|
using var font = ImRaii.PushFont( UiBuilder.IconFont );
|
||||||
|
ImGui.TextUnformatted( FontAwesomeIcon.EllipsisH.ToIconString() );
|
||||||
|
ImGui.SameLine();
|
||||||
|
shortPath = path.Substring( fileName, path.Length - fileName );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
shortPath = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGuiNative.igTextUnformatted( shortPath.Path, shortPath.Path + shortPath.Length );
|
||||||
|
if( ImGui.IsItemClicked() )
|
||||||
|
{
|
||||||
|
ImGuiNative.igSetClipboardText( path.Path );
|
||||||
|
}
|
||||||
|
|
||||||
|
if( ImGui.IsItemHovered() )
|
||||||
|
{
|
||||||
|
ImGuiNative.igSetTooltip( path.Path );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class RecordTypeColumn : ColumnFlags< RecordType, Record >
|
||||||
|
{
|
||||||
|
public RecordTypeColumn()
|
||||||
|
=> AllFlags = AllRecords;
|
||||||
|
|
||||||
|
public override float Width
|
||||||
|
=> 80 * ImGuiHelpers.GlobalScale;
|
||||||
|
|
||||||
|
public override bool FilterFunc( Record item )
|
||||||
|
=> FilterValue.HasFlag( item.RecordType );
|
||||||
|
|
||||||
|
public override RecordType FilterValue
|
||||||
|
=> Penumbra.Config.ResourceWatcherRecordTypes;
|
||||||
|
|
||||||
|
protected override void SetValue( RecordType value, bool enable )
|
||||||
|
{
|
||||||
|
if( enable )
|
||||||
|
{
|
||||||
|
Penumbra.Config.ResourceWatcherRecordTypes |= value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Penumbra.Config.ResourceWatcherRecordTypes &= ~value;
|
||||||
|
}
|
||||||
|
|
||||||
|
Penumbra.Config.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void DrawColumn( Record item, int idx )
|
||||||
|
{
|
||||||
|
ImGui.TextUnformatted( item.RecordType switch
|
||||||
|
{
|
||||||
|
ResourceWatcher.RecordType.Request => "REQ",
|
||||||
|
ResourceWatcher.RecordType.ResourceLoad => "LOAD",
|
||||||
|
ResourceWatcher.RecordType.FileLoad => "FILE",
|
||||||
|
ResourceWatcher.RecordType.Destruction => "DEST",
|
||||||
|
_ => string.Empty,
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class DateColumn : Column< Record >
|
||||||
|
{
|
||||||
|
public override float Width
|
||||||
|
=> 80 * ImGuiHelpers.GlobalScale;
|
||||||
|
|
||||||
|
public override int Compare( Record lhs, Record rhs )
|
||||||
|
=> lhs.Time.CompareTo( rhs.Time );
|
||||||
|
|
||||||
|
public override void DrawColumn( Record item, int _ )
|
||||||
|
=> ImGui.TextUnformatted( $"{item.Time.ToLongTimeString()}.{item.Time.Millisecond:D4}" );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private sealed class CollectionColumn : ColumnString< Record >
|
||||||
|
{
|
||||||
|
public override float Width
|
||||||
|
=> 80 * ImGuiHelpers.GlobalScale;
|
||||||
|
|
||||||
|
public override string ToName( Record item )
|
||||||
|
=> item.Collection?.Name ?? string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class OriginalPathColumn : ColumnString< Record >
|
||||||
|
{
|
||||||
|
public override float Width
|
||||||
|
=> 200 * ImGuiHelpers.GlobalScale;
|
||||||
|
|
||||||
|
public override string ToName( Record item )
|
||||||
|
=> item.OriginalPath.ToString();
|
||||||
|
|
||||||
|
public override int Compare( Record lhs, Record rhs )
|
||||||
|
=> lhs.OriginalPath.CompareTo( rhs.OriginalPath );
|
||||||
|
|
||||||
|
public override void DrawColumn( Record item, int _ )
|
||||||
|
=> DrawByteString( item.OriginalPath, 190 * ImGuiHelpers.GlobalScale );
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class ResourceCategoryColumn : ColumnFlags< ResourceCategoryFlag, Record >
|
||||||
|
{
|
||||||
|
public ResourceCategoryColumn()
|
||||||
|
=> AllFlags = ResourceExtensions.AllResourceCategories;
|
||||||
|
|
||||||
|
public override float Width
|
||||||
|
=> 80 * ImGuiHelpers.GlobalScale;
|
||||||
|
|
||||||
|
public override bool FilterFunc( Record item )
|
||||||
|
=> FilterValue.HasFlag( item.Category );
|
||||||
|
|
||||||
|
public override ResourceCategoryFlag FilterValue
|
||||||
|
=> Penumbra.Config.ResourceWatcherResourceCategories;
|
||||||
|
|
||||||
|
protected override void SetValue( ResourceCategoryFlag value, bool enable )
|
||||||
|
{
|
||||||
|
if( enable )
|
||||||
|
{
|
||||||
|
Penumbra.Config.ResourceWatcherResourceCategories |= value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Penumbra.Config.ResourceWatcherResourceCategories &= ~value;
|
||||||
|
}
|
||||||
|
|
||||||
|
Penumbra.Config.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void DrawColumn( Record item, int idx )
|
||||||
|
{
|
||||||
|
ImGui.TextUnformatted( item.Category.ToString() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class ResourceTypeColumn : ColumnFlags< ResourceTypeFlag, Record >
|
||||||
|
{
|
||||||
|
public ResourceTypeColumn()
|
||||||
|
{
|
||||||
|
AllFlags = Enum.GetValues< ResourceTypeFlag >().Aggregate( ( v, f ) => v | f );
|
||||||
|
for( var i = 0; i < Names.Length; ++i )
|
||||||
|
{
|
||||||
|
Names[ i ] = Names[ i ].ToLowerInvariant();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override float Width
|
||||||
|
=> 50 * ImGuiHelpers.GlobalScale;
|
||||||
|
|
||||||
|
public override bool FilterFunc( Record item )
|
||||||
|
=> FilterValue.HasFlag( item.ResourceType );
|
||||||
|
|
||||||
|
public override ResourceTypeFlag FilterValue
|
||||||
|
=> Penumbra.Config.ResourceWatcherResourceTypes;
|
||||||
|
|
||||||
|
protected override void SetValue( ResourceTypeFlag value, bool enable )
|
||||||
|
{
|
||||||
|
if( enable )
|
||||||
|
{
|
||||||
|
Penumbra.Config.ResourceWatcherResourceTypes |= value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Penumbra.Config.ResourceWatcherResourceTypes &= ~value;
|
||||||
|
}
|
||||||
|
|
||||||
|
Penumbra.Config.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void DrawColumn( Record item, int idx )
|
||||||
|
{
|
||||||
|
ImGui.TextUnformatted( item.ResourceType.ToString().ToLowerInvariant() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class HandleColumn : ColumnString< Record >
|
||||||
|
{
|
||||||
|
public override float Width
|
||||||
|
=> 120 * ImGuiHelpers.GlobalScale;
|
||||||
|
|
||||||
|
public override unsafe string ToName( Record item )
|
||||||
|
=> item.Handle == null ? string.Empty : $"0x{( ulong )item.Handle:X}";
|
||||||
|
|
||||||
|
public override unsafe void DrawColumn( Record item, int _ )
|
||||||
|
{
|
||||||
|
using var font = ImRaii.PushFont( UiBuilder.MonoFont, item.Handle != null );
|
||||||
|
ImGuiUtil.RightAlign( ToName( item ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
private enum BoolEnum : byte
|
||||||
|
{
|
||||||
|
True = 0x01,
|
||||||
|
False = 0x02,
|
||||||
|
Unknown = 0x04,
|
||||||
|
}
|
||||||
|
|
||||||
|
private class OptBoolColumn : ColumnFlags< BoolEnum, Record >
|
||||||
|
{
|
||||||
|
private BoolEnum _filter;
|
||||||
|
|
||||||
|
public OptBoolColumn()
|
||||||
|
{
|
||||||
|
AllFlags = BoolEnum.True | BoolEnum.False | BoolEnum.Unknown;
|
||||||
|
_filter = AllFlags;
|
||||||
|
Flags &= ~ImGuiTableColumnFlags.NoSort;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected bool FilterFunc( OptionalBool b )
|
||||||
|
=> b.Value switch
|
||||||
|
{
|
||||||
|
null => _filter.HasFlag( BoolEnum.Unknown ),
|
||||||
|
true => _filter.HasFlag( BoolEnum.True ),
|
||||||
|
false => _filter.HasFlag( BoolEnum.False ),
|
||||||
|
};
|
||||||
|
|
||||||
|
public override BoolEnum FilterValue
|
||||||
|
=> _filter;
|
||||||
|
|
||||||
|
protected override void SetValue( BoolEnum value, bool enable )
|
||||||
|
{
|
||||||
|
if( enable )
|
||||||
|
{
|
||||||
|
_filter |= value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_filter &= ~value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static void DrawColumn( OptionalBool b )
|
||||||
|
{
|
||||||
|
using var font = ImRaii.PushFont( UiBuilder.IconFont );
|
||||||
|
ImGui.TextUnformatted( b.Value switch
|
||||||
|
{
|
||||||
|
null => string.Empty,
|
||||||
|
true => FontAwesomeIcon.Check.ToIconString(),
|
||||||
|
false => FontAwesomeIcon.Times.ToIconString(),
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class CustomLoadColumn : OptBoolColumn
|
||||||
|
{
|
||||||
|
public override float Width
|
||||||
|
=> 60 * ImGuiHelpers.GlobalScale;
|
||||||
|
|
||||||
|
public override bool FilterFunc( Record item )
|
||||||
|
=> FilterFunc( item.CustomLoad );
|
||||||
|
|
||||||
|
public override void DrawColumn( Record item, int idx )
|
||||||
|
=> DrawColumn( item.CustomLoad );
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class SynchronousLoadColumn : OptBoolColumn
|
||||||
|
{
|
||||||
|
public override float Width
|
||||||
|
=> 45 * ImGuiHelpers.GlobalScale;
|
||||||
|
|
||||||
|
public override bool FilterFunc( Record item )
|
||||||
|
=> FilterFunc( item.Synchronously );
|
||||||
|
|
||||||
|
public override void DrawColumn( Record item, int idx )
|
||||||
|
=> DrawColumn( item.Synchronously );
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class RefCountColumn : Column< Record >
|
||||||
|
{
|
||||||
|
public override float Width
|
||||||
|
=> 30 * ImGuiHelpers.GlobalScale;
|
||||||
|
|
||||||
|
public override void DrawColumn( Record item, int _ )
|
||||||
|
=> ImGuiUtil.RightAlign( item.RefCount.ToString() );
|
||||||
|
|
||||||
|
public override int Compare( Record lhs, Record rhs )
|
||||||
|
=> lhs.RefCount.CompareTo( rhs.RefCount );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
245
Penumbra/UI/ResourceWatcher/ResourceWatcher.cs
Normal file
245
Penumbra/UI/ResourceWatcher/ResourceWatcher.cs
Normal file
|
|
@ -0,0 +1,245 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using Dalamud.Interface;
|
||||||
|
using ImGuiNET;
|
||||||
|
using OtterGui.Raii;
|
||||||
|
using Penumbra.Collections;
|
||||||
|
using Penumbra.Interop.Loader;
|
||||||
|
using Penumbra.Interop.Structs;
|
||||||
|
using Penumbra.String;
|
||||||
|
using Penumbra.String.Classes;
|
||||||
|
using Penumbra.UI.Classes;
|
||||||
|
|
||||||
|
namespace Penumbra.UI;
|
||||||
|
|
||||||
|
public partial class ResourceWatcher : IDisposable
|
||||||
|
{
|
||||||
|
public const int DefaultMaxEntries = 1024 * 1024;
|
||||||
|
|
||||||
|
private readonly ResourceLoader _loader;
|
||||||
|
private readonly List< Record > _records = new();
|
||||||
|
private readonly ConcurrentQueue< Record > _newRecords = new();
|
||||||
|
private readonly Table _table;
|
||||||
|
private bool _writeToLog;
|
||||||
|
private bool _isEnabled;
|
||||||
|
private string _logFilter = string.Empty;
|
||||||
|
private Regex? _logRegex;
|
||||||
|
private int _maxEntries;
|
||||||
|
private int _newMaxEntries;
|
||||||
|
|
||||||
|
public unsafe ResourceWatcher( ResourceLoader loader )
|
||||||
|
{
|
||||||
|
_loader = loader;
|
||||||
|
_table = new Table( _records );
|
||||||
|
_loader.ResourceRequested += OnResourceRequested;
|
||||||
|
_loader.ResourceLoaded += OnResourceLoaded;
|
||||||
|
_loader.FileLoaded += OnFileLoaded;
|
||||||
|
UpdateFilter( Penumbra.Config.ResourceLoggingFilter, false );
|
||||||
|
_writeToLog = Penumbra.Config.EnableResourceLogging;
|
||||||
|
_isEnabled = Penumbra.Config.EnableResourceWatcher;
|
||||||
|
_maxEntries = Penumbra.Config.MaxResourceWatcherRecords;
|
||||||
|
_newMaxEntries = _maxEntries;
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe void Dispose()
|
||||||
|
{
|
||||||
|
Clear();
|
||||||
|
_records.TrimExcess();
|
||||||
|
_loader.ResourceRequested -= OnResourceRequested;
|
||||||
|
_loader.ResourceLoaded -= OnResourceLoaded;
|
||||||
|
_loader.FileLoaded -= OnFileLoaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Clear()
|
||||||
|
{
|
||||||
|
_records.Clear();
|
||||||
|
_newRecords.Clear();
|
||||||
|
_table.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Draw()
|
||||||
|
{
|
||||||
|
UpdateRecords();
|
||||||
|
|
||||||
|
ImGui.SetCursorPosY( ImGui.GetCursorPosY() + ImGui.GetTextLineHeightWithSpacing() / 2 );
|
||||||
|
if( ImGui.Checkbox( "Enable", ref _isEnabled ) )
|
||||||
|
{
|
||||||
|
Penumbra.Config.EnableResourceWatcher = _isEnabled;
|
||||||
|
Penumbra.Config.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
DrawMaxEntries();
|
||||||
|
ImGui.SameLine();
|
||||||
|
if( ImGui.Button( "Clear" ) )
|
||||||
|
{
|
||||||
|
Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
if( ImGui.Checkbox( "Write to Log", ref _writeToLog ) )
|
||||||
|
{
|
||||||
|
Penumbra.Config.EnableResourceLogging = _writeToLog;
|
||||||
|
Penumbra.Config.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
DrawFilterInput();
|
||||||
|
|
||||||
|
ImGui.SetCursorPosY( ImGui.GetCursorPosY() + ImGui.GetTextLineHeightWithSpacing() / 2 );
|
||||||
|
|
||||||
|
_table.Draw( ImGui.GetTextLineHeightWithSpacing() );
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawFilterInput()
|
||||||
|
{
|
||||||
|
ImGui.SetNextItemWidth( ImGui.GetContentRegionAvail().X );
|
||||||
|
var tmp = _logFilter;
|
||||||
|
var invalidRegex = _logRegex == null && _logFilter.Length > 0;
|
||||||
|
using var color = ImRaii.PushColor( ImGuiCol.Border, Colors.RegexWarningBorder, invalidRegex );
|
||||||
|
using var style = ImRaii.PushStyle( ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale, invalidRegex );
|
||||||
|
if( ImGui.InputTextWithHint( "##logFilter", "If path matches this Regex...", ref tmp, 256 ) )
|
||||||
|
{
|
||||||
|
UpdateFilter( tmp, true );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateFilter( string newString, bool config )
|
||||||
|
{
|
||||||
|
if( newString == _logFilter )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logFilter = newString;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logRegex = new Regex( _logFilter, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase );
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
_logRegex = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( config )
|
||||||
|
{
|
||||||
|
Penumbra.Config.ResourceLoggingFilter = newString;
|
||||||
|
Penumbra.Config.Save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool FilterMatch( ByteString path, out string match )
|
||||||
|
{
|
||||||
|
match = path.ToString();
|
||||||
|
return _logFilter.Length == 0 || ( _logRegex?.IsMatch( match ) ?? false ) || match.Contains( _logFilter, StringComparison.OrdinalIgnoreCase );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void DrawMaxEntries()
|
||||||
|
{
|
||||||
|
ImGui.SetNextItemWidth( 80 * ImGuiHelpers.GlobalScale );
|
||||||
|
ImGui.InputInt( "Max. Entries", ref _newMaxEntries, 0, 0 );
|
||||||
|
var change = ImGui.IsItemDeactivatedAfterEdit();
|
||||||
|
if( ImGui.IsItemClicked( ImGuiMouseButton.Right ) && ImGui.GetIO().KeyCtrl )
|
||||||
|
{
|
||||||
|
change = true;
|
||||||
|
_newMaxEntries = DefaultMaxEntries;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( _maxEntries != DefaultMaxEntries && ImGui.IsItemHovered() )
|
||||||
|
{
|
||||||
|
ImGui.SetTooltip( $"CTRL + Right-Click to reset to default {DefaultMaxEntries}." );
|
||||||
|
}
|
||||||
|
|
||||||
|
if( !change )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_newMaxEntries = Math.Max( 16, _newMaxEntries );
|
||||||
|
if( _newMaxEntries != _maxEntries )
|
||||||
|
{
|
||||||
|
_maxEntries = _newMaxEntries;
|
||||||
|
Penumbra.Config.MaxResourceWatcherRecords = _maxEntries;
|
||||||
|
Penumbra.Config.Save();
|
||||||
|
_records.RemoveRange( 0, _records.Count - _maxEntries );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateRecords()
|
||||||
|
{
|
||||||
|
var count = _newRecords.Count;
|
||||||
|
if( count > 0 )
|
||||||
|
{
|
||||||
|
while( _newRecords.TryDequeue( out var rec ) && count-- > 0 )
|
||||||
|
{
|
||||||
|
_records.Add( rec );
|
||||||
|
}
|
||||||
|
|
||||||
|
if( _records.Count > _maxEntries )
|
||||||
|
{
|
||||||
|
_records.RemoveRange( 0, _records.Count - _maxEntries );
|
||||||
|
}
|
||||||
|
|
||||||
|
_table.Reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void OnResourceRequested( Utf8GamePath data, bool synchronous )
|
||||||
|
{
|
||||||
|
if( _writeToLog && FilterMatch( data.Path, out var match ) )
|
||||||
|
{
|
||||||
|
Penumbra.Log.Information( $"[ResourceLoader] [REQ] {match} was requested {( synchronous ? "synchronously." : "asynchronously." )}" );
|
||||||
|
}
|
||||||
|
|
||||||
|
if( _isEnabled )
|
||||||
|
{
|
||||||
|
_newRecords.Enqueue( Record.CreateRequest( data.Path, synchronous ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe void OnResourceLoaded( ResourceHandle* handle, Utf8GamePath path, FullPath? manipulatedPath, ResolveData data )
|
||||||
|
{
|
||||||
|
if( _writeToLog )
|
||||||
|
{
|
||||||
|
var log = FilterMatch( path.Path, out var name );
|
||||||
|
var name2 = string.Empty;
|
||||||
|
if( manipulatedPath != null )
|
||||||
|
{
|
||||||
|
log |= FilterMatch( manipulatedPath.Value.InternalName, out name2 );
|
||||||
|
}
|
||||||
|
|
||||||
|
if( log )
|
||||||
|
{
|
||||||
|
var pathString = manipulatedPath != null ? $"custom file {name2} instead of {name}" : name;
|
||||||
|
Penumbra.Log.Information(
|
||||||
|
$"[ResourceLoader] [LOAD] [{handle->FileType}] Loaded {pathString} to 0x{( ulong )handle:X} using collection {data.ModCollection.AnonymizedName} for {data.AssociatedName()} (Refcount {handle->RefCount}) " );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if( _isEnabled )
|
||||||
|
{
|
||||||
|
var record = manipulatedPath == null
|
||||||
|
? Record.CreateDefaultLoad( path.Path, handle, data.ModCollection )
|
||||||
|
: Record.CreateLoad( path.Path, manipulatedPath.Value.InternalName, handle, data.ModCollection );
|
||||||
|
_newRecords.Enqueue( record );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe void OnFileLoaded( ResourceHandle* resource, ByteString path, bool success, bool custom )
|
||||||
|
{
|
||||||
|
if( _writeToLog && FilterMatch( path, out var match ) )
|
||||||
|
{
|
||||||
|
Penumbra.Log.Information(
|
||||||
|
$"[ResourceLoader] [FILE] [{resource->FileType}] Loading {match} from {( custom ? "local files" : "SqPack" )} into 0x{( ulong )resource:X} returned {success}." );
|
||||||
|
}
|
||||||
|
|
||||||
|
if( _isEnabled )
|
||||||
|
{
|
||||||
|
_newRecords.Enqueue( Record.CreateFileLoad( path, resource, success, custom ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue