feat(popout): arm the auto-tell pop-out settings and add the pool self-test
This commit is contained in:
@@ -388,6 +388,7 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
|||||||
new SelfTests.OnOpenMainUiRoutesMainWindowStep(this),
|
new SelfTests.OnOpenMainUiRoutesMainWindowStep(this),
|
||||||
new SelfTests.TypingIpcStateStep(this),
|
new SelfTests.TypingIpcStateStep(this),
|
||||||
new SelfTests.ConfigMigrationV23Step(this),
|
new SelfTests.ConfigMigrationV23Step(this),
|
||||||
|
new SelfTests.ChannelPopoutBindStep(this),
|
||||||
new SelfTests.HoverSheenAllocStep(this),
|
new SelfTests.HoverSheenAllocStep(this),
|
||||||
new SelfTests.HonorificHeaderRenderStep(this),
|
new SelfTests.HonorificHeaderRenderStep(this),
|
||||||
new SelfTests.AboutIntegrationsStatusStep(this),
|
new SelfTests.AboutIntegrationsStatusStep(this),
|
||||||
|
|||||||
@@ -0,0 +1,119 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using Dalamud.Bindings.ImGui;
|
||||||
|
using Dalamud.Plugin.SelfTest;
|
||||||
|
|
||||||
|
namespace HellionChat.SelfTests;
|
||||||
|
|
||||||
|
// Exercises the ChannelPopoutPool lifecycle in-game (a behavioural step, not a
|
||||||
|
// non-null-handle check — feedback_hellion_chat_fontmanager_push_trap). Verifies
|
||||||
|
// pre-alloc == MaxParallelPopouts, unique slot ids, a TryOpen->IsOpen->TryClose
|
||||||
|
// round-trip, idempotent TryClose, and capacity-exceeded refusal (warn, no throw).
|
||||||
|
// The pool is a LIVE DI singleton, so a tester may already have real pop-outs open
|
||||||
|
// when /xlperf runs; the step tests against the FREE slots (not full capacity) and
|
||||||
|
// only ever closes ids it opened, so it neither false-REDs on a non-empty pool nor
|
||||||
|
// disturbs real pop-outs. The pure slot-map math is pinned by PopoutSlotMapTests
|
||||||
|
// (Build-Suite); this step proves the live wiring on top of it. Every slot reserved
|
||||||
|
// is released before RunStep returns, so no pop-out is left bound.
|
||||||
|
internal sealed class ChannelPopoutBindStep : ISelfTestStep
|
||||||
|
{
|
||||||
|
private readonly Plugin _plugin;
|
||||||
|
|
||||||
|
public ChannelPopoutBindStep(Plugin plugin)
|
||||||
|
{
|
||||||
|
_plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Name => "Hellion Chat - Channel popout pool lifecycle";
|
||||||
|
|
||||||
|
public SelfTestStepResult RunStep()
|
||||||
|
{
|
||||||
|
var pool = _plugin.ChannelPopoutPool;
|
||||||
|
var capacity = Plugin.Config.MaxParallelPopouts;
|
||||||
|
|
||||||
|
if (pool.Instances.Count != capacity)
|
||||||
|
{
|
||||||
|
ImGui.Text(
|
||||||
|
$"Expected {capacity} pre-allocated pop-out windows, found {pool.Instances.Count}."
|
||||||
|
);
|
||||||
|
return SelfTestStepResult.Fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pool.Instances.Select(w => w.SlotIndex).Distinct().Count() != pool.Instances.Count)
|
||||||
|
{
|
||||||
|
ImGui.Text("Pop-out windows do not have unique slot indices.");
|
||||||
|
return SelfTestStepResult.Fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free slots right now = capacity minus whatever real pop-outs are already
|
||||||
|
// bound. Testing against this (not capacity) keeps the step state-independent.
|
||||||
|
var free = capacity - pool.Instances.Count(w => w.Bound is not null);
|
||||||
|
|
||||||
|
// Round-trip on a throwaway tab, only when there's a slot to take. A bare Tab
|
||||||
|
// has CurrentChannel.Channel == Invalid + an empty SelectedChannels, so the
|
||||||
|
// pool's OnTabActivated strip is a no-op (no NRE), and the live active tab is
|
||||||
|
// passed only as `previous`, so it is never mutated. We close before
|
||||||
|
// returning, so the bound window never reaches a Draw frame.
|
||||||
|
if (free > 0)
|
||||||
|
{
|
||||||
|
var probe = new Tab { Name = "##selftest-popout-probe" };
|
||||||
|
if (pool.IsOpen(probe.Identifier))
|
||||||
|
{
|
||||||
|
ImGui.Text("Probe tab already open before TryOpen.");
|
||||||
|
return SelfTestStepResult.Fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pool.TryOpen(probe))
|
||||||
|
{
|
||||||
|
ImGui.Text("TryOpen returned false with a free slot.");
|
||||||
|
return SelfTestStepResult.Fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pool.IsOpen(probe.Identifier))
|
||||||
|
{
|
||||||
|
ImGui.Text("IsOpen is false right after a successful TryOpen.");
|
||||||
|
pool.TryClose(probe.Identifier);
|
||||||
|
return SelfTestStepResult.Fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
pool.TryClose(probe.Identifier);
|
||||||
|
if (pool.IsOpen(probe.Identifier))
|
||||||
|
{
|
||||||
|
ImGui.Text("IsOpen is still true after TryClose.");
|
||||||
|
return SelfTestStepResult.Fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Idempotent: closing an already-closed id is a silent no-op.
|
||||||
|
pool.TryClose(probe.Identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Capacity guard: fill the remaining free slots, then one more open must be
|
||||||
|
// refused (warn, no throw). Release everything we opened before reporting.
|
||||||
|
var fillers = Enumerable
|
||||||
|
.Range(0, free)
|
||||||
|
.Select(_ => new Tab { Name = "##selftest-fill" })
|
||||||
|
.ToList();
|
||||||
|
var opened = fillers.Count(pool.TryOpen);
|
||||||
|
var overflow = new Tab { Name = "##selftest-overflow" };
|
||||||
|
var overflowRejected = !pool.TryOpen(overflow);
|
||||||
|
|
||||||
|
foreach (var filler in fillers)
|
||||||
|
pool.TryClose(filler.Identifier);
|
||||||
|
pool.TryClose(overflow.Identifier);
|
||||||
|
|
||||||
|
if (opened != free)
|
||||||
|
{
|
||||||
|
ImGui.Text($"Filled only {opened}/{free} free slots before TryOpen refused.");
|
||||||
|
return SelfTestStepResult.Fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!overflowRejected)
|
||||||
|
{
|
||||||
|
ImGui.Text("Pool accepted an open beyond capacity instead of refusing.");
|
||||||
|
return SelfTestStepResult.Fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SelfTestStepResult.Pass;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CleanUp() { }
|
||||||
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
using Dalamud.Bindings.ImGui;
|
using Dalamud.Bindings.ImGui;
|
||||||
using Dalamud.Interface.Utility.Raii;
|
|
||||||
|
|
||||||
namespace HellionChat.Ui.Components.Settings.Tabs;
|
namespace HellionChat.Ui.Components.Settings.Tabs;
|
||||||
|
|
||||||
@@ -45,12 +44,11 @@ internal sealed class ChannelsTab
|
|||||||
() => Plugin.Config.AutoTellTabsShowGreetedToggle,
|
() => Plugin.Config.AutoTellTabsShowGreetedToggle,
|
||||||
v => Plugin.Config.AutoTellTabsShowGreetedToggle = v
|
v => Plugin.Config.AutoTellTabsShowGreetedToggle = v
|
||||||
);
|
);
|
||||||
// Popout is a v1.8.0 teaser — render disabled, do NOT persist.
|
DrawToggle(
|
||||||
using (ImRaii.Disabled(true))
|
"Open as popout",
|
||||||
{
|
() => Plugin.Config.AutoTellTabsOpenAsPopout,
|
||||||
var openAsPopout = Plugin.Config.AutoTellTabsOpenAsPopout;
|
v => Plugin.Config.AutoTellTabsOpenAsPopout = v
|
||||||
ImGui.Checkbox("Open as popout (lands in v1.8.0)", ref openAsPopout);
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui.CollapsingHeader("Tell auto-open mode", ImGuiTreeNodeFlags.DefaultOpen))
|
if (ImGui.CollapsingHeader("Tell auto-open mode", ImGuiTreeNodeFlags.DefaultOpen))
|
||||||
@@ -75,7 +73,7 @@ internal sealed class ChannelsTab
|
|||||||
|
|
||||||
private void DrawTellAutoOpenModeCombo()
|
private void DrawTellAutoOpenModeCombo()
|
||||||
{
|
{
|
||||||
var labels = new[] { "Off", "Sidebar", "Top tab", "Popout (lands in v1.8.0)" };
|
var labels = new[] { "Off", "Sidebar", "Top tab", "Popout" };
|
||||||
var values = Enum.GetValues<TellAutoOpenMode>();
|
var values = Enum.GetValues<TellAutoOpenMode>();
|
||||||
var current = Plugin.Config.TellAutoOpenMode;
|
var current = Plugin.Config.TellAutoOpenMode;
|
||||||
var selected = 0;
|
var selected = 0;
|
||||||
@@ -91,9 +89,7 @@ internal sealed class ChannelsTab
|
|||||||
ImGui.SetNextItemWidth(220);
|
ImGui.SetNextItemWidth(220);
|
||||||
if (ImGui.Combo("Tell auto-open mode", ref selected, labels, labels.Length))
|
if (ImGui.Combo("Tell auto-open mode", ref selected, labels, labels.Length))
|
||||||
{
|
{
|
||||||
// Popout (index 3) is a v1.8.0 teaser — revert to previous value
|
if (selected >= 0 && selected < values.Length)
|
||||||
// and skip SaveConfig.
|
|
||||||
if (selected >= 0 && selected < values.Length && selected != 3)
|
|
||||||
{
|
{
|
||||||
Plugin.Config.TellAutoOpenMode = values[selected];
|
Plugin.Config.TellAutoOpenMode = values[selected];
|
||||||
_plugin.SaveConfig();
|
_plugin.SaveConfig();
|
||||||
|
|||||||
Reference in New Issue
Block a user