Fix some issues with ResourceWatcher.

This commit is contained in:
Ottermandias 2023-03-29 14:42:34 +02:00
parent a8000fbf14
commit 185be81e73
8 changed files with 519 additions and 529 deletions

View file

@ -1,6 +1,7 @@
using System; using System;
using System.Linq; using System.Linq;
using FFXIVClientStructs.FFXIV.Client.Game.Object; using FFXIVClientStructs.FFXIV.Client.Game.Object;
using Penumbra.GameData.Actors;
namespace Penumbra.Collections; namespace Penumbra.Collections;
@ -36,30 +37,6 @@ public readonly struct ResolveData
public override string ToString() public override string ToString()
=> ModCollection.Name; => ModCollection.Name;
public unsafe string AssociatedName()
{
if (AssociatedGameObject == IntPtr.Zero)
return "no associated object.";
try
{
var id = Penumbra.Actors.FromObject((GameObject*)AssociatedGameObject, out _, false, true, true);
if (id.IsValid)
{
var name = id.ToString();
var parts = name.Split(' ', 3);
return string.Join(" ",
parts.Length != 3 ? parts.Select(n => $"{n[0]}.") : parts[..2].Select(n => $"{n[0]}.").Append(parts[2]));
}
}
catch
{
// ignored
}
return $"0x{AssociatedGameObject:X}";
}
} }
public static class ResolveDataExtensions public static class ResolveDataExtensions

View file

@ -64,7 +64,7 @@ public class Configuration : IPluginConfiguration, ISavable
public ResourceTypeFlag ResourceWatcherResourceTypes { get; set; } = ResourceExtensions.AllResourceTypes; public ResourceTypeFlag ResourceWatcherResourceTypes { get; set; } = ResourceExtensions.AllResourceTypes;
public ResourceCategoryFlag ResourceWatcherResourceCategories { get; set; } = ResourceExtensions.AllResourceCategories; public ResourceCategoryFlag ResourceWatcherResourceCategories { get; set; } = ResourceExtensions.AllResourceCategories;
public ResourceWatcher.RecordType ResourceWatcherRecordTypes { get; set; } = ResourceWatcher.AllRecords; public RecordType ResourceWatcherRecordTypes { get; set; } = ResourceWatcher.AllRecords;
[JsonConverter(typeof(SortModeConverter))] [JsonConverter(typeof(SortModeConverter))]

View file

@ -66,7 +66,7 @@ public unsafe class ResourceLoader : IDisposable
_fileReadService.ReadSqPack -= ReadSqPackDetour; _fileReadService.ReadSqPack -= ReadSqPackDetour;
} }
private void ResourceHandler(ref ResourceCategory category, ref ResourceType type, ref int hash, ref Utf8GamePath path, private void ResourceHandler(ref ResourceCategory category, ref ResourceType type, ref int hash, ref Utf8GamePath path, Utf8GamePath original,
GetResourceParameters* parameters, ref bool sync, ref ResourceHandle* returnValue) GetResourceParameters* parameters, ref bool sync, ref ResourceHandle* returnValue)
{ {
if (returnValue != null) if (returnValue != null)
@ -86,9 +86,10 @@ public unsafe class ResourceLoader : IDisposable
_texMdlService.AddCrc(type, resolvedPath); _texMdlService.AddCrc(type, resolvedPath);
// Replace the hash and path with the correct one for the replacement. // Replace the hash and path with the correct one for the replacement.
hash = ComputeHash(resolvedPath.Value.InternalName, parameters); hash = ComputeHash(resolvedPath.Value.InternalName, parameters);
var oldPath = path;
path = p; path = p;
returnValue = _resources.GetOriginalResource(sync, category, type, hash, path.Path, parameters); returnValue = _resources.GetOriginalResource(sync, category, type, hash, path.Path, parameters);
ResourceLoaded?.Invoke(returnValue, p, resolvedPath.Value, data); ResourceLoaded?.Invoke(returnValue, oldPath, resolvedPath.Value, data);
} }
private void ReadSqPackDetour(SeFileDescriptor* fileDescriptor, ref int priority, ref bool isSync, ref byte? returnValue) private void ReadSqPackDetour(SeFileDescriptor* fileDescriptor, ref int priority, ref bool isSync, ref byte? returnValue)

View file

