perf(style): make GlobalStyleScope.StackHandle GC-free via counter scope

This commit is contained in:
2026-06-16 19:23:15 +02:00
parent 430c8f235a
commit 32013babaf
+45 -11
View File
@@ -1,5 +1,4 @@
using Dalamud.Bindings.ImGui;
using Dalamud.Interface.Utility.Raii;
using HellionChat.Themes;
using HellionChat.Util;
@@ -33,6 +32,13 @@ internal static class GlobalStyleScope
var childBgWithAlpha = ResolveChildBgAlpha(c.ChildBg, windowOpacity);
var stack = new StackHandle();
// Hard contract: every push must be balanced by a pop. The pushes below
// are pure, practically non-throwing ImGui calls, but if one ever threw
// mid-stack we must still pop what we pushed before the exception escapes,
// or the global style stack stays corrupt for every other window this
// frame. Dispose then rethrow.
try
{
stack.PushStyleVar(ImGuiStyleVar.WindowRounding, l.WindowRounding);
stack.PushStyleVar(ImGuiStyleVar.ChildRounding, l.ChildRounding);
stack.PushStyleVar(ImGuiStyleVar.PopupRounding, l.PopupRounding);
@@ -87,6 +93,12 @@ internal static class GlobalStyleScope
stack.PushColorAbgr(ImGuiCol.Separator, a.Border);
stack.PushColorAbgr(ImGuiCol.SeparatorHovered, a.PrimaryLight);
stack.PushColorAbgr(ImGuiCol.SeparatorActive, a.Primary);
}
catch
{
stack.Dispose();
throw;
}
return stack;
}
@@ -101,24 +113,46 @@ internal static class GlobalStyleScope
return (themeChildBgRgba & 0xFFFFFF00u) | childBgAlpha;
}
// Counter-based scope: pushes go straight onto the global ImGui style
// stack and we only remember how many of each kind we pushed. Dispose
// pops them in one batched PopStyleColor(count)/PopStyleVar(count) call.
// This replaces the old List<IDisposable> + per-push boxed ImRaii structs
// (44 allocations/frame) with two ints — zero per-frame GC. Every style
// var pushed here is a single-float var, so PopStyleVar(count) is valid;
// both colour paths funnel into one PushStyleColor, so one colour counter
// covers them. Symmetry is the whole contract: the pop counts must equal
// the push counts or the global stack corrupts for every other window.
private sealed class StackHandle : IDisposable
{
private readonly List<IDisposable> _items = new(64);
private int _colorCount;
private int _styleVarCount;
internal void PushColor(ImGuiCol slot, uint rgba) =>
_items.Add(ImRaii.PushColor(slot, ColourUtil.RgbaToAbgr(rgba)));
internal void PushColor(ImGuiCol slot, uint rgba)
{
ImGui.PushStyleColor(slot, ColourUtil.RgbaToAbgr(rgba));
_colorCount++;
}
internal void PushColorAbgr(ImGuiCol slot, uint abgr) =>
_items.Add(ImRaii.PushColor(slot, abgr));
internal void PushColorAbgr(ImGuiCol slot, uint abgr)
{
ImGui.PushStyleColor(slot, abgr);
_colorCount++;
}
internal void PushStyleVar(ImGuiStyleVar var, float value) =>
_items.Add(ImRaii.PushStyle(var, value));
internal void PushStyleVar(ImGuiStyleVar var, float value)
{
ImGui.PushStyleVar(var, value);
_styleVarCount++;
}
public void Dispose()
{
for (var i = _items.Count - 1; i >= 0; i--)
_items[i].Dispose();
_items.Clear();
if (_styleVarCount > 0)
ImGui.PopStyleVar(_styleVarCount);
if (_colorCount > 0)
ImGui.PopStyleColor(_colorCount);
_styleVarCount = 0;
_colorCount = 0;
}
}
}