feat(autotell): wire temp-tab pop-outs to the channel-popout pool

This commit is contained in:
2026-06-16 00:39:01 +02:00
parent 8e2d333130
commit 7b6871fea4
+33 -15
View File
@@ -218,7 +218,7 @@ internal sealed class AutoTellTabsService : IDisposable
return null; return null;
} }
private static Tab? FindTempTab(string name, uint world) internal static Tab? FindTempTab(string name, uint world)
{ {
var byTarget = Plugin.Config.Tabs.FirstOrDefault(t => var byTarget = Plugin.Config.Tabs.FirstOrDefault(t =>
t.IsTempTab t.IsTempTab
@@ -256,21 +256,20 @@ internal sealed class AutoTellTabsService : IDisposable
return; return;
} }
// Pop-out-window cleanup is offline while the channel-popout pool
// is rebuilt — Tab.PopOut still flips on/off, the visible window
// disappears once the new pool comes online.
var dropped = victim.Tab; var dropped = victim.Tab;
Plugin.Config.Tabs.RemoveAt(victim.Index); Plugin.Config.Tabs.RemoveAt(victim.Index);
// Re-anchor the UI selection if it pointed at the dropped tab. This runs on // Re-anchor the UI selection if it pointed at the dropped tab, and close any
// the PendingMessage worker thread and the repair mutates the re-seeded // pop-out window the dropped tab owned. Both run on the PendingMessage worker
// tab's channel via OnTabActivated, so marshal it onto the framework thread // thread and touch window state the Draw path reads (OnTabActivated re-seed +
// to serialize with Draw (reference_dalamud_framework_thread) — otherwise a // the pool's Unbind), so marshal onto the framework thread to serialize with
// half-applied strip could race the input bar's send-routing read. // Draw (reference_dalamud_framework_thread). TryClose is idempotent: a tab that
// was never popped is a silent no-op.
Plugin.Framework.RunOnFrameworkThread(() => Plugin.Framework.RunOnFrameworkThread(() =>
_plugin.MainWindow?.ResetActiveTabIfRemoved(dropped) {
); _plugin.ChannelPopoutPool.TryClose(dropped.Identifier);
_plugin.MainWindow?.ResetActiveTabIfRemoved(dropped);
});
} }
private void SpawnTempTab((string Name, uint World) partner, Message currentMessage) private void SpawnTempTab((string Name, uint World) partner, Message currentMessage)
@@ -282,13 +281,28 @@ internal sealed class AutoTellTabsService : IDisposable
tab.AddMessage(currentMessage, unread: true); tab.AddMessage(currentMessage, unread: true);
// Open as pop-out if configured (set before Tabs.Add for next render-tick) // Open as pop-out if configured (flag set before Tabs.Add for the next render-tick).
if (Plugin.Config.AutoTellTabsOpenAsPopout) if (Plugin.Config.AutoTellTabsOpenAsPopout)
{ {
tab.PopOut = true; tab.PopOut = true;
} }
Plugin.Config.Tabs.Add(tab); Plugin.Config.Tabs.Add(tab);
// Actually open the pop-out window for the flagged tab — without this the
// flag was dead (a PopOut tab with no window). SpawnTempTab runs on the
// PendingMessage worker thread under _tempTabsLock; TryOpen does
// OnTabActivated + Bind (window state Draw reads), so marshal onto the
// framework thread. If the pool is full, drop the flag so it never claims a
// window it didn't get (flag/window parity).
if (tab.PopOut)
{
Plugin.Framework.RunOnFrameworkThread(() =>
{
if (!_plugin.ChannelPopoutPool.TryOpen(tab))
tab.PopOut = false;
});
}
} }
private static Tab BuildTempTab(string playerName, uint worldRowId) private static Tab BuildTempTab(string playerName, uint worldRowId)
@@ -427,8 +441,12 @@ internal sealed class AutoTellTabsService : IDisposable
.Config.Tabs.Where(t => TabLifecycleHelpers.IsInUnpinnedPool(t) && t.PopOut) .Config.Tabs.Where(t => TabLifecycleHelpers.IsInUnpinnedPool(t) && t.PopOut)
.Select(t => t.Identifier) .Select(t => t.Identifier)
.ToList(); .ToList();
// Pop-out-window cleanup is offline; see Disconnect path above.
_ = poppedTempTabIds; // Close each popped temp tab's window before the tabs leave the list.
// Logout is a framework-thread event (serialized with Draw), so no
// marshalling is needed here, unlike the worker-thread eviction path.
foreach (var id in poppedTempTabIds)
_plugin.ChannelPopoutPool.TryClose(id);
Plugin.Config.Tabs.RemoveAll(TabLifecycleHelpers.IsInUnpinnedPool); Plugin.Config.Tabs.RemoveAll(TabLifecycleHelpers.IsInUnpinnedPool);