diff --git a/Dalamud/Interface/Internal/Windows/Data/DataWindow.cs b/Dalamud/Interface/Internal/Windows/Data/DataWindow.cs index eb0589d59..154fc8c02 100644 --- a/Dalamud/Interface/Internal/Windows/Data/DataWindow.cs +++ b/Dalamud/Interface/Internal/Windows/Data/DataWindow.cs @@ -44,6 +44,7 @@ internal class DataWindow : Window, IDisposable new ImGuiWidget(), new InventoryWidget(), new KeyStateWidget(), + new LogMessageMonitorWidget(), new MarketBoardWidget(), new NetworkMonitorWidget(), new NounProcessorWidget(), diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/LogMessageMonitorWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/LogMessageMonitorWidget.cs new file mode 100644 index 000000000..fde46f0c7 --- /dev/null +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/LogMessageMonitorWidget.cs @@ -0,0 +1,156 @@ +using System.Buffers; +using System.Collections.Concurrent; +using System.Linq; +using System.Text.Json; +using System.Text.RegularExpressions; + +using Dalamud.Bindings.ImGui; +using Dalamud.Game.Chat; +using Dalamud.Game.Gui; +using Dalamud.Interface.Utility; +using Dalamud.Interface.Utility.Raii; + +using Lumina.Text.ReadOnly; + +using ImGuiTable = Dalamud.Interface.Utility.ImGuiTable; + +namespace Dalamud.Interface.Internal.Windows.Data.Widgets; + +/// +/// Widget to display the LogMessages. +/// +internal class LogMessageMonitorWidget : IDataWindowWidget +{ + private readonly ConcurrentQueue messages = new(); + + private bool trackMessages; + private int trackedMessages; + private Regex? filterRegex; + private string filterString = string.Empty; + + /// + public string[]? CommandShortcuts { get; init; } = ["logmessage"]; + + /// + public string DisplayName { get; init; } = "LogMessage Monitor"; + + /// + public bool Ready { get; set; } + + /// + public void Load() + { + this.trackMessages = false; + this.trackedMessages = 20; + this.filterRegex = null; + this.filterString = string.Empty; + this.messages.Clear(); + this.Ready = true; + } + + /// + public void Draw() + { + var network = Service.Get(); + if (ImGui.Checkbox("Track LogMessages"u8, ref this.trackMessages)) + { + if (this.trackMessages) + { + network.LogMessage += this.OnLogMessage; + } + else + { + network.LogMessage -= this.OnLogMessage; + } + } + + ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X / 2); + if (ImGui.DragInt("Stored Number of Messages"u8, ref this.trackedMessages, 0.1f, 1, 512)) + { + this.trackedMessages = Math.Clamp(this.trackedMessages, 1, 512); + } + + if (ImGui.Button("Clear Stored Messages"u8)) + { + this.messages.Clear(); + } + + this.DrawFilterInput(); + + ImGuiTable.DrawTable(string.Empty, this.messages.Where(m => this.filterRegex == null || this.filterRegex.IsMatch(m.Formatted.ExtractText())), this.DrawNetworkPacket, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingStretchProp, "LogMessageId", "Source", "Target", "Parameters", "Formatted"); + } + + private void DrawNetworkPacket(LogMessageData data) + { + ImGui.TableNextColumn(); + ImGui.Text(data.LogMessageId.ToString()); + + ImGui.TableNextColumn(); + ImGuiHelpers.SeStringWrapped(data.Source); + + ImGui.TableNextColumn(); + ImGuiHelpers.SeStringWrapped(data.Target); + + ImGui.TableNextColumn(); + ImGui.Text(data.Parameters); + + ImGui.TableNextColumn(); + ImGuiHelpers.SeStringWrapped(data.Formatted); + } + + private void DrawFilterInput() + { + var invalidRegEx = this.filterString.Length > 0 && this.filterRegex == null; + using var style = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, 2 * ImGuiHelpers.GlobalScale, invalidRegEx); + using var color = ImRaii.PushColor(ImGuiCol.Border, 0xFF0000FF, invalidRegEx); + ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X); + if (!ImGui.InputTextWithHint("##Filter"u8, "Regex Filter..."u8, ref this.filterString, 1024)) + { + return; + } + + if (this.filterString.Length == 0) + { + this.filterRegex = null; + } + else + { + try + { + this.filterRegex = new Regex(this.filterString, RegexOptions.Compiled | RegexOptions.ExplicitCapture); + } + catch + { + this.filterRegex = null; + } + } + } + + private void OnLogMessage(ILogMessage message) + { + var buffer = new ArrayBufferWriter(); + var writer = new Utf8JsonWriter(buffer); + + writer.WriteStartArray(); + for (var i = 0; i < message.ParameterCount; i++) + { + if (message.TryGetStringParameter(i, out var str)) + writer.WriteStringValue(str.ExtractText()); + else if (message.TryGetIntParameter(i, out var num)) + writer.WriteNumberValue(num); + else + writer.WriteNullValue(); + } + + writer.WriteEndArray(); + writer.Flush(); + + this.messages.Enqueue(new LogMessageData(message.LogMessageId, message.SourceEntity?.Name ?? default, message.TargetEntity?.Name ?? default, buffer.WrittenMemory, message.FormatLogMessageForDebugging())); + while (this.messages.Count > this.trackedMessages) + { + this.messages.TryDequeue(out _); + } + } + + private readonly record struct LogMessageData(uint LogMessageId, ReadOnlySeString Source, ReadOnlySeString Target, ReadOnlyMemory Parameters, ReadOnlySeString Formatted); +}