Restore logic
This commit is contained in:
@@ -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<CutsceneEndMessage>(this, _ => EnableSync());
|
||||
Mediator.Subscribe<GposeStartMessage>(this, _ => DisableSync());
|
||||
Mediator.Subscribe<GposeEndMessage>(this, _ => EnableSync());
|
||||
Mediator.Subscribe<DownloadFinishedMessage>(this, msg =>
|
||||
Mediator.Subscribe<DownloadFinishedMessage>(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<ObjectKind, HashSet<PlayerChanges>> BuildFullChangeSet(CharacterData characterData)
|
||||
{
|
||||
var result = new Dictionary<ObjectKind, HashSet<PlayerChanges>>();
|
||||
|
||||
foreach (var objectKind in Enum.GetValues<ObjectKind>())
|
||||
{
|
||||
var changes = new HashSet<PlayerChanges>();
|
||||
|
||||
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<ObjectKind, HashSet<PlayerChanges>> updatedData, bool forcePerformanceRecalc, bool forceApplyCustomization)
|
||||
private void DownloadAndApplyCharacter(Guid applicationBase, CharacterData charaData, Dictionary<ObjectKind, HashSet<PlayerChanges>> 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<ObjectKind, HashSet<PlayerChanges>> 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<FileReplacementData> toDownloadReplacements = TryCalculateModdedDictionary(applicationBase, charaData, out moddedPaths, downloadToken);
|
||||
|
||||
bool performedDownload = false;
|
||||
|
||||
if (updateModdedPaths)
|
||||
while (toDownloadReplacements.Count > 0 && attempts++ <= 10 && !downloadToken.IsCancellationRequested)
|
||||
{
|
||||
int attempts = 0;
|
||||
List<FileReplacementData> 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<DownloadFileTransfer>()))
|
||||
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<DownloadFileTransfer>()))
|
||||
{
|
||||
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<ObjectKind, HashSet<PlayerChanges>> 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
|
||||
|
||||
Reference in New Issue
Block a user