using MareSynchronos.API.Data; using MareSynchronos.FileCache; using MareSynchronos.PlayerData.Handlers; using MareSynchronos.Services.Mediator; using MareSynchronos.WebAPI.Files.Models; using Microsoft.Extensions.Logging; namespace MareSynchronos.Services; public class PlayerPerformanceService { private readonly FileCacheManager _fileCacheManager; private readonly XivDataAnalyzer _xivDataAnalyzer; private readonly ILogger _logger; private readonly MareMediator _mediator; public PlayerPerformanceService(ILogger logger, MareMediator mediator, FileCacheManager fileCacheManager, XivDataAnalyzer xivDataAnalyzer) { _logger = logger; _mediator = mediator; _fileCacheManager = fileCacheManager; _xivDataAnalyzer = xivDataAnalyzer; } public async Task CheckBothThresholds(PairHandler pairHandler, CharacterData charaData) { bool notPausedAfterVram = ComputeAndAutoPauseOnVRAMUsageThresholds(pairHandler, charaData, []); if (!notPausedAfterVram) return false; bool notPausedAfterTris = await CheckTriangleUsageThresholds(pairHandler, charaData).ConfigureAwait(false); if (!notPausedAfterTris) return false; return true; } public async Task CheckTriangleUsageThresholds(PairHandler pairHandler, CharacterData charaData) { var pair = pairHandler.Pair; long triUsage = 0; if (!charaData.FileReplacements.TryGetValue(API.Data.Enum.ObjectKind.Player, out List? playerReplacements)) { pair.LastAppliedDataTris = 0; return true; } var moddedModelHashes = playerReplacements.Where(p => string.IsNullOrEmpty(p.FileSwapPath) && p.GamePaths.Any(g => g.EndsWith("mdl", StringComparison.OrdinalIgnoreCase))) .Select(p => p.Hash) .Distinct(StringComparer.OrdinalIgnoreCase) .ToList(); foreach (var hash in moddedModelHashes) { triUsage += await _xivDataAnalyzer.GetTrianglesByHash(hash).ConfigureAwait(false); } pair.LastAppliedDataTris = triUsage; _logger.LogDebug("Calculated VRAM usage for {p}", pairHandler); return true; } public bool ComputeAndAutoPauseOnVRAMUsageThresholds(PairHandler pairHandler, CharacterData charaData, List toDownloadFiles) { var pair = pairHandler.Pair; long vramUsage = 0; if (!charaData.FileReplacements.TryGetValue(API.Data.Enum.ObjectKind.Player, out List? playerReplacements)) { pair.LastAppliedApproximateVRAMBytes = 0; return true; } var moddedTextureHashes = playerReplacements.Where(p => string.IsNullOrEmpty(p.FileSwapPath) && p.GamePaths.Any(g => g.EndsWith(".tex", StringComparison.OrdinalIgnoreCase))) .Select(p => p.Hash) .Distinct(StringComparer.OrdinalIgnoreCase) .ToList(); foreach (var hash in moddedTextureHashes) { long fileSize = 0; var download = toDownloadFiles.Find(f => string.Equals(hash, f.Hash, StringComparison.OrdinalIgnoreCase)); if (download != null) { fileSize = download.TotalRaw; } else { var fileEntry = _fileCacheManager.GetFileCacheByHash(hash); if (fileEntry == null) continue; if (fileEntry.Size == null) { fileEntry.Size = new FileInfo(fileEntry.ResolvedFilepath).Length; _fileCacheManager.UpdateHashedFile(fileEntry, computeProperties: true); } fileSize = fileEntry.Size.Value; } vramUsage += fileSize; } pair.LastAppliedApproximateVRAMBytes = vramUsage; _logger.LogDebug("Calculated VRAM usage for {p}", pairHandler); return true; } }