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

@ -7,112 +7,123 @@ using Penumbra.String;
namespace Penumbra.UI;
public partial class ResourceWatcher
[Flags]
public enum RecordType : byte
{
private unsafe struct Record
Request = 0x01,
ResourceLoad = 0x02,
FileLoad = 0x04,
Destruction = 0x08,
}
internal unsafe struct Record
{
public DateTime Time;
public ByteString Path;
public ByteString OriginalPath;
public string AssociatedGameObject;
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,
AssociatedGameObject = string.Empty,
};
public static Record CreateDefaultLoad(ByteString path, ResourceHandle* handle, ModCollection collection, string associatedGameObject)
{
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
{
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,
};
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,
AssociatedGameObject = associatedGameObject,
};
}
}
public static Record CreateLoad(ByteString path, ByteString originalPath, ResourceHandle* handle, ModCollection collection,
string associatedGameObject)
=> 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,
AssociatedGameObject = associatedGameObject,
};
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,
};
}

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,340 +13,328 @@ using Penumbra.String;
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",
records,
new PathColumn { Label = "Path" },
new RecordTypeColumn(config) { Label = "Record" },
new CollectionColumn { Label = "Collection" },
new ObjectColumn { Label = "Game Object" },
new CustomLoadColumn { Label = "Custom" },
new SynchronousLoadColumn { Label = "Sync" },
new OriginalPathColumn { Label = "Original Path" },
new ResourceCategoryColumn { Label = "Category" },
new ResourceTypeColumn { Label = "Type" },
new HandleColumn { Label = "Resource" },
new RefCountColumn { Label = "#Ref" },
new DateColumn { Label = "Time" }
)
{ }
public void Reset()
=> FilterDirty = true;
private sealed class PathColumn : ColumnString<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" };
public override float Width
=> 300 * UiHelpers.Scale;
private static readonly CollectionColumn Coll = new() { Label = "Collection" };
private static readonly CustomLoadColumn Custom = new() { Label = "Custom" };
private static readonly SynchronousLoadColumn Sync = new() { Label = "Sync" };
public override string ToName(Record item)
=> item.Path.ToString();
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" };
public override int Compare(Record lhs, Record rhs)
=> lhs.Path.CompareTo(rhs.Path);
private static readonly HandleColumn Handle = new() { Label = "Resource" };
private static readonly RefCountColumn Ref = new() { Label = "#Ref" };
public override void DrawColumn(Record item, int _)
=> DrawByteString(item.Path, 280 * UiHelpers.Scale);
}
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 >
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)
{
public override float Width
=> 300 * UiHelpers.Scale;
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 * UiHelpers.Scale );
ImGuiNative.igTextUnformatted(path.Path, path.Path + path.Length);
}
private static unsafe void DrawByteString( ByteString path, float length )
else
{
Vector2 vec;
ImGuiNative.igCalcTextSize( &vec, path.Path, path.Path + path.Length, 0, 0 );
if( vec.X <= length )
var fileName = path.LastIndexOf((byte)'/');
ByteString shortPath;
if (fileName != -1)
{
ImGuiNative.igTextUnformatted( path.Path, path.Path + path.Length );
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, new Vector2(2 * UiHelpers.Scale));
using var font = ImRaii.PushFont(UiBuilder.IconFont);
ImGui.TextUnformatted(FontAwesomeIcon.EllipsisH.ToIconString());
ImGui.SameLine();
shortPath = path.Substring(fileName, path.Length - fileName);
}
else
{
var fileName = path.LastIndexOf( ( byte )'/' );
ByteString shortPath;
if( fileName != -1 )
{
using var style = ImRaii.PushStyle( ImGuiStyleVar.ItemSpacing, new Vector2( 2 * UiHelpers.Scale ) );
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 * UiHelpers.Scale;
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();
shortPath = path;
}
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,
} );
}
}
ImGuiNative.igTextUnformatted(shortPath.Path, shortPath.Path + shortPath.Length);
if (ImGui.IsItemClicked())
ImGuiNative.igSetClipboardText(path.Path);
private sealed class DateColumn : Column< Record >
{
public override float Width
=> 80 * UiHelpers.Scale;
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 * UiHelpers.Scale;
public override string ToName( Record item )
=> item.Collection?.Name ?? string.Empty;
}
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()
=> AllFlags = ResourceExtensions.AllResourceCategories;
public override float Width
=> 80 * UiHelpers.Scale;
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 * UiHelpers.Scale;
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 * UiHelpers.Scale;
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 * UiHelpers.Scale;
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 * UiHelpers.Scale;
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 * UiHelpers.Scale;
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 );
if (ImGui.IsItemHovered())
ImGuiNative.igSetTooltip(path.Path);
}
}
}
private sealed class RecordTypeColumn : ColumnFlags<RecordType, Record>
{
private readonly Configuration _config;
public RecordTypeColumn(Configuration config)
{
AllFlags = ResourceWatcher.AllRecords;
_config = config;
}
public override float Width
=> 80 * UiHelpers.Scale;
public override bool FilterFunc(Record item)
=> FilterValue.HasFlag(item.RecordType);
public override RecordType FilterValue
=> _config.ResourceWatcherRecordTypes;
protected override void SetValue(RecordType value, bool enable)
{
if (enable)
_config.ResourceWatcherRecordTypes |= value;
else
_config.ResourceWatcherRecordTypes &= ~value;
Penumbra.Config.Save();
}
public override void DrawColumn(Record item, int idx)
{
ImGui.TextUnformatted(item.RecordType switch
{
RecordType.Request => "REQ",
RecordType.ResourceLoad => "LOAD",
RecordType.FileLoad => "FILE",
RecordType.Destruction => "DEST",
_ => string.Empty,
});
}
}
private sealed class DateColumn : Column<Record>
{
public override float Width
=> 80 * UiHelpers.Scale;
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 * UiHelpers.Scale;
public override string ToName(Record item)
=> item.Collection?.Name ?? string.Empty;
}
private sealed class ObjectColumn : ColumnString<Record>
{
public override float Width
=> 200 * UiHelpers.Scale;
public override string ToName(Record item)
=> item.AssociatedGameObject;
}
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()
=> AllFlags = ResourceExtensions.AllResourceCategories;
public override float Width
=> 80 * UiHelpers.Scale;
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 * UiHelpers.Scale;
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 * UiHelpers.Scale;
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 * UiHelpers.Scale;
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 * UiHelpers.Scale;
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 * UiHelpers.Scale;
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);
}
}

View file

@ -1,18 +1,19 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using System.Linq;
using System.Text.RegularExpressions;
using Dalamud.Interface;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
using FFXIVClientStructs.FFXIV.Client.System.Resource;
using ImGuiNET;
using OtterGui.Raii;
using OtterGui.Widgets;
using Penumbra.Collections;
using Penumbra.GameData.Actors;
using Penumbra.GameData.Enums;
using Penumbra.Interop.ResourceLoading;
using Penumbra.Interop.Structs;
using Penumbra.Services;
using Penumbra.String;
using Penumbra.String.Classes;
using Penumbra.UI.Classes;
@ -21,24 +22,27 @@ namespace Penumbra.UI;
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 ResourceService _resources;
private readonly ResourceLoader _loader;
private readonly ActorService _actors;
private readonly List<Record> _records = new();
private readonly ConcurrentQueue<Record> _newRecords = new();
private readonly Table _table;
private readonly ResourceWatcherTable _table;
private string _logFilter = string.Empty;
private Regex? _logRegex;
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;
_resources = resources;
_loader = loader;
_table = new Table(_records);
_table = new ResourceWatcherTable(config, _records);
_resources.ResourceRequested += OnResourceRequested;
_resources.ResourceHandleDestructor += OnResourceDestroyed;
_loader.ResourceLoaded += OnResourceLoaded;
@ -75,8 +79,8 @@ public partial class ResourceWatcher : IDisposable, ITab
var isEnabled = _config.EnableResourceWatcher;
if (ImGui.Checkbox("Enable", ref isEnabled))
{
Penumbra.Config.EnableResourceWatcher = isEnabled;
Penumbra.Config.Save();
_config.EnableResourceWatcher = isEnabled;
_config.Save();
}
ImGui.SameLine();
@ -89,16 +93,16 @@ public partial class ResourceWatcher : IDisposable, ITab
var onlyMatching = _config.OnlyAddMatchingResources;
if (ImGui.Checkbox("Store Only Matching", ref onlyMatching))
{
Penumbra.Config.OnlyAddMatchingResources = onlyMatching;
Penumbra.Config.Save();
_config.OnlyAddMatchingResources = onlyMatching;
_config.Save();
}
ImGui.SameLine();
var writeToLog = _config.EnableResourceLogging;
if (ImGui.Checkbox("Write to Log", ref writeToLog))
{
Penumbra.Config.EnableResourceLogging = writeToLog;
Penumbra.Config.Save();
_config.EnableResourceLogging = writeToLog;
_config.Save();
}
ImGui.SameLine();
@ -137,8 +141,8 @@ public partial class ResourceWatcher : IDisposable, ITab
if (config)
{
Penumbra.Config.ResourceLoggingFilter = newString;
Penumbra.Config.Save();
_config.ResourceLoggingFilter = newString;
_config.Save();
}
}
@ -168,43 +172,44 @@ public partial class ResourceWatcher : IDisposable, ITab
return;
_newMaxEntries = Math.Max(16, _newMaxEntries);
if (_newMaxEntries != maxEntries)
{
_config.MaxResourceWatcherRecords = _newMaxEntries;
Penumbra.Config.Save();
if (_newMaxEntries > _records.Count)
_records.RemoveRange(0, _records.Count - _newMaxEntries);
}
if (_newMaxEntries == maxEntries)
return;
_config.MaxResourceWatcherRecords = _newMaxEntries;
_config.Save();
if (_newMaxEntries > _records.Count)
_records.RemoveRange(0, _records.Count - _newMaxEntries);
}
private void UpdateRecords()
{
var count = _newRecords.Count;
if (count > 0)
{
while (_newRecords.TryDequeue(out var rec) && count-- > 0)
_records.Add(rec);
if (count <= 0)
return;
if (_records.Count > _config.MaxResourceWatcherRecords)
_records.RemoveRange(0, _records.Count - _config.MaxResourceWatcherRecords);
while (_newRecords.TryDequeue(out var rec) && count-- > 0)
_records.Add(rec);
_table.Reset();
}
if (_records.Count > _config.MaxResourceWatcherRecords)
_records.RemoveRange(0, _records.Count - _config.MaxResourceWatcherRecords);
_table.Reset();
}
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)
{
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.")}");
if (_config.EnableResourceWatcher)
{
var record = Record.CreateRequest(path.Path, sync);
if (!_config.OnlyAddMatchingResources || _table.WouldBeVisible(record))
_newRecords.Enqueue(record);
}
if (!_config.EnableResourceWatcher)
return;
var record = Record.CreateRequest(original.Path, sync);
if (!_config.OnlyAddMatchingResources || _table.WouldBeVisible(record))
_newRecords.Enqueue(record);
}
private unsafe void OnResourceLoaded(ResourceHandle* handle, Utf8GamePath path, FullPath? manipulatedPath, ResolveData data)
@ -220,19 +225,18 @@ public partial class ResourceWatcher : IDisposable, ITab
{
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}) ");
$"[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)
{
var record = manipulatedPath == null
? Record.CreateDefaultLoad(path.Path, handle, data.ModCollection)
: Record.CreateLoad(path.Path, manipulatedPath.Value.InternalName, handle,
data.ModCollection);
if (!_config.OnlyAddMatchingResources || _table.WouldBeVisible(record))
_newRecords.Enqueue(record);
}
if (!_config.EnableResourceWatcher)
return;
var record = manipulatedPath == null
? Record.CreateDefaultLoad(path.Path, handle, data.ModCollection, Name(data))
: Record.CreateLoad(manipulatedPath.Value.InternalName, path.Path, handle, data.ModCollection, Name(data));
if (!_config.OnlyAddMatchingResources || _table.WouldBeVisible(record))
_newRecords.Enqueue(record);
}
private unsafe void OnFileLoaded(ResourceHandle* resource, ByteString path, bool success, bool custom, ByteString _)
@ -241,12 +245,12 @@ public partial class ResourceWatcher : IDisposable, ITab
Penumbra.Log.Information(
$"[ResourceLoader] [FILE] [{resource->FileType}] Loading {match} from {(custom ? "local files" : "SqPack")} into 0x{(ulong)resource:X} returned {success}.");
if (_config.EnableResourceWatcher)
{
var record = Record.CreateFileLoad(path, resource, success, custom);
if (!_config.OnlyAddMatchingResources || _table.WouldBeVisible(record))
_newRecords.Enqueue(record);
}
if (!_config.EnableResourceWatcher)
return;
var record = Record.CreateFileLoad(path, resource, success, custom);
if (!_config.OnlyAddMatchingResources || _table.WouldBeVisible(record))
_newRecords.Enqueue(record);
}
private unsafe void OnResourceDestroyed(ResourceHandle* resource)
@ -255,11 +259,37 @@ public partial class ResourceWatcher : IDisposable, ITab
Penumbra.Log.Information(
$"[ResourceLoader] [DEST] [{resource->FileType}] Destroyed {match} at 0x{(ulong)resource:X}.");
if (_config.EnableResourceWatcher)
if (!_config.EnableResourceWatcher)
return;
var record = Record.CreateDestruction(resource);
if (!_config.OnlyAddMatchingResources || _table.WouldBeVisible(record))
_newRecords.Enqueue(record);
}
public unsafe string Name(ResolveData resolve, string none = "")
{
if (resolve.AssociatedGameObject == IntPtr.Zero || !_actors.Valid)
return none;
try
{
var record = Record.CreateDestruction(resource);
if (!_config.OnlyAddMatchingResources || _table.WouldBeVisible(record))
_newRecords.Enqueue(record);
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}";
}
}