@ -60,7 +60,7 @@ public unsafe class ResourceService : IDisposable
/// <param name="parameters">Mainly used for SCD streaming, can be null.</param> /// <param name="parameters">Mainly used for SCD streaming, can be null.</param>
/// <param name="sync">Whether to request the resource synchronously or asynchronously.</param> /// <param name="sync">Whether to request the resource synchronously or asynchronously.</param>
/// <param name="returnValue">The returned resource handle. If this is not null, calling original will be skipped. </param> /// <param name="returnValue">The returned resource handle. If this is not null, calling original will be skipped. </param>
public delegate void GetResourcePreDelegate(ref ResourceCategory category, ref ResourceType type, ref int hash, ref Utf8GamePath path, public delegate void GetResourcePreDelegate(ref ResourceCategory category, ref ResourceType type, ref int hash, ref Utf8GamePath path, Utf8GamePath original,
GetResourceParameters* parameters, ref bool sync, ref ResourceHandle* returnValue); GetResourceParameters* parameters, ref bool sync, ref ResourceHandle* returnValue);
/// <summary> <inheritdoc cref="GetResourcePreDelegate"/> <para/> /// <summary> <inheritdoc cref="GetResourcePreDelegate"/> <para/>
@ -104,7 +104,7 @@ public unsafe class ResourceService : IDisposable
} }
ResourceHandle* returnValue = null; ResourceHandle* returnValue = null;
ResourceRequested?.Invoke(ref *categoryId, ref *resourceType, ref *resourceHash, ref gamePath, pGetResParams, ref isSync, ResourceRequested?.Invoke(ref *categoryId, ref *resourceType, ref *resourceHash, ref gamePath, gamePath, pGetResParams, ref isSync,
ref returnValue); ref returnValue);
if (returnValue != null) if (returnValue != null)
return returnValue; return returnValue;

View file

@ -7,13 +7,21 @@ using Penumbra.String;
namespace Penumbra.UI; namespace Penumbra.UI;
public partial class ResourceWatcher [Flags]
public enum RecordType : byte
{
Request = 0x01,
ResourceLoad = 0x02,
FileLoad = 0x04,
Destruction = 0x08,
}
internal unsafe struct Record
{ {
private unsafe struct Record
{
public DateTime Time; public DateTime Time;
public ByteString Path; public ByteString Path;
public ByteString OriginalPath; public ByteString OriginalPath;
public string AssociatedGameObject;
public ModCollection? Collection; public ModCollection? Collection;
public ResourceHandle* Handle; public ResourceHandle* Handle;
public ResourceTypeFlag ResourceType; public ResourceTypeFlag ResourceType;
@ -24,7 +32,7 @@ public partial class ResourceWatcher
public OptionalBool ReturnValue; public OptionalBool ReturnValue;
public OptionalBool CustomLoad; public OptionalBool CustomLoad;
public static Record CreateRequest( ByteString path, bool sync ) public static Record CreateRequest(ByteString path, bool sync)
=> new() => new()
{ {
Time = DateTime.UtcNow, Time = DateTime.UtcNow,
@ -32,16 +40,17 @@ public partial class ResourceWatcher
OriginalPath = ByteString.Empty, OriginalPath = ByteString.Empty,
Collection = null, Collection = null,
Handle = null, Handle = null,
ResourceType = ResourceExtensions.Type( path ).ToFlag(), ResourceType = ResourceExtensions.Type(path).ToFlag(),
Category = ResourceExtensions.Category( path ).ToFlag(), Category = ResourceExtensions.Category(path).ToFlag(),
RefCount = 0, RefCount = 0,
RecordType = RecordType.Request, RecordType = RecordType.Request,
Synchronously = sync, Synchronously = sync,
ReturnValue = OptionalBool.Null, ReturnValue = OptionalBool.Null,
CustomLoad = OptionalBool.Null, CustomLoad = OptionalBool.Null,
AssociatedGameObject = string.Empty,
}; };
public static Record CreateDefaultLoad( ByteString path, ResourceHandle* handle, ModCollection collection ) public static Record CreateDefaultLoad(ByteString path, ResourceHandle* handle, ModCollection collection, string associatedGameObject)
{ {
path = path.IsOwned ? path : path.Clone(); path = path.IsOwned ? path : path.Clone();
return new Record return new Record
@ -58,10 +67,12 @@ public partial class ResourceWatcher
Synchronously = OptionalBool.Null, Synchronously = OptionalBool.Null,
ReturnValue = OptionalBool.Null, ReturnValue = OptionalBool.Null,
CustomLoad = false, CustomLoad = false,
AssociatedGameObject = associatedGameObject,
}; };
} }
public static Record CreateLoad( ByteString path, ByteString originalPath, ResourceHandle* handle, ModCollection collection ) public static Record CreateLoad(ByteString path, ByteString originalPath, ResourceHandle* handle, ModCollection collection,
string associatedGameObject)
=> new() => new()
{ {
Time = DateTime.UtcNow, Time = DateTime.UtcNow,
@ -76,9 +87,10 @@ public partial class ResourceWatcher
Synchronously = OptionalBool.Null, Synchronously = OptionalBool.Null,
ReturnValue = OptionalBool.Null, ReturnValue = OptionalBool.Null,
CustomLoad = true, CustomLoad = true,
AssociatedGameObject = associatedGameObject,
}; };
public static Record CreateDestruction( ResourceHandle* handle ) public static Record CreateDestruction(ResourceHandle* handle)
{ {
var path = handle->FileName().Clone(); var path = handle->FileName().Clone();
return new Record return new Record
@ -98,7 +110,7 @@ public partial class ResourceWatcher
}; };
} }
public static Record CreateFileLoad( ByteString path, ResourceHandle* handle, bool ret, bool custom ) public static Record CreateFileLoad(ByteString path, ResourceHandle* handle, bool ret, bool custom)
=> new() => new()
{ {
Time = DateTime.UtcNow, Time = DateTime.UtcNow,
@ -114,5 +126,4 @@ public partial class ResourceWatcher
ReturnValue = ret, ReturnValue = ret,
CustomLoad = custom, CustomLoad = custom,
}; };
}
} }

