The three sidebar/coupling steps add, insert and remove tabs straight from the framework thread while the message worker mutates the same list under TabsListLock. CurrentTabCouplingStep's Insert(0, ...) is the worst of them: it shifts every index, so DropOldestTempTab can remove the wrong tab between its index lookup and RemoveAt. Locks sit around the individual mutations, never around a Draw call, so no step holds the lock across rendering.
108 lines
4.2 KiB
C#
108 lines
4.2 KiB
C#
using Dalamud.Bindings.ImGui;
|
|
using Dalamud.Plugin.SelfTest;
|
|
using HellionChat.Code;
|
|
using HellionChat.GameFunctions.Types;
|
|
|
|
namespace HellionChat.SelfTests;
|
|
|
|
// B3-2: greeted glyph renders only for temp tabs when the toggle is on. Drives
|
|
// the REAL Sidebar.Draw (render precedent: HonorificHeaderRenderStep, the only
|
|
// real .Draw in this pool — NOT SidebarModeAutoSwitchStep which only calls
|
|
// IsExpanded/GetWidth) inside the /xlperf window frame and reads the render
|
|
// observability counter, then drives the real toggle hook both ways. Injects
|
|
// a temp tab and restores config in finally.
|
|
internal sealed class SidebarGreetedGlyphStep : ISelfTestStep
|
|
{
|
|
private readonly Plugin plugin;
|
|
|
|
public SidebarGreetedGlyphStep(Plugin plugin) => this.plugin = plugin;
|
|
|
|
public string Name => "Hellion Chat - Sidebar greeted glyph";
|
|
|
|
public SelfTestStepResult RunStep()
|
|
{
|
|
var sidebar = plugin.MainWindow.GetSidebarForSelfTest();
|
|
if (sidebar is null)
|
|
{
|
|
ImGui.Text("Sidebar null");
|
|
return SelfTestStepResult.Fail;
|
|
}
|
|
|
|
var savedFlag = Plugin.Config.AutoTellTabsShowGreetedToggle;
|
|
var savedSidebarWidth = Plugin.Config.SidebarWidth;
|
|
|
|
// Mirror of AutoTellTabsService.BuildTempTab (the real builder is
|
|
// private); only the sheet-based tab name is replaced with a literal.
|
|
var injected = new Tab
|
|
{
|
|
Name = "Greeted Probe@SelfTest",
|
|
IsTempTab = true,
|
|
AllSenderMessages = true,
|
|
TellTarget = new TellTarget("Greeted Probe", 0, 0, TellReason.Direct),
|
|
Channel = InputChannel.Tell,
|
|
DisplayTimestamp = true,
|
|
UnreadMode = UnreadMode.Unseen,
|
|
HideWhenInactive = false,
|
|
SelectedChannels = new Dictionary<ChatType, (ChatSource, ChatSource)>
|
|
{
|
|
[ChatType.TellIncoming] = (ChatSourceExt.All, ChatSourceExt.All),
|
|
[ChatType.TellOutgoing] = (ChatSourceExt.All, ChatSourceExt.All),
|
|
},
|
|
};
|
|
// Config.Tabs is mutated by the message worker under TabsListLock; a
|
|
// framework-thread writer must take the same lock.
|
|
lock (plugin.TabsListLock)
|
|
Plugin.Config.Tabs.Add(injected);
|
|
Tab? active = null;
|
|
var width = (float)Plugin.Config.SidebarAutoSwitchThresholdPx + 100f; // expanded
|
|
try
|
|
{
|
|
Plugin.Config.AutoTellTabsShowGreetedToggle = true;
|
|
// Default SidebarWidth (44px) has no room for the third hit area;
|
|
// pin a wide value so the glyph branch is reachable, restore after.
|
|
Plugin.Config.SidebarWidth = 220;
|
|
sidebar.Draw(width, Plugin.Config.Tabs, ref active);
|
|
if (sidebar.LastRenderedGreetedGlyphCount == 0)
|
|
{
|
|
ImGui.Text("No greeted glyph drawn with flag ON");
|
|
return SelfTestStepResult.Fail;
|
|
}
|
|
|
|
Plugin.Config.AutoTellTabsShowGreetedToggle = false;
|
|
sidebar.Draw(width, Plugin.Config.Tabs, ref active);
|
|
if (sidebar.LastRenderedGreetedGlyphCount != 0)
|
|
{
|
|
ImGui.Text("Greeted glyph drawn with flag OFF");
|
|
return SelfTestStepResult.Fail;
|
|
}
|
|
|
|
// Drive the same hook DrawRow's click handler uses (the real toggle
|
|
// path, not a direct MarkGreeted call) and assert the flip both ways.
|
|
sidebar.ToggleGreetedForSelfTest(injected);
|
|
if (!plugin.AutoTellTabsService.IsGreeted(injected))
|
|
{
|
|
ImGui.Text("Toggle did not mark the tab greeted");
|
|
return SelfTestStepResult.Fail;
|
|
}
|
|
|
|
sidebar.ToggleGreetedForSelfTest(injected);
|
|
if (plugin.AutoTellTabsService.IsGreeted(injected))
|
|
{
|
|
ImGui.Text("Toggle did not unmark the tab greeted");
|
|
return SelfTestStepResult.Fail;
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
lock (plugin.TabsListLock)
|
|
Plugin.Config.Tabs.Remove(injected);
|
|
Plugin.Config.AutoTellTabsShowGreetedToggle = savedFlag;
|
|
Plugin.Config.SidebarWidth = savedSidebarWidth;
|
|
}
|
|
|
|
return SelfTestStepResult.Pass;
|
|
}
|
|
|
|
public void CleanUp() { }
|
|
}
|