fix some existing issues

This commit is contained in:
2026-01-17 08:00:58 +09:00
parent 8be0811b4a
commit b57d54d69c
4 changed files with 191 additions and 18 deletions

View File

@@ -60,6 +60,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
private Guid _applicationId;
private Task? _applicationTask;
private CharacterData? _cachedData = null;
private CharacterData? _lastAppliedData = null;
private GameObjectHandler? _charaHandler;
private readonly Dictionary<ObjectKind, Guid?> _customizeIds = [];
private CombatData? _dataReceivedInDowntime;
@@ -256,6 +257,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
}
Mediator.Subscribe<ZoneSwitchStartMessage>(this, _ =>
{
LogDownloadCancellation("zone switch start");
_downloadCancellationTokenSource?.CancelDispose();
_charaHandler?.Invalidate();
IsVisible = false;
@@ -385,6 +387,42 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
return string.Equals(alias, Ident, StringComparison.Ordinal) ? alias : $"{alias} ({Ident})";
}
private void LogDownloadCancellation(string reason, Guid? applicationBase = null)
{
if (_downloadCancellationTokenSource is null)
{
return;
}
var inFlight = _pairDownloadTask is { IsCompleted: false };
if (inFlight)
{
if (applicationBase.HasValue)
{
Logger.LogDebug("[BASE-{appBase}] {handler}: Cancelling in-flight download ({reason})",
applicationBase.Value, GetLogIdentifier(), reason);
}
else
{
Logger.LogDebug("{handler}: Cancelling in-flight download ({reason})",
GetLogIdentifier(), reason);
}
}
else
{
if (applicationBase.HasValue)
{
Logger.LogDebug("[BASE-{appBase}] {handler}: Cancelling download token ({reason}, in-flight={inFlight})",
applicationBase.Value, GetLogIdentifier(), reason, inFlight);
}
else
{
Logger.LogDebug("{handler}: Cancelling download token ({reason}, in-flight={inFlight})",
GetLogIdentifier(), reason, inFlight);
}
}
}
private Task<Guid> EnsurePenumbraCollectionAsync()
{
if (_penumbraCollection != Guid.Empty)
@@ -851,6 +889,40 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
private bool IsForbiddenHash(string hash)
=> _downloadManager.ForbiddenTransfers.Exists(f => string.Equals(f.Hash, hash, StringComparison.Ordinal));
private bool HasMissingFiles(CharacterData data)
{
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (var replacement in data.FileReplacements.SelectMany(k => k.Value))
{
if (!string.IsNullOrEmpty(replacement.FileSwapPath))
{
continue;
}
var hash = replacement.Hash;
if (string.IsNullOrWhiteSpace(hash) || !seen.Add(hash))
{
continue;
}
var fileCache = _fileDbManager.GetFileCacheByHash(hash);
if (fileCache is null || !File.Exists(fileCache.ResolvedFilepath))
{
if (fileCache is not null)
{
_fileDbManager.RemoveHashedFile(fileCache.Hash, fileCache.PrefixedFilePath);
}
if (!IsForbiddenHash(hash))
{
return true;
}
}
}
return false;
}
private static bool IsNonPriorityModPath(string? gamePath)
{
if (string.IsNullOrEmpty(gamePath))
@@ -1012,10 +1084,11 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
"Cannot apply character data: Receiving Player is in an invalid state, deferring application")));
Logger.LogDebug("[BASE-{appBase}] Received data but player was in invalid state, charaHandlerIsNull: {charaIsNull}, playerPointerIsNull: {ptrIsNull}",
applicationBase, _charaHandler == null, PlayerCharacter == IntPtr.Zero);
var hasDiffMods = characterData.CheckUpdatedData(applicationBase, _cachedData, Logger,
var hasDiffMods = characterData.CheckUpdatedData(applicationBase, _lastAppliedData, Logger,
this, forceApplyCustomization, forceApplyMods: false)
.Any(p => p.Value.Contains(PlayerChanges.ModManip) || p.Value.Contains(PlayerChanges.ModFiles));
_forceApplyMods = hasDiffMods || _forceApplyMods || _cachedData == null;
_forceApplyMods = hasDiffMods || _forceApplyMods || _lastAppliedData == null;
_pendingModReapply = true;
_cachedData = characterData;
Logger.LogDebug("[BASE-{appBase}] Setting data: {hash}, forceApplyMods: {force}", applicationBase, _cachedData.DataHash.Value, _forceApplyMods);
return;
@@ -1023,27 +1096,34 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
SetUploading(false);
Logger.LogDebug("[BASE-{appbase}] Applying data for {player}, forceApplyCustomization: {forced}, forceApplyMods: {forceMods}", applicationBase, GetLogIdentifier(), forceApplyCustomization, _forceApplyMods);
Logger.LogDebug("[BASE-{appbase}] Hash for data is {newHash}, last applied hash is {oldHash}", applicationBase, characterData.DataHash.Value, _lastAppliedData?.DataHash.Value ?? "NODATA");
var hasMissingFiles = false;
if (string.Equals(characterData.DataHash.Value, _lastAppliedData?.DataHash.Value ?? string.Empty, StringComparison.Ordinal)
&& !forceApplyCustomization
&& !_forceApplyMods
&& !_pendingModReapply)
{
hasMissingFiles = HasMissingFiles(characterData);
if (!hasMissingFiles)
{
return;
}
}
_pendingModReapply = false;
_lastModApplyDeferred = false;
_lastMissingCriticalMods = 0;
_lastMissingNonCriticalMods = 0;
_lastMissingForbiddenMods = 0;
Logger.LogDebug("[BASE-{appbase}] Applying data for {player}, forceApplyCustomization: {forced}, forceApplyMods: {forceMods}", applicationBase, GetLogIdentifier(), forceApplyCustomization, _forceApplyMods);
Logger.LogDebug("[BASE-{appbase}] Hash for data is {newHash}, current cache hash is {oldHash}", applicationBase, characterData.DataHash.Value, _cachedData?.DataHash.Value ?? "NODATA");
if (string.Equals(characterData.DataHash.Value, _cachedData?.DataHash.Value ?? string.Empty, StringComparison.Ordinal)
&& !forceApplyCustomization)
{
return;
}
Mediator.Publish(new EventMessage(new Event(PlayerName, user, nameof(PairHandlerAdapter), EventSeverity.Informational,
"Applying Character Data")));
_forceApplyMods |= forceApplyCustomization;
_forceApplyMods |= forceApplyCustomization || hasMissingFiles;
var charaDataToUpdate = characterData.CheckUpdatedData(applicationBase, _cachedData?.DeepClone() ?? new(), Logger, this,
var charaDataToUpdate = characterData.CheckUpdatedData(applicationBase, _lastAppliedData?.DeepClone() ?? new(), Logger, this,
forceApplyCustomization, _forceApplyMods, suppressForcedModRedraw);
if (handlerReady && _forceApplyMods)
@@ -1282,6 +1362,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
Guid applicationId = Guid.NewGuid();
_applicationCancellationTokenSource?.CancelDispose();
_applicationCancellationTokenSource = null;
LogDownloadCancellation("dispose");
_downloadCancellationTokenSource?.CancelDispose();
_downloadCancellationTokenSource = null;
ClearAllOwnedObjectRetries();
@@ -1363,6 +1444,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
{
PlayerName = null;
_cachedData = null;
_lastAppliedData = null;
LastReceivedCharacterData = null;
_performanceMetricsCache.Clear(Ident);
Logger.LogDebug("Disposing {name} complete", name);
@@ -1695,6 +1777,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
var updateModdedPaths = updatedData.Values.Any(v => v.Any(p => p == PlayerChanges.ModFiles));
var updateManip = updatedData.Values.Any(v => v.Any(p => p == PlayerChanges.ModManip));
LogDownloadCancellation("new download request", applicationBase);
_downloadCancellationTokenSource = _downloadCancellationTokenSource?.CancelRecreate() ?? new CancellationTokenSource();
var downloadToken = _downloadCancellationTokenSource.Token;
_ = DownloadAndApplyCharacterAsync(applicationBase, charaData, updatedData, updateModdedPaths, updateManip, downloadToken)
@@ -1741,13 +1824,14 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
}
var handlerForDownload = _charaHandler;
_pairDownloadTask = Task.Run(async () => await _downloadManager.DownloadFiles(handlerForDownload, toDownloadReplacements, toDownloadFiles, downloadToken, skipDownscaleForPair, skipDecimationForPair).ConfigureAwait(false));
_pairDownloadTask = _downloadManager.DownloadFiles(handlerForDownload, toDownloadReplacements, toDownloadFiles, downloadToken, skipDownscaleForPair, skipDecimationForPair);
await _pairDownloadTask.ConfigureAwait(false);
if (downloadToken.IsCancellationRequested)
{
Logger.LogTrace("[BASE-{appBase}] Detected cancellation", applicationBase);
_pendingModReapply = true;
RecordFailure("Download cancelled", "Cancellation");
return;
}
@@ -1809,6 +1893,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
if (handlerForApply is null || handlerForApply.Address == nint.Zero)
{
Logger.LogDebug("[BASE-{appBase}] Handler not available for {player}, cached data for later application", applicationBase, GetLogIdentifier());
_pendingModReapply = true;
_cachedData = charaData;
_pairStateCache.Store(Ident, charaData);
RecordFailure("Handler not available for application", "HandlerUnavailable");
@@ -1836,6 +1921,11 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
_applicationTask = ApplyCharacterDataAsync(applicationBase, handlerForApply, charaData, updatedData, updateModdedPaths, updateManip, moddedPaths, token);
}
catch (OperationCanceledException) when (downloadToken.IsCancellationRequested)
{
_pendingModReapply = true;
RecordFailure("Download cancelled", "Cancellation");
}
finally
{
await concurrencyLease.DisposeAsync().ConfigureAwait(false);
@@ -1859,6 +1949,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
{
Logger.LogDebug("[BASE-{applicationId}] Timed out waiting for {handler} to fully load, caching data for later application",
applicationBase, GetLogIdentifier());
_pendingModReapply = true;
_cachedData = charaData;
_pairStateCache.Store(Ident, charaData);
RecordFailure("Actor not fully loaded within timeout", "FullyLoadedTimeout");
@@ -1875,6 +1966,7 @@ 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());
_pendingModReapply = true;
_cachedData = charaData;
_pairStateCache.Store(Ident, charaData);
RecordFailure("Penumbra collection unavailable", "PenumbraUnavailable");
@@ -1893,6 +1985,7 @@ 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());
_pendingModReapply = true;
_cachedData = charaData;
_pairStateCache.Store(Ident, charaData);
RecordFailure("Game object not available for application", "GameObjectUnavailable");
@@ -1958,6 +2051,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
}
_cachedData = charaData;
_lastAppliedData = charaData;
_pairStateCache.Store(Ident, charaData);
if (LastAppliedApproximateVRAMBytes < 0 || LastAppliedApproximateEffectiveVRAMBytes < 0)
{
@@ -1977,6 +2071,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
catch (OperationCanceledException)
{
Logger.LogDebug("[{applicationId}] Application cancelled for {handler}", _applicationId, GetLogIdentifier());
_pendingModReapply = true;
_cachedData = charaData;
_pairStateCache.Store(Ident, charaData);
RecordFailure("Application cancelled", "Cancellation");
@@ -2072,6 +2167,25 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
}
TryApplyQueuedData();
if (_pendingModReapply && IsVisible && !IsApplying && LastReceivedCharacterData is not null && CanApplyNow())
{
var now = DateTime.UtcNow;
if (!_lastApplyAttemptAt.HasValue || now - _lastApplyAttemptAt.Value > TimeSpan.FromSeconds(5))
{
_ = Task.Run(() =>
{
try
{
ApplyLastReceivedData(forced: true);
}
catch (Exception ex)
{
Logger.LogError(ex, "Failed to reapply pending data for {handler}", GetLogIdentifier());
}
});
}
}
}
private void HandleVisibilityLoss(bool logChange)
@@ -2079,6 +2193,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
IsVisible = false;
_charaHandler?.Invalidate();
ClearAllOwnedObjectRetries();
LogDownloadCancellation("visibility lost");
_downloadCancellationTokenSource?.CancelDispose();
_downloadCancellationTokenSource = null;
if (logChange)
@@ -2384,7 +2499,8 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
private async Task<bool> RevertToRestoredAsync(Guid applicationId)
{
if (_charaHandler is null || _charaHandler.Address == nint.Zero)
var handler = _charaHandler;
if (handler is null || handler.Address == nint.Zero)
{
return false;
}
@@ -2392,7 +2508,15 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
try
{
var reverted = false;
var gameObject = await _dalamudUtil.RunOnFrameworkThread(() => _charaHandler.GetGameObject()).ConfigureAwait(false);
var gameObject = await _dalamudUtil.RunOnFrameworkThread(() =>
{
if (handler.Address == nint.Zero)
{
return null;
}
return handler.GetGameObject();
}).ConfigureAwait(false);
if (gameObject is not Dalamud.Game.ClientState.Objects.Types.ICharacter character)
{
return false;
@@ -2450,6 +2574,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
private void DisableSync()
{
LogDownloadCancellation("sync disabled");
_downloadCancellationTokenSource = _downloadCancellationTokenSource?.CancelRecreate();
_applicationCancellationTokenSource = _applicationCancellationTokenSource?.CancelRecreate();
}
@@ -2457,6 +2582,11 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
private void EnableSync()
{
TryApplyQueuedData();
if (_pendingModReapply && LastReceivedCharacterData is not null && !IsApplying && CanApplyNow())
{
ApplyLastReceivedData(forced: true);
}
}
private void TryApplyQueuedData()

View File

@@ -131,7 +131,10 @@ public sealed class PerformanceCollectorService : IHostedService
DrawSeparator(sb, longestCounterName);
}
var pastEntries = limitBySeconds > 0 ? entry.Value.Where(e => e.Item1.AddMinutes(limitBySeconds / 60.0d) >= TimeOnly.FromDateTime(DateTime.Now)).ToList() : [.. entry.Value];
var snapshot = entry.Value.Snapshot();
var pastEntries = limitBySeconds > 0
? snapshot.Where(e => e.Item1.AddMinutes(limitBySeconds / 60.0d) >= TimeOnly.FromDateTime(DateTime.Now)).ToList()
: snapshot;
if (pastEntries.Any())
{
@@ -189,7 +192,11 @@ public sealed class PerformanceCollectorService : IHostedService
{
try
{
var last = entries.Value.ToList()[^1];
if (!entries.Value.TryGetLast(out var last))
{
continue;
}
if (last.Item1.AddMinutes(10) < TimeOnly.FromDateTime(DateTime.Now) && !PerformanceCounters.TryRemove(entries.Key, out _))
{
_logger.LogDebug("Could not remove performance counter {counter}", entries.Key);

View File

@@ -108,6 +108,14 @@ public sealed class OptimizationSettingsPanel
new SeStringUtils.RichTextEntry("When enabled, we cannot provide support for appearance issues caused by this setting!", UIColors.Get("DimRed"), true));
});
DrawCallout("texture-opt-info", UIColors.Get("LightlessGrey"), () =>
{
_uiSharedService.DrawNoteLine("i ", UIColors.Get("LightlessGrey"),
new SeStringUtils.RichTextEntry("Compression, downscale, and mip trimming only apply to "),
new SeStringUtils.RichTextEntry("newly downloaded pairs", UIColors.Get("LightlessYellow"), true),
new SeStringUtils.RichTextEntry(". Existing downloads are not reprocessed; re-download to apply."));
});
ImGui.Dummy(new Vector2(0f, 2f * scale));
DrawGroupHeader("Core Controls", UIColors.Get("LightlessYellow"));
@@ -282,6 +290,11 @@ public sealed class OptimizationSettingsPanel
new SeStringUtils.RichTextEntry(" will be decimated to the "),
new SeStringUtils.RichTextEntry("target ratio", UIColors.Get("LightlessGreen"), true),
new SeStringUtils.RichTextEntry(". This can reduce quality or alter intended structure."));
_uiSharedService.DrawNoteLine("i ", UIColors.Get("LightlessGreen"),
new SeStringUtils.RichTextEntry("Decimation only applies to "),
new SeStringUtils.RichTextEntry("newly downloaded pairs", UIColors.Get("LightlessYellow"), true),
new SeStringUtils.RichTextEntry(". Existing downloads are not reprocessed; re-download to apply."));
});
DrawGroupHeader("Core Controls", UIColors.Get("LightlessOrange"));

View File

@@ -29,6 +29,29 @@ public class RollingList<T> : IEnumerable<T>
}
}
public bool TryGetLast(out T value)
{
lock (_addLock)
{
if (_list.Count == 0)
{
value = default!;
return false;
}
value = _list.Last!.Value;
return true;
}
}
public List<T> Snapshot()
{
lock (_addLock)
{
return new List<T>(_list);
}
}
public void Add(T value)
{
lock (_addLock)