sigma update
This commit is contained in:
@@ -1,43 +1,44 @@
|
||||
using LightlessSync.API.Data;
|
||||
using LightlessSync.API.Data;
|
||||
|
||||
namespace LightlessSync.PlayerData.Pairs;
|
||||
namespace LightlessSync.PlayerData.Pairs;
|
||||
|
||||
/// <summary>
|
||||
/// orchestrates the lifecycle of a paired character
|
||||
/// </summary>
|
||||
public interface IPairHandlerAdapter : IDisposable, IPairPerformanceSubject
|
||||
{
|
||||
new string Ident { get; }
|
||||
bool Initialized { get; }
|
||||
bool IsVisible { get; }
|
||||
bool ScheduledForDeletion { get; set; }
|
||||
CharacterData? LastReceivedCharacterData { get; }
|
||||
long LastAppliedDataBytes { get; }
|
||||
new string? PlayerName { get; }
|
||||
string PlayerNameHash { get; }
|
||||
uint PlayerCharacterId { get; }
|
||||
DateTime? LastDataReceivedAt { get; }
|
||||
DateTime? LastApplyAttemptAt { get; }
|
||||
DateTime? LastSuccessfulApplyAt { get; }
|
||||
string? LastFailureReason { get; }
|
||||
IReadOnlyList<string> LastBlockingConditions { get; }
|
||||
bool IsApplying { get; }
|
||||
bool IsDownloading { get; }
|
||||
int PendingDownloadCount { get; }
|
||||
int ForbiddenDownloadCount { get; }
|
||||
bool PendingModReapply { get; }
|
||||
bool ModApplyDeferred { get; }
|
||||
int MissingCriticalMods { get; }
|
||||
int MissingNonCriticalMods { get; }
|
||||
int MissingForbiddenMods { get; }
|
||||
DateTime? InvisibleSinceUtc { get; }
|
||||
DateTime? VisibilityEvictionDueAtUtc { get; }
|
||||
/// <summary>
|
||||
/// orchestrates the lifecycle of a paired character
|
||||
/// </summary>
|
||||
public interface IPairHandlerAdapter : IDisposable, IPairPerformanceSubject
|
||||
{
|
||||
new string Ident { get; }
|
||||
bool Initialized { get; }
|
||||
bool IsVisible { get; }
|
||||
bool ScheduledForDeletion { get; set; }
|
||||
CharacterData? LastReceivedCharacterData { get; }
|
||||
long LastAppliedDataBytes { get; }
|
||||
new string? PlayerName { get; }
|
||||
string PlayerNameHash { get; }
|
||||
uint PlayerCharacterId { get; }
|
||||
DateTime? LastDataReceivedAt { get; }
|
||||
DateTime? LastApplyAttemptAt { get; }
|
||||
DateTime? LastSuccessfulApplyAt { get; }
|
||||
string? LastFailureReason { get; }
|
||||
IReadOnlyList<string> LastBlockingConditions { get; }
|
||||
bool IsApplying { get; }
|
||||
bool IsDownloading { get; }
|
||||
int PendingDownloadCount { get; }
|
||||
int ForbiddenDownloadCount { get; }
|
||||
bool PendingModReapply { get; }
|
||||
bool ModApplyDeferred { get; }
|
||||
int MissingCriticalMods { get; }
|
||||
int MissingNonCriticalMods { get; }
|
||||
int MissingForbiddenMods { get; }
|
||||
DateTime? InvisibleSinceUtc { get; }
|
||||
DateTime? VisibilityEvictionDueAtUtc { get; }
|
||||
|
||||
void Initialize();
|
||||
void ApplyData(CharacterData data);
|
||||
void ApplyLastReceivedData(bool forced = false);
|
||||
bool FetchPerformanceMetricsFromCache();
|
||||
void LoadCachedCharacterData(CharacterData data);
|
||||
void SetUploading(bool uploading);
|
||||
void SetPaused(bool paused);
|
||||
}
|
||||
void ApplyData(CharacterData data);
|
||||
void ApplyLastReceivedData(bool forced = false);
|
||||
Task EnsurePerformanceMetricsAsync(CancellationToken cancellationToken);
|
||||
bool FetchPerformanceMetricsFromCache();
|
||||
void LoadCachedCharacterData(CharacterData data);
|
||||
void SetUploading(bool uploading);
|
||||
void SetPaused(bool paused);
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
|
||||
private readonly XivDataAnalyzer _modelAnalyzer;
|
||||
private readonly PenumbraTempCollectionJanitor _tempCollectionJanitor;
|
||||
private readonly LightlessConfigService _configService;
|
||||
private readonly SemaphoreSlim _metricsComputeGate = new(1, 1);
|
||||
private readonly PairManager _pairManager;
|
||||
private readonly IFramework _framework;
|
||||
private CancellationTokenSource? _applicationCancellationTokenSource;
|
||||
@@ -193,8 +194,8 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
|
||||
public string? LastFailureReason => _lastFailureReason;
|
||||
public IReadOnlyList<string> LastBlockingConditions => _lastBlockingConditions;
|
||||
public bool IsApplying => _applicationTask is { IsCompleted: false };
|
||||
public bool IsDownloading => _downloadManager.IsDownloading;
|
||||
public int PendingDownloadCount => _downloadManager.CurrentDownloads.Count;
|
||||
public bool IsDownloading => _downloadManager.IsDownloadingFor(_charaHandler);
|
||||
public int PendingDownloadCount => _downloadManager.GetPendingDownloadCount(_charaHandler);
|
||||
public int ForbiddenDownloadCount => _downloadManager.ForbiddenTransfers.Count;
|
||||
|
||||
public PairHandlerAdapter(
|
||||
@@ -721,6 +722,74 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task EnsurePerformanceMetricsAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
EnsureInitialized();
|
||||
|
||||
if (LastReceivedCharacterData is null || IsApplying)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (LastAppliedApproximateVRAMBytes >= 0
|
||||
&& LastAppliedDataTris >= 0
|
||||
&& LastAppliedApproximateEffectiveVRAMBytes >= 0
|
||||
&& LastAppliedApproximateEffectiveTris >= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await _metricsComputeGate.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (LastReceivedCharacterData is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (LastAppliedApproximateVRAMBytes >= 0
|
||||
&& LastAppliedDataTris >= 0
|
||||
&& LastAppliedApproximateEffectiveVRAMBytes >= 0
|
||||
&& LastAppliedApproximateEffectiveTris >= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var sanitized = CloneAndSanitizeLastReceived(out var dataHash);
|
||||
if (sanitized is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(dataHash) && TryApplyCachedMetrics(dataHash))
|
||||
{
|
||||
_cachedData = sanitized;
|
||||
_pairStateCache.Store(Ident, sanitized);
|
||||
return;
|
||||
}
|
||||
|
||||
if (LastAppliedApproximateVRAMBytes < 0 || LastAppliedApproximateEffectiveVRAMBytes < 0)
|
||||
{
|
||||
_playerPerformanceService.ComputeAndAutoPauseOnVRAMUsageThresholds(this, sanitized, []);
|
||||
}
|
||||
|
||||
if (LastAppliedDataTris < 0 || LastAppliedApproximateEffectiveTris < 0)
|
||||
{
|
||||
await _playerPerformanceService.CheckTriangleUsageThresholds(this, sanitized).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
StorePerformanceMetrics(sanitized);
|
||||
_cachedData = sanitized;
|
||||
_pairStateCache.Store(Ident, sanitized);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_metricsComputeGate.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private CharacterData? CloneAndSanitizeLastReceived(out string? dataHash)
|
||||
{
|
||||
dataHash = null;
|
||||
@@ -1090,6 +1159,19 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
|
||||
Logger.LogDebug("[BASE-{appBase}] Setting data: {hash}, forceApplyMods: {force}", applicationBase, _cachedData.DataHash.Value, _forceApplyMods);
|
||||
}
|
||||
|
||||
var forceModsForMissing = _pendingModReapply;
|
||||
if (!forceModsForMissing && HasMissingCachedFiles(characterData))
|
||||
{
|
||||
forceModsForMissing = true;
|
||||
}
|
||||
|
||||
if (forceModsForMissing)
|
||||
{
|
||||
_forceApplyMods = true;
|
||||
}
|
||||
|
||||
var suppressForcedModRedrawOnForcedApply = suppressForcedModRedraw || forceModsForMissing;
|
||||
|
||||
SetUploading(false);
|
||||
|
||||
Logger.LogDebug("[BASE-{appbase}] Applying data for {player}, forceApplyCustomization: {forced}, forceApplyMods: {forceMods}", applicationBase, GetLogIdentifier(), forceApplyCustomization, _forceApplyMods);
|
||||
@@ -1106,7 +1188,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
|
||||
"Applying Character Data")));
|
||||
|
||||
var charaDataToUpdate = characterData.CheckUpdatedData(applicationBase, _cachedData?.DeepClone() ?? new(), Logger, this,
|
||||
forceApplyCustomization, _forceApplyMods, suppressForcedModRedraw);
|
||||
forceApplyCustomization, _forceApplyMods, suppressForcedModRedrawOnForcedApply);
|
||||
|
||||
if (handlerReady && _forceApplyMods)
|
||||
{
|
||||
@@ -1921,7 +2003,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
|
||||
}
|
||||
|
||||
var handlerForDownload = _charaHandler;
|
||||
_pairDownloadTask = Task.Run(async () => await _downloadManager.DownloadFiles(handlerForDownload, toDownloadReplacements, downloadToken, skipDownscaleForPair, skipDecimationForPair).ConfigureAwait(false));
|
||||
_pairDownloadTask = Task.Run(async () => await _downloadManager.DownloadFiles(handlerForDownload, toDownloadReplacements, toDownloadFiles, downloadToken, skipDownscaleForPair, skipDecimationForPair).ConfigureAwait(false));
|
||||
|
||||
await _pairDownloadTask.ConfigureAwait(false);
|
||||
|
||||
@@ -2136,6 +2218,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
|
||||
}
|
||||
|
||||
SplitPapMappings(moddedPaths, out var withoutPap, out var papOnly);
|
||||
var hasPap = papOnly.Count > 0;
|
||||
|
||||
await _ipcManager.Penumbra.AssignTemporaryCollectionAsync(Logger, penumbraCollection, objIndex.Value).ConfigureAwait(false);
|
||||
|
||||
@@ -2148,22 +2231,29 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
|
||||
if (handlerForApply.Address != nint.Zero)
|
||||
await _actorObjectService.WaitForFullyLoadedAsync(handlerForApply.Address, token).ConfigureAwait(false);
|
||||
|
||||
var removedPap = await StripIncompatiblePapAsync(handlerForApply, charaData, papOnly, token).ConfigureAwait(false);
|
||||
if (removedPap > 0)
|
||||
if (hasPap)
|
||||
{
|
||||
Logger.LogTrace("[{applicationId}] Removed {removedPap} incompatible PAP mappings found for {handler}", _applicationId, removedPap, GetLogIdentifier());
|
||||
var removedPap = await StripIncompatiblePapAsync(handlerForApply, charaData, papOnly, token).ConfigureAwait(false);
|
||||
if (removedPap > 0)
|
||||
{
|
||||
Logger.LogTrace("[{applicationId}] Removed {removedPap} incompatible PAP mappings found for {handler}", _applicationId, removedPap, GetLogIdentifier());
|
||||
}
|
||||
|
||||
var merged = new Dictionary<(string GamePath, string? Hash), string>(withoutPap, withoutPap.Comparer);
|
||||
foreach (var kv in papOnly)
|
||||
merged[kv.Key] = kv.Value;
|
||||
|
||||
await _ipcManager.Penumbra.SetTemporaryModsAsync(
|
||||
Logger, _applicationId, penumbraCollection,
|
||||
merged.ToDictionary(k => k.Key.GamePath, k => k.Value, StringComparer.Ordinal))
|
||||
.ConfigureAwait(false);
|
||||
|
||||
_lastAppliedModdedPaths = new Dictionary<(string GamePath, string? Hash), string>(merged, merged.Comparer);
|
||||
}
|
||||
else
|
||||
{
|
||||
_lastAppliedModdedPaths = new Dictionary<(string GamePath, string? Hash), string>(withoutPap, withoutPap.Comparer);
|
||||
}
|
||||
|
||||
var merged = new Dictionary<(string GamePath, string? Hash), string>(withoutPap, withoutPap.Comparer);
|
||||
foreach (var kv in papOnly)
|
||||
merged[kv.Key] = kv.Value;
|
||||
|
||||
await _ipcManager.Penumbra.SetTemporaryModsAsync(
|
||||
Logger, _applicationId, penumbraCollection,
|
||||
merged.ToDictionary(k => k.Key.GamePath, k => k.Value, StringComparer.Ordinal))
|
||||
.ConfigureAwait(false);
|
||||
|
||||
_lastAppliedModdedPaths = new Dictionary<(string GamePath, string? Hash), string>(merged, merged.Comparer);
|
||||
|
||||
LastAppliedDataBytes = -1;
|
||||
foreach (var path in moddedPaths.Values.Distinct(StringComparer.OrdinalIgnoreCase).Select(v => new FileInfo(v)).Where(p => p.Exists))
|
||||
@@ -2218,20 +2308,20 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
|
||||
_lastSuccessfulApplyAt = DateTime.UtcNow;
|
||||
ClearFailureState();
|
||||
Logger.LogDebug("[{applicationId}] Application finished", _applicationId);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
Logger.LogDebug("[{applicationId}] Application cancelled for {handler}", _applicationId, GetLogIdentifier());
|
||||
_cachedData = charaData;
|
||||
_pairStateCache.Store(Ident, charaData);
|
||||
_forceFullReapply = true;
|
||||
RecordFailure("Application cancelled", "Cancellation");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (ex is AggregateException aggr && aggr.InnerExceptions.Any(e => e is ArgumentNullException))
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
IsVisible = false;
|
||||
Logger.LogDebug("[{applicationId}] Application cancelled for {handler}", _applicationId, GetLogIdentifier());
|
||||
_cachedData = charaData;
|
||||
_pairStateCache.Store(Ident, charaData);
|
||||
_forceFullReapply = true;
|
||||
RecordFailure("Application cancelled", "Cancellation");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (ex is AggregateException aggr && aggr.InnerExceptions.Any(e => e is ArgumentNullException))
|
||||
{
|
||||
IsVisible = false;
|
||||
_forceApplyMods = true;
|
||||
_cachedData = charaData;
|
||||
_pairStateCache.Store(Ident, charaData);
|
||||
@@ -2471,6 +2561,11 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
|
||||
(item) =>
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
if (string.IsNullOrWhiteSpace(item.Hash))
|
||||
{
|
||||
Logger.LogTrace("[BASE-{appBase}] Skipping replacement with empty hash for paths: {paths}", applicationBase, string.Join(", ", item.GamePaths));
|
||||
return;
|
||||
}
|
||||
var fileCache = _fileDbManager.GetFileCacheByHash(item.Hash);
|
||||
if (fileCache is not null && !File.Exists(fileCache.ResolvedFilepath))
|
||||
{
|
||||
@@ -2698,10 +2793,10 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
|
||||
ApplyCharacterData(pending.ApplicationId, pending.CharacterData, pending.Forced);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Failed applying queued data for {handler}", GetLogIdentifier());
|
||||
}
|
||||
});
|
||||
{
|
||||
Logger.LogError(ex, "Failed applying queued data for {handler}", GetLogIdentifier());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void HandleActorTracked(ActorObjectService.ActorDescriptor descriptor)
|
||||
|
||||
@@ -271,7 +271,20 @@ public sealed class PairLedger : DisposableMediatorSubscriberBase
|
||||
|
||||
try
|
||||
{
|
||||
handler.ApplyLastReceivedData(forced: true);
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await handler.EnsurePerformanceMetricsAsync(CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (_logger.IsEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.LogDebug(ex, "Failed to ensure performance metrics for {Ident}", handler.Ident);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user