performance cache + queued character data application
This commit is contained in:
@@ -20,6 +20,7 @@ public sealed partial class PairCoordinator : MediatorSubscriberBase
|
|||||||
private readonly PairManager _pairManager;
|
private readonly PairManager _pairManager;
|
||||||
private readonly PairLedger _pairLedger;
|
private readonly PairLedger _pairLedger;
|
||||||
private readonly ServerConfigurationManager _serverConfigurationManager;
|
private readonly ServerConfigurationManager _serverConfigurationManager;
|
||||||
|
private readonly PairPerformanceMetricsCache _metricsCache;
|
||||||
private readonly ConcurrentDictionary<string, OnlineUserCharaDataDto> _pendingCharacterData = new(StringComparer.Ordinal);
|
private readonly ConcurrentDictionary<string, OnlineUserCharaDataDto> _pendingCharacterData = new(StringComparer.Ordinal);
|
||||||
|
|
||||||
public PairCoordinator(
|
public PairCoordinator(
|
||||||
@@ -29,7 +30,8 @@ public sealed partial class PairCoordinator : MediatorSubscriberBase
|
|||||||
PairHandlerRegistry handlerRegistry,
|
PairHandlerRegistry handlerRegistry,
|
||||||
PairManager pairManager,
|
PairManager pairManager,
|
||||||
PairLedger pairLedger,
|
PairLedger pairLedger,
|
||||||
ServerConfigurationManager serverConfigurationManager)
|
ServerConfigurationManager serverConfigurationManager,
|
||||||
|
PairPerformanceMetricsCache metricsCache)
|
||||||
: base(logger, mediator)
|
: base(logger, mediator)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
@@ -39,6 +41,7 @@ public sealed partial class PairCoordinator : MediatorSubscriberBase
|
|||||||
_pairManager = pairManager;
|
_pairManager = pairManager;
|
||||||
_pairLedger = pairLedger;
|
_pairLedger = pairLedger;
|
||||||
_serverConfigurationManager = serverConfigurationManager;
|
_serverConfigurationManager = serverConfigurationManager;
|
||||||
|
_metricsCache = metricsCache;
|
||||||
|
|
||||||
mediator.Subscribe<ActiveServerChangedMessage>(this, msg => HandleActiveServerChange(msg.ServerUrl));
|
mediator.Subscribe<ActiveServerChangedMessage>(this, msg => HandleActiveServerChange(msg.ServerUrl));
|
||||||
mediator.Subscribe<DisconnectedMessage>(this, _ => HandleDisconnected());
|
mediator.Subscribe<DisconnectedMessage>(this, _ => HandleDisconnected());
|
||||||
@@ -128,6 +131,7 @@ public sealed partial class PairCoordinator : MediatorSubscriberBase
|
|||||||
_handlerRegistry.ResetAllHandlers();
|
_handlerRegistry.ResetAllHandlers();
|
||||||
_pairManager.ClearAll();
|
_pairManager.ClearAll();
|
||||||
_pendingCharacterData.Clear();
|
_pendingCharacterData.Clear();
|
||||||
|
_metricsCache.ClearAll();
|
||||||
_mediator.Publish(new ClearProfileUserDataMessage());
|
_mediator.Publish(new ClearProfileUserDataMessage());
|
||||||
_mediator.Publish(new ClearProfileGroupDataMessage());
|
_mediator.Publish(new ClearProfileGroupDataMessage());
|
||||||
PublishPairDataChanged(groupChanged: true);
|
PublishPairDataChanged(groupChanged: true);
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ public interface IPairHandlerAdapter : IDisposable, IPairPerformanceSubject
|
|||||||
void Initialize();
|
void Initialize();
|
||||||
void ApplyData(CharacterData data);
|
void ApplyData(CharacterData data);
|
||||||
void ApplyLastReceivedData(bool forced = false);
|
void ApplyLastReceivedData(bool forced = false);
|
||||||
|
bool FetchPerformanceMetricsFromCache();
|
||||||
void LoadCachedCharacterData(CharacterData data);
|
void LoadCachedCharacterData(CharacterData data);
|
||||||
void SetUploading(bool uploading);
|
void SetUploading(bool uploading);
|
||||||
void SetPaused(bool paused);
|
void SetPaused(bool paused);
|
||||||
@@ -67,6 +68,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
|
|||||||
private readonly PluginWarningNotificationService _pluginWarningNotificationManager;
|
private readonly PluginWarningNotificationService _pluginWarningNotificationManager;
|
||||||
private readonly TextureDownscaleService _textureDownscaleService;
|
private readonly TextureDownscaleService _textureDownscaleService;
|
||||||
private readonly PairStateCache _pairStateCache;
|
private readonly PairStateCache _pairStateCache;
|
||||||
|
private readonly PairPerformanceMetricsCache _performanceMetricsCache;
|
||||||
private readonly PairManager _pairManager;
|
private readonly PairManager _pairManager;
|
||||||
private CancellationTokenSource? _applicationCancellationTokenSource = new();
|
private CancellationTokenSource? _applicationCancellationTokenSource = new();
|
||||||
private Guid _applicationId;
|
private Guid _applicationId;
|
||||||
@@ -141,7 +143,8 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
|
|||||||
PairProcessingLimiter pairProcessingLimiter,
|
PairProcessingLimiter pairProcessingLimiter,
|
||||||
ServerConfigurationManager serverConfigManager,
|
ServerConfigurationManager serverConfigManager,
|
||||||
TextureDownscaleService textureDownscaleService,
|
TextureDownscaleService textureDownscaleService,
|
||||||
PairStateCache pairStateCache) : base(logger, mediator)
|
PairStateCache pairStateCache,
|
||||||
|
PairPerformanceMetricsCache performanceMetricsCache) : base(logger, mediator)
|
||||||
{
|
{
|
||||||
_pairManager = pairManager;
|
_pairManager = pairManager;
|
||||||
Ident = ident;
|
Ident = ident;
|
||||||
@@ -157,6 +160,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
|
|||||||
_serverConfigManager = serverConfigManager;
|
_serverConfigManager = serverConfigManager;
|
||||||
_textureDownscaleService = textureDownscaleService;
|
_textureDownscaleService = textureDownscaleService;
|
||||||
_pairStateCache = pairStateCache;
|
_pairStateCache = pairStateCache;
|
||||||
|
_performanceMetricsCache = performanceMetricsCache;
|
||||||
LastAppliedDataBytes = -1;
|
LastAppliedDataBytes = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -493,7 +497,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
|
|||||||
LastAppliedApproximateEffectiveVRAMBytes = -1;
|
LastAppliedApproximateEffectiveVRAMBytes = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
var sanitized = RemoveNotSyncedFiles(LastReceivedCharacterData.DeepClone());
|
var sanitized = CloneAndSanitizeLastReceived(out var dataHash);
|
||||||
if (sanitized is null)
|
if (sanitized is null)
|
||||||
{
|
{
|
||||||
Logger.LogTrace("Sanitized data null for {Ident}", Ident);
|
Logger.LogTrace("Sanitized data null for {Ident}", Ident);
|
||||||
@@ -513,6 +517,100 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
|
|||||||
ApplyCharacterData(Guid.NewGuid(), sanitized, shouldForce);
|
ApplyCharacterData(Guid.NewGuid(), sanitized, shouldForce);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool FetchPerformanceMetricsFromCache()
|
||||||
|
{
|
||||||
|
EnsureInitialized();
|
||||||
|
var sanitized = CloneAndSanitizeLastReceived(out var dataHash);
|
||||||
|
if (sanitized is null || string.IsNullOrEmpty(dataHash))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!TryApplyCachedMetrics(dataHash))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_cachedData = sanitized;
|
||||||
|
_pairStateCache.Store(Ident, sanitized);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private CharacterData? CloneAndSanitizeLastReceived(out string? dataHash)
|
||||||
|
{
|
||||||
|
dataHash = null;
|
||||||
|
if (LastReceivedCharacterData is null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var sanitized = RemoveNotSyncedFiles(LastReceivedCharacterData.DeepClone());
|
||||||
|
if (sanitized is null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
dataHash = GetDataHashSafe(sanitized);
|
||||||
|
return sanitized;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string? GetDataHashSafe(CharacterData data)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return data.DataHash.Value;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogDebug(ex, "Failed to compute character data hash for {Ident}", Ident);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryApplyCachedMetrics(string? dataHash)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(dataHash))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_performanceMetricsCache.TryGetMetrics(Ident, dataHash, out var metrics))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplyCachedMetrics(metrics);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyCachedMetrics(PairPerformanceMetrics metrics)
|
||||||
|
{
|
||||||
|
LastAppliedDataTris = metrics.TriangleCount;
|
||||||
|
LastAppliedApproximateVRAMBytes = metrics.ApproximateVramBytes;
|
||||||
|
LastAppliedApproximateEffectiveVRAMBytes = metrics.ApproximateEffectiveVramBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StorePerformanceMetrics(CharacterData charaData)
|
||||||
|
{
|
||||||
|
if (LastAppliedDataTris < 0
|
||||||
|
|| LastAppliedApproximateVRAMBytes < 0
|
||||||
|
|| LastAppliedApproximateEffectiveVRAMBytes < 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var dataHash = GetDataHashSafe(charaData);
|
||||||
|
if (string.IsNullOrEmpty(dataHash))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_performanceMetricsCache.StoreMetrics(
|
||||||
|
Ident,
|
||||||
|
dataHash,
|
||||||
|
new PairPerformanceMetrics(LastAppliedDataTris, LastAppliedApproximateVRAMBytes, LastAppliedApproximateEffectiveVRAMBytes));
|
||||||
|
}
|
||||||
|
|
||||||
private bool HasMissingCachedFiles(CharacterData characterData)
|
private bool HasMissingCachedFiles(CharacterData characterData)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -878,6 +976,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
|
|||||||
_cachedData = null;
|
_cachedData = null;
|
||||||
_lastAppliedModdedPaths = null;
|
_lastAppliedModdedPaths = null;
|
||||||
_needsCollectionRebuild = false;
|
_needsCollectionRebuild = false;
|
||||||
|
_performanceMetricsCache.Clear(Ident);
|
||||||
Logger.LogDebug("Disposing {name} complete", name);
|
Logger.LogDebug("Disposing {name} complete", name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1262,6 +1361,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
|
|||||||
await _playerPerformanceService.CheckTriangleUsageThresholds(this, charaData).ConfigureAwait(false);
|
await _playerPerformanceService.CheckTriangleUsageThresholds(this, charaData).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StorePerformanceMetrics(charaData);
|
||||||
Logger.LogDebug("[{applicationId}] Application finished", _applicationId);
|
Logger.LogDebug("[{applicationId}] Application finished", _applicationId);
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
@@ -1693,6 +1793,7 @@ internal sealed class PairHandlerAdapterFactory : IPairHandlerAdapterFactory
|
|||||||
private readonly ServerConfigurationManager _serverConfigManager;
|
private readonly ServerConfigurationManager _serverConfigManager;
|
||||||
private readonly TextureDownscaleService _textureDownscaleService;
|
private readonly TextureDownscaleService _textureDownscaleService;
|
||||||
private readonly PairStateCache _pairStateCache;
|
private readonly PairStateCache _pairStateCache;
|
||||||
|
private readonly PairPerformanceMetricsCache _pairPerformanceMetricsCache;
|
||||||
|
|
||||||
public PairHandlerAdapterFactory(
|
public PairHandlerAdapterFactory(
|
||||||
ILoggerFactory loggerFactory,
|
ILoggerFactory loggerFactory,
|
||||||
@@ -1709,7 +1810,8 @@ internal sealed class PairHandlerAdapterFactory : IPairHandlerAdapterFactory
|
|||||||
PairProcessingLimiter pairProcessingLimiter,
|
PairProcessingLimiter pairProcessingLimiter,
|
||||||
ServerConfigurationManager serverConfigManager,
|
ServerConfigurationManager serverConfigManager,
|
||||||
TextureDownscaleService textureDownscaleService,
|
TextureDownscaleService textureDownscaleService,
|
||||||
PairStateCache pairStateCache)
|
PairStateCache pairStateCache,
|
||||||
|
PairPerformanceMetricsCache pairPerformanceMetricsCache)
|
||||||
{
|
{
|
||||||
_loggerFactory = loggerFactory;
|
_loggerFactory = loggerFactory;
|
||||||
_mediator = mediator;
|
_mediator = mediator;
|
||||||
@@ -1726,6 +1828,7 @@ internal sealed class PairHandlerAdapterFactory : IPairHandlerAdapterFactory
|
|||||||
_serverConfigManager = serverConfigManager;
|
_serverConfigManager = serverConfigManager;
|
||||||
_textureDownscaleService = textureDownscaleService;
|
_textureDownscaleService = textureDownscaleService;
|
||||||
_pairStateCache = pairStateCache;
|
_pairStateCache = pairStateCache;
|
||||||
|
_pairPerformanceMetricsCache = pairPerformanceMetricsCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IPairHandlerAdapter Create(string ident)
|
public IPairHandlerAdapter Create(string ident)
|
||||||
@@ -1748,6 +1851,7 @@ internal sealed class PairHandlerAdapterFactory : IPairHandlerAdapterFactory
|
|||||||
_pairProcessingLimiter,
|
_pairProcessingLimiter,
|
||||||
_serverConfigManager,
|
_serverConfigManager,
|
||||||
_textureDownscaleService,
|
_textureDownscaleService,
|
||||||
_pairStateCache);
|
_pairStateCache,
|
||||||
|
_pairPerformanceMetricsCache);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,25 +10,32 @@ namespace LightlessSync.PlayerData.Pairs;
|
|||||||
public sealed class PairHandlerRegistry : IDisposable
|
public sealed class PairHandlerRegistry : IDisposable
|
||||||
{
|
{
|
||||||
private readonly object _gate = new();
|
private readonly object _gate = new();
|
||||||
|
private readonly object _pendingGate = new();
|
||||||
private readonly Dictionary<string, PairHandlerEntry> _entriesByIdent = new(StringComparer.Ordinal);
|
private readonly Dictionary<string, PairHandlerEntry> _entriesByIdent = new(StringComparer.Ordinal);
|
||||||
private readonly Dictionary<IPairHandlerAdapter, PairHandlerEntry> _entriesByHandler = new();
|
private readonly Dictionary<IPairHandlerAdapter, PairHandlerEntry> _entriesByHandler = new();
|
||||||
|
|
||||||
private readonly IPairHandlerAdapterFactory _handlerFactory;
|
private readonly IPairHandlerAdapterFactory _handlerFactory;
|
||||||
private readonly PairManager _pairManager;
|
private readonly PairManager _pairManager;
|
||||||
private readonly PairStateCache _pairStateCache;
|
private readonly PairStateCache _pairStateCache;
|
||||||
|
private readonly PairPerformanceMetricsCache _pairPerformanceMetricsCache;
|
||||||
private readonly ILogger<PairHandlerRegistry> _logger;
|
private readonly ILogger<PairHandlerRegistry> _logger;
|
||||||
|
|
||||||
private readonly TimeSpan _deletionGracePeriod = TimeSpan.FromMinutes(5);
|
private readonly TimeSpan _deletionGracePeriod = TimeSpan.FromMinutes(5);
|
||||||
|
private static readonly TimeSpan _handlerReadyTimeout = TimeSpan.FromMinutes(3);
|
||||||
|
private const int _handlerReadyPollDelayMs = 500;
|
||||||
|
private readonly Dictionary<string, CancellationTokenSource> _pendingCharacterData = new(StringComparer.Ordinal);
|
||||||
|
|
||||||
public PairHandlerRegistry(
|
public PairHandlerRegistry(
|
||||||
IPairHandlerAdapterFactory handlerFactory,
|
IPairHandlerAdapterFactory handlerFactory,
|
||||||
PairManager pairManager,
|
PairManager pairManager,
|
||||||
PairStateCache pairStateCache,
|
PairStateCache pairStateCache,
|
||||||
|
PairPerformanceMetricsCache pairPerformanceMetricsCache,
|
||||||
ILogger<PairHandlerRegistry> logger)
|
ILogger<PairHandlerRegistry> logger)
|
||||||
{
|
{
|
||||||
_handlerFactory = handlerFactory;
|
_handlerFactory = handlerFactory;
|
||||||
_pairManager = pairManager;
|
_pairManager = pairManager;
|
||||||
_pairStateCache = pairStateCache;
|
_pairStateCache = pairStateCache;
|
||||||
|
_pairPerformanceMetricsCache = pairPerformanceMetricsCache;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,7 +157,8 @@ public sealed class PairHandlerRegistry : IDisposable
|
|||||||
|
|
||||||
if (!TryGetHandler(registration.CharacterIdent, out handler) || handler is null)
|
if (!TryGetHandler(registration.CharacterIdent, out handler) || handler is null)
|
||||||
{
|
{
|
||||||
return PairOperationResult.Fail($"Handler not ready for {registration.PairIdent.UserId}.");
|
QueuePendingCharacterData(registration, dto);
|
||||||
|
return PairOperationResult.Ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,6 +293,8 @@ public sealed class PairHandlerRegistry : IDisposable
|
|||||||
_entriesByHandler.Clear();
|
_entriesByHandler.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CancelAllPendingCharacterData();
|
||||||
|
|
||||||
foreach (var handler in handlers)
|
foreach (var handler in handlers)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -298,6 +308,10 @@ public sealed class PairHandlerRegistry : IDisposable
|
|||||||
_logger.LogDebug(ex, "Failed to dispose handler for {Ident}", handler.Ident);
|
_logger.LogDebug(ex, "Failed to dispose handler for {Ident}", handler.Ident);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_pairPerformanceMetricsCache.Clear(handler.Ident);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -311,9 +325,12 @@ public sealed class PairHandlerRegistry : IDisposable
|
|||||||
_entriesByHandler.Clear();
|
_entriesByHandler.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CancelAllPendingCharacterData();
|
||||||
|
|
||||||
foreach (var handler in handlers)
|
foreach (var handler in handlers)
|
||||||
{
|
{
|
||||||
handler.Dispose();
|
handler.Dispose();
|
||||||
|
_pairPerformanceMetricsCache.Clear(handler.Ident);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -343,6 +360,7 @@ public sealed class PairHandlerRegistry : IDisposable
|
|||||||
|
|
||||||
private bool TryFinalizeHandlerRemoval(IPairHandlerAdapter handler)
|
private bool TryFinalizeHandlerRemoval(IPairHandlerAdapter handler)
|
||||||
{
|
{
|
||||||
|
string? ident = null;
|
||||||
lock (_gate)
|
lock (_gate)
|
||||||
{
|
{
|
||||||
if (!_entriesByHandler.TryGetValue(handler, out var entry) || entry.HasPairs)
|
if (!_entriesByHandler.TryGetValue(handler, out var entry) || entry.HasPairs)
|
||||||
@@ -351,9 +369,126 @@ public sealed class PairHandlerRegistry : IDisposable
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ident = entry.Ident;
|
||||||
_entriesByHandler.Remove(handler);
|
_entriesByHandler.Remove(handler);
|
||||||
_entriesByIdent.Remove(entry.Ident);
|
_entriesByIdent.Remove(entry.Ident);
|
||||||
return true;
|
}
|
||||||
|
|
||||||
|
if (ident is not null)
|
||||||
|
{
|
||||||
|
_pairPerformanceMetricsCache.Clear(ident);
|
||||||
|
CancelPendingCharacterData(ident);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void QueuePendingCharacterData(PairRegistration registration, OnlineUserCharaDataDto dto)
|
||||||
|
{
|
||||||
|
if (registration.CharacterIdent is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CancellationTokenSource? previous = null;
|
||||||
|
CancellationTokenSource cts;
|
||||||
|
lock (_pendingGate)
|
||||||
|
{
|
||||||
|
if (_pendingCharacterData.TryGetValue(registration.CharacterIdent, out previous))
|
||||||
|
{
|
||||||
|
previous.Cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
cts = new CancellationTokenSource();
|
||||||
|
_pendingCharacterData[registration.CharacterIdent] = cts;
|
||||||
|
}
|
||||||
|
|
||||||
|
previous?.Dispose();
|
||||||
|
cts.CancelAfter(_handlerReadyTimeout);
|
||||||
|
_ = Task.Run(() => WaitThenApplyPendingCharacterDataAsync(registration, dto, cts.Token, cts));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CancelPendingCharacterData(string ident)
|
||||||
|
{
|
||||||
|
CancellationTokenSource? cts = null;
|
||||||
|
lock (_pendingGate)
|
||||||
|
{
|
||||||
|
if (_pendingCharacterData.TryGetValue(ident, out cts))
|
||||||
|
{
|
||||||
|
_pendingCharacterData.Remove(ident);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cts is not null)
|
||||||
|
{
|
||||||
|
cts.Cancel();
|
||||||
|
cts.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CancelAllPendingCharacterData()
|
||||||
|
{
|
||||||
|
List<CancellationTokenSource>? snapshot = null;
|
||||||
|
lock (_pendingGate)
|
||||||
|
{
|
||||||
|
if (_pendingCharacterData.Count > 0)
|
||||||
|
{
|
||||||
|
snapshot = _pendingCharacterData.Values.ToList();
|
||||||
|
_pendingCharacterData.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (snapshot is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var cts in snapshot)
|
||||||
|
{
|
||||||
|
cts.Cancel();
|
||||||
|
cts.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task WaitThenApplyPendingCharacterDataAsync(
|
||||||
|
PairRegistration registration,
|
||||||
|
OnlineUserCharaDataDto dto,
|
||||||
|
CancellationToken token,
|
||||||
|
CancellationTokenSource source)
|
||||||
|
{
|
||||||
|
if (registration.CharacterIdent is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
while (!token.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
if (TryGetHandler(registration.CharacterIdent, out var handler) && handler is not null && handler.Initialized)
|
||||||
|
{
|
||||||
|
handler.ApplyData(dto.CharaData);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.Delay(_handlerReadyPollDelayMs, token).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
// expected
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
lock (_pendingGate)
|
||||||
|
{
|
||||||
|
if (_pendingCharacterData.TryGetValue(registration.CharacterIdent, out var current) && ReferenceEquals(current, source))
|
||||||
|
{
|
||||||
|
_pendingCharacterData.Remove(registration.CharacterIdent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
source.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -263,6 +263,11 @@ public sealed class PairLedger : DisposableMediatorSubscriberBase
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (handler.FetchPerformanceMetricsFromCache())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
handler.ApplyLastReceivedData(forced: true);
|
handler.ApplyLastReceivedData(forced: true);
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
using System.Collections.Concurrent;
|
||||||
|
|
||||||
|
namespace LightlessSync.PlayerData.Pairs;
|
||||||
|
|
||||||
|
public readonly record struct PairPerformanceMetrics(
|
||||||
|
long TriangleCount,
|
||||||
|
long ApproximateVramBytes,
|
||||||
|
long ApproximateEffectiveVramBytes);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// caches performance metrics keyed by pair ident
|
||||||
|
/// </summary>
|
||||||
|
public sealed class PairPerformanceMetricsCache
|
||||||
|
{
|
||||||
|
private sealed record CacheEntry(string DataHash, PairPerformanceMetrics Metrics);
|
||||||
|
|
||||||
|
private readonly ConcurrentDictionary<string, CacheEntry> _cache = new(StringComparer.Ordinal);
|
||||||
|
|
||||||
|
public bool TryGetMetrics(string ident, string dataHash, out PairPerformanceMetrics metrics)
|
||||||
|
{
|
||||||
|
metrics = default;
|
||||||
|
if (string.IsNullOrEmpty(ident) || string.IsNullOrEmpty(dataHash))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_cache.TryGetValue(ident, out var entry))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.Equals(entry.DataHash, dataHash, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
metrics = entry.Metrics;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StoreMetrics(string ident, string dataHash, PairPerformanceMetrics metrics)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(ident) || string.IsNullOrEmpty(dataHash))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_cache[ident] = new CacheEntry(dataHash, metrics);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear(string ident)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(ident))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_cache.TryRemove(ident, out _);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClearAll()
|
||||||
|
{
|
||||||
|
_cache.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -174,11 +174,13 @@ public sealed class Plugin : IDalamudPlugin
|
|||||||
s.GetRequiredService<LightlessConfigService>(), s.GetRequiredService<PlayerPerformanceConfigService>(), new Lazy<PairFactory>(() => s.GetRequiredService<PairFactory>())));
|
s.GetRequiredService<LightlessConfigService>(), s.GetRequiredService<PlayerPerformanceConfigService>(), new Lazy<PairFactory>(() => s.GetRequiredService<PairFactory>())));
|
||||||
collection.AddSingleton<PairManager>();
|
collection.AddSingleton<PairManager>();
|
||||||
collection.AddSingleton<PairStateCache>();
|
collection.AddSingleton<PairStateCache>();
|
||||||
|
collection.AddSingleton<PairPerformanceMetricsCache>();
|
||||||
collection.AddSingleton<IPairHandlerAdapterFactory, PairHandlerAdapterFactory>();
|
collection.AddSingleton<IPairHandlerAdapterFactory, PairHandlerAdapterFactory>();
|
||||||
collection.AddSingleton(s => new PairHandlerRegistry(
|
collection.AddSingleton(s => new PairHandlerRegistry(
|
||||||
s.GetRequiredService<IPairHandlerAdapterFactory>(),
|
s.GetRequiredService<IPairHandlerAdapterFactory>(),
|
||||||
s.GetRequiredService<PairManager>(),
|
s.GetRequiredService<PairManager>(),
|
||||||
s.GetRequiredService<PairStateCache>(),
|
s.GetRequiredService<PairStateCache>(),
|
||||||
|
s.GetRequiredService<PairPerformanceMetricsCache>(),
|
||||||
s.GetRequiredService<ILogger<PairHandlerRegistry>>()));
|
s.GetRequiredService<ILogger<PairHandlerRegistry>>()));
|
||||||
collection.AddSingleton<PairLedger>();
|
collection.AddSingleton<PairLedger>();
|
||||||
collection.AddSingleton<PairUiService>();
|
collection.AddSingleton<PairUiService>();
|
||||||
@@ -201,7 +203,8 @@ public sealed class Plugin : IDalamudPlugin
|
|||||||
s.GetRequiredService<PairHandlerRegistry>(),
|
s.GetRequiredService<PairHandlerRegistry>(),
|
||||||
s.GetRequiredService<PairManager>(),
|
s.GetRequiredService<PairManager>(),
|
||||||
s.GetRequiredService<PairLedger>(),
|
s.GetRequiredService<PairLedger>(),
|
||||||
s.GetRequiredService<ServerConfigurationManager>()));
|
s.GetRequiredService<ServerConfigurationManager>(),
|
||||||
|
s.GetRequiredService<PairPerformanceMetricsCache>()));
|
||||||
collection.AddSingleton<RedrawManager>();
|
collection.AddSingleton<RedrawManager>();
|
||||||
collection.AddSingleton<LightFinderService>();
|
collection.AddSingleton<LightFinderService>();
|
||||||
collection.AddSingleton(addonLifecycle);
|
collection.AddSingleton(addonLifecycle);
|
||||||
|
|||||||
Reference in New Issue
Block a user