diff --git a/MareSynchronos/PlayerData/Data/CharacterData.cs b/MareSynchronos/PlayerData/Data/CharacterData.cs index b8b99ae..652ca9a 100644 --- a/MareSynchronos/PlayerData/Data/CharacterData.cs +++ b/MareSynchronos/PlayerData/Data/CharacterData.cs @@ -8,13 +8,12 @@ public class CharacterData { public Dictionary CustomizePlusScale { get; set; } = []; public Dictionary> FileReplacements { get; set; } = []; - public Dictionary GlamourerString { get; set; } = []; - public string HeelsData { get; set; } = string.Empty; public string HonorificData { get; set; } = string.Empty; public string ManipulationString { get; set; } = string.Empty; public string MoodlesData { get; set; } = string.Empty; + public string PetNamesData { get; set; } = string.Empty; public API.Data.CharacterData ToAPI() { @@ -44,7 +43,8 @@ public class CharacterData HeelsData = HeelsData, CustomizePlusData = CustomizePlusScale.ToDictionary(d => d.Key, d => d.Value), HonorificData = HonorificData, - MoodlesData = MoodlesData + MoodlesData = MoodlesData, + PetNamesData = PetNamesData }; } } \ No newline at end of file diff --git a/MareSynchronos/PlayerData/Data/PlayerChanges.cs b/MareSynchronos/PlayerData/Data/PlayerChanges.cs index f6e8464..e1d6358 100644 --- a/MareSynchronos/PlayerData/Data/PlayerChanges.cs +++ b/MareSynchronos/PlayerData/Data/PlayerChanges.cs @@ -10,4 +10,5 @@ public enum PlayerChanges Honorific = 7, ForcedRedraw = 8, Moodles = 9, + PetNames = 10, } \ No newline at end of file diff --git a/MareSynchronos/PlayerData/Factories/PlayerDataFactory.cs b/MareSynchronos/PlayerData/Factories/PlayerDataFactory.cs index 0326cd3..37bcd42 100644 --- a/MareSynchronos/PlayerData/Factories/PlayerDataFactory.cs +++ b/MareSynchronos/PlayerData/Factories/PlayerDataFactory.cs @@ -218,8 +218,8 @@ public class PlayerDataFactory previousData.MoodlesData = await _ipcManager.Moodles.GetStatusAsync(playerRelatedObject.Address).ConfigureAwait(false) ?? string.Empty; _logger.LogDebug("Moodles is now: {moodles}", previousData.MoodlesData); - previousData.MoodlesData = _ipcManager.PetNames.GetLocalNames(); - _logger.LogDebug("Pet Nicknames is now: {moodles}", previousData.MoodlesData); + previousData.PetNamesData = _ipcManager.PetNames.GetLocalNames(); + _logger.LogDebug("Pet Nicknames is now: {petnames}", previousData.PetNamesData); } if (previousData.FileReplacements.TryGetValue(objectKind, out HashSet? fileReplacements)) diff --git a/MareSynchronos/PlayerData/Handlers/PairHandler.cs b/MareSynchronos/PlayerData/Handlers/PairHandler.cs index e09dd27..5ef92d7 100644 --- a/MareSynchronos/PlayerData/Handlers/PairHandler.cs +++ b/MareSynchronos/PlayerData/Handlers/PairHandler.cs @@ -1,5 +1,4 @@ using MareSynchronos.API.Data; -using MareSynchronos.API.Dto.User; using MareSynchronos.FileCache; using MareSynchronos.Interop.Ipc; using MareSynchronos.PlayerData.Factories; @@ -344,8 +343,10 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase case PlayerChanges.Moodles: await _ipcManager.Moodles.SetStatusAsync(handler.Address, charaData.MoodlesData).ConfigureAwait(false); + break; - await _ipcManager.PetNames.SetPlayerData(handler.Address, charaData.MoodlesData).ConfigureAwait(false); + case PlayerChanges.PetNames: + await _ipcManager.PetNames.SetPlayerData(handler.Address, charaData.PetNamesData).ConfigureAwait(false); break; case PlayerChanges.ForcedRedraw: @@ -378,135 +379,135 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase _downloadCancellationTokenSource = _downloadCancellationTokenSource?.CancelRecreate() ?? new CancellationTokenSource(); var downloadToken = _downloadCancellationTokenSource.Token; - _ = Task.Run(async () => + _ = DownloadAndApplyCharacterAsync(applicationBase, charaData, updatedData, updateModdedPaths, updateManip, downloadToken).ConfigureAwait(false); + } + + private async Task DownloadAndApplyCharacterAsync(Guid applicationBase, CharacterData charaData, Dictionary> updatedData, + bool updateModdedPaths, bool updateManip, CancellationToken downloadToken) + { + Dictionary<(string GamePath, string? Hash), string> moddedPaths = []; + + if (updateModdedPaths) { - Dictionary<(string GamePath, string? Hash), string> moddedPaths = []; + int attempts = 0; + List toDownloadReplacements = TryCalculateModdedDictionary(applicationBase, charaData, out moddedPaths, downloadToken); + + while (toDownloadReplacements.Count > 0 && attempts++ <= 10 && !downloadToken.IsCancellationRequested) + { + _downloadManager.CancelDownload(); + Logger.LogDebug("[BASE-{appBase}] Downloading missing files for player {name}, {kind}", applicationBase, PlayerName, updatedData); + + Mediator.Publish(new EventMessage(new Event(PlayerName, Pair.UserData, nameof(PairHandler), EventSeverity.Informational, + $"Starting download for {toDownloadReplacements.Count} files"))); + var toDownloadFiles = await _downloadManager.InitiateDownloadList(_charaHandler!, toDownloadReplacements, downloadToken).ConfigureAwait(false); + + if (!_playerPerformanceService.ComputeAndAutoPauseOnVRAMUsageThresholds(this, charaData, toDownloadFiles)) + { + _downloadManager.CancelDownload(); + return; + } + + await _downloadManager.DownloadFiles(_charaHandler!, toDownloadReplacements, downloadToken).ConfigureAwait(false); + _downloadManager.CancelDownload(); + + if (downloadToken.IsCancellationRequested) + { + Logger.LogTrace("[BASE-{appBase}] Detected cancellation", applicationBase); + _downloadManager.CancelDownload(); + return; + } + + toDownloadReplacements = TryCalculateModdedDictionary(applicationBase, charaData, out moddedPaths, downloadToken); + + if (toDownloadReplacements.TrueForAll(c => _downloadManager.ForbiddenTransfers.Exists(f => string.Equals(f.Hash, c.Hash, StringComparison.Ordinal)))) + { + break; + } + + await Task.Delay(TimeSpan.FromSeconds(2)).ConfigureAwait(false); + } + + if (!await _playerPerformanceService.CheckBothThresholds(this, charaData).ConfigureAwait(false)) + return; + } + + downloadToken.ThrowIfCancellationRequested(); + + var appToken = _applicationCancellationTokenSource?.Token; + while ((!_applicationTask?.IsCompleted ?? false) + && !downloadToken.IsCancellationRequested + && (!appToken?.IsCancellationRequested ?? false)) + { + // block until current application is done + Logger.LogDebug("[BASE-{appBase}] Waiting for current data application (Id: {id}) for player ({handler}) to finish", applicationBase, _applicationId, PlayerName); + await Task.Delay(250).ConfigureAwait(false); + } + + if (downloadToken.IsCancellationRequested || (appToken?.IsCancellationRequested ?? false)) return; + + _applicationCancellationTokenSource = _applicationCancellationTokenSource.CancelRecreate() ?? new CancellationTokenSource(); + var token = _applicationCancellationTokenSource.Token; + + _applicationTask = ApplyCharacterDataAsync(applicationBase, charaData, updatedData, updateModdedPaths, updateManip, moddedPaths, token); + } + + private async Task ApplyCharacterDataAsync(Guid applicationBase, CharacterData charaData, Dictionary> updatedData, bool updateModdedPaths, bool updateManip, + Dictionary<(string GamePath, string? Hash), string> moddedPaths, CancellationToken token) + { + try + { + _applicationId = Guid.NewGuid(); + Logger.LogDebug("[BASE-{applicationId}] Starting application task for {this}: {appId}", applicationBase, this, _applicationId); + + Logger.LogDebug("[{applicationId}] Waiting for initial draw for for {handler}", _applicationId, _charaHandler); + await _dalamudUtil.WaitWhileCharacterIsDrawing(Logger, _charaHandler!, _applicationId, 30000, token).ConfigureAwait(false); + + token.ThrowIfCancellationRequested(); if (updateModdedPaths) { - int attempts = 0; - List toDownloadReplacements = TryCalculateModdedDictionary(applicationBase, charaData, out moddedPaths, downloadToken); - - while (toDownloadReplacements.Count > 0 && attempts++ <= 10 && !downloadToken.IsCancellationRequested) + await _ipcManager.Penumbra.SetTemporaryModsAsync(Logger, _applicationId, _penumbraCollection, + moddedPaths.ToDictionary(k => k.Key.GamePath, k => k.Value, StringComparer.Ordinal)).ConfigureAwait(false); + LastAppliedDataBytes = -1; + foreach (var path in moddedPaths.Values.Distinct(StringComparer.OrdinalIgnoreCase).Select(v => new FileInfo(v)).Where(p => p.Exists)) { - _downloadManager.CancelDownload(); - Logger.LogDebug("[BASE-{appBase}] Downloading missing files for player {name}, {kind}", applicationBase, PlayerName, updatedData); + if (LastAppliedDataBytes == -1) LastAppliedDataBytes = 0; - Mediator.Publish(new EventMessage(new Event(PlayerName, Pair.UserData, nameof(PairHandler), EventSeverity.Informational, - $"Starting download for {toDownloadReplacements.Count} files"))); - var toDownloadFiles = await _downloadManager.InitiateDownloadList(_charaHandler!, toDownloadReplacements, downloadToken).ConfigureAwait(false); - - if (!_playerPerformanceService.CheckVRAMUsageThresholds(this, charaData, toDownloadFiles)) - { - _downloadManager.CancelDownload(); - return; - } - - await _downloadManager.DownloadFiles(_charaHandler!, toDownloadReplacements, downloadToken).ConfigureAwait(false); - _downloadManager.CancelDownload(); - - if (downloadToken.IsCancellationRequested) - { - Logger.LogTrace("[BASE-{appBase}] Detected cancellation", applicationBase); - _downloadManager.CancelDownload(); - return; - } - - toDownloadReplacements = TryCalculateModdedDictionary(applicationBase, charaData, out moddedPaths, downloadToken); - - if (toDownloadReplacements.TrueForAll(c => _downloadManager.ForbiddenTransfers.Exists(f => string.Equals(f.Hash, c.Hash, StringComparison.Ordinal)))) - { - break; - } - - await Task.Delay(TimeSpan.FromSeconds(2)).ConfigureAwait(false); - } - - if (attempts == 0 && !_playerPerformanceService.CheckVRAMUsageThresholds(this, charaData, [])) - { - return; - } - - if (!await _playerPerformanceService.CheckTriangleUsageThresholds(this, charaData).ConfigureAwait(false)) - { - return; + LastAppliedDataBytes += path.Length; } } - downloadToken.ThrowIfCancellationRequested(); - - var appToken = _applicationCancellationTokenSource?.Token; - while ((!_applicationTask?.IsCompleted ?? false) - && !downloadToken.IsCancellationRequested - && (!appToken?.IsCancellationRequested ?? false)) + if (updateManip) { - // block until current application is done - Logger.LogDebug("[BASE-{appBase}] Waiting for current data application (Id: {id}) for player ({handler}) to finish", applicationBase, _applicationId, PlayerName); - await Task.Delay(250).ConfigureAwait(false); + await _ipcManager.Penumbra.SetManipulationDataAsync(Logger, _applicationId, _penumbraCollection, charaData.ManipulationData).ConfigureAwait(false); } - if (downloadToken.IsCancellationRequested || (appToken?.IsCancellationRequested ?? false)) return; + token.ThrowIfCancellationRequested(); - _applicationCancellationTokenSource = _applicationCancellationTokenSource.CancelRecreate() ?? new CancellationTokenSource(); - var token = _applicationCancellationTokenSource.Token; - _applicationTask = Task.Run(async () => + foreach (var kind in updatedData) { - try - { - _applicationId = Guid.NewGuid(); - Logger.LogDebug("[BASE-{applicationId}] Starting application task for {this}: {appId}", applicationBase, this, _applicationId); + await ApplyCustomizationDataAsync(_applicationId, kind, charaData, token).ConfigureAwait(false); + token.ThrowIfCancellationRequested(); + } - Logger.LogDebug("[{applicationId}] Waiting for initial draw for for {handler}", _applicationId, _charaHandler); - await _dalamudUtil.WaitWhileCharacterIsDrawing(Logger, _charaHandler!, _applicationId, 30000, token).ConfigureAwait(false); + _cachedData = charaData; - token.ThrowIfCancellationRequested(); - - if (updateModdedPaths) - { - await _ipcManager.Penumbra.SetTemporaryModsAsync(Logger, _applicationId, _penumbraCollection, - moddedPaths.ToDictionary(k => k.Key.GamePath, k => k.Value, StringComparer.Ordinal)).ConfigureAwait(false); - LastAppliedDataBytes = -1; - foreach (var path in moddedPaths.Values.Distinct(StringComparer.OrdinalIgnoreCase).Select(v => new FileInfo(v)).Where(p => p.Exists)) - { - if (LastAppliedDataBytes == -1) LastAppliedDataBytes = 0; - - LastAppliedDataBytes += path.Length; - } - } - - if (updateManip) - { - await _ipcManager.Penumbra.SetManipulationDataAsync(Logger, _applicationId, _penumbraCollection, charaData.ManipulationData).ConfigureAwait(false); - } - - token.ThrowIfCancellationRequested(); - - foreach (var kind in updatedData) - { - await ApplyCustomizationDataAsync(_applicationId, kind, charaData, token).ConfigureAwait(false); - token.ThrowIfCancellationRequested(); - } - - _cachedData = charaData; - - Logger.LogDebug("[{applicationId}] Application finished", _applicationId); - } - catch (Exception ex) - { - if (ex is AggregateException aggr && aggr.InnerExceptions.Any(e => e is ArgumentNullException)) - { - IsVisible = false; - _forceApplyMods = true; - _cachedData = charaData; - Logger.LogDebug("[{applicationId}] Cancelled, player turned null during application", _applicationId); - } - else - { - Logger.LogWarning(ex, "[{applicationId}] Cancelled", _applicationId); - } - } - }, token); - }, downloadToken); + Logger.LogDebug("[{applicationId}] Application finished", _applicationId); + } + catch (Exception ex) + { + if (ex is AggregateException aggr && aggr.InnerExceptions.Any(e => e is ArgumentNullException)) + { + IsVisible = false; + _forceApplyMods = true; + _cachedData = charaData; + Logger.LogDebug("[{applicationId}] Cancelled, player turned null during application", _applicationId); + } + else + { + Logger.LogWarning(ex, "[{applicationId}] Cancelled", _applicationId); + } + } } private void FrameworkUpdate() @@ -595,7 +596,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase await _ipcManager.Honorific.ClearTitleAsync(address).ConfigureAwait(false); Logger.LogDebug("[{applicationId}] Restoring Moodles for {alias}/{name}", applicationId, Pair.UserData.AliasOrUID, name); await _ipcManager.Moodles.RevertStatusAsync(address).ConfigureAwait(false); - Logger.LogDebug("[{applicationId}] Restoring Pet Nicknames for {alias}/{name}", applicationId, OnlineUser.User.AliasOrUID, name); + Logger.LogDebug("[{applicationId}] Restoring Pet Nicknames for {alias}/{name}", applicationId, Pair.UserData.AliasOrUID, name); await _ipcManager.PetNames.ClearPlayerData(address).ConfigureAwait(false); } else if (objectKind == ObjectKind.MinionOrMount) diff --git a/MareSynchronos/PlayerData/Services/CacheCreationService.cs b/MareSynchronos/PlayerData/Services/CacheCreationService.cs index cf43f65..febe45e 100644 --- a/MareSynchronos/PlayerData/Services/CacheCreationService.cs +++ b/MareSynchronos/PlayerData/Services/CacheCreationService.cs @@ -127,7 +127,7 @@ public sealed class CacheCreationService : DisposableMediatorSubscriberBase Mediator.Subscribe(this, (msg) => { if (_isZoning) return; - if (!string.Equals(msg.PetNicknamesData, _playerData.MoodlesData, StringComparison.Ordinal)) + if (!string.Equals(msg.PetNicknamesData, _playerData.PetNamesData, StringComparison.Ordinal)) { Logger.LogDebug("Received Pet Nicknames change, updating player"); PetNicknamesChanged(); diff --git a/MareSynchronos/Services/PlayerPerformanceService.cs b/MareSynchronos/Services/PlayerPerformanceService.cs index 340edba..dd85eb4 100644 --- a/MareSynchronos/Services/PlayerPerformanceService.cs +++ b/MareSynchronos/Services/PlayerPerformanceService.cs @@ -29,6 +29,67 @@ public class PlayerPerformanceService _xivDataAnalyzer = xivDataAnalyzer; } + public async Task CheckBothThresholds(PairHandler pairHandler, CharacterData charaData) + { + var config = _playerPerformanceConfigService.Current; + bool notPausedAfterVram = ComputeAndAutoPauseOnVRAMUsageThresholds(pairHandler, charaData, []); + if (!notPausedAfterVram) return false; + bool notPausedAfterTris = await CheckTriangleUsageThresholds(pairHandler, charaData).ConfigureAwait(false); + if (!notPausedAfterTris) return false; + + if (config.UIDsToIgnore + .Exists(uid => string.Equals(uid, pairHandler.Pair.UserData.Alias, StringComparison.Ordinal) || string.Equals(uid, pairHandler.Pair.UserData.UID, StringComparison.Ordinal))) + return true; + + var vramUsage = pairHandler.Pair.LastAppliedApproximateVRAMBytes; + var triUsage = pairHandler.Pair.LastAppliedDataTris; + + bool isPrefPerm = pairHandler.Pair.UserPair.OwnPermissions.HasFlag(API.Data.Enum.UserPermissions.Sticky); + + bool exceedsTris = CheckForThreshold(config.WarnOnExceedingThresholds, config.TrisWarningThresholdThousands * 1000, + triUsage, config.WarnOnPreferredPermissionsExceedingThresholds, isPrefPerm); + bool exceedsVram = CheckForThreshold(config.WarnOnExceedingThresholds, config.VRAMSizeWarningThresholdMiB * 1024 * 1024, + vramUsage, config.WarnOnPreferredPermissionsExceedingThresholds, isPrefPerm); + + if (exceedsVram) + { + _mediator.Publish(new EventMessage(new Event(pairHandler.Pair.PlayerName, pairHandler.Pair.UserData, nameof(PlayerPerformanceService), EventSeverity.Warning, + $"Exceeds VRAM threshold: ({UiSharedService.ByteToString(vramUsage, addSuffix: true)}/{config.VRAMSizeWarningThresholdMiB} MiB)"))); + } + + if (exceedsTris) + { + _mediator.Publish(new EventMessage(new Event(pairHandler.Pair.PlayerName, pairHandler.Pair.UserData, nameof(PlayerPerformanceService), EventSeverity.Warning, + $"Exceeds triangle threshold: ({triUsage}/{config.TrisAutoPauseThresholdThousands * 1000} triangles)"))); + } + + if (exceedsTris || exceedsVram) + { + string warningText = string.Empty; + if (exceedsTris && !exceedsVram) + { + warningText = $"Player {pairHandler.Pair.PlayerName} exceeds your configured triangle warning threshold (" + + $"{triUsage}/{config.TrisAutoPauseThresholdThousands * 1000} triangles)."; + } + else if (!exceedsTris) + { + warningText = $"Player {pairHandler.Pair.PlayerName} exceeds your configured VRAM warning threshold (" + + $"{UiSharedService.ByteToString(vramUsage, true)}/{config.VRAMSizeWarningThresholdMiB} MiB)."; + } + else + { + warningText = $"Player {pairHandler.Pair.PlayerName} exceeds both VRAM warning threshold (" + + $"{UiSharedService.ByteToString(vramUsage, true)}/{config.VRAMSizeWarningThresholdMiB} MiB) and " + + $"triangle warning threshold ({triUsage}/{config.TrisAutoPauseThresholdThousands * 1000} triangles)."; + } + + _mediator.Publish(new NotificationMessage($"{pairHandler.Pair.PlayerName} ({pairHandler.Pair.UserData.AliasOrUID}) exceeds performance threshold(s)", + warningText, MareConfiguration.Models.NotificationType.Warning)); + } + + return true; + } + public async Task CheckTriangleUsageThresholds(PairHandler pairHandler, CharacterData charaData) { var config = _playerPerformanceConfigService.Current; @@ -74,30 +135,17 @@ public class PlayerPerformanceService MareConfiguration.Models.NotificationType.Warning)); _mediator.Publish(new EventMessage(new Event(pair.PlayerName, pair.UserData, nameof(PlayerPerformanceService), EventSeverity.Warning, - $"Exceeds triangle threshold: automatically paused ({triUsage}/{triUsage * 1000} triangles)"))); + $"Exceeds triangle threshold: automatically paused ({triUsage}/{config.TrisAutoPauseThresholdThousands * 1000} triangles)"))); _mediator.Publish(new PauseMessage(pair.UserData)); return false; } - // and fucking warnings - if (CheckForThreshold(config.WarnOnExceedingThresholds, config.TrisWarningThresholdThousands * 1000, - triUsage, config.WarnOnPreferredPermissionsExceedingThresholds, isPrefPerm)) - { - _mediator.Publish(new NotificationMessage($"{pair.PlayerName} ({pair.UserData.AliasOrUID}) exceeds performance threshold", - $"Player {pair.PlayerName} exceeds your configured triangle warning threshold (" + - $"{triUsage}/{triUsage * 1000} triangles).", - MareConfiguration.Models.NotificationType.Warning)); - - _mediator.Publish(new EventMessage(new Event(pair.PlayerName, pair.UserData, nameof(PlayerPerformanceService), EventSeverity.Warning, - $"Exceeds triangle threshold: ({triUsage}/{triUsage * 1000} triangles)"))); - } - return true; } - public bool CheckVRAMUsageThresholds(PairHandler pairHandler, CharacterData charaData, List toDownloadFiles) + public bool ComputeAndAutoPauseOnVRAMUsageThresholds(PairHandler pairHandler, CharacterData charaData, List toDownloadFiles) { var config = _playerPerformanceConfigService.Current; var pair = pairHandler.Pair; @@ -165,24 +213,11 @@ public class PlayerPerformanceService _mediator.Publish(new PauseMessage(pair.UserData)); _mediator.Publish(new EventMessage(new Event(pair.PlayerName, pair.UserData, nameof(PlayerPerformanceService), EventSeverity.Warning, - $"Exceeds VRAM threshold: automatically paused ({UiSharedService.ByteToString(vramUsage, addSuffix: true)}/{config.VRAMSizeAutoPauseThresholdMiB}MiB)"))); + $"Exceeds VRAM threshold: automatically paused ({UiSharedService.ByteToString(vramUsage, addSuffix: true)}/{config.VRAMSizeAutoPauseThresholdMiB} MiB)"))); return false; } - // and fucking warnings - if (CheckForThreshold(config.WarnOnExceedingThresholds, config.VRAMSizeWarningThresholdMiB * 1024 * 1024, - vramUsage, config.WarnOnPreferredPermissionsExceedingThresholds, isPrefPerm)) - { - _mediator.Publish(new NotificationMessage($"{pair.PlayerName} ({pair.UserData.AliasOrUID}) exceeds performance threshold", - $"Player {pair.PlayerName} exceeds your configured VRAM warning threshold (" + - $"{UiSharedService.ByteToString(vramUsage, true)}/{config.VRAMSizeWarningThresholdMiB}MiB).", - MareConfiguration.Models.NotificationType.Warning)); - - _mediator.Publish(new EventMessage(new Event(pair.PlayerName, pair.UserData, nameof(PlayerPerformanceService), EventSeverity.Warning, - $"Exceeds triangle threshold: ({UiSharedService.ByteToString(vramUsage, addSuffix: true)}/{config.VRAMSizeAutoPauseThresholdMiB}MiB)"))); - } - return true; } diff --git a/MareSynchronos/Services/PluginWarningNotificationService.cs b/MareSynchronos/Services/PluginWarningNotificationService.cs index f883e8e..39ddc53 100644 --- a/MareSynchronos/Services/PluginWarningNotificationService.cs +++ b/MareSynchronos/Services/PluginWarningNotificationService.cs @@ -60,7 +60,7 @@ public class PluginWarningNotificationService warning.ShownMoodlesWarning = true; } - if (changes.Contains(PlayerChanges.Moodles) && !warning.ShowPetNicknamesWarning && !_ipcManager.PetNames.APIAvailable) + if (changes.Contains(PlayerChanges.PetNames) && !warning.ShowPetNicknamesWarning && !_ipcManager.PetNames.APIAvailable) { missingPluginsForData.Add("PetNicknames"); warning.ShowPetNicknamesWarning = true; diff --git a/MareSynchronos/UI/Components/DrawUserPair.cs b/MareSynchronos/UI/Components/DrawUserPair.cs index 7917137..296ebff 100644 --- a/MareSynchronos/UI/Components/DrawUserPair.cs +++ b/MareSynchronos/UI/Components/DrawUserPair.cs @@ -6,6 +6,7 @@ using ImGuiNET; using MareSynchronos.API.Data.Extensions; using MareSynchronos.API.Dto.Group; using MareSynchronos.API.Dto.User; +using MareSynchronos.MareConfiguration; using MareSynchronos.PlayerData.Pairs; using MareSynchronos.Services.Mediator; using MareSynchronos.Services.ServerConfiguration; @@ -26,6 +27,7 @@ public class DrawUserPair private readonly SelectTagForPairUi _selectTagForPairUi; private readonly ServerConfigurationManager _serverConfigurationManager; private readonly UiSharedService _uiSharedService; + private readonly PlayerPerformanceConfigService _performanceConfigService; private float _menuWidth = -1; private bool _wasHovered = false; @@ -34,7 +36,7 @@ public class DrawUserPair ApiController apiController, IdDisplayHandler uIDDisplayHandler, MareMediator mareMediator, SelectTagForPairUi selectTagForPairUi, ServerConfigurationManager serverConfigurationManager, - UiSharedService uiSharedService) + UiSharedService uiSharedService, PlayerPerformanceConfigService performanceConfigService) { _id = id; _pair = entry; @@ -46,6 +48,7 @@ public class DrawUserPair _selectTagForPairUi = selectTagForPairUi; _serverConfigurationManager = serverConfigurationManager; _uiSharedService = uiSharedService; + _performanceConfigService = performanceConfigService; } public Pair Pair => _pair; @@ -235,11 +238,11 @@ public class DrawUserPair userPairText += "Files Size: " + UiSharedService.ByteToString(_pair.LastAppliedDataBytes, true); if (_pair.LastAppliedApproximateVRAMBytes >= 0) { - userPairText += Environment.NewLine + "Approximate max. VRAM Usage: " + UiSharedService.ByteToString(_pair.LastAppliedApproximateVRAMBytes, true); + userPairText += Environment.NewLine + "Approx. VRAM Usage: " + UiSharedService.ByteToString(_pair.LastAppliedApproximateVRAMBytes, true); } if (_pair.LastAppliedDataTris >= 0) { - userPairText += Environment.NewLine + "Triangle Count (excl. Vanilla): " + userPairText += Environment.NewLine + "Approx. Triangle Count (excl. Vanilla): " + (_pair.LastAppliedDataTris > 1000 ? (_pair.LastAppliedDataTris / 1000d).ToString("0.0'k'") : _pair.LastAppliedDataTris); } } @@ -257,6 +260,34 @@ public class DrawUserPair UiSharedService.AttachToolTip(userPairText); + if (_performanceConfigService.Current.ShowPerformanceIndicator + && !_performanceConfigService.Current.UIDsToIgnore + .Exists(uid => string.Equals(uid, UserPair.User.Alias, StringComparison.Ordinal) || string.Equals(uid, UserPair.User.UID, StringComparison.Ordinal)) + && (_performanceConfigService.Current.VRAMSizeWarningThresholdMiB * 1024 * 1024 < _pair.LastAppliedApproximateVRAMBytes + || _performanceConfigService.Current.TrisWarningThresholdThousands * 1000 < _pair.LastAppliedDataTris) + && (!_pair.UserPair.OwnPermissions.IsSticky() + || _performanceConfigService.Current.WarnOnPreferredPermissionsExceedingThresholds)) + { + ImGui.SameLine(); + + _uiSharedService.IconText(FontAwesomeIcon.ExclamationTriangle, ImGuiColors.DalamudYellow); + + string userWarningText = "WARNING: This user exceeds one or more of your defined thresholds:" + UiSharedService.TooltipSeparator; + bool shownVram = false; + if (_performanceConfigService.Current.VRAMSizeWarningThresholdMiB * 1024 * 1024 < _pair.LastAppliedApproximateVRAMBytes) + { + shownVram = true; + userWarningText += $"Approx. VRAM Usage: Used: {UiSharedService.ByteToString(_pair.LastAppliedApproximateVRAMBytes)}, Threshold: {_performanceConfigService.Current.VRAMSizeWarningThresholdMiB} MiB"; + } + if (_performanceConfigService.Current.TrisWarningThresholdThousands * 1024 < _pair.LastAppliedDataTris) + { + if (shownVram) userWarningText += Environment.NewLine; + userWarningText += $"Approx. Triangle count: Used: {_pair.LastAppliedDataTris}, Threshold: {_performanceConfigService.Current.TrisWarningThresholdThousands * 1000}"; + } + + UiSharedService.AttachToolTip(userWarningText); + } + ImGui.SameLine(); } diff --git a/MareSynchronos/UI/DrawEntityFactory.cs b/MareSynchronos/UI/DrawEntityFactory.cs index f52889b..65952cf 100644 --- a/MareSynchronos/UI/DrawEntityFactory.cs +++ b/MareSynchronos/UI/DrawEntityFactory.cs @@ -1,4 +1,5 @@ using MareSynchronos.API.Dto.Group; +using MareSynchronos.MareConfiguration; using MareSynchronos.PlayerData.Pairs; using MareSynchronos.Services.Mediator; using MareSynchronos.Services.ServerConfiguration; @@ -18,6 +19,7 @@ public class DrawEntityFactory private readonly SelectPairForTagUi _selectPairForTagUi; private readonly ServerConfigurationManager _serverConfigurationManager; private readonly UiSharedService _uiSharedService; + private readonly PlayerPerformanceConfigService _playerPerformanceConfigService; private readonly SelectTagForPairUi _selectTagForPairUi; private readonly TagHandler _tagHandler; private readonly IdDisplayHandler _uidDisplayHandler; @@ -25,7 +27,8 @@ public class DrawEntityFactory public DrawEntityFactory(ILogger logger, ApiController apiController, IdDisplayHandler uidDisplayHandler, SelectTagForPairUi selectTagForPairUi, MareMediator mediator, TagHandler tagHandler, SelectPairForTagUi selectPairForTagUi, - ServerConfigurationManager serverConfigurationManager, UiSharedService uiSharedService) + ServerConfigurationManager serverConfigurationManager, UiSharedService uiSharedService, + PlayerPerformanceConfigService playerPerformanceConfigService) { _logger = logger; _apiController = apiController; @@ -36,6 +39,7 @@ public class DrawEntityFactory _selectPairForTagUi = selectPairForTagUi; _serverConfigurationManager = serverConfigurationManager; _uiSharedService = uiSharedService; + _playerPerformanceConfigService = playerPerformanceConfigService; } public DrawFolderGroup CreateDrawGroupFolder(GroupFullInfoDto groupFullInfoDto, @@ -58,6 +62,6 @@ public class DrawEntityFactory public DrawUserPair CreateDrawPair(string id, Pair user, List groups, GroupFullInfoDto? currentGroup) { return new DrawUserPair(id + user.UserData.UID, user, groups, currentGroup, _apiController, _uidDisplayHandler, - _mediator, _selectTagForPairUi, _serverConfigurationManager, _uiSharedService); + _mediator, _selectTagForPairUi, _serverConfigurationManager, _uiSharedService, _playerPerformanceConfigService); } } \ No newline at end of file diff --git a/MareSynchronos/UI/SettingsUi.cs b/MareSynchronos/UI/SettingsUi.cs index 2dd3743..e1c900a 100644 --- a/MareSynchronos/UI/SettingsUi.cs +++ b/MareSynchronos/UI/SettingsUi.cs @@ -1400,7 +1400,7 @@ public class SettingsUi : WindowMediatorSubscriberBase _playerPerformanceConfigService.Save(); } _uiShared.DrawHelpText("Mare will print a warning in chat once per session of meeting those people. Will not warn on players with preferred permissions."); - using (ImRaii.Disabled(!warnOnExceedingThresholds)) + using (ImRaii.Disabled(!warnOnExceedingThresholds && !showPerformanceIndicator)) { using var indent = ImRaii.PushIndent(); var warnOnPref = _playerPerformanceConfigService.Current.WarnOnPreferredPermissionsExceedingThresholds; @@ -1409,7 +1409,7 @@ public class SettingsUi : WindowMediatorSubscriberBase _playerPerformanceConfigService.Current.WarnOnPreferredPermissionsExceedingThresholds = warnOnPref; _playerPerformanceConfigService.Save(); } - _uiShared.DrawHelpText("Mare will also print warnings for players where you enabled preferred permissions."); + _uiShared.DrawHelpText("Mare will also print warnings and show performance indicator for players where you enabled preferred permissions."); } using (ImRaii.Disabled(!showPerformanceIndicator && !warnOnExceedingThresholds)) { @@ -1456,8 +1456,7 @@ public class SettingsUi : WindowMediatorSubscriberBase _playerPerformanceConfigService.Save(); } _uiShared.DrawHelpText("When enabled, will automatically pause all players regardless of preferred permissions that exceed thresholds defined below." + UiSharedService.TooltipSeparator + - "Warning: this will not automatically unpause those people again, you will have to do this manually." + UiSharedService.TooltipSeparator - + "Default: 550 MiB"); + "Warning: this will not automatically unpause those people again, you will have to do this manually."); var vramAuto = _playerPerformanceConfigService.Current.VRAMSizeAutoPauseThresholdMiB; var trisAuto = _playerPerformanceConfigService.Current.TrisAutoPauseThresholdThousands; ImGui.SetNextItemWidth(100); @@ -1468,7 +1467,8 @@ public class SettingsUi : WindowMediatorSubscriberBase } ImGui.SameLine(); ImGui.Text("(MiB)"); - _uiShared.DrawHelpText("When a loading in player and their VRAM usage exceeds this amount, automatically pauses the synced player."); + _uiShared.DrawHelpText("When a loading in player and their VRAM usage exceeds this amount, automatically pauses the synced player." + UiSharedService.TooltipSeparator + + "Default: 550 MiB"); ImGui.SetNextItemWidth(100); if (ImGui.InputInt("Auto Pause Triangle threshold", ref trisAuto)) { diff --git a/MareSynchronos/Utils/VariousExtensions.cs b/MareSynchronos/Utils/VariousExtensions.cs index 766acf3..cffa495 100644 --- a/MareSynchronos/Utils/VariousExtensions.cs +++ b/MareSynchronos/Utils/VariousExtensions.cs @@ -190,6 +190,13 @@ public static class VariousExtensions logger.LogDebug("[BASE-{appBase}] Updating {object}/{kind} (Diff moodles data) => {change}", applicationBase, cachedPlayer, objectKind, PlayerChanges.Moodles); charaDataToUpdate[objectKind].Add(PlayerChanges.Moodles); } + + bool petNamesDataDifferent = !string.Equals(oldData.PetNamesData, newData.PetNamesData, StringComparison.Ordinal); + if (petNamesDataDifferent || (forceApplyCustomization && !string.IsNullOrEmpty(newData.PetNamesData))) + { + logger.LogDebug("[BASE-{appBase}] Updating {object}/{kind} (Diff petnames data) => {change}", applicationBase, cachedPlayer, objectKind, PlayerChanges.PetNames); + charaDataToUpdate[objectKind].Add(PlayerChanges.PetNames); + } } foreach (KeyValuePair> data in charaDataToUpdate.ToList())