diff --git a/LightlessSync/LightlessConfiguration/Configurations/LightlessConfig.cs b/LightlessSync/LightlessConfiguration/Configurations/LightlessConfig.cs index 9b4055b..829bca5 100644 --- a/LightlessSync/LightlessConfiguration/Configurations/LightlessConfig.cs +++ b/LightlessSync/LightlessConfiguration/Configurations/LightlessConfig.cs @@ -154,4 +154,5 @@ public class LightlessConfig : ILightlessConfiguration public bool SyncshellFinderEnabled { get; set; } = false; public string? SelectedFinderSyncshell { get; set; } = null; public string LastSeenVersion { get; set; } = string.Empty; + public HashSet OrphanableTempCollections { get; set; } = []; } diff --git a/LightlessSync/PlayerData/Pairs/PairHandlerAdapter.cs b/LightlessSync/PlayerData/Pairs/PairHandlerAdapter.cs index 99ada4e..178daa8 100644 --- a/LightlessSync/PlayerData/Pairs/PairHandlerAdapter.cs +++ b/LightlessSync/PlayerData/Pairs/PairHandlerAdapter.cs @@ -46,6 +46,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa private readonly TextureDownscaleService _textureDownscaleService; private readonly PairStateCache _pairStateCache; private readonly PairPerformanceMetricsCache _performanceMetricsCache; + private readonly PenumbraTempCollectionJanitor _tempCollectionJanitor; private readonly PairManager _pairManager; private CancellationTokenSource? _applicationCancellationTokenSource; private Guid _applicationId; @@ -181,7 +182,8 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa ServerConfigurationManager serverConfigManager, TextureDownscaleService textureDownscaleService, PairStateCache pairStateCache, - PairPerformanceMetricsCache performanceMetricsCache) : base(logger, mediator) + PairPerformanceMetricsCache performanceMetricsCache, + PenumbraTempCollectionJanitor tempCollectionJanitor) : base(logger, mediator) { _pairManager = pairManager; Ident = ident; @@ -199,7 +201,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa _textureDownscaleService = textureDownscaleService; _pairStateCache = pairStateCache; _performanceMetricsCache = performanceMetricsCache; - LastAppliedDataBytes = -1; + _tempCollectionJanitor = tempCollectionJanitor; } public void Initialize() @@ -422,6 +424,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa { _penumbraCollection = created; _pairStateCache.StoreTemporaryCollection(Ident, created); + _tempCollectionJanitor.Register(created); } return _penumbraCollection; @@ -454,6 +457,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa _needsCollectionRebuild = true; _forceFullReapply = true; _forceApplyMods = true; + _tempCollectionJanitor.Unregister(toRelease); } if (!releaseFromPenumbra || toRelease == Guid.Empty || !_ipcManager.Penumbra.APIAvailable) diff --git a/LightlessSync/PlayerData/Pairs/PairHandlerAdapterFactory.cs b/LightlessSync/PlayerData/Pairs/PairHandlerAdapterFactory.cs index 2001f1f..5169820 100644 --- a/LightlessSync/PlayerData/Pairs/PairHandlerAdapterFactory.cs +++ b/LightlessSync/PlayerData/Pairs/PairHandlerAdapterFactory.cs @@ -31,6 +31,7 @@ internal sealed class PairHandlerAdapterFactory : IPairHandlerAdapterFactory private readonly TextureDownscaleService _textureDownscaleService; private readonly PairStateCache _pairStateCache; private readonly PairPerformanceMetricsCache _pairPerformanceMetricsCache; + private readonly PenumbraTempCollectionJanitor _tempCollectionJanitor; public PairHandlerAdapterFactory( ILoggerFactory loggerFactory, @@ -48,7 +49,8 @@ internal sealed class PairHandlerAdapterFactory : IPairHandlerAdapterFactory ServerConfigurationManager serverConfigManager, TextureDownscaleService textureDownscaleService, PairStateCache pairStateCache, - PairPerformanceMetricsCache pairPerformanceMetricsCache) + PairPerformanceMetricsCache pairPerformanceMetricsCache, + PenumbraTempCollectionJanitor tempCollectionJanitor) { _loggerFactory = loggerFactory; _mediator = mediator; @@ -66,6 +68,7 @@ internal sealed class PairHandlerAdapterFactory : IPairHandlerAdapterFactory _textureDownscaleService = textureDownscaleService; _pairStateCache = pairStateCache; _pairPerformanceMetricsCache = pairPerformanceMetricsCache; + _tempCollectionJanitor = tempCollectionJanitor; } public IPairHandlerAdapter Create(string ident) @@ -91,6 +94,7 @@ internal sealed class PairHandlerAdapterFactory : IPairHandlerAdapterFactory _serverConfigManager, _textureDownscaleService, _pairStateCache, - _pairPerformanceMetricsCache); + _pairPerformanceMetricsCache, + _tempCollectionJanitor); } } diff --git a/LightlessSync/Plugin.cs b/LightlessSync/Plugin.cs index a6e33ac..40d4077 100644 --- a/LightlessSync/Plugin.cs +++ b/LightlessSync/Plugin.cs @@ -135,6 +135,7 @@ public sealed class Plugin : IDalamudPlugin services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(sp => new TextureMetadataHelper(sp.GetRequiredService>(), gameData)); diff --git a/LightlessSync/Services/PenumbraTempCollectionJanitor.cs b/LightlessSync/Services/PenumbraTempCollectionJanitor.cs new file mode 100644 index 0000000..03fb53b --- /dev/null +++ b/LightlessSync/Services/PenumbraTempCollectionJanitor.cs @@ -0,0 +1,71 @@ +using LightlessSync.Interop.Ipc; +using LightlessSync.LightlessConfiguration; +using LightlessSync.Services.Mediator; +using Microsoft.Extensions.Logging; + +namespace LightlessSync.Services; + +public sealed class PenumbraTempCollectionJanitor : DisposableMediatorSubscriberBase +{ + private readonly IpcManager _ipc; + private readonly LightlessConfigService _config; + private int _ran; + + public PenumbraTempCollectionJanitor( + ILogger logger, + LightlessMediator mediator, + IpcManager ipc, + LightlessConfigService config) : base(logger, mediator) + { + _ipc = ipc; + _config = config; + + Mediator.Subscribe(this, _ => CleanupOrphansOnBoot()); + } + + public void Register(Guid id) + { + if (id == Guid.Empty) return; + if (_config.Current.OrphanableTempCollections.Add(id)) + _config.Save(); + } + + public void Unregister(Guid id) + { + if (id == Guid.Empty) return; + if (_config.Current.OrphanableTempCollections.Remove(id)) + _config.Save(); + } + + private void CleanupOrphansOnBoot() + { + if (Interlocked.Exchange(ref _ran, 1) == 1) + return; + + if (!_ipc.Penumbra.APIAvailable) + return; + + var ids = _config.Current.OrphanableTempCollections.ToArray(); + if (ids.Length == 0) + return; + + var appId = Guid.NewGuid(); + Logger.LogInformation("Cleaning up {count} orphaned Lightless temp collections found in configuration", ids.Length); + + foreach (var id in ids) + { + try + { + _ipc.Penumbra.RemoveTemporaryCollectionAsync(Logger, appId, id) + .GetAwaiter().GetResult(); + } + catch (Exception ex) + { + Logger.LogDebug(ex, "Failed removing orphaned temp collection {id}", id); + } + } + + _config.Current.OrphanableTempCollections.Clear(); + _config.Save(); + } +} \ No newline at end of file diff --git a/LightlessSync/UI/DownloadUi.cs b/LightlessSync/UI/DownloadUi.cs index c326c58..2d9cdc1 100644 --- a/LightlessSync/UI/DownloadUi.cs +++ b/LightlessSync/UI/DownloadUi.cs @@ -178,6 +178,11 @@ public class DownloadUi : WindowMediatorSubscriberBase foreach (var transfer in transfers) { var transferKey = transfer.Key; + + // Skip if no valid game object + if (transferKey.GetGameObject() == null) + continue; + var rawPos = _dalamudUtilService.WorldToScreen(transferKey.GetGameObject()); // If RawPos is zero, remove it from smoothed dictionary