Show logs on loading dialog

This commit is contained in:
Soreepeong 2024-07-21 19:09:05 +09:00
parent 9db4e2f3a1
commit 877906ae15
4 changed files with 81 additions and 28 deletions

View file

@ -7,6 +7,7 @@ using System.Threading.Tasks;
using Dalamud.Common;
using Dalamud.Configuration.Internal;
using Dalamud.Interface.Internal.Windows;
using Dalamud.Logging.Internal;
using Dalamud.Logging.Retention;
using Dalamud.Plugin.Internal;
@ -232,6 +233,10 @@ public sealed class EntryPoint
private static void SerilogOnLogLine(object? sender, (string Line, LogEvent LogEvent) ev)
{
if (!LoadingDialog.IsGloballyHidden)
LoadingDialog.NewLogEntries.Enqueue(ev);
ConsoleWindow.NewLogEntries.Enqueue(ev);
if (ev.LogEvent.Exception == null)
return;

View file

@ -18,7 +18,6 @@ using Dalamud.Interface.ImGuiNotification.Internal;
using Dalamud.Interface.Utility;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Interface.Windowing;
using Dalamud.Logging.Internal;
using Dalamud.Plugin.Internal;
using Dalamud.Plugin.Services;
using Dalamud.Utility;
@ -39,9 +38,6 @@ internal class ConsoleWindow : Window, IDisposable
private const int LogLinesMaximum = 1000000;
private const int HistorySize = 50;
// Only this field may be touched from any thread.
private readonly ConcurrentQueue<(string Line, LogEvent LogEvent)> newLogEntries;
// Fields below should be touched only from the main thread.
private readonly RollingList<LogEntry> logText;
private readonly RollingList<LogEntry> filteredLogEntries;
@ -94,7 +90,6 @@ internal class ConsoleWindow : Window, IDisposable
this.autoScroll = configuration.LogAutoScroll;
this.autoOpen = configuration.LogOpenAtStartup;
SerilogEventSink.Instance.LogLine += this.OnLogLine;
Service<Framework>.GetAsync().ContinueWith(r => r.Result.Update += this.FrameworkOnUpdate);
@ -114,7 +109,6 @@ internal class ConsoleWindow : Window, IDisposable
this.logLinesLimit = configuration.LogLinesLimit;
var limit = Math.Max(LogLinesMinimum, this.logLinesLimit);
this.newLogEntries = new();
this.logText = new(limit);
this.filteredLogEntries = new(limit);
@ -126,6 +120,9 @@ internal class ConsoleWindow : Window, IDisposable
}
}
/// <summary>Gets the queue where log entries that are not processed yet are stored.</summary>
public static ConcurrentQueue<(string Line, LogEvent LogEvent)> NewLogEntries { get; } = new();
/// <inheritdoc/>
public override void OnOpen()
{
@ -136,7 +133,6 @@ internal class ConsoleWindow : Window, IDisposable
/// <inheritdoc/>
public void Dispose()
{
SerilogEventSink.Instance.LogLine -= this.OnLogLine;
this.configuration.DalamudConfigurationSaved -= this.OnDalamudConfigurationSaved;
if (Service<Framework>.GetNullable() is { } framework)
framework.Update -= this.FrameworkOnUpdate;
@ -324,7 +320,7 @@ internal class ConsoleWindow : Window, IDisposable
ImGuiInputTextFlags.CallbackHistory | ImGuiInputTextFlags.CallbackEdit,
this.CommandInputCallback))
{
this.newLogEntries.Enqueue((this.commandText, new LogEvent(DateTimeOffset.Now, LogEventLevel.Information, null, new MessageTemplate(string.Empty, []), [])));
NewLogEntries.Enqueue((this.commandText, new LogEvent(DateTimeOffset.Now, LogEventLevel.Information, null, new MessageTemplate(string.Empty, []), [])));
this.ProcessCommand();
getFocus = true;
}
@ -372,7 +368,7 @@ internal class ConsoleWindow : Window, IDisposable
this.pendingClearLog = false;
this.logText.Clear();
this.filteredLogEntries.Clear();
this.newLogEntries.Clear();
NewLogEntries.Clear();
}
if (this.pendingRefilter)
@ -388,7 +384,7 @@ internal class ConsoleWindow : Window, IDisposable
var numPrevFilteredLogEntries = this.filteredLogEntries.Count;
var addedLines = 0;
while (this.newLogEntries.TryDequeue(out var logLine))
while (NewLogEntries.TryDequeue(out var logLine))
addedLines += this.HandleLogLine(logLine.Line, logLine.LogEvent);
this.newRolledLines = addedLines - (this.filteredLogEntries.Count - numPrevFilteredLogEntries);
}
@ -1062,11 +1058,6 @@ internal class ConsoleWindow : Window, IDisposable
/// <summary>Queues filtering the log entries again, before next call to <see cref="Draw"/>.</summary>
private void QueueRefilter() => this.pendingRefilter = true;
/// <summary>Enqueues the new log line to the log-to-be-processed queue.</summary>
/// <remarks>See <see cref="FrameworkOnUpdate"/> for the handler for the queued log entries.</remarks>
private void OnLogLine(object sender, (string Line, LogEvent LogEvent) logEvent) =>
this.newLogEntries.Enqueue(logEvent);
private bool DrawToggleButtonWithTooltip(
string buttonId, string tooltip, FontAwesomeIcon icon, ref bool enabledState)
{

View file

@ -1,4 +1,5 @@
using System.ComponentModel;
using System.Collections.Concurrent;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.IO;
@ -14,6 +15,7 @@ using Dalamud.Plugin.Internal;
using Dalamud.Utility;
using Serilog;
using Serilog.Events;
using TerraFX.Interop.Windows;
@ -31,7 +33,7 @@ namespace Dalamud;
Justification = "Multiple fixed blocks")]
internal sealed unsafe class LoadingDialog
{
private static int wasGloballyHidden;
private readonly RollingList<string> logs = new(20);
private Thread? thread;
private HWND hwndTaskDialog;
@ -60,6 +62,13 @@ internal sealed unsafe class LoadingDialog
AutoUpdatePlugins,
}
/// <summary>Gets the queue where log entries that are not processed yet are stored.</summary>
public static ConcurrentQueue<(string Line, LogEvent LogEvent)> NewLogEntries { get; } = new();
/// <summary>Gets a value indicating whether the initial Dalamud loading dialog will not show again until next
/// game restart.</summary>
public static bool IsGloballyHidden { get; private set; }
/// <summary>
/// Gets or sets the current state of the dialog.
/// </summary>
@ -98,7 +107,7 @@ internal sealed unsafe class LoadingDialog
/// </summary>
public void Show()
{
if (Volatile.Read(ref wasGloballyHidden) == 1)
if (IsGloballyHidden)
return;
if (this.thread?.IsAlive == true)
@ -119,6 +128,7 @@ internal sealed unsafe class LoadingDialog
/// </summary>
public void HideAndJoin()
{
IsGloballyHidden = true;
if (this.thread?.IsAlive is not true)
return;
@ -210,6 +220,42 @@ internal sealed unsafe class LoadingDialog
}
}
private void UpdateExpandedInformation()
{
const int maxCharactersPerLine = 80;
if (NewLogEntries.IsEmpty)
return;
while (NewLogEntries.TryDequeue(out var e))
{
var t = e.Line.AsSpan();
while (!t.IsEmpty)
{
var i = t.IndexOfAny('\r', '\n');
var line = i == -1 ? t : t[..i];
t = i == -1 ? ReadOnlySpan<char>.Empty : t[(i + 1)..];
if (line.IsEmpty)
continue;
this.logs.Add(
line.Length < maxCharactersPerLine ? line.ToString() : $"{line[..(maxCharactersPerLine - 3)]}...");
}
}
var sb = new StringBuilder();
foreach (var l in this.logs)
sb.AppendLine(l);
fixed (void* pszText = sb.ToString())
{
SendMessageW(
this.hwndTaskDialog,
(uint)TASKDIALOG_MESSAGES.TDM_SET_ELEMENT_TEXT,
(WPARAM)(int)TASKDIALOG_ELEMENTS.TDE_EXPANDED_INFORMATION,
(LPARAM)pszText);
}
}
private void UpdateButtonEnabled()
{
if (this.hwndTaskDialog == default)
@ -227,6 +273,7 @@ internal sealed unsafe class LoadingDialog
this.UpdateMainInstructionText();
this.UpdateContentText();
this.UpdateExpandedInformation();
this.UpdateButtonEnabled();
SendMessageW(hwnd, (int)TASKDIALOG_MESSAGES.TDM_SET_PROGRESS_BAR_MARQUEE, 1, 0);
@ -245,6 +292,7 @@ internal sealed unsafe class LoadingDialog
case TASKDIALOG_NOTIFICATIONS.TDN_TIMER:
this.UpdateContentText();
this.UpdateExpandedInformation();
return S.S_OK;
}
@ -260,10 +308,13 @@ internal sealed unsafe class LoadingDialog
? null
: Icon.ExtractAssociatedIcon(Path.Combine(workingDirectory, "Dalamud.Injector.exe"));
fixed (void* pszEmpty = "-")
fixed (void* pszWindowTitle = "Dalamud")
fixed (void* pszHide = Loc.Localize("LoadingDialogHide", "Hide"))
fixed (void* pszThemesManifestResourceName = "RT_MANIFEST_THEMES")
fixed (void* pszDalamudBoot = "Dalamud.Boot.dll")
fixed (void* pszThemesManifestResourceName = "RT_MANIFEST_THEMES")
fixed (void* pszHide = Loc.Localize("LoadingDialogHide", "Hide"))
fixed (void* pszShowLatestLogs = Loc.Localize("LoadingDialogShowLatestLogs", "Show Latest Logs"))
fixed (void* pszHideLatestLogs = Loc.Localize("LoadingDialogHideLatestLogs", "Hide Latest Logs"))
{
var taskDialogButton = new TASKDIALOG_BUTTON
{
@ -277,6 +328,7 @@ internal sealed unsafe class LoadingDialog
hInstance = (HINSTANCE)Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().ManifestModule),
dwFlags = (int)TDF_CAN_BE_MINIMIZED |
(int)TDF_SHOW_MARQUEE_PROGRESS_BAR |
(int)TDF_EXPAND_FOOTER_AREA |
(int)TDF_CALLBACK_TIMER |
(extractedIcon is null ? 0 : (int)TDF_USE_HICON_MAIN),
dwCommonButtons = 0,
@ -291,14 +343,14 @@ internal sealed unsafe class LoadingDialog
pRadioButtons = null,
nDefaultRadioButton = 0,
pszVerificationText = null,
pszExpandedInformation = null,
pszExpandedControlText = null,
pszCollapsedControlText = null,
pszExpandedInformation = (ushort*)pszEmpty,
pszExpandedControlText = (ushort*)pszShowLatestLogs,
pszCollapsedControlText = (ushort*)pszHideLatestLogs,
pszFooterIcon = null,
pszFooter = null,
pfCallback = &HResultFuncBinder,
lpCallbackData = 0,
cxWidth = 0,
cxWidth = 360,
};
HANDLE hActCtx = default;
@ -338,7 +390,7 @@ internal sealed unsafe class LoadingDialog
}
}
Interlocked.Exchange(ref wasGloballyHidden, 1);
IsGloballyHidden = true;
return;

View file

@ -250,19 +250,20 @@ internal static class ServiceManager
try
{
// Wait for all blocking constructors to complete first.
await WaitWithTimeoutConsent(blockingEarlyLoadingServices.Select(x => getAsyncTaskMap[x]),
await WaitWithTimeoutConsent(
blockingEarlyLoadingServices.Select(x => getAsyncTaskMap[x]),
LoadingDialog.State.LoadingDalamud);
// All the BlockingEarlyLoadedService constructors have been run,
// and blockerTasks now will not change. Now wait for them.
// Note that ServiceManager.CallWhenServicesReady does not get to register a blocker.
await WaitWithTimeoutConsent(blockerTasks,
await WaitWithTimeoutConsent(
blockerTasks,
LoadingDialog.State.LoadingPlugins);
Log.Verbose("=============== BLOCKINGSERVICES & TASKS INITIALIZED ===============");
Timings.Event("BlockingServices Initialized");
BlockingServicesLoadedTaskCompletionSource.SetResult();
loadingDialog.HideAndJoin();
}
catch (Exception e)
{
@ -277,6 +278,10 @@ internal static class ServiceManager
Log.Error(e, "Failed resolving blocking services");
}
finally
{
loadingDialog.HideAndJoin();
}
return;