View file

@ -1,17 +0,0 @@
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;
}

View file

@ -13,164 +13,167 @@ using Penumbra.String;
namespace Penumbra.UI; namespace Penumbra.UI;
public partial class ResourceWatcher internal sealed class ResourceWatcherTable : Table<Record>
{ {
private sealed class Table : Table< Record > public ResourceWatcherTable(Configuration config, ICollection<Record> records)
{ : base("##records",
private static readonly PathColumn Path = new() { Label = "Path" }; records,
private static readonly RecordTypeColumn RecordType = new() { Label = "Record" }; new PathColumn { Label = "Path" },
private static readonly DateColumn Date = new() { Label = "Time" }; new RecordTypeColumn(config) { Label = "Record" },
new CollectionColumn { Label = "Collection" },
private static readonly CollectionColumn Coll = new() { Label = "Collection" }; new ObjectColumn { Label = "Game Object" },
private static readonly CustomLoadColumn Custom = new() { Label = "Custom" }; new CustomLoadColumn { Label = "Custom" },
private static readonly SynchronousLoadColumn Sync = new() { Label = "Sync" }; new SynchronousLoadColumn { Label = "Sync" },
new OriginalPathColumn { Label = "Original Path" },
private static readonly OriginalPathColumn Orig = new() { Label = "Original Path" }; new ResourceCategoryColumn { Label = "Category" },
private static readonly ResourceCategoryColumn Cat = new() { Label = "Category" }; new ResourceTypeColumn { Label = "Type" },
private static readonly ResourceTypeColumn Type = new() { Label = "Type" }; new HandleColumn { Label = "Resource" },
new RefCountColumn { Label = "#Ref" },
private static readonly HandleColumn Handle = new() { Label = "Resource" }; new DateColumn { Label = "Time" }
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() public void Reset()
=> FilterDirty = true; => FilterDirty = true;
private sealed class PathColumn : ColumnString< Record > private sealed class PathColumn : ColumnString<Record>
{ {
public override float Width public override float Width
=> 300 * UiHelpers.Scale; => 300 * UiHelpers.Scale;
public override string ToName( Record item ) public override string ToName(Record item)
=> item.Path.ToString(); => item.Path.ToString();
public override int Compare( Record lhs, Record rhs ) public override int Compare(Record lhs, Record rhs)
=> lhs.Path.CompareTo( rhs.Path ); => lhs.Path.CompareTo(rhs.Path);
public override void DrawColumn( Record item, int _ ) public override void DrawColumn(Record item, int _)
=> DrawByteString( item.Path, 290 * UiHelpers.Scale ); => DrawByteString(item.Path, 280 * UiHelpers.Scale);
} }
private static unsafe void DrawByteString( ByteString path, float length ) private static unsafe void DrawByteString(ByteString path, float length)
{ {
Vector2 vec; Vector2 vec;
ImGuiNative.igCalcTextSize( &vec, path.Path, path.Path + path.Length, 0, 0 ); ImGuiNative.igCalcTextSize(&vec, path.Path, path.Path + path.Length, 0, 0);
if( vec.X <= length ) if (vec.X <= length)
{ {
ImGuiNative.igTextUnformatted( path.Path, path.Path + path.Length ); ImGuiNative.igTextUnformatted(path.Path, path.Path + path.Length);
} }
else else
{ {
var fileName = path.LastIndexOf( ( byte )'/' ); var fileName = path.LastIndexOf((byte)'/');
ByteString shortPath; ByteString shortPath;
if( fileName != -1 ) if (fileName != -1)
{ {
using var style = ImRaii.PushStyle( ImGuiStyleVar.ItemSpacing, new Vector2( 2 * UiHelpers.Scale ) ); using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, new Vector2(2 * UiHelpers.Scale));
using var font = ImRaii.PushFont( UiBuilder.IconFont ); using var font = ImRaii.PushFont(UiBuilder.IconFont);
ImGui.TextUnformatted( FontAwesomeIcon.EllipsisH.ToIconString() ); ImGui.TextUnformatted(FontAwesomeIcon.EllipsisH.ToIconString());
ImGui.SameLine(); ImGui.SameLine();
shortPath = path.Substring( fileName, path.Length - fileName ); shortPath = path.Substring(fileName, path.Length - fileName);
} }
else else
{ {
shortPath = path; shortPath = path;
} }
ImGuiNative.igTextUnformatted( shortPath.Path, shortPath.Path + shortPath.Length ); ImGuiNative.igTextUnformatted(shortPath.Path, shortPath.Path + shortPath.Length);
if( ImGui.IsItemClicked() ) if (ImGui.IsItemClicked())
{ ImGuiNative.igSetClipboardText(path.Path);
ImGuiNative.igSetClipboardText( path.Path );
}
if( ImGui.IsItemHovered() ) if (ImGui.IsItemHovered())
{ ImGuiNative.igSetTooltip(path.Path);
ImGuiNative.igSetTooltip( path.Path );
}
} }
} }
private sealed class RecordTypeColumn : ColumnFlags< RecordType, Record > private sealed class RecordTypeColumn : ColumnFlags<RecordType, Record>
{ {
public RecordTypeColumn() private readonly Configuration _config;
=> AllFlags = AllRecords;
public RecordTypeColumn(Configuration config)
{
AllFlags = ResourceWatcher.AllRecords;
_config = config;
}
public override float Width public override float Width
=> 80 * UiHelpers.Scale; => 80 * UiHelpers.Scale;
public override bool FilterFunc( Record item ) public override bool FilterFunc(Record item)
=> FilterValue.HasFlag( item.RecordType ); => FilterValue.HasFlag(item.RecordType);
public override RecordType FilterValue public override RecordType FilterValue
=> Penumbra.Config.ResourceWatcherRecordTypes; => _config.ResourceWatcherRecordTypes;
protected override void SetValue( RecordType value, bool enable ) protected override void SetValue(RecordType value, bool enable)
{ {
if( enable ) if (enable)
{ _config.ResourceWatcherRecordTypes |= value;
Penumbra.Config.ResourceWatcherRecordTypes |= value;
}
else else
{ _config.ResourceWatcherRecordTypes &= ~value;
Penumbra.Config.ResourceWatcherRecordTypes &= ~value;
}
Penumbra.Config.Save(); Penumbra.Config.Save();
} }
public override void DrawColumn( Record item, int idx ) public override void DrawColumn(Record item, int idx)
{ {
ImGui.TextUnformatted( item.RecordType switch ImGui.TextUnformatted(item.RecordType switch
{ {
ResourceWatcher.RecordType.Request => "REQ", RecordType.Request => "REQ",
ResourceWatcher.RecordType.ResourceLoad => "LOAD", RecordType.ResourceLoad => "LOAD",
ResourceWatcher.RecordType.FileLoad => "FILE", RecordType.FileLoad => "FILE",
ResourceWatcher.RecordType.Destruction => "DEST", RecordType.Destruction => "DEST",
_ => string.Empty, _ => string.Empty,
} ); });
} }
} }
private sealed class DateColumn : Column< Record > private sealed class DateColumn : Column<Record>
{ {
public override float Width public override float Width
=> 80 * UiHelpers.Scale; => 80 * UiHelpers.Scale;
public override int Compare( Record lhs, Record rhs ) public override int Compare(Record lhs, Record rhs)
=> lhs.Time.CompareTo( rhs.Time ); => lhs.Time.CompareTo(rhs.Time);
public override void DrawColumn( Record item, int _ ) public override void DrawColumn(Record item, int _)
=> ImGui.TextUnformatted( $"{item.Time.ToLongTimeString()}.{item.Time.Millisecond:D4}" ); => ImGui.TextUnformatted($"{item.Time.ToLongTimeString()}.{item.Time.Millisecond:D4}");
} }
private sealed class CollectionColumn : ColumnString< Record > private sealed class CollectionColumn : ColumnString<Record>
{ {
public override float Width public override float Width
=> 80 * UiHelpers.Scale; => 80 * UiHelpers.Scale;
public override string ToName( Record item ) public override string ToName(Record item)
=> item.Collection?.Name ?? string.Empty; => item.Collection?.Name ?? string.Empty;
} }
private sealed class OriginalPathColumn : ColumnString< Record > private sealed class ObjectColumn : ColumnString<Record>
{ {
public override float Width public override float Width
=> 200 * UiHelpers.Scale; => 200 * UiHelpers.Scale;
public override string ToName( Record item ) public override string ToName(Record item)
=> item.OriginalPath.ToString(); => item.AssociatedGameObject;
public override int Compare( Record lhs, Record rhs )
=> lhs.OriginalPath.CompareTo( rhs.OriginalPath );
public override void DrawColumn( Record item, int _ )
=> DrawByteString( item.OriginalPath, 190 * UiHelpers.Scale );
} }
private sealed class ResourceCategoryColumn : ColumnFlags< ResourceCategoryFlag, Record > private sealed class OriginalPathColumn : ColumnString<Record>
{
public override float Width
=> 200 * UiHelpers.Scale;
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 * UiHelpers.Scale);
}
private sealed class ResourceCategoryColumn : ColumnFlags<ResourceCategoryFlag, Record>
{ {
public ResourceCategoryColumn() public ResourceCategoryColumn()
=> AllFlags = ResourceExtensions.AllResourceCategories; => AllFlags = ResourceExtensions.AllResourceCategories;
@ -178,84 +181,74 @@ public partial class ResourceWatcher
public override float Width public override float Width
=> 80 * UiHelpers.Scale; => 80 * UiHelpers.Scale;
public override bool FilterFunc( Record item ) public override bool FilterFunc(Record item)
=> FilterValue.HasFlag( item.Category ); => FilterValue.HasFlag(item.Category);
public override ResourceCategoryFlag FilterValue public override ResourceCategoryFlag FilterValue
=> Penumbra.Config.ResourceWatcherResourceCategories; => Penumbra.Config.ResourceWatcherResourceCategories;
protected override void SetValue( ResourceCategoryFlag value, bool enable ) protected override void SetValue(ResourceCategoryFlag value, bool enable)
{
if( enable )
{ {
if (enable)
Penumbra.Config.ResourceWatcherResourceCategories |= value; Penumbra.Config.ResourceWatcherResourceCategories |= value;
}
else else
{
Penumbra.Config.ResourceWatcherResourceCategories &= ~value; Penumbra.Config.ResourceWatcherResourceCategories &= ~value;
}
Penumbra.Config.Save(); Penumbra.Config.Save();
} }
public override void DrawColumn( Record item, int idx ) public override void DrawColumn(Record item, int idx)
{ {
ImGui.TextUnformatted( item.Category.ToString() ); ImGui.TextUnformatted(item.Category.ToString());
} }
} }
private sealed class ResourceTypeColumn : ColumnFlags< ResourceTypeFlag, Record > private sealed class ResourceTypeColumn : ColumnFlags<ResourceTypeFlag, Record>
{ {
public ResourceTypeColumn() public ResourceTypeColumn()
{ {
AllFlags = Enum.GetValues< ResourceTypeFlag >().Aggregate( ( v, f ) => v | f ); AllFlags = Enum.GetValues<ResourceTypeFlag>().Aggregate((v, f) => v | f);
for( var i = 0; i < Names.Length; ++i ) for (var i = 0; i < Names.Length; ++i)
{ Names[i] = Names[i].ToLowerInvariant();
Names[ i ] = Names[ i ].ToLowerInvariant();
}
} }
public override float Width public override float Width
=> 50 * UiHelpers.Scale; => 50 * UiHelpers.Scale;
public override bool FilterFunc( Record item ) public override bool FilterFunc(Record item)
=> FilterValue.HasFlag( item.ResourceType ); => FilterValue.HasFlag(item.ResourceType);
public override ResourceTypeFlag FilterValue public override ResourceTypeFlag FilterValue
=> Penumbra.Config.ResourceWatcherResourceTypes; => Penumbra.Config.ResourceWatcherResourceTypes;
protected override void SetValue( ResourceTypeFlag value, bool enable ) protected override void SetValue(ResourceTypeFlag value, bool enable)
{
if( enable )
{ {
if (enable)
Penumbra.Config.ResourceWatcherResourceTypes |= value; Penumbra.Config.ResourceWatcherResourceTypes |= value;
}
else else
{
Penumbra.Config.ResourceWatcherResourceTypes &= ~value; Penumbra.Config.ResourceWatcherResourceTypes &= ~value;
}
Penumbra.Config.Save(); Penumbra.Config.Save();
} }
public override void DrawColumn( Record item, int idx ) public override void DrawColumn(Record item, int idx)
{ {
ImGui.TextUnformatted( item.ResourceType.ToString().ToLowerInvariant() ); ImGui.TextUnformatted(item.ResourceType.ToString().ToLowerInvariant());
} }
} }
private sealed class HandleColumn : ColumnString< Record > private sealed class HandleColumn : ColumnString<Record>
{ {
public override float Width public override float Width
=> 120 * UiHelpers.Scale; => 120 * UiHelpers.Scale;
public override unsafe string ToName( Record item ) public override unsafe string ToName(Record item)
=> item.Handle == null ? string.Empty : $"0x{( ulong )item.Handle:X}"; => item.Handle == null ? string.Empty : $"0x{(ulong)item.Handle:X}";
public override unsafe void DrawColumn( Record item, int _ ) public override unsafe void DrawColumn(Record item, int _)
{ {
using var font = ImRaii.PushFont( UiBuilder.MonoFont, item.Handle != null ); using var font = ImRaii.PushFont(UiBuilder.MonoFont, item.Handle != null);
ImGuiUtil.RightAlign( ToName( item ) ); ImGuiUtil.RightAlign(ToName(item));
} }
} }
@ -267,7 +260,7 @@ public partial class ResourceWatcher
Unknown = 0x04, Unknown = 0x04,
} }
private class OptBoolColumn : ColumnFlags< BoolEnum, Record > private class OptBoolColumn : ColumnFlags<BoolEnum, Record>
{ {
private BoolEnum _filter; private BoolEnum _filter;
@ -278,38 +271,34 @@ public partial class ResourceWatcher
Flags &= ~ImGuiTableColumnFlags.NoSort; Flags &= ~ImGuiTableColumnFlags.NoSort;
} }
protected bool FilterFunc( OptionalBool b ) protected bool FilterFunc(OptionalBool b)
=> b.Value switch => b.Value switch
{ {
null => _filter.HasFlag( BoolEnum.Unknown ), null => _filter.HasFlag(BoolEnum.Unknown),
true => _filter.HasFlag( BoolEnum.True ), true => _filter.HasFlag(BoolEnum.True),
false => _filter.HasFlag( BoolEnum.False ), false => _filter.HasFlag(BoolEnum.False),
}; };
public override BoolEnum FilterValue public override BoolEnum FilterValue
=> _filter; => _filter;
protected override void SetValue( BoolEnum value, bool enable ) protected override void SetValue(BoolEnum value, bool enable)
{
if( enable )
{ {
if (enable)
_filter |= value; _filter |= value;
}
else else
{
_filter &= ~value; _filter &= ~value;
} }
}
protected static void DrawColumn( OptionalBool b ) protected static void DrawColumn(OptionalBool b)
{ {
using var font = ImRaii.PushFont( UiBuilder.IconFont ); using var font = ImRaii.PushFont(UiBuilder.IconFont);
ImGui.TextUnformatted( b.Value switch ImGui.TextUnformatted(b.Value switch
{ {
null => string.Empty, null => string.Empty,
true => FontAwesomeIcon.Check.ToIconString(), true => FontAwesomeIcon.Check.ToIconString(),
false => FontAwesomeIcon.Times.ToIconString(), false => FontAwesomeIcon.Times.ToIconString(),
} ); });
} }
} }
@ -318,11 +307,11 @@ public partial class ResourceWatcher
public override float Width public override float Width
=> 60 * UiHelpers.Scale; => 60 * UiHelpers.Scale;
public override bool FilterFunc( Record item ) public override bool FilterFunc(Record item)
=> FilterFunc( item.CustomLoad ); => FilterFunc(item.CustomLoad);
public override void DrawColumn( Record item, int idx ) public override void DrawColumn(Record item, int idx)
=> DrawColumn( item.CustomLoad ); => DrawColumn(item.CustomLoad);
} }
private sealed class SynchronousLoadColumn : OptBoolColumn private sealed class SynchronousLoadColumn : OptBoolColumn
@ -330,23 +319,22 @@ public partial class ResourceWatcher
public override float Width public override float Width
=> 45 * UiHelpers.Scale; => 45 * UiHelpers.Scale;
public override bool FilterFunc( Record item ) public override bool FilterFunc(Record item)
=> FilterFunc( item.Synchronously ); => FilterFunc(item.Synchronously);
public override void DrawColumn( Record item, int idx ) public override void DrawColumn(Record item, int idx)
=> DrawColumn( item.Synchronously ); => DrawColumn(item.Synchronously);
} }
private sealed class RefCountColumn : Column< Record > private sealed class RefCountColumn : Column<Record>
{ {
public override float Width public override float Width
=> 30 * UiHelpers.Scale; => 30 * UiHelpers.Scale;
public override void DrawColumn( Record item, int _ ) public override void DrawColumn(Record item, int _)
=> ImGuiUtil.RightAlign( item.RefCount.ToString() ); => ImGuiUtil.RightAlign(item.RefCount.ToString());
public override int Compare( Record lhs, Record rhs ) public override int Compare(Record lhs, Record rhs)
=> lhs.RefCount.CompareTo( rhs.RefCount ); => lhs.RefCount.CompareTo(rhs.RefCount);
}
} }
} }

