From 9098b5b3b36ca532b56c1baeba381204ec6b0f43 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 16 Feb 2023 16:53:31 +0100 Subject: [PATCH] Revamp resource logging. --- OtterGui | 2 +- Penumbra.GameData/Enums/ResourceType.cs | 368 ++++++++++++++---- Penumbra/Configuration.cs | 9 +- Penumbra/Penumbra.cs | 12 +- Penumbra/Penumbra.csproj.DotSettings | 3 +- .../UI/ConfigWindow.SettingsTab.Advanced.cs | 55 --- Penumbra/UI/ConfigWindow.cs | 18 +- .../ResourceWatcher/ResourceWatcher.Record.cs | 118 ++++++ .../ResourceWatcher.RecordType.cs | 17 + .../ResourceWatcher/ResourceWatcher.Table.cs | 352 +++++++++++++++++ .../UI/ResourceWatcher/ResourceWatcher.cs | 245 ++++++++++++ 11 files changed, 1051 insertions(+), 148 deletions(-) create mode 100644 Penumbra/UI/ResourceWatcher/ResourceWatcher.Record.cs create mode 100644 Penumbra/UI/ResourceWatcher/ResourceWatcher.RecordType.cs create mode 100644 Penumbra/UI/ResourceWatcher/ResourceWatcher.Table.cs create mode 100644 Penumbra/UI/ResourceWatcher/ResourceWatcher.cs diff --git a/OtterGui b/OtterGui index 9a574c4a..fb6526d0 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 9a574c4a50b86a5ff84544d989608d2339b713b2 +Subproject commit fb6526d0c034e97ee079ee88ca931e536f99c5a7 diff --git a/Penumbra.GameData/Enums/ResourceType.cs b/Penumbra.GameData/Enums/ResourceType.cs index 42783c97..80ba03e9 100644 --- a/Penumbra.GameData/Enums/ResourceType.cs +++ b/Penumbra.GameData/Enums/ResourceType.cs @@ -1,5 +1,7 @@ using System; using System.IO; +using System.Linq; +using FFXIVClientStructs.FFXIV.Client.System.Resource; using Penumbra.String; using Penumbra.String.Functions; @@ -7,105 +9,311 @@ namespace Penumbra.GameData.Enums; public enum ResourceType : uint { - Aet = 0x00616574, - Amb = 0x00616D62, - Atch = 0x61746368, - Atex = 0x61746578, - Avfx = 0x61766678, - Awt = 0x00617774, - Cmp = 0x00636D70, - Dic = 0x00646963, - Eid = 0x00656964, - Envb = 0x656E7662, - Eqdp = 0x65716470, - Eqp = 0x00657170, - Essb = 0x65737362, - Est = 0x00657374, - Exd = 0x00657864, - Exh = 0x00657868, - Exl = 0x0065786C, - Fdt = 0x00666474, - Gfd = 0x00676664, - Ggd = 0x00676764, - Gmp = 0x00676D70, - Gzd = 0x00677A64, - Imc = 0x00696D63, - Lcb = 0x006C6362, - Lgb = 0x006C6762, - Luab = 0x6C756162, - Lvb = 0x006C7662, - Mdl = 0x006D646C, - Mlt = 0x006D6C74, - Mtrl = 0x6D74726C, - Obsb = 0x6F627362, - Pap = 0x00706170, - Pbd = 0x00706264, - Pcb = 0x00706362, - Phyb = 0x70687962, - Plt = 0x00706C74, - Scd = 0x00736364, - Sgb = 0x00736762, - Shcd = 0x73686364, - Shpk = 0x7368706B, - Sklb = 0x736B6C62, - Skp = 0x00736B70, - Stm = 0x0073746D, - Svb = 0x00737662, - Tera = 0x74657261, - Tex = 0x00746578, - Tmb = 0x00746D62, - Ugd = 0x00756764, - Uld = 0x00756C64, - Waoe = 0x77616F65, - Wtd = 0x00777464, + Unknown = 0, + Aet = 0x00616574, + Amb = 0x00616D62, + Atch = 0x61746368, + Atex = 0x61746578, + Avfx = 0x61766678, + Awt = 0x00617774, + Cmp = 0x00636D70, + Dic = 0x00646963, + Eid = 0x00656964, + Envb = 0x656E7662, + Eqdp = 0x65716470, + Eqp = 0x00657170, + Essb = 0x65737362, + Est = 0x00657374, + Evp = 0x00657670, + Exd = 0x00657864, + Exh = 0x00657868, + Exl = 0x0065786C, + Fdt = 0x00666474, + Gfd = 0x00676664, + Ggd = 0x00676764, + Gmp = 0x00676D70, + Gzd = 0x00677A64, + Imc = 0x00696D63, + Lcb = 0x006C6362, + Lgb = 0x006C6762, + Luab = 0x6C756162, + Lvb = 0x006C7662, + Mdl = 0x006D646C, + Mlt = 0x006D6C74, + Mtrl = 0x6D74726C, + Obsb = 0x6F627362, + Pap = 0x00706170, + Pbd = 0x00706264, + Pcb = 0x00706362, + Phyb = 0x70687962, + Plt = 0x00706C74, + Scd = 0x00736364, + Sgb = 0x00736762, + Shcd = 0x73686364, + Shpk = 0x7368706B, + Sklb = 0x736B6C62, + Skp = 0x00736B70, + Stm = 0x0073746D, + Svb = 0x00737662, + Tera = 0x74657261, + Tex = 0x00746578, + Tmb = 0x00746D62, + Ugd = 0x00756764, + Uld = 0x00756C64, + Waoe = 0x77616F65, + Wtd = 0x00777464, } -public static class ResourceTypeExtensions +[Flags] +public enum ResourceTypeFlag : ulong { - public static ResourceType FromBytes( byte a1, byte a2, byte a3 ) - => ( ResourceType )( ( ( uint )ByteStringFunctions.AsciiToLower( a1 ) << 16 ) - | ( ( uint )ByteStringFunctions.AsciiToLower( a2 ) << 8 ) - | ByteStringFunctions.AsciiToLower( a3 ) ); + 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, +} - public static ResourceType FromBytes( byte a1, byte a2, byte a3, byte a4 ) - => ( ResourceType )( ( ( uint )ByteStringFunctions.AsciiToLower( a1 ) << 24 ) - | ( ( uint )ByteStringFunctions.AsciiToLower( a2 ) << 16 ) - | ( ( uint )ByteStringFunctions.AsciiToLower( a3 ) << 8 ) - | ByteStringFunctions.AsciiToLower( a4 ) ); +[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 ResourceType FromBytes( char a1, char a2, char a3 ) - => FromBytes( ( byte )a1, ( byte )a2, ( byte )a3 ); +public static class ResourceExtensions +{ + public static readonly ResourceTypeFlag AllResourceTypes = Enum.GetValues().Aggregate((v, f) => v | f); + public static readonly ResourceCategoryFlag AllResourceCategories = Enum.GetValues().Aggregate((v, f) => v | f); - public static ResourceType FromBytes( char a1, char a2, char a3, char a4 ) - => FromBytes( ( byte )a1, ( byte )a2, ( byte )a3, ( byte )a4 ); + 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 ResourceType FromString( string path ) + 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) + => (ResourceType)(((uint)ByteStringFunctions.AsciiToLower(a1) << 16) + | ((uint)ByteStringFunctions.AsciiToLower(a2) << 8) + | ByteStringFunctions.AsciiToLower(a3)); + + public static ResourceType FromBytes(byte a1, byte a2, byte a3, byte a4) + => (ResourceType)(((uint)ByteStringFunctions.AsciiToLower(a1) << 24) + | ((uint)ByteStringFunctions.AsciiToLower(a2) << 16) + | ((uint)ByteStringFunctions.AsciiToLower(a3) << 8) + | ByteStringFunctions.AsciiToLower(a4)); + + public static ResourceType FromBytes(char a1, char a2, char a3) + => FromBytes((byte)a1, (byte)a2, (byte)a3); + + public static ResourceType FromBytes(char a1, char a2, char a3, char a4) + => FromBytes((byte)a1, (byte)a2, (byte)a3, (byte)a4); + + public static ResourceType Type(string path) { - var ext = Path.GetExtension( path.AsSpan() ); - ext = ext.Length == 0 ? path.AsSpan() : ext[ 1.. ]; + var ext = Path.GetExtension(path.AsSpan()); + ext = ext.Length == 0 ? path.AsSpan() : ext[1..]; return ext.Length switch { 0 => 0, - 1 => ( ResourceType )ext[ ^1 ], - 2 => FromBytes( '\0', ext[ ^2 ], ext[ ^1 ] ), - 3 => FromBytes( ext[ ^3 ], ext[ ^2 ], ext[ ^1 ] ), - _ => FromBytes( ext[ ^4 ], ext[ ^3 ], ext[ ^2 ], ext[ ^1 ] ), + 1 => (ResourceType)ext[^1], + 2 => FromBytes('\0', ext[^2], ext[^1]), + 3 => FromBytes(ext[^3], ext[^2], ext[^1]), + _ => FromBytes(ext[^4], ext[^3], ext[^2], ext[^1]), }; } - public static ResourceType FromString( ByteString path ) + public static ResourceType Type(ByteString path) { - var extIdx = path.LastIndexOf( ( byte )'.' ); - var ext = extIdx == -1 ? path : extIdx == path.Length - 1 ? ByteString.Empty : path.Substring( extIdx + 1 ); + var extIdx = path.LastIndexOf((byte)'.'); + var ext = extIdx == -1 ? path : extIdx == path.Length - 1 ? ByteString.Empty : path.Substring(extIdx + 1); return ext.Length switch { 0 => 0, - 1 => ( ResourceType )ext[ ^1 ], - 2 => FromBytes( 0, ext[ ^2 ], ext[ ^1 ] ), - 3 => FromBytes( ext[ ^3 ], ext[ ^2 ], ext[ ^1 ] ), - _ => FromBytes( ext[ ^4 ], ext[ ^3 ], ext[ ^2 ], ext[ ^1 ] ), + 1 => (ResourceType)ext[^1], + 2 => FromBytes(0, ext[^2], ext[^1]), + 3 => FromBytes(ext[^3], ext[^2], ext[^1]), + _ => FromBytes(ext[^4], ext[^3], ext[^2], ext[^1]), }; } -} \ No newline at end of file + + 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, + }; + } +} diff --git a/Penumbra/Configuration.cs b/Penumbra/Configuration.cs index 167be4f4..9b7e4c5e 100644 --- a/Penumbra/Configuration.cs +++ b/Penumbra/Configuration.cs @@ -8,6 +8,7 @@ using OtterGui; using OtterGui.Classes; using OtterGui.Filesystem; using OtterGui.Widgets; +using Penumbra.GameData.Enums; using Penumbra.Import; using Penumbra.Mods; using Penumbra.UI; @@ -49,9 +50,15 @@ public partial class Configuration : IPluginConfiguration public int TutorialStep { get; set; } = 0; - public bool EnableFullResourceLogging { get; set; } = false; public bool EnableResourceLogging { get; set; } = false; 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 ) )] [JsonProperty( Order = int.MaxValue )] diff --git a/Penumbra/Penumbra.cs b/Penumbra/Penumbra.cs index fd988889..8c6b153e 100644 --- a/Penumbra/Penumbra.cs +++ b/Penumbra/Penumbra.cs @@ -87,6 +87,7 @@ public class Penumbra : IDalamudPlugin private readonly WindowSystem _windowSystem; private readonly Changelog _changelog; private readonly CommandHandler _commandHandler; + private readonly ResourceWatcher _resourceWatcher; internal WebServer? WebServer; @@ -129,6 +130,7 @@ public class Penumbra : IDalamudPlugin MetaFileManager = new MetaFileManager(); ResourceLoader = new ResourceLoader( this ); ResourceLoader.EnableHooks(); + _resourceWatcher = new ResourceWatcher( ResourceLoader ); ResourceLogger = new ResourceLogger( ResourceLoader ); ResidentResources = new ResidentResourceManager(); StartTimer.Measure( StartTimeType.Mods, () => @@ -167,11 +169,6 @@ public class Penumbra : IDalamudPlugin _configWindow.IsOpen = true; } - if( Config.EnableFullResourceLogging ) - { - ResourceLoader.EnableFullLogging(); - } - using( var tAPI = StartTimer.Measure( StartTimeType.Api ) ) { 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 ) { using var tInterface = StartTimer.Measure( StartTimeType.Interface ); - cfg = new ConfigWindow( this ); + cfg = new ConfigWindow( this, _resourceWatcher ); btn = new LaunchButton( _configWindow ); system = new WindowSystem( Name ); changelog = ConfigWindow.CreateChangelog(); @@ -338,6 +335,7 @@ public class Penumbra : IDalamudPlugin CollectionManager?.Dispose(); PathResolver?.Dispose(); ResourceLogger?.Dispose(); + _resourceWatcher?.Dispose(); ResourceLoader?.Dispose(); GameEvents?.Dispose(); CharacterUtility?.Dispose(); @@ -374,7 +372,7 @@ public class Penumbra : IDalamudPlugin sb.Append( $"> **`Auto-Deduplication: `** {Config.AutoDeduplicateOnImport}\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( $"> **`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.AppendLine( "**Mods**" ); sb.Append( $"> **`Installed Mods: `** {ModManager.Count}\n" ); diff --git a/Penumbra/Penumbra.csproj.DotSettings b/Penumbra/Penumbra.csproj.DotSettings index 4e906820..d89860c0 100644 --- a/Penumbra/Penumbra.csproj.DotSettings +++ b/Penumbra/Penumbra.csproj.DotSettings @@ -1,4 +1,5 @@  True True - True \ No newline at end of file + True + True \ No newline at end of file diff --git a/Penumbra/UI/ConfigWindow.SettingsTab.Advanced.cs b/Penumbra/UI/ConfigWindow.SettingsTab.Advanced.cs index b4bd7dc8..810140a5 100644 --- a/Penumbra/UI/ConfigWindow.SettingsTab.Advanced.cs +++ b/Penumbra/UI/ConfigWindow.SettingsTab.Advanced.cs @@ -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.", Penumbra.Config.KeepDefaultMetaChanges, v => Penumbra.Config.KeepDefaultMetaChanges = v ); DrawWaitForPluginsReflection(); - DrawRequestedResourceLogging(); DrawEnableHttpApiBox(); DrawEnableDebugModeBox(); - DrawEnableFullResourceLoggingBox(); DrawReloadResourceButton(); DrawReloadFontsButton(); 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. 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." ); } - // 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. private static void DrawEnableDebugModeBox() { diff --git a/Penumbra/UI/ConfigWindow.cs b/Penumbra/UI/ConfigWindow.cs index 99646b21..6aab4f88 100644 --- a/Penumbra/UI/ConfigWindow.cs +++ b/Penumbra/UI/ConfigWindow.cs @@ -20,14 +20,16 @@ public sealed partial class ConfigWindow : Window, IDisposable private readonly EffectiveTab _effectiveTab; private readonly DebugTab _debugTab; private readonly ResourceTab _resourceTab; + private readonly ResourceWatcher _resourceWatcher; public readonly ModEditWindow ModEditPopup = new(); - public ConfigWindow( Penumbra penumbra ) + public ConfigWindow( Penumbra penumbra, ResourceWatcher watcher ) : base( GetLabel() ) { - _penumbra = penumbra; + _penumbra = penumbra; + _resourceWatcher = watcher; + _settingsTab = new SettingsTab( this ); - _selector = new ModFileSystemSelector( _penumbra.ModFileSystem ); _modPanel = new ModPanel( this ); _selector.SelectionChanged += _modPanel.OnSelectionChange; @@ -99,6 +101,7 @@ public sealed partial class ConfigWindow : Window, IDisposable _effectiveTab.Draw(); _debugTab.Draw(); _resourceTab.Draw(); + DrawResourceWatcher(); } } catch( Exception e ) @@ -160,4 +163,13 @@ public sealed partial class ConfigWindow : Window, IDisposable _inputTextWidth = new Vector2( 350f * ImGuiHelpers.GlobalScale, 0 ); _iconButtonSize = new Vector2( ImGui.GetFrameHeight() ); } + + private void DrawResourceWatcher() + { + using var tab = ImRaii.TabItem( "Resource Logger" ); + if (tab) + { + _resourceWatcher.Draw(); + } + } } \ No newline at end of file diff --git a/Penumbra/UI/ResourceWatcher/ResourceWatcher.Record.cs b/Penumbra/UI/ResourceWatcher/ResourceWatcher.Record.cs new file mode 100644 index 00000000..3cc82173 --- /dev/null +++ b/Penumbra/UI/ResourceWatcher/ResourceWatcher.Record.cs @@ -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, + }; + } +} \ No newline at end of file diff --git a/Penumbra/UI/ResourceWatcher/ResourceWatcher.RecordType.cs b/Penumbra/UI/ResourceWatcher/ResourceWatcher.RecordType.cs new file mode 100644 index 00000000..3b3fed73 --- /dev/null +++ b/Penumbra/UI/ResourceWatcher/ResourceWatcher.RecordType.cs @@ -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; +} \ No newline at end of file diff --git a/Penumbra/UI/ResourceWatcher/ResourceWatcher.Table.cs b/Penumbra/UI/ResourceWatcher/ResourceWatcher.Table.cs new file mode 100644 index 00000000..0f97d883 --- /dev/null +++ b/Penumbra/UI/ResourceWatcher/ResourceWatcher.Table.cs @@ -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 ); + } + } +} \ No newline at end of file diff --git a/Penumbra/UI/ResourceWatcher/ResourceWatcher.cs b/Penumbra/UI/ResourceWatcher/ResourceWatcher.cs new file mode 100644 index 00000000..e180082f --- /dev/null +++ b/Penumbra/UI/ResourceWatcher/ResourceWatcher.cs @@ -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 ) ); + } + } +} \ No newline at end of file