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

@@ -205,6 +205,8 @@ public sealed class Plugin : IDalamudPlugin
collection.AddSingleton<ConfigurationSaveService>(); collection.AddSingleton<ConfigurationSaveService>();
collection.AddSingleton<HubFactory>(); collection.AddSingleton<HubFactory>();
collection.AddSingleton<NameplateHandler>(); collection.AddSingleton<NameplateHandler>();
collection.AddSingleton(s => new BroadcastScannerService( s.GetRequiredService<ILogger<BroadcastScannerService>>(), clientState, objectTable, framework, s.GetRequiredService<BroadcastService>(), s.GetRequiredService<LightlessMediator>(), s.GetRequiredService<NameplateHandler>(), s.GetRequiredService<DalamudUtilService>(), s.GetRequiredService<LightlessConfigService>()));
// add scoped services // add scoped services
collection.AddScoped<DrawEntityFactory>(); collection.AddScoped<DrawEntityFactory>();
@@ -227,8 +229,8 @@ public sealed class Plugin : IDalamudPlugin
s.GetRequiredService<LightlessMediator>(), s.GetRequiredService<ApiController>(), s.GetRequiredService<UiSharedService>(), s.GetRequiredService<FileDialogManager>(), s.GetRequiredService<LightlessMediator>(), s.GetRequiredService<ApiController>(), s.GetRequiredService<UiSharedService>(), s.GetRequiredService<FileDialogManager>(),
s.GetRequiredService<LightlessProfileManager>(), s.GetRequiredService<PerformanceCollectorService>())); s.GetRequiredService<LightlessProfileManager>(), s.GetRequiredService<PerformanceCollectorService>()));
collection.AddScoped<WindowMediatorSubscriberBase, PopupHandler>(); collection.AddScoped<WindowMediatorSubscriberBase, PopupHandler>();
collection.AddScoped<WindowMediatorSubscriberBase, BroadcastUI>((s) => new BroadcastUI(s.GetRequiredService<ILogger<BroadcastUI>>(), s.GetRequiredService<LightlessMediator>(), s.GetRequiredService<PerformanceCollectorService>(), s.GetRequiredService<BroadcastService>(), s.GetRequiredService<LightlessConfigService>(), s.GetRequiredService<UiSharedService>(), s.GetRequiredService<ApiController>(), s.GetRequiredService<NameplateService>())); collection.AddScoped<WindowMediatorSubscriberBase, BroadcastUI>((s) => new BroadcastUI(s.GetRequiredService<ILogger<BroadcastUI>>(), s.GetRequiredService<LightlessMediator>(), s.GetRequiredService<PerformanceCollectorService>(), s.GetRequiredService<BroadcastService>(), s.GetRequiredService<LightlessConfigService>(), s.GetRequiredService<UiSharedService>(), s.GetRequiredService<ApiController>(), s.GetRequiredService<BroadcastScannerService>()));
collection.AddScoped<WindowMediatorSubscriberBase, SyncshellFinderUI>((s) => new SyncshellFinderUI(s.GetRequiredService<ILogger<SyncshellFinderUI>>(), s.GetRequiredService<LightlessMediator>(), s.GetRequiredService<PerformanceCollectorService>(), s.GetRequiredService<BroadcastService>(), s.GetRequiredService<LightlessConfigService>(), s.GetRequiredService<UiSharedService>(), s.GetRequiredService<ApiController>(), s.GetRequiredService<NameplateService>())); collection.AddScoped<WindowMediatorSubscriberBase, SyncshellFinderUI>((s) => new SyncshellFinderUI(s.GetRequiredService<ILogger<SyncshellFinderUI>>(), s.GetRequiredService<LightlessMediator>(), s.GetRequiredService<PerformanceCollectorService>(), s.GetRequiredService<BroadcastService>(), s.GetRequiredService<LightlessConfigService>(), s.GetRequiredService<UiSharedService>(), s.GetRequiredService<ApiController>(), s.GetRequiredService<BroadcastScannerService>()));
collection.AddScoped<IPopupHandler, BanUserPopupHandler>(); collection.AddScoped<IPopupHandler, BanUserPopupHandler>();
collection.AddScoped<IPopupHandler, CensusPopupHandler>(); collection.AddScoped<IPopupHandler, CensusPopupHandler>();
collection.AddScoped<CacheCreationService>(); collection.AddScoped<CacheCreationService>();
@@ -246,7 +248,7 @@ public sealed class Plugin : IDalamudPlugin
pluginInterface, textureProvider, s.GetRequiredService<Dalamud.Localization>(), s.GetRequiredService<ServerConfigurationManager>(), s.GetRequiredService<TokenProvider>(), pluginInterface, textureProvider, s.GetRequiredService<Dalamud.Localization>(), s.GetRequiredService<ServerConfigurationManager>(), s.GetRequiredService<TokenProvider>(),
s.GetRequiredService<LightlessMediator>())); s.GetRequiredService<LightlessMediator>()));
collection.AddScoped((s) => new NameplateService(s.GetRequiredService<ILogger<NameplateService>>(), s.GetRequiredService<LightlessConfigService>(), namePlateGui, clientState, collection.AddScoped((s) => new NameplateService(s.GetRequiredService<ILogger<NameplateService>>(), s.GetRequiredService<LightlessConfigService>(), namePlateGui, clientState,
s.GetRequiredService<PairManager>(), s.GetRequiredService<BroadcastService>(), s.GetRequiredService<LightlessMediator>(), s.GetRequiredService<DalamudUtilService>(), s.GetRequiredService<NameplateHandler>(), s.GetRequiredService<IGameGui>())); s.GetRequiredService<PairManager>(), s.GetRequiredService<LightlessMediator>()));
collection.AddHostedService(p => p.GetRequiredService<ConfigurationSaveService>()); collection.AddHostedService(p => p.GetRequiredService<ConfigurationSaveService>());
collection.AddHostedService(p => p.GetRequiredService<LightlessMediator>()); collection.AddHostedService(p => p.GetRequiredService<LightlessMediator>());

View File

@@ -0,0 +1,216 @@
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Plugin.Services;
using LightlessSync.API.Dto.User;
using LightlessSync.LightlessConfiguration;
using LightlessSync.Services.Mediator;
using Microsoft.Extensions.Logging;
using System.Collections.Concurrent;
namespace LightlessSync.Services;
public class BroadcastScannerService : DisposableMediatorSubscriberBase, IDisposable
{
private readonly ILogger<BroadcastScannerService> _logger;
private readonly IObjectTable _objectTable;
private readonly IFramework _framework;
private readonly BroadcastService _broadcastService;
private readonly NameplateHandler _nameplateHandler;
private readonly ConcurrentDictionary<string, BroadcastEntry> _broadcastCache = new();
private readonly Queue<string> _lookupQueue = new();
private readonly HashSet<string> _lookupQueuedCids = new();
private readonly HashSet<string> _syncshellCids = new();
private static readonly TimeSpan MaxAllowedTtl = TimeSpan.FromMinutes(4);
private static readonly TimeSpan RetryDelay = TimeSpan.FromMinutes(1);
private readonly CancellationTokenSource _cleanupCts = new();
private Task? _cleanupTask;
private int _frameCounter = 0;
private int _lookupsThisFrame = 0;
private const int MaxLookupsPerFrame = 15;
private const int MaxQueueSize = 100;
public IReadOnlyDictionary<string, BroadcastEntry> BroadcastCache => _broadcastCache;
public readonly record struct BroadcastEntry(bool IsBroadcasting, DateTime ExpiryTime, string? GID);
public BroadcastScannerService(ILogger<BroadcastScannerService> logger,
IClientState clientState,
IObjectTable objectTable,
IFramework framework,
BroadcastService broadcastService,
LightlessMediator mediator,
NameplateHandler nameplateHandler,
DalamudUtilService dalamudUtil,
LightlessConfigService configService) : base(logger, mediator)
{
_logger = logger;
_objectTable = objectTable;
_broadcastService = broadcastService;
_nameplateHandler = nameplateHandler;
_logger = logger;
_framework = framework;
_framework.Update += OnFrameworkUpdate;
Mediator.Subscribe<BroadcastStatusChangedMessage>(this, OnBroadcastStatusChanged);
_cleanupTask = Task.Run(ExpiredBroadcastCleanupLoop);
_nameplateHandler.Init();
}
private void OnFrameworkUpdate(IFramework framework) => Update();
public void Update()
{
_frameCounter++;
_lookupsThisFrame = 0;
if (!_broadcastService.IsBroadcasting)
return;
var now = DateTime.UtcNow;
foreach (var obj in _objectTable)
{
if (obj is not IPlayerCharacter player || player.Address == IntPtr.Zero)
continue;
var cid = DalamudUtilService.GetHashedCIDFromPlayerPointer(player.Address);
var isStale = !_broadcastCache.TryGetValue(cid, out var entry) || entry.ExpiryTime <= now;
if (isStale && _lookupQueuedCids.Add(cid) && _lookupQueue.Count < MaxQueueSize)
_lookupQueue.Enqueue(cid);
}
if (_frameCounter % 2 == 0 && _lookupQueue.Count > 0)
{
var cidsToLookup = new List<string>();
while (_lookupQueue.Count > 0 && _lookupsThisFrame < MaxLookupsPerFrame)
{
var cid = _lookupQueue.Dequeue();
_lookupQueuedCids.Remove(cid);
cidsToLookup.Add(cid);
_lookupsThisFrame++;
}
if (cidsToLookup.Count > 0)
_ = BatchUpdateBroadcastCacheAsync(cidsToLookup);
}
}
private async Task BatchUpdateBroadcastCacheAsync(List<string> cids)
{
var results = await _broadcastService.AreUsersBroadcastingAsync(cids).ConfigureAwait(false);
var now = DateTime.UtcNow;
foreach (var (cid, info) in results)
{
if (string.IsNullOrWhiteSpace(cid) || info == null)
continue;
var ttl = info.IsBroadcasting && info.TTL.HasValue
? TimeSpan.FromTicks(Math.Min(info.TTL.Value.Ticks, MaxAllowedTtl.Ticks))
: RetryDelay;
var expiry = now + ttl;
_broadcastCache.AddOrUpdate(cid,
new BroadcastEntry(info.IsBroadcasting, expiry, info.GID),
(_, old) => new BroadcastEntry(info.IsBroadcasting, expiry, info.GID));
}
var activeCids = _broadcastCache
.Where(e => e.Value.IsBroadcasting && e.Value.ExpiryTime > now)
.Select(e => e.Key)
.ToList();
_nameplateHandler.UpdateBroadcastingCids(activeCids);
UpdateSyncshellBroadcasts();
}
private void OnBroadcastStatusChanged(BroadcastStatusChangedMessage msg)
{
if (!msg.Enabled)
{
_broadcastCache.Clear();
_lookupQueue.Clear();
_lookupQueuedCids.Clear();
_syncshellCids.Clear();
_nameplateHandler.UpdateBroadcastingCids(Enumerable.Empty<string>());
}
}
private void UpdateSyncshellBroadcasts()
{
var now = DateTime.UtcNow;
var newSet = _broadcastCache
.Where(e => e.Value.IsBroadcasting && e.Value.ExpiryTime > now && !string.IsNullOrEmpty(e.Value.GID))
.Select(e => e.Key)
.ToHashSet();
if (!_syncshellCids.SetEquals(newSet))
{
_syncshellCids.Clear();
foreach (var cid in newSet)
_syncshellCids.Add(cid);
Mediator.Publish(new SyncshellBroadcastsUpdatedMessage());
}
}
public List<BroadcastStatusInfoDto> GetActiveSyncshellBroadcasts()
{
var now = DateTime.UtcNow;
return _broadcastCache
.Where(e => e.Value.IsBroadcasting && e.Value.ExpiryTime > now && !string.IsNullOrEmpty(e.Value.GID))
.Select(e => new BroadcastStatusInfoDto
{
HashedCID = e.Key,
IsBroadcasting = true,
TTL = e.Value.ExpiryTime - now,
GID = e.Value.GID
})
.ToList();
}
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)
_broadcastCache.TryRemove(cid, out _);
}
}
}
catch (OperationCanceledException) { }
catch (Exception ex)
{
_logger.LogError(ex, "Broadcast cleanup loop crashed");
}
UpdateSyncshellBroadcasts();
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
_framework.Update -= OnFrameworkUpdate;
_cleanupCts.Cancel();
_cleanupTask?.Wait(100);
_nameplateHandler.Uninit();
}
}

View File

@@ -151,6 +151,7 @@ public class BroadcastService : IHostedService, IMediatorSubscriber
_config.Save(); _config.Save();
_mediator.Publish(new BroadcastStatusChangedMessage(false, null)); _mediator.Publish(new BroadcastStatusChangedMessage(false, null));
Mediator.Publish(new EventMessage(new Services.Events.Event(nameof(BroadcastService), Services.Events.EventSeverity.Informational,$"Disabled Lightfinder for Player: {msg.HashedCid}")));
return; return;
} }
@@ -166,6 +167,7 @@ public class BroadcastService : IHostedService, IMediatorSubscriber
_logger.LogInformation("Fetched TTL from server: {TTL}", remaining); _logger.LogInformation("Fetched TTL from server: {TTL}", remaining);
_mediator.Publish(new BroadcastStatusChangedMessage(true, remaining)); _mediator.Publish(new BroadcastStatusChangedMessage(true, remaining));
Mediator.Publish(new EventMessage(new Services.Events.Event(nameof(BroadcastService), Services.Events.EventSeverity.Informational, $"Enabled Lightfinder for Player: {msg.HashedCid}")));
} }
else else
{ {

View File

@@ -5,6 +5,7 @@ using FFXIVClientStructs.FFXIV.Client.System.Framework;
using FFXIVClientStructs.FFXIV.Client.UI; using FFXIVClientStructs.FFXIV.Client.UI;
using FFXIVClientStructs.FFXIV.Component.GUI; using FFXIVClientStructs.FFXIV.Component.GUI;
using LightlessSync.Services.Mediator; using LightlessSync.Services.Mediator;
using LightlessSync.UI;
using LightlessSync.Utils; using LightlessSync.Utils;
// Created using https://github.com/PunishedPineapple/Distance as a reference, thank you! // Created using https://github.com/PunishedPineapple/Distance as a reference, thank you!
@@ -205,6 +206,9 @@ public unsafe class NameplateHandler : IMediatorSubscriber
var nameContainer = nameplateObject.NameContainer; var nameContainer = nameplateObject.NameContainer;
var nameText = nameplateObject.NameText; var nameText = nameplateObject.NameText;
var labelColor = UIColors.Get("LightlessPurple");
var edgeColor = UIColors.Get("FullBlack");
var labelY = nameContainer->Height - nameplateObject.TextH - (int)(24 * nameText->AtkResNode.ScaleY); var labelY = nameContainer->Height - nameplateObject.TextH - (int)(24 * nameText->AtkResNode.ScaleY);
pNode->AtkResNode.SetPositionShort(58, (short)labelY); pNode->AtkResNode.SetPositionShort(58, (short)labelY);
@@ -213,15 +217,15 @@ public unsafe class NameplateHandler : IMediatorSubscriber
pNode->AtkResNode.Color.A = 255; pNode->AtkResNode.Color.A = 255;
pNode->TextColor.A = 255; pNode->TextColor.R = (byte)(labelColor.X * 255);
pNode->TextColor.R = 173; pNode->TextColor.G = (byte)(labelColor.Y * 255);
pNode->TextColor.G = 138; pNode->TextColor.B = (byte)(labelColor.Z * 255);
pNode->TextColor.B = 245; pNode->TextColor.A = (byte)(labelColor.W * 255);
pNode->EdgeColor.A = 255; pNode->EdgeColor.R = (byte)(edgeColor.X * 255);
pNode->EdgeColor.R = 0; pNode->EdgeColor.G = (byte)(edgeColor.Y * 255);
pNode->EdgeColor.G = 0; pNode->EdgeColor.B = (byte)(edgeColor.Z * 255);
pNode->EdgeColor.B = 0; pNode->EdgeColor.A = (byte)(edgeColor.W * 255);
pNode->FontSize = 24; pNode->FontSize = 24;
pNode->AlignmentType = AlignmentType.Center; pNode->AlignmentType = AlignmentType.Center;

View File

@@ -3,13 +3,11 @@ using Dalamud.Game.Gui.NamePlate;
using Dalamud.Game.Text.SeStringHandling; using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using Dalamud.Utility; using Dalamud.Utility;
using LightlessSync.API.Dto.User;
using LightlessSync.LightlessConfiguration; using LightlessSync.LightlessConfiguration;
using LightlessSync.PlayerData.Pairs; using LightlessSync.PlayerData.Pairs;
using LightlessSync.Services.Mediator; using LightlessSync.Services.Mediator;
using LightlessSync.UI; using LightlessSync.UI;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System.Collections.Concurrent;
namespace LightlessSync.Services; namespace LightlessSync.Services;
@@ -20,80 +18,28 @@ public class NameplateService : DisposableMediatorSubscriberBase
private readonly IClientState _clientState; private readonly IClientState _clientState;
private readonly INamePlateGui _namePlateGui; private readonly INamePlateGui _namePlateGui;
private readonly PairManager _pairManager; 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, public NameplateService(ILogger<NameplateService> logger,
LightlessConfigService configService, LightlessConfigService configService,
INamePlateGui namePlateGui, INamePlateGui namePlateGui,
IClientState clientState, IClientState clientState,
PairManager pairManager, PairManager pairManager,
BroadcastService broadcastService, LightlessMediator lightlessMediator) : base(logger, lightlessMediator)
LightlessMediator lightlessMediator,
DalamudUtilService dalamudUtil,
NameplateHandler nameplatehandler,
IGameGui gameGui) : base(logger, lightlessMediator)
{ {
_logger = logger; _logger = logger;
_configService = configService; _configService = configService;
_namePlateGui = namePlateGui; _namePlateGui = namePlateGui;
_clientState = clientState; _clientState = clientState;
_pairManager = pairManager; _pairManager = pairManager;
_broadcastService = broadcastService;
_dalamudUtil = dalamudUtil;
_nameplatehandler = nameplatehandler;
_gameGui = gameGui;
_namePlateGui.OnNamePlateUpdate += OnNamePlateUpdate; _namePlateGui.OnNamePlateUpdate += OnNamePlateUpdate;
_namePlateGui.RequestRedraw(); _namePlateGui.RequestRedraw();
Mediator.Subscribe<VisibilityChange>(this, (_) => _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) private void OnNamePlateUpdate(INamePlateUpdateContext context, IReadOnlyList<INamePlateUpdateHandler> handlers)
{ {
_frameCounter++;
_lookupsThisFrame = 0;
if (!_configService.Current.IsNameplateColorsEnabled || (_configService.Current.IsNameplateColorsEnabled && _clientState.IsPvPExcludingDen)) if (!_configService.Current.IsNameplateColorsEnabled || (_configService.Current.IsNameplateColorsEnabled && _clientState.IsPvPExcludingDen))
return; return;
@@ -112,11 +58,6 @@ public class NameplateService : DisposableMediatorSubscriberBase
if (playerCharacter == null) if (playerCharacter == null)
continue; 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 isInParty = playerCharacter.StatusFlags.HasFlag(StatusFlags.PartyMember);
var isFriend = playerCharacter.StatusFlags.HasFlag(StatusFlags.Friend); var isFriend = playerCharacter.StatusFlags.HasFlag(StatusFlags.Friend);
bool partyColorAllowed = (_configService.Current.overridePartyColor && isInParty); bool partyColorAllowed = (_configService.Current.overridePartyColor && isInParty);
@@ -132,167 +73,7 @@ public class NameplateService : DisposableMediatorSubscriberBase
handler.NameParts.TextWrap = CreateTextWrap(colors); 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() public void RequestRedraw()
@@ -319,11 +100,7 @@ public class NameplateService : DisposableMediatorSubscriberBase
{ {
base.Dispose(disposing); base.Dispose(disposing);
_cleanupCts.Cancel();
_cleanupTask?.Wait(100);
_namePlateGui.OnNamePlateUpdate -= OnNamePlateUpdate; _namePlateGui.OnNamePlateUpdate -= OnNamePlateUpdate;
_namePlateGui.RequestRedraw(); _namePlateGui.RequestRedraw();
_nameplatehandler.Uninit();
} }
} }

View File

@@ -17,7 +17,7 @@ namespace LightlessSync.UI
private readonly LightlessConfigService _configService; private readonly LightlessConfigService _configService;
private readonly BroadcastService _broadcastService; private readonly BroadcastService _broadcastService;
private readonly UiSharedService _uiSharedService; private readonly UiSharedService _uiSharedService;
private readonly NameplateService _nameplateService; private readonly BroadcastScannerService _broadcastScannerService;
private IReadOnlyList<GroupFullInfoDto> _allSyncshells; private IReadOnlyList<GroupFullInfoDto> _allSyncshells;
private string _userUid = string.Empty; private string _userUid = string.Empty;
@@ -32,20 +32,20 @@ namespace LightlessSync.UI
LightlessConfigService configService, LightlessConfigService configService,
UiSharedService uiShared, UiSharedService uiShared,
ApiController apiController, ApiController apiController,
NameplateService nameplateService BroadcastScannerService broadcastScannerService
) : base(logger, mediator, "Lightfinder###LightlessLightfinderUI", performanceCollectorService) ) : base(logger, mediator, "Lightfinder###LightlessLightfinderUI", performanceCollectorService)
{ {
_broadcastService = broadcastService; _broadcastService = broadcastService;
_uiSharedService = uiShared; _uiSharedService = uiShared;
_configService = configService; _configService = configService;
_apiController = apiController; _apiController = apiController;
_nameplateService = nameplateService; _broadcastScannerService = broadcastScannerService;
IsOpen = false; IsOpen = false;
this.SizeConstraints = new() this.SizeConstraints = new()
{ {
MinimumSize = new(590, 340), MinimumSize = new(600, 340),
MaximumSize = new(590, 340) MaximumSize = new(750, 400)
}; };
mediator.Subscribe<RefreshUiMessage>(this, async _ => await RefreshSyncshells()); mediator.Subscribe<RefreshUiMessage>(this, async _ => await RefreshSyncshells());
@@ -126,9 +126,7 @@ namespace LightlessSync.UI
{ {
if (!_broadcastService.IsLightFinderAvailable) if (!_broadcastService.IsLightFinderAvailable)
{ {
ImGui.PushStyleColor(ImGuiCol.Text, UIColors.Get("LightlessYellow")); _uiSharedService.MediumText("This server doesn't support Lightfinder.", UIColors.Get("LightlessYellow"));
ImGui.TextWrapped("This server doesn't support LightFinder.");
ImGui.PopStyleColor();
ImGuiHelpers.ScaledDummy(0.25f); ImGuiHelpers.ScaledDummy(0.25f);
} }
@@ -203,7 +201,7 @@ namespace LightlessSync.UI
else else
ImGui.PushStyleColor(ImGuiCol.Button, UIColors.Get("PairBlue")); ImGui.PushStyleColor(ImGuiCol.Button, UIColors.Get("PairBlue"));
if (isOnCooldown) if (isOnCooldown || !_broadcastService.IsLightFinderAvailable)
ImGui.BeginDisabled(); ImGui.BeginDisabled();
string buttonText = isBroadcasting ? "Disable Lightfinder" : "Enable Lightfinder"; string buttonText = isBroadcasting ? "Disable Lightfinder" : "Enable Lightfinder";
@@ -213,7 +211,7 @@ namespace LightlessSync.UI
_broadcastService.ToggleBroadcast(); _broadcastService.ToggleBroadcast();
} }
if (isOnCooldown) if (isOnCooldown || !_broadcastService.IsLightFinderAvailable)
ImGui.EndDisabled(); ImGui.EndDisabled();
ImGui.PopStyleColor(); ImGui.PopStyleColor();
@@ -316,7 +314,7 @@ namespace LightlessSync.UI
{ {
ImGui.Text("Broadcast Cache"); ImGui.Text("Broadcast Cache");
if (ImGui.BeginTable("##BroadcastCacheTable", 4, ImGuiTableFlags.RowBg | ImGuiTableFlags.Borders | ImGuiTableFlags.ScrollY, new Vector2(-1, 200f))) if (ImGui.BeginTable("##BroadcastCacheTable", 4, ImGuiTableFlags.RowBg | ImGuiTableFlags.Borders | ImGuiTableFlags.ScrollY, new Vector2(-1, 225f)))
{ {
ImGui.TableSetupColumn("CID", ImGuiTableColumnFlags.WidthStretch); ImGui.TableSetupColumn("CID", ImGuiTableColumnFlags.WidthStretch);
ImGui.TableSetupColumn("IsBroadcasting", ImGuiTableColumnFlags.WidthStretch); ImGui.TableSetupColumn("IsBroadcasting", ImGuiTableColumnFlags.WidthStretch);
@@ -326,7 +324,7 @@ namespace LightlessSync.UI
var now = DateTime.UtcNow; var now = DateTime.UtcNow;
foreach (var (cid, entry) in _nameplateService.BroadcastCache) foreach (var (cid, entry) in _broadcastScannerService.BroadcastCache)
{ {
ImGui.TableNextRow(); ImGui.TableNextRow();

View File

@@ -23,7 +23,7 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
private readonly LightlessConfigService _configService; private readonly LightlessConfigService _configService;
private readonly BroadcastService _broadcastService; private readonly BroadcastService _broadcastService;
private readonly UiSharedService _uiSharedService; private readonly UiSharedService _uiSharedService;
private readonly NameplateService _nameplateService; private readonly BroadcastScannerService _broadcastScannerService;
private readonly List<GroupJoinDto> _nearbySyncshells = new(); private readonly List<GroupJoinDto> _nearbySyncshells = new();
private int _selectedNearbyIndex = -1; private int _selectedNearbyIndex = -1;
@@ -40,23 +40,24 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
LightlessConfigService configService, LightlessConfigService configService,
UiSharedService uiShared, UiSharedService uiShared,
ApiController apiController, ApiController apiController,
NameplateService nameplateService BroadcastScannerService broadcastScannerService
) : base(logger, mediator, "Shellfinder###LightlessSyncshellFinderUI", performanceCollectorService) ) : base(logger, mediator, "Shellfinder###LightlessSyncshellFinderUI", performanceCollectorService)
{ {
_broadcastService = broadcastService; _broadcastService = broadcastService;
_uiSharedService = uiShared; _uiSharedService = uiShared;
_configService = configService; _configService = configService;
_apiController = apiController; _apiController = apiController;
_nameplateService = nameplateService; _broadcastScannerService = broadcastScannerService;
IsOpen = false; IsOpen = false;
SizeConstraints = new() SizeConstraints = new()
{ {
MinimumSize = new(600, 400), MinimumSize = new(600, 400),
MaximumSize = new(600, 400) MaximumSize = new(600, 550)
}; };
Mediator.Subscribe<SyncshellBroadcastsUpdatedMessage>(this, async _ => await RefreshSyncshellsAsync()); Mediator.Subscribe<SyncshellBroadcastsUpdatedMessage>(this, async _ => await RefreshSyncshellsAsync());
Mediator.Subscribe<BroadcastStatusChangedMessage>(this, async _ => await RefreshSyncshellsAsync());
} }
public override async void OnOpen() public override async void OnOpen()
@@ -222,7 +223,7 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
private async Task RefreshSyncshellsAsync() private async Task RefreshSyncshellsAsync()
{ {
var syncshellBroadcasts = _nameplateService.GetActiveSyncshellBroadcasts(); var syncshellBroadcasts = _broadcastScannerService.GetActiveSyncshellBroadcasts();
if (syncshellBroadcasts.Count == 0) if (syncshellBroadcasts.Count == 0)
{ {

View File

@@ -13,6 +13,7 @@ namespace LightlessSync.UI
{ "LightlessPurpleDefault", "#9375d1" }, { "LightlessPurpleDefault", "#9375d1" },
{ "ButtonDefault", "#323232" }, { "ButtonDefault", "#323232" },
{ "FullBlack", "#000000" },
{ "LightlessBlue", "#a6c2ff" }, { "LightlessBlue", "#a6c2ff" },
{ "LightlessYellow", "#ffe97a" }, { "LightlessYellow", "#ffe97a" },