diff --git a/LightlessSync/FileCache/TransientResourceManager.cs b/LightlessSync/FileCache/TransientResourceManager.cs index a8b467e..11073dc 100644 --- a/LightlessSync/FileCache/TransientResourceManager.cs +++ b/LightlessSync/FileCache/TransientResourceManager.cs @@ -297,7 +297,7 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase private void DalamudUtil_FrameworkUpdate() { - RefreshPlayerRelatedAddressMap(); + _ = Task.Run(() => RefreshPlayerRelatedAddressMap()); lock (_cacheAdditionLock) { @@ -306,20 +306,64 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase if (_lastClassJobId != _dalamudUtil.ClassJobId) { - _lastClassJobId = _dalamudUtil.ClassJobId; - if (SemiTransientResources.TryGetValue(ObjectKind.Pet, out HashSet? value)) - { - value?.Clear(); - } - - PlayerConfig.JobSpecificCache.TryGetValue(_dalamudUtil.ClassJobId, out var jobSpecificData); - SemiTransientResources[ObjectKind.Player] = PlayerConfig.GlobalPersistentCache.Concat(jobSpecificData ?? []).ToHashSet(StringComparer.OrdinalIgnoreCase); - PlayerConfig.JobSpecificPetCache.TryGetValue(_dalamudUtil.ClassJobId, out var petSpecificData); - SemiTransientResources[ObjectKind.Pet] = new HashSet( - petSpecificData ?? [], - StringComparer.OrdinalIgnoreCase); + UpdateClassJobCache(); } + CleanupAbsentObjects(); + } + + private void RefreshPlayerRelatedAddressMap() + { + var tempMap = new ConcurrentDictionary(); + var updatedFrameAddresses = new ConcurrentDictionary(); + + lock (_playerRelatedLock) + { + foreach (var handler in _playerRelatedPointers) + { + var address = (nint)handler.Address; + if (address != nint.Zero) + { + tempMap[address] = handler; + updatedFrameAddresses[address] = handler.ObjectKind; + } + } + } + + _playerRelatedByAddress.Clear(); + foreach (var kvp in tempMap) + { + _playerRelatedByAddress[kvp.Key] = kvp.Value; + } + + _cachedFrameAddresses.Clear(); + foreach (var kvp in updatedFrameAddresses) + { + _cachedFrameAddresses[kvp.Key] = kvp.Value; + } + } + + private void UpdateClassJobCache() + { + _lastClassJobId = _dalamudUtil.ClassJobId; + if (SemiTransientResources.TryGetValue(ObjectKind.Pet, out HashSet? value)) + { + value?.Clear(); + } + + PlayerConfig.JobSpecificCache.TryGetValue(_dalamudUtil.ClassJobId, out var jobSpecificData); + SemiTransientResources[ObjectKind.Player] = PlayerConfig.GlobalPersistentCache + .Concat(jobSpecificData ?? []) + .ToHashSet(StringComparer.OrdinalIgnoreCase); + + PlayerConfig.JobSpecificPetCache.TryGetValue(_dalamudUtil.ClassJobId, out var petSpecificData); + SemiTransientResources[ObjectKind.Pet] = new HashSet( + petSpecificData ?? [], + StringComparer.OrdinalIgnoreCase); + } + + private void CleanupAbsentObjects() + { foreach (var kind in Enum.GetValues(typeof(ObjectKind)).Cast()) { if (!_cachedFrameAddresses.Any(k => k.Value == kind) && TransientResources.Remove(kind, out _)) @@ -349,26 +393,6 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase _semiTransientResources = null; } - private void RefreshPlayerRelatedAddressMap() - { - _playerRelatedByAddress.Clear(); - var updatedFrameAddresses = new ConcurrentDictionary(); - lock (_playerRelatedLock) - { - foreach (var handler in _playerRelatedPointers) - { - var address = (nint)handler.Address; - if (address != nint.Zero) - { - _playerRelatedByAddress[address] = handler; - updatedFrameAddresses[address] = handler.ObjectKind; - } - } - } - - _cachedFrameAddresses = updatedFrameAddresses; - } - private void HandleActorTracked(ActorObjectService.ActorDescriptor descriptor) { if (descriptor.IsInGpose) diff --git a/LightlessSync/PlayerData/Factories/PlayerDataFactory.cs b/LightlessSync/PlayerData/Factories/PlayerDataFactory.cs index 31df865..aeb04f3 100644 --- a/LightlessSync/PlayerData/Factories/PlayerDataFactory.cs +++ b/LightlessSync/PlayerData/Factories/PlayerDataFactory.cs @@ -142,29 +142,23 @@ public class PlayerDataFactory ct.ThrowIfCancellationRequested(); - fragment.FileReplacements = - [.. new HashSet(resolvedPaths.Select(c => new FileReplacement([.. c.Value], c.Key)), FileReplacementComparer.Instance).Where(p => p.HasFileReplacement)]; - fragment.FileReplacements.RemoveWhere(c => c.GamePaths.Any(g => !CacheMonitor.AllowedFileExtensions.Any(e => g.EndsWith(e, StringComparison.OrdinalIgnoreCase)))); + var fileReplacementsTask = Task.Run(() => + { + var replacements = new HashSet( + resolvedPaths.Select(c => new FileReplacement([.. c.Value], c.Key)), + FileReplacementComparer.Instance) + .Where(p => p.HasFileReplacement) + .ToHashSet(); + + replacements.RemoveWhere(c => c.GamePaths.Any(g => + !CacheMonitor.AllowedFileExtensions.Any(e => + g.EndsWith(e, StringComparison.OrdinalIgnoreCase)))); + + return replacements; + }, ct); ct.ThrowIfCancellationRequested(); - if (logDebug) - { - _logger.LogDebug("== Static Replacements =="); - foreach (var replacement in fragment.FileReplacements.Where(i => i.HasFileReplacement).OrderBy(i => i.GamePaths.First(), StringComparer.OrdinalIgnoreCase)) - { - _logger.LogDebug("=> {repl}", replacement); - ct.ThrowIfCancellationRequested(); - } - } - else - { - foreach (var replacement in fragment.FileReplacements.Where(i => i.HasFileReplacement)) - { - ct.ThrowIfCancellationRequested(); - } - } - Task? getHeelsOffset = null; Task? getGlamourerData = null; Task? getCustomizeData = null; @@ -185,38 +179,32 @@ public class PlayerDataFactory getCustomizeData = _ipcManager.CustomizePlus.GetScaleAsync(playerRelatedObject.Address); } + fragment.FileReplacements = await fileReplacementsTask.ConfigureAwait(false); + + if (logDebug) + { + _logger.LogDebug("== Static Replacements =="); + foreach (var replacement in fragment.FileReplacements.Where(i => i.HasFileReplacement).OrderBy(i => i.GamePaths.First(), StringComparer.OrdinalIgnoreCase)) + { + _logger.LogDebug("=> {repl}", replacement); + ct.ThrowIfCancellationRequested(); + } + } + else + { + foreach (var replacement in fragment.FileReplacements.Where(i => i.HasFileReplacement)) + { + ct.ThrowIfCancellationRequested(); + } + } + var staticReplacements = fragment.FileReplacements.ToHashSet(); - Task<(IReadOnlyDictionary ResolvedPaths, HashSet? ClearedReplacements)> transientTask = Task.Run(async () => - { - await _transientResourceManager.WaitForRecording(ct).ConfigureAwait(false); - - HashSet? clearedReplacements = null; - - if (objectKind == ObjectKind.Pet) - { - foreach (var item in staticReplacements.Where(i => i.HasFileReplacement).SelectMany(p => p.GamePaths)) - { - if (_transientResourceManager.AddTransientResource(objectKind, item)) - { - _logger.LogDebug("Marking static {item} for Pet as transient", item); - } - } - - _logger.LogTrace("Clearing {count} Static Replacements for Pet", staticReplacements.Count); - clearedReplacements = staticReplacements; - } - - ct.ThrowIfCancellationRequested(); - - _logger.LogDebug("Handling transient update for {obj}", playerRelatedObject); - - _transientResourceManager.ClearTransientPaths(objectKind, [.. staticReplacements.SelectMany(c => c.GamePaths)]); - - var transientPaths = ManageSemiTransientData(objectKind); - IReadOnlyDictionary resolved = await GetFileReplacementsFromPaths(playerRelatedObject, transientPaths, new HashSet(StringComparer.Ordinal)).ConfigureAwait(false); - return (resolved, clearedReplacements); - }, ct); + Task<(IReadOnlyDictionary ResolvedPaths, HashSet? ClearedReplacements)> transientTask = ProcessTransientDataAsync( + objectKind, + playerRelatedObject, + staticReplacements, + ct); ct.ThrowIfCancellationRequested(); @@ -278,12 +266,17 @@ public class PlayerDataFactory var toCompute = fragment.FileReplacements.Where(f => !f.IsFileSwap).ToArray(); _logger.LogDebug("Getting Hashes for {amount} Files", toCompute.Length); - var computedPaths = _fileCacheManager.GetFileCachesByPaths(toCompute.Select(c => c.ResolvedPath).ToArray()); - foreach (var file in toCompute) + + await Task.Run(() => { - ct.ThrowIfCancellationRequested(); - file.Hash = computedPaths[file.ResolvedPath]?.Hash ?? string.Empty; - } + var computedPaths = _fileCacheManager.GetFileCachesByPaths(toCompute.Select(c => c.ResolvedPath).ToArray()); + foreach (var file in toCompute) + { + ct.ThrowIfCancellationRequested(); + file.Hash = computedPaths[file.ResolvedPath]?.Hash ?? string.Empty; + } + }, ct).ConfigureAwait(false); + var removed = fragment.FileReplacements.RemoveWhere(f => !f.IsFileSwap && string.IsNullOrEmpty(f.Hash)); if (removed > 0) { @@ -507,6 +500,48 @@ public class PlayerDataFactory } } + private async Task<(IReadOnlyDictionary ResolvedPaths, HashSet? ClearedReplacements)> ProcessTransientDataAsync( + ObjectKind objectKind, + GameObjectHandler playerRelatedObject, + HashSet staticReplacements, + CancellationToken ct) + { + await _transientResourceManager.WaitForRecording(ct).ConfigureAwait(false); + + HashSet? clearedReplacements = null; + + var gamePaths = staticReplacements + .Where(i => i.HasFileReplacement) + .SelectMany(p => p.GamePaths) + .ToList(); + + if (objectKind == ObjectKind.Pet) + { + foreach (var item in gamePaths) + { + if (_transientResourceManager.AddTransientResource(objectKind, item)) + { + _logger.LogDebug("Marking static {item} for Pet as transient", item); + } + } + + _logger.LogTrace("Clearing {count} Static Replacements for Pet", staticReplacements.Count); + clearedReplacements = staticReplacements; + } + + ct.ThrowIfCancellationRequested(); + + _transientResourceManager.ClearTransientPaths(objectKind, gamePaths); + + var transientPaths = ManageSemiTransientData(objectKind); + IReadOnlyDictionary resolved = await GetFileReplacementsFromPaths( + playerRelatedObject, + transientPaths, + new HashSet(StringComparer.Ordinal)).ConfigureAwait(false); + + return (resolved, clearedReplacements); + } + private async Task> GetFileReplacementsFromPaths(GameObjectHandler handler, HashSet forwardResolve, HashSet reverseResolve) {