Skip to content

Commit

Permalink
hide assert logs that spam every frame automatically, add "verbose" mode
Browse files Browse the repository at this point in the history
  • Loading branch information
goaaats committed Dec 26, 2024
1 parent 10f4009 commit 3f3f5f4
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 37 deletions.
57 changes: 46 additions & 11 deletions Dalamud/Interface/Internal/Asserts/AssertHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ namespace Dalamud.Interface.Internal.Asserts;
/// </summary>
internal class AssertHandler : IDisposable
{
private const int HideThreshold = 20;
private const int HidePrintEvery = 500;

private readonly HashSet<string> ignoredAsserts = [];
private readonly Dictionary<string, uint> assertCounts = new();

// Store callback to avoid it from being GC'd
private readonly AssertCallbackDelegate callback;
Expand All @@ -37,7 +41,13 @@ private delegate void AssertCallbackDelegate(
/// Gets or sets a value indicating whether ImGui asserts should be shown to the user.
/// </summary>
public bool ShowAsserts { get; set; }


/// <summary>
/// Gets or sets a value indicating whether we want to hide asserts that occur frequently (= every update)
/// and whether we want to log callstacks.
/// </summary>
public bool EnableVerboseLogging { get; set; }

/// <summary>
/// Register the cimgui assert handler with the native library.
/// </summary>
Expand All @@ -62,18 +72,43 @@ public void Dispose()

private void OnImGuiAssert(string expr, string file, int line)
{
Log.Warning("ImGui assertion failed: {Expr} at {File}:{Line}", expr, file, line);

if (!this.ShowAsserts)
return;

var key = $"{file}:{line}";
if (this.ignoredAsserts.Contains(key))
return;

// TODO: It would be nice to get unmanaged stack frames here, seems hard though without pulling that
// entire code in from the crash handler
var originalStackTrace = new StackTrace(2).ToString();
Lazy<string> stackTrace = new(() => new StackTrace(3).ToString());

if (!this.EnableVerboseLogging)
{
if (this.assertCounts.TryGetValue(key, out var count))
{
this.assertCounts[key] = count + 1;

if (count <= HideThreshold || count % HidePrintEvery == 0)
{
Log.Warning("ImGui assertion failed: {Expr} at {File}:{Line} (repeated {Count} times)",
expr,
file,
line,
count);
}
}
else
{
this.assertCounts[key] = 1;
}
}
else
{
Log.Warning("ImGui assertion failed: {Expr} at {File}:{Line}\n{StackTrace:l}",
expr,
file,
line,
stackTrace.Value);
}

if (!this.ShowAsserts)
return;

string? GetRepoUrl()
{
Expand Down Expand Up @@ -108,7 +143,7 @@ private void OnImGuiAssert(string expr, string file, int line)
Text = "Break",
AllowCloseDialog = true,
};

var disableButton = new TaskDialogButton
{
Text = "Disable for this session",
Expand All @@ -132,7 +167,7 @@ void DialogThreadStart()
{
CollapsedButtonText = "Show stack trace",
ExpandedButtonText = "Hide stack trace",
Text = originalStackTrace,
Text = stackTrace.Value,
},
Text = $"Some code in a plugin or Dalamud itself has caused an internal assertion in ImGui to fail. The game will most likely crash now.\n\n{expr}\nAt: {file}:{line}",
Icon = TaskDialogIcon.Warning,
Expand Down
48 changes: 28 additions & 20 deletions Dalamud/Interface/Internal/DalamudInterface.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ internal class DalamudInterface : IInternalDisposableService
private bool isImPlotDrawDemoWindow = false;
private bool isImGuiTestWindowsInMonospace = false;
private bool isImGuiDrawMetricsWindow = false;

[ServiceManager.ServiceConstructor]
private DalamudInterface(
Dalamud dalamud,
Expand All @@ -112,7 +112,7 @@ private DalamudInterface(
this.interfaceManager = interfaceManager;

this.WindowSystem = new WindowSystem("DalamudCore");

this.colorDemoWindow = new ColorDemoWindow() { IsOpen = false };
this.componentDemoWindow = new ComponentDemoWindow() { IsOpen = false };
this.dataWindow = new DataWindow() { IsOpen = false };
Expand Down Expand Up @@ -193,7 +193,7 @@ private DalamudInterface(

this.creditsDarkeningAnimation.Point1 = Vector2.Zero;
this.creditsDarkeningAnimation.Point2 = new Vector2(CreditsDarkeningMaxAlpha);

// This is temporary, until we know the repercussions of vtable hooking mode
consoleManager.AddCommand(
"dalamud.interface.swapchain_mode",
Expand All @@ -212,14 +212,14 @@ private DalamudInterface(
Log.Error("Unknown swapchain mode: {Mode}", mode);
return false;
}

this.configuration.QueueSave();
return true;
});
}

private delegate nint CrashDebugDelegate(nint self);

/// <summary>
/// Gets the number of frames since Dalamud has loaded.
/// </summary>
Expand Down Expand Up @@ -319,7 +319,7 @@ public void OpenPluginStats()
this.pluginStatWindow.IsOpen = true;
this.pluginStatWindow.BringToFront();
}

/// <summary>
/// Opens the <see cref="PluginInstallerWindow"/> on the plugin installed.
/// </summary>
Expand Down Expand Up @@ -384,7 +384,7 @@ public void OpenProfiler()
this.profilerWindow.IsOpen = true;
this.profilerWindow.BringToFront();
}

/// <summary>
/// Opens the <see cref="HitchSettingsWindow"/>.
/// </summary>
Expand Down Expand Up @@ -696,7 +696,7 @@ private void DrawDevMenu()

ImGui.EndMenu();
}

var logSynchronously = this.configuration.LogSynchronously;
if (ImGui.MenuItem("Log Synchronously", null, ref logSynchronously))
{
Expand Down Expand Up @@ -788,14 +788,14 @@ private void DrawDevMenu()
}

ImGui.Separator();

if (ImGui.BeginMenu("Crash game"))
{
if (ImGui.MenuItem("Access Violation"))
{
Marshal.ReadByte(IntPtr.Zero);
}
}

if (ImGui.MenuItem("Set UiModule to NULL"))
{
unsafe
Expand All @@ -804,7 +804,7 @@ private void DrawDevMenu()
framework->UIModule = (UIModule*)0;
}
}

if (ImGui.MenuItem("Set UiModule to invalid ptr"))
{
unsafe
Expand All @@ -813,7 +813,7 @@ private void DrawDevMenu()
framework->UIModule = (UIModule*)0x12345678;
}
}

