From 28d9110cb061be2ee95df021b476315aef722377 Mon Sep 17 00:00:00 2001 From: azyges Date: Tue, 25 Nov 2025 09:42:34 +0900 Subject: [PATCH] Restore logic --- .../PlayerData/Pairs/PairHandlerAdapter.cs | 375 ++++-------------- .../Pairs/VisibleUserDataDistributor.cs | 169 ++------ 2 files changed, 114 insertions(+), 430 deletions(-) diff --git a/LightlessSync/PlayerData/Pairs/PairHandlerAdapter.cs b/LightlessSync/PlayerData/Pairs/PairHandlerAdapter.cs index a0239c2..f00176d 100644 --- a/LightlessSync/PlayerData/Pairs/PairHandlerAdapter.cs +++ b/LightlessSync/PlayerData/Pairs/PairHandlerAdapter.cs @@ -70,8 +70,6 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa private readonly TextureDownscaleService _textureDownscaleService; private readonly PairStateCache _pairStateCache; private readonly PairManager _pairManager; - private Guid _currentDownloadOwnerToken; - private bool _downloadInProgress; private CancellationTokenSource? _applicationCancellationTokenSource = new(); private Guid _applicationId; private Task? _applicationTask; @@ -81,7 +79,6 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa private CombatData? _dataReceivedInDowntime; private CancellationTokenSource? _downloadCancellationTokenSource = new(); private bool _forceApplyMods = false; - private bool _forceFullReapply; private bool _isVisible; private Guid _penumbraCollection; private readonly object _collectionGate = new(); @@ -90,7 +87,6 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa private readonly object _pauseLock = new(); private Task _pauseTransitionTask = Task.CompletedTask; private bool _pauseRequested; - private int _restoreRequested; public string Ident { get; } public bool Initialized { get; private set; } @@ -106,7 +102,6 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa _isVisible = value; if (!_isVisible) { - ResetRestoreState(); DisableSync(); ResetPenumbraCollection(reason: "VisibilityLost"); } @@ -226,19 +221,14 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa Mediator.Subscribe(this, _ => EnableSync()); Mediator.Subscribe(this, _ => DisableSync()); Mediator.Subscribe(this, _ => EnableSync()); - Mediator.Subscribe(this, msg => + Mediator.Subscribe(this, msg => + { + if (_charaHandler is null || !ReferenceEquals(msg.DownloadId, _charaHandler)) { - if (_charaHandler is null || !ReferenceEquals(msg.DownloadId, _charaHandler)) - { - return; - } - - if (_downloadManager.CurrentOwnerToken.HasValue - && _downloadManager.CurrentOwnerToken == _currentDownloadOwnerToken) - { - TryApplyQueuedData(); - } - }); + return; + } + TryApplyQueuedData(); + }); Initialized = true; } @@ -446,7 +436,6 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa { EnsureInitialized(); LastReceivedCharacterData = data; - ResetRestoreState(); ApplyLastReceivedData(); } @@ -458,10 +447,8 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa } LastReceivedCharacterData = data; - ResetRestoreState(); _cachedData = null; _forceApplyMods = true; - _forceFullReapply = true; LastAppliedDataBytes = -1; LastAppliedDataTris = -1; LastAppliedApproximateVRAMBytes = -1; @@ -474,10 +461,6 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa if (LastReceivedCharacterData is null) { Logger.LogTrace("No cached data to apply for {Ident}", Ident); - if (forced) - { - EnsureRestoredStateWhileWaitingForData("ForcedReapplyWithoutCache", skipIfAlreadyRestored: false); - } return; } @@ -493,7 +476,6 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa { _forceApplyMods = true; _cachedData = null; - _forceFullReapply = true; LastAppliedDataBytes = -1; LastAppliedDataTris = -1; LastAppliedApproximateVRAMBytes = -1; @@ -513,7 +495,6 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa { Logger.LogTrace("Handler for {Ident} not visible, caching sanitized data for later", Ident); _cachedData = sanitized; - _forceFullReapply = true; return; } @@ -620,118 +601,6 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa return data; } - private void ResetRestoreState() - { - Volatile.Write(ref _restoreRequested, 0); - } - - private void EnsureRestoredStateWhileWaitingForData(string reason, bool skipIfAlreadyRestored = true) - { - if (!IsVisible || _charaHandler is null || _charaHandler.Address == nint.Zero) - { - return; - } - - if (_cachedData is not null || LastReceivedCharacterData is not null) - { - return; - } - - if (!skipIfAlreadyRestored) - { - ResetRestoreState(); - } - else if (Volatile.Read(ref _restoreRequested) == 1) - { - return; - } - - if (Interlocked.CompareExchange(ref _restoreRequested, 1, 0) != 0) - { - return; - } - - var applicationId = Guid.NewGuid(); - _ = Task.Run(async () => - { - try - { - Logger.LogDebug("[{applicationId}] Restoring vanilla state for {handler} while waiting for data ({reason})", applicationId, GetLogIdentifier(), reason); - await RevertToRestoredAsync(applicationId).ConfigureAwait(false); - } - catch (Exception ex) - { - Logger.LogDebug(ex, "[{applicationId}] Failed to restore vanilla state for {handler} ({reason})", applicationId, GetLogIdentifier(), reason); - ResetRestoreState(); - } - }); - } - - private static Dictionary> BuildFullChangeSet(CharacterData characterData) - { - var result = new Dictionary>(); - - foreach (var objectKind in Enum.GetValues()) - { - var changes = new HashSet(); - - if (characterData.FileReplacements.TryGetValue(objectKind, out var replacements) && replacements.Count > 0) - { - changes.Add(PlayerChanges.ModFiles); - if (objectKind == ObjectKind.Player) - { - changes.Add(PlayerChanges.ForcedRedraw); - } - } - - if (characterData.GlamourerData.TryGetValue(objectKind, out var glamourer) && !string.IsNullOrEmpty(glamourer)) - { - changes.Add(PlayerChanges.Glamourer); - } - - if (characterData.CustomizePlusData.TryGetValue(objectKind, out var customize) && !string.IsNullOrEmpty(customize)) - { - changes.Add(PlayerChanges.Customize); - } - - if (objectKind == ObjectKind.Player) - { - if (!string.IsNullOrEmpty(characterData.ManipulationData)) - { - changes.Add(PlayerChanges.ModManip); - changes.Add(PlayerChanges.ForcedRedraw); - } - - if (!string.IsNullOrEmpty(characterData.HeelsData)) - { - changes.Add(PlayerChanges.Heels); - } - - if (!string.IsNullOrEmpty(characterData.HonorificData)) - { - changes.Add(PlayerChanges.Honorific); - } - - if (!string.IsNullOrEmpty(characterData.MoodlesData)) - { - changes.Add(PlayerChanges.Moodles); - } - - if (!string.IsNullOrEmpty(characterData.PetNamesData)) - { - changes.Add(PlayerChanges.PetNames); - } - } - - if (changes.Count > 0) - { - result[objectKind] = changes; - } - } - - return result; - } - private bool CanApplyNow() { return !_dalamudUtil.IsInCombat @@ -825,7 +694,6 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa this, forceApplyCustomization, forceApplyMods: false) .Any(p => p.Value.Contains(PlayerChanges.ModManip) || p.Value.Contains(PlayerChanges.ModFiles)); _forceApplyMods = hasDiffMods || _forceApplyMods || _cachedData == null; - _forceFullReapply = true; _cachedData = characterData; Logger.LogDebug("[BASE-{appBase}] Setting data: {hash}, forceApplyMods: {force}", applicationBase, _cachedData.DataHash.Value, _forceApplyMods); } @@ -847,11 +715,6 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa var charaDataToUpdate = characterData.CheckUpdatedData(applicationBase, _cachedData?.DeepClone() ?? new(), Logger, this, forceApplyCustomization, _forceApplyMods); - if (_forceFullReapply) - { - charaDataToUpdate = BuildFullChangeSet(characterData); - } - if (handlerReady && _forceApplyMods) { _forceApplyMods = false; @@ -870,9 +733,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa Logger.LogDebug("[BASE-{appbase}] Downloading and applying character for {name}", applicationBase, GetPrimaryAliasOrUidSafe()); - var forcesReapply = _forceFullReapply || forceApplyCustomization || LastAppliedApproximateVRAMBytes < 0 || LastAppliedDataTris < 0; - - DownloadAndApplyCharacter(applicationBase, characterData.DeepClone(), charaDataToUpdate, forcesReapply, forceApplyCustomization); + DownloadAndApplyCharacter(applicationBase, characterData.DeepClone(), charaDataToUpdate); } public override string ToString() @@ -1064,185 +925,113 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa } } - private void DownloadAndApplyCharacter(Guid applicationBase, CharacterData charaData, Dictionary> updatedData, bool forcePerformanceRecalc, bool forceApplyCustomization) + private void DownloadAndApplyCharacter(Guid applicationBase, CharacterData charaData, Dictionary> updatedData) { if (!updatedData.Any()) { - if (forcePerformanceRecalc) - { - updatedData = BuildFullChangeSet(charaData); - } - - if (!updatedData.Any()) - { - Logger.LogDebug("[BASE-{appBase}] Nothing to update for {obj}", applicationBase, GetLogIdentifier()); - _forceFullReapply = false; - return; - } + Logger.LogDebug("[BASE-{appBase}] Nothing to update for {obj}", applicationBase, GetLogIdentifier()); + return; } var updateModdedPaths = updatedData.Values.Any(v => v.Any(p => p == PlayerChanges.ModFiles)); var updateManip = updatedData.Values.Any(v => v.Any(p => p == PlayerChanges.ModManip)); - if (_downloadInProgress) - { - Logger.LogDebug("[BASE-{appBase}] Download already in progress for {handler}, queueing data", applicationBase, GetLogIdentifier()); - EnqueueDeferredCharacterData(charaData, forceApplyCustomization || _forceApplyMods); - return; - } - _downloadCancellationTokenSource = _downloadCancellationTokenSource?.CancelRecreate() ?? new CancellationTokenSource(); var downloadToken = _downloadCancellationTokenSource.Token; - var downloadOwnerToken = Guid.NewGuid(); - _currentDownloadOwnerToken = downloadOwnerToken; - _downloadInProgress = true; - - _ = DownloadAndApplyCharacterAsync(applicationBase, charaData, updatedData, updateModdedPaths, updateManip, forcePerformanceRecalc, downloadOwnerToken, downloadToken).ConfigureAwait(false); + _ = DownloadAndApplyCharacterAsync(applicationBase, charaData, updatedData, updateModdedPaths, updateManip, downloadToken).ConfigureAwait(false); } private Task? _pairDownloadTask; private async Task DownloadAndApplyCharacterAsync(Guid applicationBase, CharacterData charaData, Dictionary> updatedData, - bool updateModdedPaths, bool updateManip, bool forcePerformanceRecalc, Guid downloadOwnerToken, CancellationToken downloadToken) + bool updateModdedPaths, bool updateManip, CancellationToken downloadToken) { - try + await using var concurrencyLease = await _pairProcessingLimiter.AcquireAsync(downloadToken).ConfigureAwait(false); + Dictionary<(string GamePath, string? Hash), string> moddedPaths = []; + bool skipDownscaleForPair = ShouldSkipDownscale(); + var user = GetPrimaryUserData(); + + if (updateModdedPaths) { - await using var concurrencyLease = await _pairProcessingLimiter.AcquireAsync(downloadToken).ConfigureAwait(false); - Dictionary<(string GamePath, string? Hash), string> moddedPaths = []; - bool skipDownscaleForPair = ShouldSkipDownscale(); - var user = GetPrimaryUserData(); + int attempts = 0; + List toDownloadReplacements = TryCalculateModdedDictionary(applicationBase, charaData, out moddedPaths, downloadToken); - bool performedDownload = false; - - if (updateModdedPaths) + while (toDownloadReplacements.Count > 0 && attempts++ <= 10 && !downloadToken.IsCancellationRequested) { - int attempts = 0; - List toDownloadReplacements = TryCalculateModdedDictionary(applicationBase, charaData, out moddedPaths, downloadToken); - Logger.LogDebug("[BASE-{appBase}] Initial missing files for {handler}: {count}", applicationBase, GetLogIdentifier(), toDownloadReplacements.Count); - - while (toDownloadReplacements.Count > 0 && attempts++ <= 10 && !downloadToken.IsCancellationRequested) + if (_pairDownloadTask != null && !_pairDownloadTask.IsCompleted) { - if (_pairDownloadTask != null && !_pairDownloadTask.IsCompleted) - { - Logger.LogDebug("[BASE-{appBase}] Finishing prior running download task for player {name}, {kind}", applicationBase, PlayerName, updatedData); - await _pairDownloadTask.ConfigureAwait(false); - } + Logger.LogDebug("[BASE-{appBase}] Finishing prior running download task for player {name}, {kind}", applicationBase, PlayerName, updatedData); + await _pairDownloadTask.ConfigureAwait(false); + } - Logger.LogDebug("[BASE-{appBase}] Downloading missing files for player {name}, {kind}", applicationBase, PlayerName, updatedData); + Logger.LogDebug("[BASE-{appBase}] Downloading missing files for player {name}, {kind}", applicationBase, PlayerName, updatedData); - Mediator.Publish(new EventMessage(new Event(PlayerName, user, nameof(PairHandlerAdapter), EventSeverity.Informational, - $"Starting download for {toDownloadReplacements.Count} files"))); - var currentHandler = _charaHandler; - var toDownloadFiles = await _downloadManager.InitiateDownloadList(currentHandler, toDownloadReplacements, downloadToken, downloadOwnerToken).ConfigureAwait(false); - Logger.LogDebug("[BASE-{appBase}] Download plan prepared for {handler}: {current} transfers, forbidden so far: {forbidden}", applicationBase, GetLogIdentifier(), toDownloadFiles.Count, _downloadManager.ForbiddenTransfers.Count); + Mediator.Publish(new EventMessage(new Event(PlayerName, user, nameof(PairHandlerAdapter), EventSeverity.Informational, + $"Starting download for {toDownloadReplacements.Count} files"))); + var toDownloadFiles = await _downloadManager.InitiateDownloadList(_charaHandler!, toDownloadReplacements, downloadToken).ConfigureAwait(false); if (!_playerPerformanceService.ComputeAndAutoPauseOnVRAMUsageThresholds(this, charaData, toDownloadFiles)) { _downloadManager.ClearDownload(); - MarkApplicationDeferred(charaData); return; } - performedDownload = true; - - var handlerForDownload = currentHandler; - _pairDownloadTask = Task.Run(async () => await _downloadManager.DownloadFiles(handlerForDownload, toDownloadReplacements, downloadToken, skipDownscaleForPair).ConfigureAwait(false)); + var handlerForDownload = _charaHandler; + _pairDownloadTask = Task.Run(async () => await _downloadManager.DownloadFiles(handlerForDownload, toDownloadReplacements, downloadToken, skipDownscaleForPair).ConfigureAwait(false)); await _pairDownloadTask.ConfigureAwait(false); if (downloadToken.IsCancellationRequested) { Logger.LogTrace("[BASE-{appBase}] Detected cancellation", applicationBase); - MarkApplicationDeferred(charaData); return; } - toDownloadReplacements = TryCalculateModdedDictionary(applicationBase, charaData, out moddedPaths, downloadToken); + toDownloadReplacements = TryCalculateModdedDictionary(applicationBase, charaData, out moddedPaths, downloadToken); - if (toDownloadReplacements.TrueForAll(c => _downloadManager.ForbiddenTransfers.Exists(f => string.Equals(f.Hash, c.Hash, StringComparison.Ordinal)))) - { - break; - } - - await Task.Delay(TimeSpan.FromSeconds(2), downloadToken).ConfigureAwait(false); - Logger.LogDebug("[BASE-{appBase}] Re-evaluating missing files for {handler}: {count} remaining after attempt {attempt}", applicationBase, GetLogIdentifier(), toDownloadReplacements.Count, attempts); - } - - if (!performedDownload) - { - if (!_playerPerformanceService.ComputeAndAutoPauseOnVRAMUsageThresholds(this, charaData, new List())) + if (toDownloadReplacements.TrueForAll(c => _downloadManager.ForbiddenTransfers.Exists(f => string.Equals(f.Hash, c.Hash, StringComparison.Ordinal)))) { - _downloadManager.ClearDownload(); - MarkApplicationDeferred(charaData); - return; + break; } + + await Task.Delay(TimeSpan.FromSeconds(2), downloadToken).ConfigureAwait(false); } if (!await _playerPerformanceService.CheckBothThresholds(this, charaData).ConfigureAwait(false)) { - MarkApplicationDeferred(charaData); return; } } - else if (forcePerformanceRecalc) + + downloadToken.ThrowIfCancellationRequested(); + + var handlerForApply = _charaHandler; + if (handlerForApply is null || handlerForApply.Address == nint.Zero) { - if (!_playerPerformanceService.ComputeAndAutoPauseOnVRAMUsageThresholds(this, charaData, new List())) - { - MarkApplicationDeferred(charaData); - return; - } - - if (!await _playerPerformanceService.CheckBothThresholds(this, charaData).ConfigureAwait(false)) - { - MarkApplicationDeferred(charaData); - return; - } + Logger.LogDebug("[BASE-{appBase}] Handler not available for {player}, cached data for later application", applicationBase, GetLogIdentifier()); + _cachedData = charaData; + _pairStateCache.Store(Ident, charaData); + return; } - downloadToken.ThrowIfCancellationRequested(); - - var handlerForApply = _charaHandler; - if (handlerForApply is null || handlerForApply.Address == nint.Zero) - { - Logger.LogDebug("[BASE-{appBase}] Handler not available for {player}, cached data for later application", applicationBase, GetLogIdentifier()); - _cachedData = charaData; - _pairStateCache.Store(Ident, charaData); - _forceFullReapply = true; - MarkApplicationDeferred(charaData); - return; - } - - var appToken = _applicationCancellationTokenSource?.Token; - while ((!_applicationTask?.IsCompleted ?? false) - && !downloadToken.IsCancellationRequested - && (!appToken?.IsCancellationRequested ?? false)) - { - // block until current application is done - Logger.LogDebug("[BASE-{appBase}] Waiting for current data application (Id: {id}) for player ({handler}) to finish", applicationBase, _applicationId, PlayerName); - await Task.Delay(250).ConfigureAwait(false); - } - - if (downloadToken.IsCancellationRequested || (appToken?.IsCancellationRequested ?? false)) - { - MarkApplicationDeferred(charaData); - return; - } - - _applicationCancellationTokenSource = _applicationCancellationTokenSource.CancelRecreate() ?? new CancellationTokenSource(); - var token = _applicationCancellationTokenSource.Token; - - _forceFullReapply = false; - _applicationTask = ApplyCharacterDataAsync(applicationBase, handlerForApply, charaData, updatedData, updateModdedPaths, updateManip, moddedPaths, token); - } - catch (OperationCanceledException ex) when (downloadToken.IsCancellationRequested || ex.CancellationToken == downloadToken) + var appToken = _applicationCancellationTokenSource?.Token; + while ((!_applicationTask?.IsCompleted ?? false) + && !downloadToken.IsCancellationRequested + && (!appToken?.IsCancellationRequested ?? false)) { - Logger.LogDebug("[BASE-{appBase}] Download cancelled for {handler}", applicationBase, GetLogIdentifier()); - MarkApplicationDeferred(charaData); + Logger.LogDebug("[BASE-{appBase}] Waiting for current data application (Id: {id}) for player ({handler}) to finish", applicationBase, _applicationId, PlayerName); + await Task.Delay(250).ConfigureAwait(false); } - finally + + if (downloadToken.IsCancellationRequested || (appToken?.IsCancellationRequested ?? false)) { - _downloadInProgress = false; + return; } + + _applicationCancellationTokenSource = _applicationCancellationTokenSource.CancelRecreate() ?? new CancellationTokenSource(); + var token = _applicationCancellationTokenSource.Token; + + _applicationTask = ApplyCharacterDataAsync(applicationBase, handlerForApply, charaData, updatedData, updateModdedPaths, updateManip, moddedPaths, token); } private async Task ApplyCharacterDataAsync(Guid applicationBase, GameObjectHandler handlerForApply, CharacterData charaData, Dictionary> updatedData, bool updateModdedPaths, bool updateManip, @@ -1265,7 +1054,8 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa if (penumbraCollection == Guid.Empty) { Logger.LogTrace("[BASE-{applicationId}] Penumbra collection unavailable for {handler}, caching data for later application", applicationBase, GetLogIdentifier()); - MarkApplicationDeferred(charaData); + _cachedData = charaData; + _pairStateCache.Store(Ident, charaData); return; } } @@ -1282,7 +1072,8 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa if (!objIndex.HasValue) { Logger.LogDebug("[BASE-{applicationId}] GameObject not available for {handler}, caching data for later application", applicationBase, GetLogIdentifier()); - MarkApplicationDeferred(charaData); + _cachedData = charaData; + _pairStateCache.Store(Ident, charaData); return; } @@ -1325,22 +1116,20 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa Logger.LogDebug("[{applicationId}] Application finished", _applicationId); } - catch (OperationCanceledException ex) when (ex.CancellationToken == token || token.IsCancellationRequested) + catch (OperationCanceledException) { - Logger.LogDebug("[{applicationId}] Application cancelled via request token for {handler}", _applicationId, GetLogIdentifier()); - MarkApplicationDeferred(charaData); - } - catch (OperationCanceledException ex) - { - MarkApplicationDeferred(charaData); - Logger.LogDebug("[{applicationId}] Application deferred; redraw or apply operation cancelled ({reason}) for {handler}", _applicationId, ex.Message, GetLogIdentifier()); + Logger.LogDebug("[{applicationId}] Application cancelled for {handler}", _applicationId, GetLogIdentifier()); + _cachedData = charaData; + _pairStateCache.Store(Ident, charaData); } catch (Exception ex) { if (ex is AggregateException aggr && aggr.InnerExceptions.Any(e => e is ArgumentNullException)) { IsVisible = false; - MarkApplicationDeferred(charaData); + _forceApplyMods = true; + _cachedData = charaData; + _pairStateCache.Store(Ident, charaData); Logger.LogDebug("[{applicationId}] Cancelled, player turned null during application", _applicationId); } else @@ -1403,7 +1192,6 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa else { Logger.LogTrace("{handler} visibility changed, now: {visi}, no cached or received data exists", GetLogIdentifier(), IsVisible); - EnsureRestoredStateWhileWaitingForData("VisibleWithoutCachedData"); } } else if (_charaHandler?.Address == nint.Zero && IsVisible) @@ -1735,27 +1523,6 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa pending.CharacterData, pending.Forced); } - private void MarkApplicationDeferred(CharacterData charaData) - { - _forceApplyMods = true; - _forceFullReapply = true; - _currentDownloadOwnerToken = Guid.Empty; - _cachedData = charaData; - _pairStateCache.Store(Ident, charaData); - EnqueueDeferredCharacterData(charaData); - } - - private void EnqueueDeferredCharacterData(CharacterData charaData, bool forced = true) - { - try - { - _dataReceivedInDowntime = new(Guid.NewGuid(), charaData.DeepClone(), forced); - } - catch (Exception ex) - { - Logger.LogDebug(ex, "Failed to queue deferred data for {handler}", GetLogIdentifier()); - } - } } internal sealed class PairHandlerAdapterFactory : IPairHandlerAdapterFactory diff --git a/LightlessSync/PlayerData/Pairs/VisibleUserDataDistributor.cs b/LightlessSync/PlayerData/Pairs/VisibleUserDataDistributor.cs index da53332..1840813 100644 --- a/LightlessSync/PlayerData/Pairs/VisibleUserDataDistributor.cs +++ b/LightlessSync/PlayerData/Pairs/VisibleUserDataDistributor.cs @@ -1,17 +1,16 @@ using System; -using LightlessSync.API.Data; -using LightlessSync.API.Data.Comparer; -using LightlessSync.PlayerData.Pairs; -using LightlessSync.Utils; -using LightlessSync.Services.Mediator; -using LightlessSync.Services; -using LightlessSync.WebAPI; -using LightlessSync.WebAPI.Files; -using Microsoft.Extensions.Logging; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using LightlessSync.API.Data; +using LightlessSync.API.Data.Comparer; +using LightlessSync.Services; +using LightlessSync.Services.Mediator; +using LightlessSync.Utils; +using LightlessSync.WebAPI; +using LightlessSync.WebAPI.Files; +using Microsoft.Extensions.Logging; namespace LightlessSync.PlayerData.Pairs; @@ -28,9 +27,6 @@ public class VisibleUserDataDistributor : DisposableMediatorSubscriberBase private readonly HashSet _usersToPushDataTo = new(UserDataComparer.Instance); private readonly SemaphoreSlim _pushDataSemaphore = new(1, 1); private readonly CancellationTokenSource _runtimeCts = new(); - private readonly Dictionary _lastPushedHashes = new(StringComparer.Ordinal); - private readonly object _pushSync = new(); - public VisibleUserDataDistributor(ILogger logger, ApiController apiController, DalamudUtilService dalamudUtil, PairLedger pairLedger, LightlessMediator mediator, FileUploadManager fileTransferManager) : base(logger, mediator) @@ -56,7 +52,14 @@ public class VisibleUserDataDistributor : DisposableMediatorSubscriberBase }); Mediator.Subscribe(this, (_) => PushToAllVisibleUsers()); - Mediator.Subscribe(this, (_) => HandleDisconnected()); + Mediator.Subscribe(this, (_) => + { + _fileTransferManager.CancelUpload(); + _previouslyVisiblePlayers.Clear(); + _usersToPushDataTo.Clear(); + _uploadingCharacterData = null; + _fileUploadTask = null; + }); } protected override void Dispose(bool disposing) @@ -72,18 +75,15 @@ public class VisibleUserDataDistributor : DisposableMediatorSubscriberBase private void PushToAllVisibleUsers(bool forced = false) { - lock (_pushSync) + foreach (var user in GetVisibleUsers()) { - foreach (var user in GetVisibleUsers()) - { - _usersToPushDataTo.Add(user); - } + _usersToPushDataTo.Add(user); + } - if (_usersToPushDataTo.Count > 0) - { - Logger.LogDebug("Pushing data {hash} for {count} visible players", _lastCreatedData?.DataHash.Value ?? "UNKNOWN", _usersToPushDataTo.Count); - PushCharacterData_internalLocked(forced); - } + if (_usersToPushDataTo.Count > 0) + { + Logger.LogDebug("Pushing data {hash} for {count} visible players", _lastCreatedData?.DataHash.Value ?? "UNKNOWN", _usersToPushDataTo.Count); + PushCharacterData(forced); } } @@ -92,9 +92,7 @@ public class VisibleUserDataDistributor : DisposableMediatorSubscriberBase if (!_dalamudUtil.GetIsPlayerPresent() || !_apiController.IsConnected) return; var allVisibleUsers = GetVisibleUsers(); - var newVisibleUsers = allVisibleUsers - .Except(_previouslyVisiblePlayers, UserDataComparer.Instance) - .ToList(); + var newVisibleUsers = allVisibleUsers.Except(_previouslyVisiblePlayers, UserDataComparer.Instance).ToList(); _previouslyVisiblePlayers.Clear(); _previouslyVisiblePlayers.AddRange(allVisibleUsers); if (newVisibleUsers.Count == 0) return; @@ -102,115 +100,48 @@ public class VisibleUserDataDistributor : DisposableMediatorSubscriberBase Logger.LogDebug("Scheduling character data push of {data} to {users}", _lastCreatedData?.DataHash.Value ?? string.Empty, string.Join(", ", newVisibleUsers.Select(k => k.AliasOrUID))); - lock (_pushSync) + foreach (var user in newVisibleUsers) { - foreach (var user in newVisibleUsers) - { - _usersToPushDataTo.Add(user); - } - PushCharacterData_internalLocked(); + _usersToPushDataTo.Add(user); } + PushCharacterData(); } private void PushCharacterData(bool forced = false) - { - lock (_pushSync) - { - PushCharacterData_internalLocked(forced); - } - } - - private void PushCharacterData_internalLocked(bool forced = false) { if (_lastCreatedData == null || _usersToPushDataTo.Count == 0) return; - if (!_apiController.IsConnected || !_fileTransferManager.IsReady) - { - Logger.LogTrace("Skipping character push. Connected: {connected}, UploadManagerReady: {ready}", - _apiController.IsConnected, _fileTransferManager.IsReady); - return; - } _ = Task.Run(async () => { try { - Task? uploadTask; - bool forcedPush; - lock (_pushSync) - { - if (_lastCreatedData == null || _usersToPushDataTo.Count == 0) return; - forcedPush = forced | (_uploadingCharacterData?.DataHash != _lastCreatedData.DataHash); + forced |= _uploadingCharacterData?.DataHash != _lastCreatedData.DataHash; - if (_fileUploadTask == null || (_fileUploadTask?.IsCompleted ?? false) || forcedPush) - { - _uploadingCharacterData = _lastCreatedData.DeepClone(); - Logger.LogDebug("Starting UploadTask for {hash}, Reason: TaskIsNull: {task}, TaskIsCompleted: {taskCpl}, Forced: {frc}", - _lastCreatedData.DataHash, _fileUploadTask == null, _fileUploadTask?.IsCompleted ?? false, forcedPush); - _fileUploadTask = _fileTransferManager.UploadFiles(_uploadingCharacterData, [.. _usersToPushDataTo]); - } + if (_fileUploadTask == null || (_fileUploadTask?.IsCompleted ?? false) || forced) + { + _uploadingCharacterData = _lastCreatedData.DeepClone(); + Logger.LogDebug("Starting UploadTask for {hash}, Reason: TaskIsNull: {task}, TaskIsCompleted: {taskCpl}, Forced: {frc}", + _lastCreatedData.DataHash, _fileUploadTask == null, _fileUploadTask?.IsCompleted ?? false, forced); + _fileUploadTask = _fileTransferManager.UploadFiles(_uploadingCharacterData, [.. _usersToPushDataTo]); + } - uploadTask = _fileUploadTask; - } - - var dataToSend = await uploadTask.ConfigureAwait(false); - var dataHash = dataToSend.DataHash.Value; + if (_fileUploadTask != null) + { + var dataToSend = await _fileUploadTask.ConfigureAwait(false); await _pushDataSemaphore.WaitAsync(_runtimeCts.Token).ConfigureAwait(false); try { - List recipients; - bool shouldSkip = false; - lock (_pushSync) - { - if (_usersToPushDataTo.Count == 0) return; - recipients = forcedPush - ? _usersToPushDataTo.ToList() - : _usersToPushDataTo - .Where(user => !_lastPushedHashes.TryGetValue(user.UID, out var sentHash) || !string.Equals(sentHash, dataHash, StringComparison.Ordinal)) - .ToList(); - - if (recipients.Count == 0 && !forcedPush) - { - Logger.LogTrace("All recipients already have character data hash {hash}, skipping push.", dataHash); - _usersToPushDataTo.Clear(); - shouldSkip = true; - } - } - - if (shouldSkip) - return; - - Logger.LogDebug("Pushing {data} to {users}", dataHash, string.Join(", ", recipients.Select(k => k.AliasOrUID))); - await _apiController.PushCharacterData(dataToSend, recipients).ConfigureAwait(false); - - lock (_pushSync) - { - foreach (var user in recipients) - { - _lastPushedHashes[user.UID] = dataHash; - _usersToPushDataTo.Remove(user); - } - - if (!forcedPush && _usersToPushDataTo.Count > 0) - { - foreach (var satisfied in _usersToPushDataTo - .Where(user => _lastPushedHashes.TryGetValue(user.UID, out var sentHash) && string.Equals(sentHash, dataHash, StringComparison.Ordinal)) - .ToList()) - { - _usersToPushDataTo.Remove(satisfied); - } - } - - if (forcedPush) - { - _usersToPushDataTo.Clear(); - } - } + if (_usersToPushDataTo.Count == 0) return; + Logger.LogDebug("Pushing {data} to {users}", dataToSend.DataHash, string.Join(", ", _usersToPushDataTo.Select(k => k.AliasOrUID))); + await _apiController.PushCharacterData(dataToSend, [.. _usersToPushDataTo]).ConfigureAwait(false); + _usersToPushDataTo.Clear(); } finally { _pushDataSemaphore.Release(); } } + } catch (OperationCanceledException) when (_runtimeCts.IsCancellationRequested) { Logger.LogDebug("PushCharacterData cancelled"); @@ -222,20 +153,6 @@ public class VisibleUserDataDistributor : DisposableMediatorSubscriberBase }); } - private void HandleDisconnected() - { - _fileTransferManager.CancelUpload(); - _previouslyVisiblePlayers.Clear(); - - lock (_pushSync) - { - _usersToPushDataTo.Clear(); - _lastPushedHashes.Clear(); - _uploadingCharacterData = null; - _fileUploadTask = null; - } - } - private List GetVisibleUsers() { return _pairLedger.GetVisiblePairs()