From 877906ae15e58c606429c5517f3bc193826ea409 Mon Sep 17 00:00:00 2001 From: Soreepeong Date: Sun, 21 Jul 2024 19:09:05 +0900 Subject: [PATCH] Show logs on loading dialog --- Dalamud/EntryPoint.cs | 5 ++ .../Internal/Windows/ConsoleWindow.cs | 21 ++---- Dalamud/Service/LoadingDialog.cs | 72 ++++++++++++++++--- Dalamud/Service/ServiceManager.cs | 11 ++- 4 files changed, 81 insertions(+), 28 deletions(-) diff --git a/Dalamud/EntryPoint.cs b/Dalamud/EntryPoint.cs index 1e6fccd8b..fcf33fe28 100644 --- a/Dalamud/EntryPoint.cs +++ b/Dalamud/EntryPoint.cs @@ -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; diff --git a/Dalamud/Interface/Internal/Windows/ConsoleWindow.cs b/Dalamud/Interface/Internal/Windows/ConsoleWindow.cs index 8f7c0e36c..f7ce5d145 100644 --- a/Dalamud/Interface/Internal/Windows/ConsoleWindow.cs +++ b/Dalamud/Interface/Internal/Windows/ConsoleWindow.cs @@ -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 logText; private readonly RollingList filteredLogEntries; @@ -94,7 +90,6 @@ internal class ConsoleWindow : Window, IDisposable this.autoScroll = configuration.LogAutoScroll; this.autoOpen = configuration.LogOpenAtStartup; - SerilogEventSink.Instance.LogLine += this.OnLogLine; Service.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 } } + /// Gets the queue where log entries that are not processed yet are stored. + public static ConcurrentQueue<(string Line, LogEvent LogEvent)> NewLogEntries { get; } = new(); + /// public override void OnOpen() { @@ -136,7 +133,6 @@ internal class ConsoleWindow : Window, IDisposable /// public void Dispose() { - SerilogEventSink.Instance.LogLine -= this.OnLogLine; this.configuration.DalamudConfigurationSaved -= this.OnDalamudConfigurationSaved; if (Service.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 /// Queues filtering the log entries again, before next call to . private void QueueRefilter() => this.pendingRefilter = true; - /// Enqueues the new log line to the log-to-be-processed queue. - /// See for the handler for the queued log entries. - 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) { diff --git a/Dalamud/Service/LoadingDialog.cs b/Dalamud/Service/LoadingDialog.cs index eebeb5b1c..42676386c 100644 --- a/Dalamud/Service/LoadingDialog.cs +++ b/Dalamud/Service/LoadingDialog.cs @@ -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 logs = new(20); private Thread? thread; private HWND hwndTaskDialog; @@ -60,6 +62,13 @@ internal sealed unsafe class LoadingDialog AutoUpdatePlugins, } + /// Gets the queue where log entries that are not processed yet are stored. + public static ConcurrentQueue<(string Line, LogEvent LogEvent)> NewLogEntries { get; } = new(); + + /// Gets a value indicating whether the initial Dalamud loading dialog will not show again until next + /// game restart. + public static bool IsGloballyHidden { get; private set; } + /// /// Gets or sets the current state of the dialog. /// @@ -98,7 +107,7 @@ internal sealed unsafe class LoadingDialog /// 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 /// 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.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; diff --git a/Dalamud/Service/ServiceManager.cs b/Dalamud/Service/ServiceManager.cs index 446e404dc..5e22ed0c1 100644 --- a/Dalamud/Service/ServiceManager.cs +++ b/Dalamud/Service/ServiceManager.cs @@ -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;