if (ImGui.MenuItem("Deref nullptr in Hook"))
{
unsafe
Expand All @@ -834,7 +834,7 @@ private void DrawDevMenu()
ImGui.PopStyleVar();
ImGui.PopStyleVar();
}

ImGui.EndMenu();
}

Expand All @@ -850,7 +850,7 @@ private void DrawDevMenu()
{
this.OpenBranchSwitcher();
}

ImGui.MenuItem(this.dalamud.StartInfo.GameVersion?.ToString() ?? "Unknown version", false);
ImGui.MenuItem($"D: {Util.GetScmVersion()} CS: {Util.GetGitHashClientStructs()}[{FFXIVClientStructs.ThisAssembly.Git.Commits}]", false);
ImGui.MenuItem($"CLR: {Environment.Version}", false);
Expand All @@ -867,10 +867,16 @@ private void DrawDevMenu()

ImGui.Separator();

var val = this.interfaceManager.ShowAsserts;
if (ImGui.MenuItem("Enable Asserts", string.Empty, ref val))
var showAsserts = this.interfaceManager.ShowAsserts;
if (ImGui.MenuItem("Enable assert popups", string.Empty, ref showAsserts))
{
this.interfaceManager.ShowAsserts = val;
this.interfaceManager.ShowAsserts = showAsserts;
}

