Limit console log lines held in memory (#1683)

* Add AG.Collections.RollingList

* Use RollingList for logs + Adaption changes

* Create Dalamud.Utility.ThrowHelper

* Create Dalamud.Utility.RollingList

* ConsoleWindow: Remove dependency

* Remove NuGet Dependency

* Add Log Lines Limit configuration

* Use Log Lines Limit configuration and handle changes

* Make log lines limit configurable
This commit is contained in:
AzureGem 2024-02-27 13:15:11 -05:00 committed by GitHub
parent e6c97f0f18
commit 0651c643b1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 375 additions and 15 deletions

View file

@ -6,6 +6,7 @@ using System.Numerics;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using Dalamud.Configuration.Internal;
using Dalamud.Game.Command;
@ -28,7 +29,11 @@ namespace Dalamud.Interface.Internal.Windows;
/// </summary>
internal class ConsoleWindow : Window, IDisposable
{
private readonly List<LogEntry> logText = new();
private const int LogLinesMinimum = 100;
private const int LogLinesMaximum = 1000000;
private readonly RollingList<LogEntry> logText;
private volatile int newRolledLines;
private readonly object renderLock = new();
private readonly List<string> history = new();
@ -42,12 +47,14 @@ internal class ConsoleWindow : Window, IDisposable
private string pluginFilter = string.Empty;
private bool filterShowUncaughtExceptions;
private bool settingsPopupWasOpen;
private bool showFilterToolbar;
private bool clearLog;
private bool copyLog;
private bool copyMode;
private bool killGameArmed;
private bool autoScroll;
private int logLinesLimit;
private bool autoOpen;
private bool regexError;
@ -74,9 +81,17 @@ internal class ConsoleWindow : Window, IDisposable
};
this.RespectCloseHotkey = false;
this.logLinesLimit = configuration.LogLinesLimit;
var limit = Math.Max(LogLinesMinimum, this.logLinesLimit);
this.logText = new(limit);
this.FilteredLogEntries = new(limit);
configuration.DalamudConfigurationSaved += this.OnDalamudConfigurationSaved;
}
private List<LogEntry> FilteredLogEntries { get; set; } = new();
private RollingList<LogEntry> FilteredLogEntries { get; set; }
/// <inheritdoc/>
public override void OnOpen()
@ -91,6 +106,7 @@ internal class ConsoleWindow : Window, IDisposable
public void Dispose()
{
SerilogEventSink.Instance.LogLine -= this.OnLogLine;
Service<DalamudConfiguration>.Get().DalamudConfigurationSaved -= this.OnDalamudConfigurationSaved;
}
/// <summary>
@ -180,6 +196,9 @@ internal class ConsoleWindow : Window, IDisposable
var dividerOffset = ImGui.CalcTextSize("00:00:00.000 | AAA ").X + (ImGui.CalcTextSize(" ").X / 2);
var cursorLogLine = ImGui.CalcTextSize("00:00:00.000 | AAA | ").X;
var lastLinePosY = 0.0f;
var logLineHeight = 0.0f;
lock (this.renderLock)
{
clipper.Begin(this.FilteredLogEntries.Count);
@ -187,7 +206,8 @@ internal class ConsoleWindow : Window, IDisposable
{
for (var i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
{
var line = this.FilteredLogEntries[i];
var index = Math.Max(i - this.newRolledLines, 0); // Prevents flicker effect. Also workaround to avoid negative indexes.
var line = this.FilteredLogEntries[index];
if (!line.IsMultiline && !this.copyLog)
ImGui.Separator();
@ -228,6 +248,10 @@ internal class ConsoleWindow : Window, IDisposable
ImGui.SetCursorPosX(cursorLogLine);
ImGui.TextUnformatted(line.Line);
var currentLinePosY = ImGui.GetCursorPosY();
logLineHeight = currentLinePosY - lastLinePosY;
lastLinePosY = currentLinePosY;
}
}
@ -239,6 +263,12 @@ internal class ConsoleWindow : Window, IDisposable
ImGui.PopStyleVar();
var newRolledLinesCount = Interlocked.Exchange(ref this.newRolledLines, 0);
if (!this.autoScroll || ImGui.GetScrollY() < ImGui.GetScrollMaxY())
{
ImGui.SetScrollY(ImGui.GetScrollY() - (logLineHeight * newRolledLinesCount));
}
if (this.autoScroll && ImGui.GetScrollY() >= ImGui.GetScrollMaxY())
{
ImGui.SetScrollHereY(1.0f);
@ -363,21 +393,21 @@ internal class ConsoleWindow : Window, IDisposable
ImGui.SameLine();
this.autoScroll = configuration.LogAutoScroll;
if (this.DrawToggleButtonWithTooltip("auto_scroll", "Auto-scroll", FontAwesomeIcon.Sync, ref this.autoScroll))
var settingsPopup = ImGui.BeginPopup("##console_settings");
if (settingsPopup)
{
configuration.LogAutoScroll = !configuration.LogAutoScroll;
configuration.QueueSave();
this.DrawSettingsPopup(configuration);
ImGui.EndPopup();
}
else if (this.settingsPopupWasOpen)
{
// Prevent side effects in case Apply wasn't clicked
this.logLinesLimit = configuration.LogLinesLimit;
}
ImGui.SameLine();
this.settingsPopupWasOpen = settingsPopup;
this.autoOpen = configuration.LogOpenAtStartup;
if (this.DrawToggleButtonWithTooltip("auto_open", "Open at startup", FontAwesomeIcon.WindowRestore, ref this.autoOpen))
{
configuration.LogOpenAtStartup = !configuration.LogOpenAtStartup;
configuration.QueueSave();
}
if (this.DrawToggleButtonWithTooltip("show_settings", "Show settings", FontAwesomeIcon.List, ref settingsPopup)) ImGui.OpenPopup("##console_settings");
ImGui.SameLine();
@ -447,6 +477,33 @@ internal class ConsoleWindow : Window, IDisposable
}
}
private void DrawSettingsPopup(DalamudConfiguration configuration)
{
if (ImGui.Checkbox("Open at startup", ref this.autoOpen))
{
configuration.LogOpenAtStartup = this.autoOpen;
configuration.QueueSave();
}
if (ImGui.Checkbox("Auto-scroll", ref this.autoScroll))
{
configuration.LogAutoScroll = this.autoScroll;
configuration.QueueSave();
}
ImGui.TextUnformatted("Logs buffer");
ImGui.SliderInt("lines", ref this.logLinesLimit, LogLinesMinimum, LogLinesMaximum);
if (ImGui.Button("Apply"))
{
this.logLinesLimit = Math.Max(LogLinesMinimum, this.logLinesLimit);
configuration.LogLinesLimit = this.logLinesLimit;
configuration.QueueSave();
ImGui.CloseCurrentPopup();
}
}
private void DrawFilterToolbar()
{
if (!this.showFilterToolbar) return;
@ -686,8 +743,12 @@ internal class ConsoleWindow : Window, IDisposable
this.logText.Add(entry);
var avoidScroll = this.FilteredLogEntries.Count == this.FilteredLogEntries.Size;
if (this.IsFilterApplicable(entry))
{
this.FilteredLogEntries.Add(entry);
if (avoidScroll) Interlocked.Increment(ref this.newRolledLines);
}
}
private bool IsFilterApplicable(LogEntry entry)
@ -740,7 +801,7 @@ internal class ConsoleWindow : Window, IDisposable
lock (this.renderLock)
{
this.regexError = false;
this.FilteredLogEntries = this.logText.Where(this.IsFilterApplicable).ToList();
this.FilteredLogEntries = new RollingList<LogEntry>(this.logText.Where(this.IsFilterApplicable), Math.Max(LogLinesMinimum, this.logLinesLimit));
}
}
@ -789,6 +850,14 @@ internal class ConsoleWindow : Window, IDisposable
return result;
}
private void OnDalamudConfigurationSaved(DalamudConfiguration dalamudConfiguration)
{
this.logLinesLimit = dalamudConfiguration.LogLinesLimit;
var limit = Math.Max(LogLinesMinimum, this.logLinesLimit);
this.logText.Size = limit;
this.FilteredLogEntries.Size = limit;
}
private class LogEntry
{
public string Line { get; init; } = string.Empty;