seperate scanning service not relying on nameplate updates & other improvements/fixes

This commit is contained in:
2025-09-24 22:28:32 +09:00
parent d91f1a3356
commit 7569b15993
8 changed files with 253 additions and 252 deletions

View File

@@ -3,13 +3,11 @@ using Dalamud.Game.Gui.NamePlate;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Plugin.Services;
using Dalamud.Utility;
using LightlessSync.API.Dto.User;
using LightlessSync.LightlessConfiguration;
using LightlessSync.PlayerData.Pairs;
using LightlessSync.Services.Mediator;
using LightlessSync.UI;
using Microsoft.Extensions.Logging;
using System.Collections.Concurrent;
namespace LightlessSync.Services;
@@ -20,80 +18,28 @@ public class NameplateService : DisposableMediatorSubscriberBase
private readonly IClientState _clientState;
private readonly INamePlateGui _namePlateGui;
private readonly PairManager _pairManager;
private readonly BroadcastService _broadcastService;
private readonly DalamudUtilService _dalamudUtil;
private readonly NameplateHandler _nameplatehandler;
private readonly IFramework _framework;
private readonly IGameGui _gameGui;
private readonly ConcurrentDictionary<string, BroadcastEntry> _broadcastCache = new();
private static readonly TimeSpan MaxAllowedTtl = TimeSpan.FromMinutes(5);
private static readonly TimeSpan RetryDelay = TimeSpan.FromMinutes(1);
private readonly Queue<string> _lookupQueue = new();
private readonly HashSet<string> _lookupQueuedCids = new();
private readonly HashSet<string> _syncshellCids = new();
private readonly CancellationTokenSource _cleanupCts = new();
private Task? _cleanupTask;
private const int MaxLookupsPerFrame = 15;
private const int MaxQueueSize = 100;
private int _lookupsThisFrame = 0;
private int _frameCounter = 0;
public IReadOnlyDictionary<string, BroadcastEntry> BroadcastCache => _broadcastCache;
public readonly struct BroadcastEntry
{
public readonly bool IsBroadcasting;
public readonly DateTime ExpiryTime;
public readonly bool PrefixApplied;
public readonly string? GID;
public BroadcastEntry(bool isBroadcasting, DateTime expiryTime, bool prefixApplied, string? gid = null)
{
IsBroadcasting = isBroadcasting;
ExpiryTime = expiryTime;
PrefixApplied = prefixApplied;
GID = gid;
}
}
public NameplateService(ILogger<NameplateService> logger,
LightlessConfigService configService,
INamePlateGui namePlateGui,
IClientState clientState,
PairManager pairManager,
BroadcastService broadcastService,
LightlessMediator lightlessMediator,
DalamudUtilService dalamudUtil,
NameplateHandler nameplatehandler,
IGameGui gameGui) : base(logger, lightlessMediator)
LightlessMediator lightlessMediator) : base(logger, lightlessMediator)
{
_logger = logger;
_configService = configService;
_namePlateGui = namePlateGui;
_clientState = clientState;
_pairManager = pairManager;
_broadcastService = broadcastService;
_dalamudUtil = dalamudUtil;
_nameplatehandler = nameplatehandler;
_gameGui = gameGui;
_namePlateGui.OnNamePlateUpdate += OnNamePlateUpdate;
_namePlateGui.RequestRedraw();
Mediator.Subscribe<VisibilityChange>(this, (_) => _namePlateGui.RequestRedraw());
Mediator.Subscribe<BroadcastStatusChangedMessage>(this, OnBroadcastStatusChanged);
_nameplatehandler.Init();
_cleanupTask = Task.Run(ExpiredBroadcastCleanupLoop);
}
private void OnNamePlateUpdate(INamePlateUpdateContext context, IReadOnlyList<INamePlateUpdateHandler> handlers)
{
_frameCounter++;
_lookupsThisFrame = 0;
if (!_configService.Current.IsNameplateColorsEnabled || (_configService.Current.IsNameplateColorsEnabled && _clientState.IsPvPExcludingDen))
return;
@@ -112,11 +58,6 @@ public class NameplateService : DisposableMediatorSubscriberBase
if (playerCharacter == null)
continue;
var cid = DalamudUtilService.GetHashedCIDFromPlayerPointer(playerCharacter.Address);
var hasEntry = _broadcastCache.TryGetValue(cid, out var entry);
var isEntryStale = !hasEntry || entry.ExpiryTime <= now;
var isInParty = playerCharacter.StatusFlags.HasFlag(StatusFlags.PartyMember);
var isFriend = playerCharacter.StatusFlags.HasFlag(StatusFlags.Friend);
bool partyColorAllowed = (_configService.Current.overridePartyColor && isInParty);
@@ -132,167 +73,7 @@ public class NameplateService : DisposableMediatorSubscriberBase
handler.NameParts.TextWrap = CreateTextWrap(colors);
}
if (!_broadcastService.IsBroadcasting)
continue;
if (isEntryStale && _lookupQueuedCids.Add(cid) && _lookupQueue.Count < MaxQueueSize)
{
_lookupQueue.Enqueue(cid);
}
}
if (_broadcastService.IsBroadcasting && _frameCounter % 2 == 0)
{
var cidsToLookup = new List<string>();
while (_lookupQueue.Count > 0 && _lookupsThisFrame < MaxLookupsPerFrame)
{
var nextCid = _lookupQueue.Dequeue();
_lookupQueuedCids.Remove(nextCid);
cidsToLookup.Add(nextCid);
_lookupsThisFrame++;
}
if (cidsToLookup.Count > 0)
_ = BatchUpdateBroadcastCacheAsync(cidsToLookup);
}
}
private async Task BatchUpdateBroadcastCacheAsync(List<string> cidList)
{
var results = await _broadcastService.AreUsersBroadcastingAsync(cidList).ConfigureAwait(false);
var now = DateTime.UtcNow;
foreach (var (cid, info) in results)
{
if (string.IsNullOrWhiteSpace(cid) || info == null)
{
_logger.LogWarning("Skipping broadcast entry: cid={Cid}, info=null or empty", cid);
continue;
}
bool isBroadcasting = info.IsBroadcasting;
TimeSpan effectiveTtl = isBroadcasting && info.TTL.HasValue
? TimeSpan.FromTicks(Math.Min(info.TTL.Value.Ticks, MaxAllowedTtl.Ticks))
: RetryDelay;
var expiryTime = now + effectiveTtl;
_broadcastCache.AddOrUpdate(cid,
new BroadcastEntry(isBroadcasting, expiryTime, false, info.GID),
(_, old) => new BroadcastEntry(isBroadcasting, expiryTime, old.PrefixApplied, info.GID));
}
var activeCids = _broadcastCache
.Where(kvp => kvp.Value.IsBroadcasting)
.Select(kvp => kvp.Key)
.ToList();
_nameplatehandler.UpdateBroadcastingCids(activeCids);
_namePlateGui.RequestRedraw();
UpdateSyncshellBroadcasts();
}
private void OnBroadcastStatusChanged(BroadcastStatusChangedMessage msg)
{
if (!msg.Enabled)
{
_logger.LogInformation("Broadcast disabled, clearing prefix cache and queue");
_broadcastCache.Clear();
_lookupQueue.Clear();
_lookupQueuedCids.Clear();
_syncshellCids.Clear();
_nameplatehandler.UpdateBroadcastingCids(Enumerable.Empty<string>());
_namePlateGui.RequestRedraw();
}
}
public List<BroadcastStatusInfoDto> GetActiveSyncshellBroadcasts()
{
var now = DateTime.UtcNow;
return _broadcastCache
.Where(kvp =>
kvp.Value.IsBroadcasting &&
kvp.Value.ExpiryTime > now &&
!string.IsNullOrEmpty(kvp.Value.GID))
.Select(kvp => new BroadcastStatusInfoDto
{
HashedCID = kvp.Key,
IsBroadcasting = true,
TTL = kvp.Value.ExpiryTime - now,
GID = kvp.Value.GID
})
.ToList();
}
private void UpdateSyncshellBroadcasts()
{
var now = DateTime.UtcNow;
var newSet = _broadcastCache
.Where(kvp => kvp.Value.IsBroadcasting && kvp.Value.ExpiryTime > now && !string.IsNullOrEmpty(kvp.Value.GID))
.Select(kvp => kvp.Key)
.ToHashSet();
if (!_syncshellCids.SetEquals(newSet))
{
_syncshellCids.Clear();
foreach (var cid in newSet)
_syncshellCids.Add(cid);
_logger.LogInformation("Syncshell broadcast entries changed, sending update lol");
Mediator.Publish(new SyncshellBroadcastsUpdatedMessage());
}
}
public bool IsBroadcastingKnown(string cidHash, out bool isBroadcasting)
{
if (_broadcastCache.TryGetValue(cidHash, out var entry))
{
isBroadcasting = entry.IsBroadcasting;
return true;
}
isBroadcasting = false;
return false;
}
private async Task ExpiredBroadcastCleanupLoop()
{
var token = _cleanupCts.Token;
try
{
while (!token.IsCancellationRequested)
{
await Task.Delay(TimeSpan.FromSeconds(10), token);
var now = DateTime.UtcNow;
foreach (var (cid, entry) in _broadcastCache.ToArray())
{
if (entry.ExpiryTime <= now)
{
if (_broadcastCache.TryRemove(cid, out _))
{
_logger.LogInformation("Removed expired broadcast entry: {Cid}", cid);
}
}
}
}
}
catch (OperationCanceledException)
{
}
catch (Exception ex)
{
_logger.LogError(ex, "Error in ExpiredBroadcastCleanupLoop");
}
UpdateSyncshellBroadcasts();
}
public void RequestRedraw()
@@ -319,11 +100,7 @@ public class NameplateService : DisposableMediatorSubscriberBase
{
base.Dispose(disposing);
_cleanupCts.Cancel();
_cleanupTask?.Wait(100);
_namePlateGui.OnNamePlateUpdate -= OnNamePlateUpdate;
_namePlateGui.RequestRedraw();
_nameplatehandler.Uninit();
}
}