var enableVerboseAsserts = this.interfaceManager.EnableVerboseAssertLogging;
if (ImGui.MenuItem("Enable verbose assert logging", string.Empty, ref enableVerboseAsserts))
{
this.interfaceManager.EnableVerboseAssertLogging = enableVerboseAsserts;
}

var assertsEnabled = this.configuration.ImGuiAssertsEnabledAtStartup ?? false;
Expand All @@ -880,6 +886,8 @@ private void DrawDevMenu()
this.configuration.QueueSave();
}

ImGui.Separator();

if (ImGui.MenuItem("Clear focus"))
{
ImGui.SetWindowFocus(null);
Expand Down Expand Up @@ -927,7 +935,7 @@ private void DrawDevMenu()
{
this.configuration.ShowDevBarInfo = !this.configuration.ShowDevBarInfo;
}

ImGui.Separator();

if (ImGui.MenuItem("Show loading window"))
Expand Down
19 changes: 13 additions & 6 deletions Dalamud/Interface/Internal/InterfaceManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ internal partial class InterfaceManager : IInternalDisposableService
public const float DefaultFontSizePx = (DefaultFontSizePt * 4.0f) / 3.0f;

private static readonly ModuleLog Log = new("INTERFACE");

private readonly ConcurrentBag<IDeferredDisposable> deferredDisposeTextures = new();
private readonly ConcurrentBag<IDisposable> deferredDisposeDisposables = new();

Expand All @@ -93,7 +93,7 @@ internal partial class InterfaceManager : IInternalDisposableService

private readonly ConcurrentQueue<Action> runBeforeImGuiRender = new();
private readonly ConcurrentQueue<Action> runAfterImGuiRender = new();

private readonly AssertHandler assertHandler = new();

private RawDX11Scene? scene;
Expand Down Expand Up @@ -276,6 +276,13 @@ public bool ShowAsserts
set => this.assertHandler.ShowAsserts = value;
}

/// <inheritdoc cref="AssertHandler.EnableVerboseLogging"/>
public bool EnableVerboseAssertLogging
{
get => this.assertHandler.EnableVerboseLogging;
set => this.assertHandler.EnableVerboseLogging = value;
}

/// <summary>
/// Dispose of managed and unmanaged resources.
/// </summary>
Expand Down Expand Up @@ -809,14 +816,14 @@ private unsafe void ContinueConstruction(
});
};
}

// This will wait for scene on its own. We just wait for this.dalamudAtlas.BuildTask in this.InitScene.
_ = this.dalamudAtlas.BuildFontsAsync();

SwapChainHelper.BusyWaitForGameDeviceSwapChain();
var swapChainDesc = default(DXGI_SWAP_CHAIN_DESC);
if (SwapChainHelper.GameDeviceSwapChain->GetDesc(&swapChainDesc).SUCCEEDED)
this.gameWindowHandle = swapChainDesc.OutputWindow;
this.gameWindowHandle = swapChainDesc.OutputWindow;

try
{
Expand Down Expand Up @@ -959,7 +966,7 @@ private unsafe void ContinueConstruction(

switch (this.dalamudConfiguration.SwapChainHookMode)
{
case SwapChainHelper.HookMode.ByteCode:
case SwapChainHelper.HookMode.ByteCode:
default:
{
Log.Information("Hooking using bytecode...");
Expand Down Expand Up @@ -1149,7 +1156,7 @@ private void Display()
catch (Exception ex)
{
Log.Error(ex, "Error when invoking global Draw");

// We should always handle this in the callbacks.
Util.Fatal("An internal error occurred while drawing the Dalamud UI and the game must close.\nPlease report this error.", "Dalamud");
}
Expand Down

0 comments on commit 3f3f5f4

Please sign in to comment.