perf(autotell): replace lock-protected count with Interlocked counter
F2.1: ActiveTempTabCount was doing a LINQ Count under _tempTabsLock on every read, including the hot-path HandleTell guard. Replace with an Interlocked counter kept in sync with Config.Tabs from inside the existing mutation paths (SpawnTempTab, DropOldestTempTab, OnLogout). Initialize from the persisted Tabs list on Initialize() to handle configs that already contain TempTabs from a prior session. Plugin.cs SaveConfig snapshot-restore mutates Config.Tabs outside of AutoTellTabsService; expose ResyncTempTabCounter() and call it after AddRange so the counter stays consistent. Plugin.cs:168 crash-recovery RemoveAll runs before Initialize() and is covered by the init snapshot.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Dalamud.Game.Text;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using HellionChat.Code;
|
||||
@@ -19,6 +20,14 @@ internal sealed class AutoTellTabsService : IDisposable
|
||||
private readonly MessageStore _store;
|
||||
private readonly object _tempTabsLock = new();
|
||||
|
||||
// F2.1: lock-free counter mirrors Config.Tabs.Count(IsTempTab) so the
|
||||
// hot-path getter doesn't contend with HandleTell on every render frame.
|
||||
// Bumped from inside the existing mutation paths so it stays consistent
|
||||
// with the underlying list — see SpawnTempTab, DropOldestTempTab, OnLogout
|
||||
// and ResyncTempTabCounter (used by Plugin.cs snapshot-restore).
|
||||
// TEST-MIRROR: ../../Hellion Build test/_Helpers/TempTabCounterTests.cs
|
||||
private int _activeTempTabCount;
|
||||
|
||||
private bool _initialized;
|
||||
|
||||
internal AutoTellTabsService(Plugin plugin, MessageManager messageManager, MessageStore store)
|
||||
@@ -28,16 +37,7 @@ internal sealed class AutoTellTabsService : IDisposable
|
||||
_store = store;
|
||||
}
|
||||
|
||||
internal int ActiveTempTabCount
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_tempTabsLock)
|
||||
{
|
||||
return Plugin.Config.Tabs.Count(t => t.IsTempTab);
|
||||
}
|
||||
}
|
||||
}
|
||||
internal int ActiveTempTabCount => Volatile.Read(ref _activeTempTabCount);
|
||||
|
||||
internal void Initialize()
|
||||
{
|
||||
@@ -46,11 +46,31 @@ internal sealed class AutoTellTabsService : IDisposable
|
||||
return;
|
||||
}
|
||||
|
||||
// Seed the counter from the persisted Tabs list so a config that already
|
||||
// contains TempTabs from a prior session starts in sync. Plugin.cs:168
|
||||
// crash-recovery has already dropped TempTabs by the time we get here,
|
||||
// so the snapshot reflects post-recovery reality.
|
||||
Interlocked.Exchange(
|
||||
ref _activeTempTabCount,
|
||||
Plugin.Config.Tabs.Count(t => t.IsTempTab)
|
||||
);
|
||||
|
||||
_messageManager.MessageProcessed += HandleTell;
|
||||
Plugin.ClientState.Logout += OnLogout;
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
// F2.1: callable from outside paths that mutate Config.Tabs directly
|
||||
// (Plugin.cs snapshot-restore). Atomically re-pegs the counter to the
|
||||
// live IsTempTab count.
|
||||
internal void ResyncTempTabCounter()
|
||||
{
|
||||
Interlocked.Exchange(
|
||||
ref _activeTempTabCount,
|
||||
Plugin.Config.Tabs.Count(t => t.IsTempTab)
|
||||
);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_initialized)
|
||||
@@ -184,6 +204,7 @@ internal sealed class AutoTellTabsService : IDisposable
|
||||
}
|
||||
|
||||
Plugin.Config.Tabs.RemoveAt(victim.Index);
|
||||
Interlocked.Decrement(ref _activeTempTabCount);
|
||||
|
||||
// Re-anchor active tab to avoid silent switch when tab is dropped
|
||||
if (victim.Index <= _plugin.LastTab)
|
||||
@@ -208,6 +229,7 @@ internal sealed class AutoTellTabsService : IDisposable
|
||||
}
|
||||
|
||||
Plugin.Config.Tabs.Add(tab);
|
||||
Interlocked.Increment(ref _activeTempTabCount);
|
||||
}
|
||||
|
||||
private static Tab BuildTempTab(string playerName, uint worldRowId)
|
||||
@@ -361,7 +383,8 @@ internal sealed class AutoTellTabsService : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
Plugin.Config.Tabs.RemoveAll(t => t.IsTempTab);
|
||||
var removed = Plugin.Config.Tabs.RemoveAll(t => t.IsTempTab);
|
||||
Interlocked.Add(ref _activeTempTabCount, -removed);
|
||||
|
||||
// Force switch to tab 0 if active tab was temp or index is now out of range
|
||||
var stillValid = lastIndex >= 0 && lastIndex < Plugin.Config.Tabs.Count;
|
||||
|
||||
Reference in New Issue
Block a user