From dda515da85933130a2f61eb001590e2e1b2b59a6 Mon Sep 17 00:00:00 2001 From: Raymond Date: Fri, 10 Sep 2021 07:58:48 -0400 Subject: [PATCH 1/2] feat: ImGuiManagedAsserts --- .../ManagedAsserts/ImGuiContextOffsets.cs | 21 +++ .../ManagedAsserts/ImGuiManagedAsserts.cs | 136 ++++++++++++++++++ Dalamud/Interface/UiBuilder.cs | 13 ++ Dalamud/Interface/Windowing/WindowSystem.cs | 7 +- 4 files changed, 176 insertions(+), 1 deletion(-) create mode 100644 Dalamud/Interface/Internal/ManagedAsserts/ImGuiContextOffsets.cs create mode 100644 Dalamud/Interface/Internal/ManagedAsserts/ImGuiManagedAsserts.cs diff --git a/Dalamud/Interface/Internal/ManagedAsserts/ImGuiContextOffsets.cs b/Dalamud/Interface/Internal/ManagedAsserts/ImGuiContextOffsets.cs new file mode 100644 index 000000000..ee0a282d7 --- /dev/null +++ b/Dalamud/Interface/Internal/ManagedAsserts/ImGuiContextOffsets.cs @@ -0,0 +1,21 @@ +namespace Dalamud.Interface.Internal.ManagedAsserts +{ + /// + /// Offsets to various data for ImGui.Context + /// + /// + /// Last updated for ImGui 1.83 + /// + internal static class ImGuiContextOffsets + { + public const int CurrentWindowStackOffset = 0x73A; + + public const int ColorStackOffset = 0x79C; + + public const int StyleVarStackOffset = 0x7A0; + + public const int FontStackOffset = 0x7A4; + + public const int BeginPopupStackOffset = 0x7B8; + } +} diff --git a/Dalamud/Interface/Internal/ManagedAsserts/ImGuiManagedAsserts.cs b/Dalamud/Interface/Internal/ManagedAsserts/ImGuiManagedAsserts.cs new file mode 100644 index 000000000..b0df91162 --- /dev/null +++ b/Dalamud/Interface/Internal/ManagedAsserts/ImGuiManagedAsserts.cs @@ -0,0 +1,136 @@ +using System.Diagnostics; + +using ImGuiNET; + +using static Dalamud.NativeFunctions; + +namespace Dalamud.Interface.Internal.ManagedAsserts +{ + /// + /// Report ImGui problems with a MessageBox dialog. + /// + internal static class ImGuiManagedAsserts + { + /// + /// Gets a value indicating whether asserts are enabled for ImGui. + /// + public static bool AssertsEnabled { get; private set; } + + /// + /// Create a snapshot of the current ImGui context. + /// Should be called before rendering an ImGui frame. + /// + /// A snapshot of the current context. + public static unsafe ImGuiContextSnapshot GetSnapshot() + { + var contextPtr = ImGui.GetCurrentContext(); + + var styleVarStack = *((int*)contextPtr + ImGuiContextOffsets.StyleVarStackOffset); // ImVector.Size + var colorStack = *((int*)contextPtr + ImGuiContextOffsets.ColorStackOffset); // ImVector.Size + var fontStack = *((int*)contextPtr + ImGuiContextOffsets.FontStackOffset); // ImVector.Size + var popupStack = *((int*)contextPtr + ImGuiContextOffsets.BeginPopupStackOffset); // ImVector.Size + var windowStack = *((int*)contextPtr + ImGuiContextOffsets.CurrentWindowStackOffset); // ImVector.Size + + return new ImGuiContextSnapshot + { + StyleVarStackSize = styleVarStack, + ColorStackSize = colorStack, + FontStackSize = fontStack, + BeginPopupStackSize = popupStack, + WindowStackSize = windowStack, + }; + } + + /// + /// Compare a snapshot to the current post-draw state and report any errors in a MessageBox dialog. + /// + /// The source of any problems, something to blame. + /// ImGui context snapshot. + public static void ReportProblems(string source, ImGuiContextSnapshot before) + { + if (!AssertsEnabled) + { + return; + } + + var cSnap = GetSnapshot(); + + if (before.StyleVarStackSize != cSnap.StyleVarStackSize) + { + ShowAssert(source, $"You forgot to pop a style var!\n\nBefore: {before.StyleVarStackSize}, after: {cSnap.StyleVarStackSize}"); + return; + } + + if (before.ColorStackSize != cSnap.ColorStackSize) + { + ShowAssert(source, $"You forgot to pop a color!\n\nBefore: {before.ColorStackSize}, after: {cSnap.ColorStackSize}"); + return; + } + + if (before.FontStackSize != cSnap.FontStackSize) + { + ShowAssert(source, $"You forgot to pop a font!\n\nBefore: {before.FontStackSize}, after: {cSnap.FontStackSize}"); + return; + } + + if (before.BeginPopupStackSize != cSnap.BeginPopupStackSize) + { + ShowAssert(source, $"You forgot to end a popup!\n\nBefore: {before.BeginPopupStackSize}, after: {cSnap.BeginPopupStackSize}"); + return; + } + + if (cSnap.WindowStackSize != 1) + { + if (cSnap.WindowStackSize > 1) + { + ShowAssert(source, $"Mismatched Begin/BeginChild vs End/EndChild calls: did you forget to call End/EndChild?\n\ncSnap.WindowStackSize = {cSnap.WindowStackSize}"); + } + else + { + ShowAssert(source, $"Mismatched Begin/BeginChild vs End/EndChild calls: did you call End/EndChild too much?\n\ncSnap.WindowStackSize = {cSnap.WindowStackSize}"); + } + } + } + + private static void ShowAssert(string source, string message) + { + var caption = $"You fucked up"; + message = $"{message}\n\nSource: {source}\n\nAsserts are now disabled. You may re-enable them."; + var flags = MessageBoxType.Ok | MessageBoxType.IconError; + + _ = MessageBoxW(Process.GetCurrentProcess().MainWindowHandle, message, caption, flags); + AssertsEnabled = false; + } + + /// + /// A snapshot of various ImGui context properties. + /// + public class ImGuiContextSnapshot + { + /// + /// Gets the ImGui style var stack size. + /// + public int StyleVarStackSize { get; init; } + + /// + /// Gets the ImGui color stack size. + /// + public int ColorStackSize { get; init; } + + /// + /// Gets the ImGui font stack size. + /// + public int FontStackSize { get; init; } + + /// + /// Gets the ImGui begin popup stack size. + /// + public int BeginPopupStackSize { get; init; } + + /// + /// Gets the ImGui window stack size. + /// + public int WindowStackSize { get; init; } + } + } +} diff --git a/Dalamud/Interface/UiBuilder.cs b/Dalamud/Interface/UiBuilder.cs index 3e3694407..5ff2fac4c 100644 --- a/Dalamud/Interface/UiBuilder.cs +++ b/Dalamud/Interface/UiBuilder.cs @@ -6,6 +6,7 @@ using Dalamud.Configuration.Internal; using Dalamud.Game.ClientState.Conditions; using Dalamud.Game.Gui; using Dalamud.Interface.Internal; +using Dalamud.Interface.Internal.ManagedAsserts; using Dalamud.Interface.Internal.Notifications; using ImGuiNET; using ImGuiScene; @@ -275,6 +276,12 @@ namespace Dalamud.Interface ImGui.End(); } + ImGuiManagedAsserts.ImGuiContextSnapshot? snapshot = null; + if (this.Draw != null) + { + snapshot = ImGuiManagedAsserts.GetSnapshot(); + } + try { this.Draw?.Invoke(); @@ -288,6 +295,12 @@ namespace Dalamud.Interface this.hasErrorWindow = true; } + // Only if Draw was successful + if (this.Draw != null && snapshot != null) + { + ImGuiManagedAsserts.ReportProblems(this.namespaceName, snapshot); + } + this.FrameCount++; if (DoStats) diff --git a/Dalamud/Interface/Windowing/WindowSystem.cs b/Dalamud/Interface/Windowing/WindowSystem.cs index 4d3a03a0d..b734c8c6a 100644 --- a/Dalamud/Interface/Windowing/WindowSystem.cs +++ b/Dalamud/Interface/Windowing/WindowSystem.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; - +using Dalamud.Interface.Internal.ManagedAsserts; using ImGuiNET; using Serilog; @@ -98,8 +98,13 @@ namespace Dalamud.Interface.Windowing #if DEBUG // Log.Verbose($"[WS{(hasNamespace ? "/" + this.Namespace : string.Empty)}] Drawing {window.WindowName}"); #endif + var snapshot = ImGuiManagedAsserts.GetSnapshot(); window.DrawInternal(); + + var source = ($"{this.Namespace}::" ?? string.Empty) + window.WindowName; + ImGuiManagedAsserts.ReportProblems(source, snapshot); + } var focusedWindow = this.windows.FirstOrDefault(x => x.IsFocused && x.RespectCloseHotkey); From 1920d91eff13c25d6d9877783a3767454a0818af Mon Sep 17 00:00:00 2001 From: Raymond Date: Fri, 10 Sep 2021 08:01:44 -0400 Subject: [PATCH 2/2] formatting --- .../Interface/Internal/ManagedAsserts/ImGuiContextOffsets.cs | 5 +++-- Dalamud/Interface/Windowing/WindowSystem.cs | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Dalamud/Interface/Internal/ManagedAsserts/ImGuiContextOffsets.cs b/Dalamud/Interface/Internal/ManagedAsserts/ImGuiContextOffsets.cs index ee0a282d7..5c76854d2 100644 --- a/Dalamud/Interface/Internal/ManagedAsserts/ImGuiContextOffsets.cs +++ b/Dalamud/Interface/Internal/ManagedAsserts/ImGuiContextOffsets.cs @@ -1,11 +1,12 @@ namespace Dalamud.Interface.Internal.ManagedAsserts { /// - /// Offsets to various data for ImGui.Context + /// Offsets to various data in ImGui context. /// /// - /// Last updated for ImGui 1.83 + /// Last updated for ImGui 1.83. /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Document the unsage instead.")] internal static class ImGuiContextOffsets { public const int CurrentWindowStackOffset = 0x73A; diff --git a/Dalamud/Interface/Windowing/WindowSystem.cs b/Dalamud/Interface/Windowing/WindowSystem.cs index b734c8c6a..5f6334e8e 100644 --- a/Dalamud/Interface/Windowing/WindowSystem.cs +++ b/Dalamud/Interface/Windowing/WindowSystem.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; + using Dalamud.Interface.Internal.ManagedAsserts; using ImGuiNET; using Serilog; @@ -104,7 +105,6 @@ namespace Dalamud.Interface.Windowing var source = ($"{this.Namespace}::" ?? string.Empty) + window.WindowName; ImGuiManagedAsserts.ReportProblems(source, snapshot); - } var focusedWindow = this.windows.FirstOrDefault(x => x.IsFocused && x.RespectCloseHotkey);