test(selftest): add GlobalStyleScope GC-reserve alloc probe

This commit is contained in:
2026-06-16 19:23:15 +02:00
parent 32013babaf
commit 0508a05bab
2 changed files with 62 additions and 0 deletions
+1
View File
@@ -409,6 +409,7 @@ public sealed class Plugin : IAsyncDalamudPlugin
new SelfTests.HonorificHeaderRenderStep(this),
new SelfTests.AboutIntegrationsStatusStep(this),
new SelfTests.PerformanceBaselineStep(this),
new SelfTests.GlobalStyleScopeAllocStep(this),
new SelfTests.MainWindowFocusOpacityStep(this),
new SelfTests.MainWindowFlagsStep(this),
new SelfTests.SenderNameReformatStep(this),
@@ -0,0 +1,61 @@
using System;
using Dalamud.Bindings.ImGui;
using Dalamud.Plugin.SelfTest;
using HellionChat.Ui.StyleEngine;
namespace HellionChat.SelfTests;
// GC-reserve probe for the v1.9.0 B4a refactor: GlobalStyleScope.Push runs
// once per draw frame, so its StackHandle must allocate nothing. Before the
// counter rewrite it boxed 44 ImRaii structs + a List per frame; after, it
// holds two ints and pushes straight onto the ImGui stack. This step drives a
// real Push()->Dispose() cycle and asserts the per-thread allocation delta is
// ~0 (not a non-null-handle check — feedback_hellion_chat_fontmanager_push_trap).
// A warm-up cycle pays the one-time JIT/first-touch cost so the measured cycle
// reflects steady state, matching the real per-frame hot path.
internal sealed class GlobalStyleScopeAllocStep : ISelfTestStep
{
// Headroom for incidental managed noise (GC bookkeeping, boxing inside
// ImGui bindings we do not control). The pre-fix path allocated ~1-2 KB
// per cycle (44 boxes + List), so anything under this threshold proves
// the StackHandle itself stopped allocating. Tighten only if a future
// binding upgrade removes all incidental noise.
private const long AllocBudgetBytes = 256;
private readonly Plugin _plugin;
public GlobalStyleScopeAllocStep(Plugin plugin)
{
_plugin = plugin;
}
public string Name => "Hellion Chat - GlobalStyleScope GC reserve";
public SelfTestStepResult RunStep()
{
var registry = _plugin.ThemeRegistry;
var theme = registry.Active;
var opacity = Plugin.Config.WindowOpacity;
// Warm-up: JIT the Push/Dispose path + first-touch any lazy ImGui
// stack growth, so the measured cycle is steady-state only.
GlobalStyleScope.Push(theme, registry, opacity).Dispose();
var before = GC.GetAllocatedBytesForCurrentThread();
GlobalStyleScope.Push(theme, registry, opacity).Dispose();
var delta = GC.GetAllocatedBytesForCurrentThread() - before;
if (delta > AllocBudgetBytes)
{
ImGui.Text(
$"GlobalStyleScope.Push allocated {delta} bytes/cycle "
+ $"(budget {AllocBudgetBytes}) — StackHandle is not GC-free."
);
return SelfTestStepResult.Fail;
}
return SelfTestStepResult.Pass;
}
public void CleanUp() { }
}