View file

@ -1,18 +1,19 @@
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.Linq;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Dalamud.Interface; using FFXIVClientStructs.FFXIV.Client.Game.Object;
using FFXIVClientStructs.FFXIV.Client.System.Resource; using FFXIVClientStructs.FFXIV.Client.System.Resource;
using ImGuiNET; using ImGuiNET;
using OtterGui.Raii; using OtterGui.Raii;
using OtterGui.Widgets; using OtterGui.Widgets;
using Penumbra.Collections; using Penumbra.Collections;
using Penumbra.GameData.Actors;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.Interop.ResourceLoading; using Penumbra.Interop.ResourceLoading;
using Penumbra.Interop.Structs; using Penumbra.Interop.Structs;
using Penumbra.Services;
using Penumbra.String; using Penumbra.String;
using Penumbra.String.Classes; using Penumbra.String.Classes;
using Penumbra.UI.Classes; using Penumbra.UI.Classes;
@ -22,23 +23,26 @@ namespace Penumbra.UI;
public partial class ResourceWatcher : IDisposable, ITab public partial class ResourceWatcher : IDisposable, ITab
{ {
public const int DefaultMaxEntries = 1024; public const int DefaultMaxEntries = 1024;
public const RecordType AllRecords = RecordType.Request | RecordType.ResourceLoad | RecordType.FileLoad | RecordType.Destruction;
private readonly Configuration _config; private readonly Configuration _config;
private readonly ResourceService _resources; private readonly ResourceService _resources;
private readonly ResourceLoader _loader; private readonly ResourceLoader _loader;
private readonly ActorService _actors;
private readonly List<Record> _records = new(); private readonly List<Record> _records = new();
private readonly ConcurrentQueue<Record> _newRecords = new(); private readonly ConcurrentQueue<Record> _newRecords = new();
private readonly Table _table; private readonly ResourceWatcherTable _table;
private string _logFilter = string.Empty; private string _logFilter = string.Empty;
private Regex? _logRegex; private Regex? _logRegex;
private int _newMaxEntries; private int _newMaxEntries;
public unsafe ResourceWatcher(Configuration config, ResourceService resources, ResourceLoader loader) public unsafe ResourceWatcher(ActorService actors, Configuration config, ResourceService resources, ResourceLoader loader)
{ {
_actors = actors;
_config = config; _config = config;
_resources = resources; _resources = resources;
_loader = loader; _loader = loader;
_table = new Table(_records); _table = new ResourceWatcherTable(config, _records);
_resources.ResourceRequested += OnResourceRequested; _resources.ResourceRequested += OnResourceRequested;
_resources.ResourceHandleDestructor += OnResourceDestroyed; _resources.ResourceHandleDestructor += OnResourceDestroyed;
_loader.ResourceLoaded += OnResourceLoaded; _loader.ResourceLoaded += OnResourceLoaded;
@ -75,8 +79,8 @@ public partial class ResourceWatcher : IDisposable, ITab
var isEnabled = _config.EnableResourceWatcher; var isEnabled = _config.EnableResourceWatcher;
if (ImGui.Checkbox("Enable", ref isEnabled)) if (ImGui.Checkbox("Enable", ref isEnabled))
{ {
Penumbra.Config.EnableResourceWatcher = isEnabled; _config.EnableResourceWatcher = isEnabled;
Penumbra.Config.Save(); _config.Save();
} }
ImGui.SameLine(); ImGui.SameLine();
@ -89,16 +93,16 @@ public partial class ResourceWatcher : IDisposable, ITab
var onlyMatching = _config.OnlyAddMatchingResources; var onlyMatching = _config.OnlyAddMatchingResources;
if (ImGui.Checkbox("Store Only Matching", ref onlyMatching)) if (ImGui.Checkbox("Store Only Matching", ref onlyMatching))
{ {
Penumbra.Config.OnlyAddMatchingResources = onlyMatching; _config.OnlyAddMatchingResources = onlyMatching;
Penumbra.Config.Save(); _config.Save();
} }
ImGui.SameLine(); ImGui.SameLine();
var writeToLog = _config.EnableResourceLogging; var writeToLog = _config.EnableResourceLogging;
if (ImGui.Checkbox("Write to Log", ref writeToLog)) if (ImGui.Checkbox("Write to Log", ref writeToLog))
{ {
Penumbra.Config.EnableResourceLogging = writeToLog; _config.EnableResourceLogging = writeToLog;
Penumbra.Config.Save(); _config.Save();
} }
ImGui.SameLine(); ImGui.SameLine();
@ -137,8 +141,8 @@ public partial class ResourceWatcher : IDisposable, ITab
if (config) if (config)
{ {
Penumbra.Config.ResourceLoggingFilter = newString; _config.ResourceLoggingFilter = newString;
Penumbra.Config.Save(); _config.Save();
} }
} }
@ -168,20 +172,21 @@ public partial class ResourceWatcher : IDisposable, ITab
return; return;
_newMaxEntries = Math.Max(16, _newMaxEntries); _newMaxEntries = Math.Max(16, _newMaxEntries);
if (_newMaxEntries != maxEntries) if (_newMaxEntries == maxEntries)
{ return;
_config.MaxResourceWatcherRecords = _newMaxEntries; _config.MaxResourceWatcherRecords = _newMaxEntries;
Penumbra.Config.Save(); _config.Save();
if (_newMaxEntries > _records.Count) if (_newMaxEntries > _records.Count)
_records.RemoveRange(0, _records.Count - _newMaxEntries); _records.RemoveRange(0, _records.Count - _newMaxEntries);
} }
}
private void UpdateRecords() private void UpdateRecords()
{ {
var count = _newRecords.Count; var count = _newRecords.Count;
if (count > 0) if (count <= 0)
{ return;
while (_newRecords.TryDequeue(out var rec) && count-- > 0) while (_newRecords.TryDequeue(out var rec) && count-- > 0)
_records.Add(rec); _records.Add(rec);
@ -190,22 +195,22 @@ public partial class ResourceWatcher : IDisposable, ITab
_table.Reset(); _table.Reset();
} }
}
private unsafe void OnResourceRequested(ref ResourceCategory category, ref ResourceType type, ref int hash, ref Utf8GamePath path, private unsafe void OnResourceRequested(ref ResourceCategory category, ref ResourceType type, ref int hash, ref Utf8GamePath path,
Utf8GamePath original,
GetResourceParameters* parameters, ref bool sync, ref ResourceHandle* returnValue) GetResourceParameters* parameters, ref bool sync, ref ResourceHandle* returnValue)
{ {
if (_config.EnableResourceLogging && FilterMatch(path.Path, out var match)) if (_config.EnableResourceLogging && FilterMatch(original.Path, out var match))
Penumbra.Log.Information($"[ResourceLoader] [REQ] {match} was requested {(sync ? "synchronously." : "asynchronously.")}"); Penumbra.Log.Information($"[ResourceLoader] [REQ] {match} was requested {(sync ? "synchronously." : "asynchronously.")}");
if (_config.EnableResourceWatcher) if (!_config.EnableResourceWatcher)
{ return;
var record = Record.CreateRequest(path.Path, sync);
var record = Record.CreateRequest(original.Path, sync);
if (!_config.OnlyAddMatchingResources || _table.WouldBeVisible(record)) if (!_config.OnlyAddMatchingResources || _table.WouldBeVisible(record))
_newRecords.Enqueue(record); _newRecords.Enqueue(record);
} }
}
private unsafe void OnResourceLoaded(ResourceHandle* handle, Utf8GamePath path, FullPath? manipulatedPath, ResolveData data) private unsafe void OnResourceLoaded(ResourceHandle* handle, Utf8GamePath path, FullPath? manipulatedPath, ResolveData data)
{ {
@ -220,20 +225,19 @@ public partial class ResourceWatcher : IDisposable, ITab
{ {
var pathString = manipulatedPath != null ? $"custom file {name2} instead of {name}" : name; var pathString = manipulatedPath != null ? $"custom file {name2} instead of {name}" : name;
Penumbra.Log.Information( 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}) "); $"[ResourceLoader] [LOAD] [{handle->FileType}] Loaded {pathString} to 0x{(ulong)handle:X} using collection {data.ModCollection.AnonymizedName} for {Name(data, "no associated object.")} (Refcount {handle->RefCount}) ");
} }
} }
if (_config.EnableResourceWatcher) if (!_config.EnableResourceWatcher)
{ return;
var record = manipulatedPath == null var record = manipulatedPath == null
? Record.CreateDefaultLoad(path.Path, handle, data.ModCollection) ? Record.CreateDefaultLoad(path.Path, handle, data.ModCollection, Name(data))
: Record.CreateLoad(path.Path, manipulatedPath.Value.InternalName, handle, : Record.CreateLoad(manipulatedPath.Value.InternalName, path.Path, handle, data.ModCollection, Name(data));
data.ModCollection);
if (!_config.OnlyAddMatchingResources || _table.WouldBeVisible(record)) if (!_config.OnlyAddMatchingResources || _table.WouldBeVisible(record))
_newRecords.Enqueue(record); _newRecords.Enqueue(record);
} }
}
private unsafe void OnFileLoaded(ResourceHandle* resource, ByteString path, bool success, bool custom, ByteString _) private unsafe void OnFileLoaded(ResourceHandle* resource, ByteString path, bool success, bool custom, ByteString _)
{ {
@ -241,13 +245,13 @@ public partial class ResourceWatcher : IDisposable, ITab
Penumbra.Log.Information( Penumbra.Log.Information(
$"[ResourceLoader] [FILE] [{resource->FileType}] Loading {match} from {(custom ? "local files" : "SqPack")} into 0x{(ulong)resource:X} returned {success}."); $"[ResourceLoader] [FILE] [{resource->FileType}] Loading {match} from {(custom ? "local files" : "SqPack")} into 0x{(ulong)resource:X} returned {success}.");
if (_config.EnableResourceWatcher) if (!_config.EnableResourceWatcher)
{ return;
var record = Record.CreateFileLoad(path, resource, success, custom); var record = Record.CreateFileLoad(path, resource, success, custom);
if (!_config.OnlyAddMatchingResources || _table.WouldBeVisible(record)) if (!_config.OnlyAddMatchingResources || _table.WouldBeVisible(record))
_newRecords.Enqueue(record); _newRecords.Enqueue(record);
} }
}
private unsafe void OnResourceDestroyed(ResourceHandle* resource) private unsafe void OnResourceDestroyed(ResourceHandle* resource)
{ {
@ -255,11 +259,37 @@ public partial class ResourceWatcher : IDisposable, ITab
Penumbra.Log.Information( Penumbra.Log.Information(
$"[ResourceLoader] [DEST] [{resource->FileType}] Destroyed {match} at 0x{(ulong)resource:X}."); $"[ResourceLoader] [DEST] [{resource->FileType}] Destroyed {match} at 0x{(ulong)resource:X}.");
if (_config.EnableResourceWatcher) if (!_config.EnableResourceWatcher)
{ return;
var record = Record.CreateDestruction(resource); var record = Record.CreateDestruction(resource);
if (!_config.OnlyAddMatchingResources || _table.WouldBeVisible(record)) if (!_config.OnlyAddMatchingResources || _table.WouldBeVisible(record))
_newRecords.Enqueue(record); _newRecords.Enqueue(record);
} }
public unsafe string Name(ResolveData resolve, string none = "")
{
if (resolve.AssociatedGameObject == IntPtr.Zero || !_actors.Valid)
return none;
try
{
var id = _actors.AwaitedService.FromObject((GameObject*)resolve.AssociatedGameObject, out _, false, true, true);
if (id.IsValid)
{
if (id.Type is not (IdentifierType.Player or IdentifierType.Owned))
return id.ToString();
var parts = id.ToString().Split(' ', 3);
return string.Join(" ",
parts.Length != 3 ? parts.Select(n => $"{n[0]}.") : parts[..2].Select(n => $"{n[0]}.").Append(parts[2]));
}
}
catch
{
// ignored
}
return $"0x{resolve.AssociatedGameObject:X}";
} }
} }