using LightlessSync.API.Dto.Group; using LightlessSync.Services.Mediator; using LightlessSync.UI.Models; using Microsoft.Extensions.Logging; namespace LightlessSync.PlayerData.Pairs; /// /// keeps pair info for ui and reapplication /// public sealed class PairLedger : DisposableMediatorSubscriberBase { private readonly PairManager _pairManager; private readonly PairHandlerRegistry _registry; private readonly ILogger _logger; private readonly object _metricsGate = new(); private CancellationTokenSource? _ensureMetricsCts; public PairLedger( ILogger logger, LightlessMediator mediator, PairManager pairManager, PairHandlerRegistry registry) : base(logger, mediator) { _pairManager = pairManager; _registry = registry; _logger = logger; Mediator.Subscribe(this, _ => ReapplyAll(forced: true)); Mediator.Subscribe(this, _ => ReapplyAll()); Mediator.Subscribe(this, _ => ReapplyAll(forced: true)); Mediator.Subscribe(this, _ => ReapplyAll(forced: true)); Mediator.Subscribe(this, _ => Reset()); Mediator.Subscribe(this, _ => ScheduleEnsureMetrics(TimeSpan.FromSeconds(2))); Mediator.Subscribe(this, _ => ScheduleEnsureMetrics(TimeSpan.FromSeconds(2))); Mediator.Subscribe(this, _ => ScheduleEnsureMetrics(TimeSpan.FromSeconds(2))); Mediator.Subscribe(this, _ => EnsureMetricsForVisiblePairs()); } public bool IsPairVisible(PairUniqueIdentifier pairIdent) { var connectionResult = _pairManager.GetPair(pairIdent.UserId); if (!connectionResult.Success) { return false; } var connection = connectionResult.Value; if (connection.Ident is null) { return false; } return _registry.IsIdentVisible(connection.Ident); } public IPairHandlerAdapter? GetHandler(PairUniqueIdentifier pairIdent) { var connectionResult = _pairManager.GetPair(pairIdent.UserId); if (!connectionResult.Success) { return null; } var connection = connectionResult.Value; if (connection.Ident is null) { return null; } return _registry.TryGetHandler(connection.Ident, out var handler) ? handler : null; } public IReadOnlyList GetVisiblePairs() { return _pairManager.GetAllPairs() .Select(kv => kv.Value) .Where(connection => connection.Ident is not null && _registry.IsIdentVisible(connection.Ident)) .ToList(); } public IReadOnlyList GetAllGroupInfos() { return _pairManager.GetAllGroups() .Select(kv => kv.Value.GroupFullInfo) .ToList(); } public IReadOnlyDictionary GetAllSyncshells() { return _pairManager.GetAllGroups(); } public void ReapplyAll(bool forced = false) { if (_logger.IsEnabled(LogLevel.Trace)) { _logger.LogTrace("Reapplying cached data for all handlers (forced: {Forced})", forced); } _registry.ReapplyAll(forced); } public void ReapplyPair(PairUniqueIdentifier pairIdent, bool forced = false) { var connectionResult = _pairManager.GetPair(pairIdent.UserId); if (!connectionResult.Success) { return; } var connection = connectionResult.Value; if (connection.Ident is null) { return; } var result = _registry.ApplyLastReceivedData(pairIdent, connection.Ident, forced); if (!result.Success && _logger.IsEnabled(LogLevel.Debug)) { _logger.LogDebug("Failed to reapply data for {UserId}: {Error}", pairIdent.UserId, result.Error); } } private void Reset() { if (_logger.IsEnabled(LogLevel.Trace)) { _logger.LogTrace("Resetting pair handlers after disconnect."); } CancelScheduledMetrics(); } public IReadOnlyList GetAllEntries() { var groups = _pairManager.GetAllGroups(); var list = new List(); foreach (var (userId, connection) in _pairManager.GetAllPairs()) { var ident = new PairUniqueIdentifier(userId); IPairHandlerAdapter? handler = null; if (connection.Ident is not null) { _registry.TryGetHandler(connection.Ident, out handler); } var groupInfos = connection.Groups.Keys .Select(gid => { if (groups.TryGetValue(gid, out var shell)) { return shell.GroupFullInfo; } return null; }) .Where(dto => dto is not null) .Cast() .ToList(); list.Add(new PairDisplayEntry(ident, connection, groupInfos, handler)); } return list; } public bool TryGetEntry(PairUniqueIdentifier ident, out PairDisplayEntry? entry) { entry = null; var connectionResult = _pairManager.GetPair(ident.UserId); if (!connectionResult.Success) { return false; } var connection = connectionResult.Value; var groups = connection.Groups.Keys .Select(gid => { var groupResult = _pairManager.GetGroup(gid); return groupResult.Success ? groupResult.Value.GroupFullInfo : null; }) .Where(dto => dto is not null) .Cast() .ToList(); IPairHandlerAdapter? handler = null; if (connection.Ident is not null) { _registry.TryGetHandler(connection.Ident, out handler); } entry = new PairDisplayEntry(ident, connection, groups, handler); return true; } private void ScheduleEnsureMetrics(TimeSpan? delay = null) { lock (_metricsGate) { _ensureMetricsCts?.Cancel(); var cts = new CancellationTokenSource(); _ensureMetricsCts = cts; _ = Task.Run(async () => { try { if (delay is { } d && d > TimeSpan.Zero) { await Task.Delay(d, cts.Token).ConfigureAwait(false); } EnsureMetricsForVisiblePairs(); } catch (OperationCanceledException) { // ignored } finally { lock (_metricsGate) { if (_ensureMetricsCts == cts) { _ensureMetricsCts = null; } } cts.Dispose(); } }); } } private void CancelScheduledMetrics() { lock (_metricsGate) { _ensureMetricsCts?.Cancel(); _ensureMetricsCts = null; } } private void EnsureMetricsForVisiblePairs() { var handlers = _registry.GetHandlerSnapshot(); foreach (var handler in handlers) { if (!handler.IsVisible) { continue; } if (handler.LastReceivedCharacterData is null) { continue; } if (handler.LastAppliedApproximateVRAMBytes >= 0 && handler.LastAppliedDataTris >= 0 && handler.LastAppliedApproximateEffectiveVRAMBytes >= 0) { continue; } if (handler.FetchPerformanceMetricsFromCache()) { continue; } try { handler.ApplyLastReceivedData(forced: true); } catch (Exception ex) { if (_logger.IsEnabled(LogLevel.Debug)) { _logger.LogDebug(ex, "Failed to ensure performance metrics for {Ident}", handler.Ident); } } } } protected override void Dispose(bool disposing) { if (disposing) { CancelScheduledMetrics(); } base.Dispose(disposing); } }