Penumbra/Penumbra/UI/ResourceWatcher/ResourceWatcherTable.cs
Ottermandias 924c9b9f7e
Some checks failed
.NET Build / build (push) Has been cancelled
Update to Luna state.
2025-09-17 18:05:07 +02:00

472 lines
15 KiB
C#

using Dalamud.Bindings.ImGui;
using Dalamud.Interface;
using Luna;
using OtterGui;
using OtterGui.Raii;
using OtterGui.Table;
using OtterGui.Text;
using Penumbra.Enums;
using Penumbra.Interop.Structs;
using Penumbra.String;
using Penumbra.UI.Classes;
namespace Penumbra.UI.ResourceWatcher;
internal sealed class ResourceWatcherTable : Table<Record>
{
public ResourceWatcherTable(EphemeralConfig config, IReadOnlyCollection<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(config) { Label = "Category" },
new ResourceTypeColumn(config) { Label = "Type" },
new HandleColumn { Label = "Resource" },
new LoadStateColumn { Label = "State" },
new RefCountColumn { Label = "#Ref" },
new DateColumn { Label = "Time" },
new Crc64Column { Label = "Crc64" },
new OsThreadColumn { Label = "TID" }
)
{ }
public void Reset()
=> FilterDirty = true;
private sealed class PathColumn : ColumnString<Record>
{
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, 280 * UiHelpers.Scale);
}
private static unsafe void DrawByteString(CiByteString path, float length)
{
if (path.IsEmpty)
return;
var size = ImUtf8.CalcTextSize(path.Span);
var clicked = false;
if (size.X <= length)
{
clicked = ImUtf8.Selectable(path.Span);
}
else
{
var fileName = path.LastIndexOf((byte)'/');
using (ImRaii.Group())
{
CiByteString shortPath;
if (fileName != -1)
{
using var font = ImRaii.PushFont(UiBuilder.IconFont);
clicked = ImUtf8.Selectable(FontAwesomeIcon.EllipsisH.ToIconString());
ImUtf8.SameLineInner();
shortPath = path.Substring(fileName, path.Length - fileName);
}
else
{
shortPath = path;
}
clicked |= ImUtf8.Selectable(shortPath.Span, false, ImGuiSelectableFlags.AllowItemOverlap);
}
ImUtf8.HoverTooltip(path.Span);
}
if (clicked)
ImUtf8.SetClipboardText(path.Span);
}
private sealed class RecordTypeColumn : ColumnFlags<RecordType, Record>
{
private readonly EphemeralConfig _config;
public RecordTypeColumn(EphemeralConfig 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;
_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",
RecordType.ResourceComplete => "DONE",
_ => 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 Crc64Column : ColumnString<Record>
{
public override float Width
=> UiBuilder.MonoFont.GetCharAdvance('0') * 17;
public override unsafe string ToName(Record item)
=> item.Crc64 != 0 ? $"{item.Crc64:X16}" : string.Empty;
public override unsafe void DrawColumn(Record item, int _)
{
using var font = ImRaii.PushFont(UiBuilder.MonoFont, item.Handle != null);
ImUtf8.Text(ToName(item));
}
}
private sealed class CollectionColumn : ColumnString<Record>
{
public override float Width
=> 80 * UiHelpers.Scale;
public override string ToName(Record item)
=> (item.Collection != null ? item.Collection.Identity.Name : null) ?? 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>
{
private readonly EphemeralConfig _config;
public ResourceCategoryColumn(EphemeralConfig config)
{
_config = config;
AllFlags = ResourceExtensions.AllResourceCategories;
}
public override float Width
=> 80 * UiHelpers.Scale;
public override bool FilterFunc(Record item)
=> FilterValue.HasFlag(item.Category);
public override ResourceCategoryFlag FilterValue
=> _config.ResourceWatcherResourceCategories;
protected override void SetValue(ResourceCategoryFlag value, bool enable)
{
if (enable)
_config.ResourceWatcherResourceCategories |= value;
else
_config.ResourceWatcherResourceCategories &= ~value;
_config.Save();
}
public override void DrawColumn(Record item, int idx)
{
ImGui.TextUnformatted(item.Category.ToString());
}
}
private sealed class ResourceTypeColumn : ColumnFlags<ResourceTypeFlag, Record>
{
private readonly EphemeralConfig _config;
public ResourceTypeColumn(EphemeralConfig config)
{
_config = config;
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
=> _config.ResourceWatcherResourceTypes;
protected override void SetValue(ResourceTypeFlag value, bool enable)
{
if (enable)
_config.ResourceWatcherResourceTypes |= value;
else
_config.ResourceWatcherResourceTypes &= ~value;
_config.Save();
}
public override void DrawColumn(Record item, int idx)
{
ImGui.TextUnformatted(item.ResourceType.ToString().ToLowerInvariant());
}
}
private sealed class LoadStateColumn : ColumnFlags<LoadStateColumn.LoadStateFlag, Record>
{
public override float Width
=> 50 * UiHelpers.Scale;
[Flags]
public enum LoadStateFlag : byte
{
Success = 0x01,
Async = 0x02,
Failed = 0x04,
FailedSub = 0x08,
Unknown = 0x10,
None = 0xFF,
}
protected override string[] Names
=> new[]
{
"Loaded",
"Loading",
"Failed",
"Dependency Failed",
"Unknown",
"None",
};
public LoadStateColumn()
{
AllFlags = Enum.GetValues<LoadStateFlag>().Aggregate((v, f) => v | f);
_filterValue = AllFlags;
}
private LoadStateFlag _filterValue;
public override LoadStateFlag FilterValue
=> _filterValue;
protected override void SetValue(LoadStateFlag value, bool enable)
{
if (enable)
_filterValue |= value;
else
_filterValue &= ~value;
}
public override bool FilterFunc(Record item)
=> item.LoadState switch
{
LoadState.None => FilterValue.HasFlag(LoadStateFlag.None),
LoadState.Success => FilterValue.HasFlag(LoadStateFlag.Success),
LoadState.FailedSubResource => FilterValue.HasFlag(LoadStateFlag.FailedSub),
<= LoadState.Constructed => FilterValue.HasFlag(LoadStateFlag.Unknown),
< LoadState.Success => FilterValue.HasFlag(LoadStateFlag.Async),
> LoadState.Success => FilterValue.HasFlag(LoadStateFlag.Failed),
};
public override void DrawColumn(Record item, int _)
{
if (item.LoadState == LoadState.None)
return;
var (icon, color, tt) = item.LoadState switch
{
LoadState.Success => (FontAwesomeIcon.CheckCircle, ColorId.IncreasedMetaValue.Value(),
$"Successfully loaded ({(byte)item.LoadState})."),
LoadState.FailedSubResource => (FontAwesomeIcon.ExclamationCircle, ColorId.DecreasedMetaValue.Value(),
$"Dependencies failed to load ({(byte)item.LoadState})."),
<= LoadState.Constructed => (FontAwesomeIcon.QuestionCircle, ColorId.UndefinedMod.Value(), $"Not yet loaded ({(byte)item.LoadState})."),
< LoadState.Success => (FontAwesomeIcon.Clock, ColorId.FolderLine.Value(), $"Loading asynchronously ({(byte)item.LoadState})."),
> LoadState.Success => (FontAwesomeIcon.Times, ColorId.DecreasedMetaValue.Value(),
$"Failed to load ({(byte)item.LoadState})."),
};
using (var font = ImRaii.PushFont(UiBuilder.IconFont))
{
using var c = ImRaii.PushColor(ImGuiCol.Text, color);
ImGui.TextUnformatted(icon.ToIconString());
}
ImGuiUtil.HoverTooltip(tt);
}
public override int Compare(Record lhs, Record rhs)
=> lhs.LoadState.CompareTo(rhs.LoadState);
}
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);
}
private sealed class OsThreadColumn : ColumnString<Record>
{
public override float Width
=> 60 * UiHelpers.Scale;
public override string ToName(Record item)
=> item.OsThreadId.ToString();
public override void DrawColumn(Record item, int _)
=> ImGuiUtil.RightAlign(ToName(item));
public override int Compare(Record lhs, Record rhs)
=> lhs.OsThreadId.CompareTo(rhs.OsThreadId);
}
}