mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 18:27:24 +01:00
Fix some issues with ResourceWatcher.
This commit is contained in:
parent
a8000fbf14
commit
185be81e73
8 changed files with 519 additions and 529 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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))]
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,21 @@ using Penumbra.String;
|
||||||
|
|
||||||
namespace Penumbra.UI;
|
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 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;
|
||||||
|
|
@ -39,9 +47,10 @@ public partial class ResourceWatcher
|
||||||
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,6 +87,7 @@ 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)
|
||||||
|
|
@ -115,4 +127,3 @@ public partial class ResourceWatcher
|
||||||
CustomLoad = custom,
|
CustomLoad = custom,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
@ -13,27 +13,24 @@ 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()
|
||||||
|
|
@ -51,7 +48,7 @@ public partial class ResourceWatcher
|
||||||
=> 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)
|
||||||
|
|
@ -81,21 +78,22 @@ public partial class ResourceWatcher
|
||||||
|
|
||||||
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;
|
||||||
|
|
@ -104,18 +102,14 @@ public partial class ResourceWatcher
|
||||||
=> 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();
|
||||||
}
|
}
|
||||||
|
|
@ -124,10 +118,10 @@ public partial class ResourceWatcher
|
||||||
{
|
{
|
||||||
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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -155,6 +149,15 @@ public partial class ResourceWatcher
|
||||||
=> item.Collection?.Name ?? string.Empty;
|
=> 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>
|
private sealed class OriginalPathColumn : ColumnString<Record>
|
||||||
{
|
{
|
||||||
public override float Width
|
public override float Width
|
||||||
|
|
@ -187,13 +190,9 @@ public partial class ResourceWatcher
|
||||||
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();
|
||||||
}
|
}
|
||||||
|
|
@ -210,10 +209,8 @@ public partial class ResourceWatcher
|
||||||
{
|
{
|
||||||
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;
|
||||||
|
|
@ -227,13 +224,9 @@ public partial class ResourceWatcher
|
||||||
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();
|
||||||
}
|
}
|
||||||
|
|
@ -292,14 +285,10 @@ public partial class ResourceWatcher
|
||||||
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)
|
||||||
{
|
{
|
||||||
|
|
@ -349,4 +338,3 @@ public partial class ResourceWatcher
|
||||||
=> lhs.RefCount.CompareTo(rhs.RefCount);
|
=> lhs.RefCount.CompareTo(rhs.RefCount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
@ -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}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue