From 81bb5885e9dce9c313cff31c80ac5dd93e9d10a7 Mon Sep 17 00:00:00 2001 From: Stanley Dimant Date: Mon, 9 Sep 2024 15:39:41 +0200 Subject: [PATCH] implement triangle check --- MareAPI | 2 +- MareSynchronos/.editorconfig | 7 +- .../Interop/Ipc/IpcCallerGlamourer.cs | 6 +- MareSynchronos/MareSynchronos.csproj | 16 +-- .../Factories/PairHandlerFactory.cs | 6 +- .../PlayerData/Handlers/PairHandler.cs | 105 +++++++--------- .../PlayerData/Pairs/OnlinePlayerManager.cs | 2 +- MareSynchronos/PlayerData/Pairs/Pair.cs | 6 +- MareSynchronos/Plugin.cs | 1 - MareSynchronos/Services/DalamudUtilService.cs | 2 +- .../Services/PlayerPerformanceService.cs | 114 ++++++++++++++---- MareSynchronos/UI/SettingsUi.cs | 2 +- .../Files/Models/DownloadFileTransfer.cs | 2 + 13 files changed, 161 insertions(+), 110 deletions(-) diff --git a/MareAPI b/MareAPI index 4a0ee24..7cc3ec4 160000 --- a/MareAPI +++ b/MareAPI @@ -1 +1 @@ -Subproject commit 4a0ee24688ec35786f4a77d5efbff20f2309c163 +Subproject commit 7cc3ec43e2e805ae4ace99e308a8825803963675 diff --git a/MareSynchronos/.editorconfig b/MareSynchronos/.editorconfig index f6abd9f..441acd7 100644 --- a/MareSynchronos/.editorconfig +++ b/MareSynchronos/.editorconfig @@ -102,10 +102,13 @@ dotnet_diagnostic.MA0075.severity = silent dotnet_diagnostic.S3358.severity = suggestion # S6678: Use PascalCase for named placeholders -dotnet_diagnostic.S6678.severity = suggestion +dotnet_diagnostic.S6678.severity = none # S6605: Collection-specific "Exists" method should be used instead of the "Any" extension -dotnet_diagnostic.S6605.severity = suggestion +dotnet_diagnostic.S6605.severity = none # S6667: Logging in a catch clause should pass the caught exception as a parameter. dotnet_diagnostic.S6667.severity = suggestion + +# IDE0290: Use primary constructor +csharp_style_prefer_primary_constructors = false diff --git a/MareSynchronos/Interop/Ipc/IpcCallerGlamourer.cs b/MareSynchronos/Interop/Ipc/IpcCallerGlamourer.cs index b78924e..65d72b4 100644 --- a/MareSynchronos/Interop/Ipc/IpcCallerGlamourer.cs +++ b/MareSynchronos/Interop/Ipc/IpcCallerGlamourer.cs @@ -59,6 +59,7 @@ public sealed class IpcCallerGlamourer : DisposableMediatorSubscriberBase, IIpcC base.Dispose(disposing); _redrawManager.Cancel(); + _glamourerStateChanged?.Dispose(); } public bool APIAvailable { get; private set; } @@ -102,11 +103,6 @@ public sealed class IpcCallerGlamourer : DisposableMediatorSubscriberBase, IIpcC } } - public void Dispose() - { - _glamourerStateChanged?.Dispose(); - } - public async Task ApplyAllAsync(ILogger logger, GameObjectHandler handler, string? customization, Guid applicationId, CancellationToken token, bool fireAndForget = false) { if (!APIAvailable || string.IsNullOrEmpty(customization) || _dalamudUtil.IsZoning) return; diff --git a/MareSynchronos/MareSynchronos.csproj b/MareSynchronos/MareSynchronos.csproj index f8990f6..3a1791d 100644 --- a/MareSynchronos/MareSynchronos.csproj +++ b/MareSynchronos/MareSynchronos.csproj @@ -32,23 +32,23 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + - + - + - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/MareSynchronos/PlayerData/Factories/PairHandlerFactory.cs b/MareSynchronos/PlayerData/Factories/PairHandlerFactory.cs index ee4b49e..755ba78 100644 --- a/MareSynchronos/PlayerData/Factories/PairHandlerFactory.cs +++ b/MareSynchronos/PlayerData/Factories/PairHandlerFactory.cs @@ -20,14 +20,13 @@ public class PairHandlerFactory private readonly IpcManager _ipcManager; private readonly ILoggerFactory _loggerFactory; private readonly MareMediator _mareMediator; - private readonly XivDataAnalyzer _xivDataAnalyzer; private readonly PlayerPerformanceService _playerPerformanceService; private readonly PluginWarningNotificationService _pluginWarningNotificationManager; public PairHandlerFactory(ILoggerFactory loggerFactory, GameObjectHandlerFactory gameObjectHandlerFactory, IpcManager ipcManager, FileDownloadManagerFactory fileDownloadManagerFactory, DalamudUtilService dalamudUtilService, PluginWarningNotificationService pluginWarningNotificationManager, IHostApplicationLifetime hostApplicationLifetime, - FileCacheManager fileCacheManager, MareMediator mareMediator, XivDataAnalyzer modelAnalyzer, PlayerPerformanceService playerPerformanceService) + FileCacheManager fileCacheManager, MareMediator mareMediator, PlayerPerformanceService playerPerformanceService) { _loggerFactory = loggerFactory; _gameObjectHandlerFactory = gameObjectHandlerFactory; @@ -38,7 +37,6 @@ public class PairHandlerFactory _hostApplicationLifetime = hostApplicationLifetime; _fileCacheManager = fileCacheManager; _mareMediator = mareMediator; - _xivDataAnalyzer = modelAnalyzer; _playerPerformanceService = playerPerformanceService; } @@ -46,6 +44,6 @@ public class PairHandlerFactory { return new PairHandler(_loggerFactory.CreateLogger(), pair, _gameObjectHandlerFactory, _ipcManager, _fileDownloadManagerFactory.Create(), _pluginWarningNotificationManager, _dalamudUtilService, _hostApplicationLifetime, - _fileCacheManager, _mareMediator, _xivDataAnalyzer, _playerPerformanceService); + _fileCacheManager, _mareMediator, _playerPerformanceService); } } \ No newline at end of file diff --git a/MareSynchronos/PlayerData/Handlers/PairHandler.cs b/MareSynchronos/PlayerData/Handlers/PairHandler.cs index a4d7b80..ea99323 100644 --- a/MareSynchronos/PlayerData/Handlers/PairHandler.cs +++ b/MareSynchronos/PlayerData/Handlers/PairHandler.cs @@ -24,27 +24,23 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase private readonly DalamudUtilService _dalamudUtil; private readonly FileDownloadManager _downloadManager; private readonly FileCacheManager _fileDbManager; - private readonly XivDataAnalyzer _xivDataAnalyzer; - private readonly PlayerPerformanceService _playerPerformanceService; private readonly GameObjectHandlerFactory _gameObjectHandlerFactory; private readonly IpcManager _ipcManager; private readonly IHostApplicationLifetime _lifetime; + private readonly PlayerPerformanceService _playerPerformanceService; private readonly PluginWarningNotificationService _pluginWarningNotificationManager; private CancellationTokenSource? _applicationCancellationTokenSource = new(); private Guid _applicationId; private Task? _applicationTask; private CharacterData? _cachedData = null; private GameObjectHandler? _charaHandler; + private readonly Dictionary _customizeIds = []; + private CombatData? _dataReceivedInDowntime; private CancellationTokenSource? _downloadCancellationTokenSource = new(); private bool _forceApplyMods = false; private bool _isVisible; private Guid _penumbraCollection; - private Dictionary _customizeIds = []; private bool _redrawOnNextApplication = false; - private CombatData? _dataReceivedInDowntime; - public long LastAppliedDataBytes { get; private set; } - public long LastAppliedDataTris { get; private set; } - public long LastAppliedApproximateVRAMBytes { get; private set; } public PairHandler(ILogger logger, Pair pair, GameObjectHandlerFactory gameObjectHandlerFactory, @@ -52,9 +48,9 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase PluginWarningNotificationService pluginWarningNotificationManager, DalamudUtilService dalamudUtil, IHostApplicationLifetime lifetime, FileCacheManager fileDbManager, MareMediator mediator, - XivDataAnalyzer modelAnalyzer, PlayerPerformanceService playerPerformanceService) : base(logger, mediator) + PlayerPerformanceService playerPerformanceService) : base(logger, mediator) { - OnlineUser = pair; + Pair = pair; _gameObjectHandlerFactory = gameObjectHandlerFactory; _ipcManager = ipcManager; _downloadManager = transferManager; @@ -62,9 +58,8 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase _dalamudUtil = dalamudUtil; _lifetime = lifetime; _fileDbManager = fileDbManager; - _xivDataAnalyzer = modelAnalyzer; _playerPerformanceService = playerPerformanceService; - _penumbraCollection = _ipcManager.Penumbra.CreateTemporaryCollectionAsync(logger, OnlineUser.UserData.UID).ConfigureAwait(false).GetAwaiter().GetResult(); + _penumbraCollection = _ipcManager.Penumbra.CreateTemporaryCollectionAsync(logger, Pair.UserData.UID).ConfigureAwait(false).GetAwaiter().GetResult(); Mediator.Subscribe(this, (_) => FrameworkUpdate()); Mediator.Subscribe(this, (_) => @@ -75,7 +70,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase }); Mediator.Subscribe(this, (_) => { - _penumbraCollection = _ipcManager.Penumbra.CreateTemporaryCollectionAsync(logger, OnlineUser.UserData.UID).ConfigureAwait(false).GetAwaiter().GetResult(); + _penumbraCollection = _ipcManager.Penumbra.CreateTemporaryCollectionAsync(logger, Pair.UserData.UID).ConfigureAwait(false).GetAwaiter().GetResult(); if (!IsVisible && _charaHandler != null) { PlayerName = string.Empty; @@ -107,7 +102,6 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase }); LastAppliedDataBytes = -1; - LastAppliedDataTris = -1; } public bool IsVisible @@ -119,25 +113,27 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase { _isVisible = value; string text = "User Visibility Changed, now: " + (_isVisible ? "Is Visible" : "Is not Visible"); - Mediator.Publish(new EventMessage(new Event(PlayerName, OnlineUser.UserData, nameof(PairHandler), + Mediator.Publish(new EventMessage(new Event(PlayerName, Pair.UserData, nameof(PairHandler), EventSeverity.Informational, text))); Mediator.Publish(new RefreshUiMessage()); } } } - public Pair OnlineUser { get; private set; } + + public long LastAppliedDataBytes { get; private set; } + public Pair Pair { get; private set; } public nint PlayerCharacter => _charaHandler?.Address ?? nint.Zero; public unsafe uint PlayerCharacterId => (_charaHandler?.Address ?? nint.Zero) == nint.Zero ? uint.MaxValue : ((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)_charaHandler!.Address)->EntityId; public string? PlayerName { get; private set; } - public string PlayerNameHash => OnlineUser.Ident; + public string PlayerNameHash => Pair.Ident; public void ApplyCharacterData(Guid applicationBase, CharacterData characterData, bool forceApplyCustomization = false) { if (_dalamudUtil.IsInCombatOrPerforming) { - Mediator.Publish(new EventMessage(new Event(PlayerName, OnlineUser.UserData, nameof(PairHandler), EventSeverity.Warning, + Mediator.Publish(new EventMessage(new Event(PlayerName, Pair.UserData, nameof(PairHandler), EventSeverity.Warning, "Cannot apply character data: you are in combat or performing music, deferring application"))); Logger.LogDebug("[BASE-{appBase}] Received data but player is in combat or performing", applicationBase); _dataReceivedInDowntime = new(applicationBase, characterData, forceApplyCustomization); @@ -147,7 +143,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase if (_charaHandler == null || (PlayerCharacter == IntPtr.Zero)) { - Mediator.Publish(new EventMessage(new Event(PlayerName, OnlineUser.UserData, nameof(PairHandler), EventSeverity.Warning, + Mediator.Publish(new EventMessage(new Event(PlayerName, Pair.UserData, nameof(PairHandler), EventSeverity.Warning, "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); @@ -169,13 +165,13 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase if (_dalamudUtil.IsInCutscene || _dalamudUtil.IsInGpose || !_ipcManager.Penumbra.APIAvailable || !_ipcManager.Glamourer.APIAvailable) { - Mediator.Publish(new EventMessage(new Event(PlayerName, OnlineUser.UserData, nameof(PairHandler), EventSeverity.Warning, + Mediator.Publish(new EventMessage(new Event(PlayerName, Pair.UserData, nameof(PairHandler), EventSeverity.Warning, "Cannot apply character data: you are in GPose, a Cutscene or Penumbra/Glamourer is not available"))); Logger.LogInformation("[BASE-{appbase}] Application of data for {player} while in cutscene/gpose or Penumbra/Glamourer unavailable, returning", applicationBase, this); return; } - Mediator.Publish(new EventMessage(new Event(PlayerName, OnlineUser.UserData, nameof(PairHandler), EventSeverity.Informational, + Mediator.Publish(new EventMessage(new Event(PlayerName, Pair.UserData, nameof(PairHandler), EventSeverity.Informational, "Applying Character Data"))); _forceApplyMods |= forceApplyCustomization; @@ -195,7 +191,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase if (charaDataToUpdate.TryGetValue(ObjectKind.Player, out var playerChanges)) { - _pluginWarningNotificationManager.NotifyForMissingPlugins(OnlineUser.UserData, PlayerName!, playerChanges); + _pluginWarningNotificationManager.NotifyForMissingPlugins(Pair.UserData, PlayerName!, playerChanges); } Logger.LogDebug("[BASE-{appbase}] Downloading and applying character for {name}", applicationBase, this); @@ -205,9 +201,9 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase public override string ToString() { - return OnlineUser == null + return Pair == null ? base.ToString() ?? string.Empty - : OnlineUser.UserData.AliasOrUID + ":" + PlayerName + ":" + (PlayerCharacter != nint.Zero ? "HasChar" : "NoChar"); + : Pair.UserData.AliasOrUID + ":" + PlayerName + ":" + (PlayerCharacter != nint.Zero ? "HasChar" : "NoChar"); } internal void SetUploading(bool isUploading = true) @@ -226,7 +222,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase SetUploading(isUploading: false); _downloadManager.Dispose(); var name = PlayerName; - Logger.LogDebug("Disposing {name} ({user})", name, OnlineUser); + Logger.LogDebug("Disposing {name} ({user})", name, Pair); try { Guid applicationId = Guid.NewGuid(); @@ -239,19 +235,19 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase if (!string.IsNullOrEmpty(name)) { - Mediator.Publish(new EventMessage(new Event(name, OnlineUser.UserData, nameof(PairHandler), EventSeverity.Informational, "Disposing User"))); + Mediator.Publish(new EventMessage(new Event(name, Pair.UserData, nameof(PairHandler), EventSeverity.Informational, "Disposing User"))); } if (_lifetime.ApplicationStopping.IsCancellationRequested) return; if (_dalamudUtil is { IsZoning: false, IsInCutscene: false } && !string.IsNullOrEmpty(name)) { - Logger.LogTrace("[{applicationId}] Restoring state for {name} ({OnlineUser})", applicationId, name, OnlineUser.UserPair); - Logger.LogDebug("[{applicationId}] Removing Temp Collection for {name} ({user})", applicationId, name, OnlineUser.UserPair); + Logger.LogTrace("[{applicationId}] Restoring state for {name} ({OnlineUser})", applicationId, name, Pair.UserPair); + Logger.LogDebug("[{applicationId}] Removing Temp Collection for {name} ({user})", applicationId, name, Pair.UserPair); _ipcManager.Penumbra.RemoveTemporaryCollectionAsync(Logger, applicationId, _penumbraCollection).GetAwaiter().GetResult(); if (!IsVisible) { - Logger.LogDebug("[{applicationId}] Restoring Glamourer for {name} ({user})", applicationId, name, OnlineUser.UserPair); + Logger.LogDebug("[{applicationId}] Restoring Glamourer for {name} ({user})", applicationId, name, Pair.UserPair); _ipcManager.Glamourer.RevertByNameAsync(Logger, name, applicationId).GetAwaiter().GetResult(); } else @@ -382,33 +378,28 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase _ = Task.Run(async () => { - Dictionary<(string GamePath, string? Hash), string> moddedPaths = new(); + Dictionary<(string GamePath, string? Hash), string> moddedPaths = []; if (updateModdedPaths) { int attempts = 0; List toDownloadReplacements = TryCalculateModdedDictionary(applicationBase, charaData, out moddedPaths, downloadToken); - LastAppliedApproximateVRAMBytes = -1; - 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, OnlineUser.UserData, nameof(PairHandler), EventSeverity.Informational, + 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.TryCalculateVRAMUsage(this, charaData, toDownloadFiles, out long vramDuringDl)) + if (!_playerPerformanceService.CheckVRAMUsageThresholds(this, charaData, toDownloadFiles)) { - LastAppliedApproximateVRAMBytes = vramDuringDl; _downloadManager.CancelDownload(); return; } - LastAppliedApproximateVRAMBytes = vramDuringDl; - await _downloadManager.DownloadFiles(_charaHandler!, toDownloadReplacements, downloadToken).ConfigureAwait(false); _downloadManager.CancelDownload(); @@ -429,10 +420,13 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase await Task.Delay(TimeSpan.FromSeconds(2)).ConfigureAwait(false); } - if (LastAppliedApproximateVRAMBytes == -1 - && !_playerPerformanceService.TryCalculateVRAMUsage(this, charaData, [], out long vramUsage)) + if (attempts == 0 && !_playerPerformanceService.CheckVRAMUsageThresholds(this, charaData, [])) + { + return; + } + + if (!await _playerPerformanceService.CheckTriangleUsageThresholds(this, charaData).ConfigureAwait(false)) { - LastAppliedApproximateVRAMBytes = vramUsage; return; } } @@ -470,22 +464,11 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase await _ipcManager.Penumbra.SetTemporaryModsAsync(Logger, _applicationId, _penumbraCollection, moddedPaths.ToDictionary(k => k.Key.GamePath, k => k.Value, StringComparer.Ordinal)).ConfigureAwait(false); LastAppliedDataBytes = -1; - LastAppliedDataTris = -1; foreach (var path in moddedPaths.Values.Distinct(StringComparer.OrdinalIgnoreCase).Select(v => new FileInfo(v)).Where(p => p.Exists)) { if (LastAppliedDataBytes == -1) LastAppliedDataBytes = 0; - if (LastAppliedApproximateVRAMBytes == -1) LastAppliedApproximateVRAMBytes = 0; LastAppliedDataBytes += path.Length; - if (path.Name.EndsWith(".tex", StringComparison.OrdinalIgnoreCase)) - { - LastAppliedApproximateVRAMBytes += path.Length; - } - } - foreach (var key in moddedPaths.Keys.Where(k => !string.IsNullOrEmpty(k.Hash))) - { - if (LastAppliedDataTris == -1) LastAppliedDataTris = 0; - LastAppliedDataTris += await _xivDataAnalyzer.GetTrianglesByHash(key.Hash!).ConfigureAwait(false); } } @@ -528,12 +511,12 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase { if (string.IsNullOrEmpty(PlayerName)) { - var pc = _dalamudUtil.FindPlayerByNameHash(OnlineUser.Ident); + var pc = _dalamudUtil.FindPlayerByNameHash(Pair.Ident); if (pc == default((string, nint))) return; Logger.LogDebug("One-Time Initializing {this}", this); Initialize(pc.Name); Logger.LogDebug("One-Time Initialized {this}", this); - Mediator.Publish(new EventMessage(new Event(PlayerName, OnlineUser.UserData, nameof(PairHandler), EventSeverity.Informational, + Mediator.Publish(new EventMessage(new Event(PlayerName, Pair.UserData, nameof(PairHandler), EventSeverity.Informational, $"Initializing User For Character {pc.Name}"))); } @@ -569,7 +552,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase private void Initialize(string name) { PlayerName = name; - _charaHandler = _gameObjectHandlerFactory.Create(ObjectKind.Player, () => _dalamudUtil.GetPlayerCharacterFromCachedTableByIdent(OnlineUser.Ident), isWatched: false).GetAwaiter().GetResult(); + _charaHandler = _gameObjectHandlerFactory.Create(ObjectKind.Player, () => _dalamudUtil.GetPlayerCharacterFromCachedTableByIdent(Pair.Ident), isWatched: false).GetAwaiter().GetResult(); Mediator.Subscribe(this, async (_) => { @@ -583,10 +566,10 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase private async Task RevertCustomizationDataAsync(ObjectKind objectKind, string name, Guid applicationId, CancellationToken cancelToken) { - nint address = _dalamudUtil.GetPlayerCharacterFromCachedTableByIdent(OnlineUser.Ident); + nint address = _dalamudUtil.GetPlayerCharacterFromCachedTableByIdent(Pair.Ident); if (address == nint.Zero) return; - Logger.LogDebug("[{applicationId}] Reverting all Customization for {alias}/{name} {objectKind}", applicationId, OnlineUser.UserData.AliasOrUID, name, objectKind); + Logger.LogDebug("[{applicationId}] Reverting all Customization for {alias}/{name} {objectKind}", applicationId, Pair.UserData.AliasOrUID, name, objectKind); if (_customizeIds.TryGetValue(objectKind, out var customizeId)) { @@ -597,18 +580,18 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase { using GameObjectHandler tempHandler = await _gameObjectHandlerFactory.Create(ObjectKind.Player, () => address, isWatched: false).ConfigureAwait(false); tempHandler.CompareNameAndThrow(name); - Logger.LogDebug("[{applicationId}] Restoring Customization and Equipment for {alias}/{name}", applicationId, OnlineUser.UserData.AliasOrUID, name); + Logger.LogDebug("[{applicationId}] Restoring Customization and Equipment for {alias}/{name}", applicationId, Pair.UserData.AliasOrUID, name); await _ipcManager.Glamourer.RevertAsync(Logger, name, tempHandler, applicationId, cancelToken).ConfigureAwait(false); tempHandler.CompareNameAndThrow(name); - Logger.LogDebug("[{applicationId}] Restoring Heels for {alias}/{name}", applicationId, OnlineUser.UserData.AliasOrUID, name); + Logger.LogDebug("[{applicationId}] Restoring Heels for {alias}/{name}", applicationId, Pair.UserData.AliasOrUID, name); await _ipcManager.Heels.RestoreOffsetForPlayerAsync(address).ConfigureAwait(false); tempHandler.CompareNameAndThrow(name); - Logger.LogDebug("[{applicationId}] Restoring C+ for {alias}/{name}", applicationId, OnlineUser.UserData.AliasOrUID, name); + Logger.LogDebug("[{applicationId}] Restoring C+ for {alias}/{name}", applicationId, Pair.UserData.AliasOrUID, name); await _ipcManager.CustomizePlus.RevertByIdAsync(customizeId).ConfigureAwait(false); tempHandler.CompareNameAndThrow(name); - Logger.LogDebug("[{applicationId}] Restoring Honorific for {alias}/{name}", applicationId, OnlineUser.UserData.AliasOrUID, name); + Logger.LogDebug("[{applicationId}] Restoring Honorific for {alias}/{name}", applicationId, Pair.UserData.AliasOrUID, name); await _ipcManager.Honorific.ClearTitleAsync(address).ConfigureAwait(false); - Logger.LogDebug("[{applicationId}] Restoring Moodles for {alias}/{name}", applicationId, OnlineUser.UserData.AliasOrUID, name); + Logger.LogDebug("[{applicationId}] Restoring Moodles for {alias}/{name}", applicationId, Pair.UserData.AliasOrUID, name); await _ipcManager.Moodles.RevertStatusAsync(address).ConfigureAwait(false); } else if (objectKind == ObjectKind.MinionOrMount) @@ -650,7 +633,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase { Stopwatch st = Stopwatch.StartNew(); ConcurrentBag missingFiles = []; - moddedDictionary = new Dictionary<(string GamePath, string? Hash), string>(); + moddedDictionary = []; ConcurrentDictionary<(string GamePath, string? Hash), string> outputDict = new(); bool hasMigrationChanges = false; diff --git a/MareSynchronos/PlayerData/Pairs/OnlinePlayerManager.cs b/MareSynchronos/PlayerData/Pairs/OnlinePlayerManager.cs index d314151..7655b72 100644 --- a/MareSynchronos/PlayerData/Pairs/OnlinePlayerManager.cs +++ b/MareSynchronos/PlayerData/Pairs/OnlinePlayerManager.cs @@ -53,7 +53,7 @@ public class OnlinePlayerManager : DisposableMediatorSubscriberBase var newVisiblePlayers = _newVisiblePlayers.ToList(); _newVisiblePlayers.Clear(); Logger.LogTrace("Has new visible players, pushing character data"); - PushCharacterData(newVisiblePlayers.Select(c => c.OnlineUser.UserData).ToList()); + PushCharacterData(newVisiblePlayers.Select(c => c.Pair.UserData).ToList()); } private void PlayerManagerOnPlayerHasChanged() diff --git a/MareSynchronos/PlayerData/Pairs/Pair.cs b/MareSynchronos/PlayerData/Pairs/Pair.cs index 6bfb274..7fb2455 100644 --- a/MareSynchronos/PlayerData/Pairs/Pair.cs +++ b/MareSynchronos/PlayerData/Pairs/Pair.cs @@ -20,7 +20,7 @@ public class Pair private readonly ILogger _logger; private readonly MareMediator _mediator; private readonly ServerConfigurationManager _serverConfigurationManager; - private CancellationTokenSource _applicationCts = new CancellationTokenSource(); + private CancellationTokenSource _applicationCts = new(); private OnlineUserIdentDto? _onlineUserIdentDto = null; public Pair(ILogger logger, UserFullPairDto userPair, PairHandlerFactory cachedPlayerFactory, @@ -45,8 +45,8 @@ public class Pair public CharacterData? LastReceivedCharacterData { get; set; } public string? PlayerName => CachedPlayer?.PlayerName ?? string.Empty; public long LastAppliedDataBytes => CachedPlayer?.LastAppliedDataBytes ?? -1; - public long LastAppliedDataTris => CachedPlayer?.LastAppliedDataTris ?? -1; - public long LastAppliedApproximateVRAMBytes => CachedPlayer?.LastAppliedApproximateVRAMBytes ?? -1; + public long LastAppliedDataTris { get; set; } = -1; + public long LastAppliedApproximateVRAMBytes { get; set; } = -1; public string Ident => _onlineUserIdentDto?.Ident ?? string.Empty; public UserData UserData => UserPair.User; diff --git a/MareSynchronos/Plugin.cs b/MareSynchronos/Plugin.cs index bdab575..c4f75b6 100644 --- a/MareSynchronos/Plugin.cs +++ b/MareSynchronos/Plugin.cs @@ -199,7 +199,6 @@ public sealed class Plugin : IDalamudPlugin collection.AddHostedService(p => p.GetRequiredService()); collection.AddHostedService(p => p.GetRequiredService()); collection.AddHostedService(p => p.GetRequiredService()); - collection.AddHostedService(p => p.GetRequiredService()); collection.AddHostedService(p => p.GetRequiredService()); collection.AddHostedService(p => p.GetRequiredService()); collection.AddHostedService(p => p.GetRequiredService()); diff --git a/MareSynchronos/Services/DalamudUtilService.cs b/MareSynchronos/Services/DalamudUtilService.cs index dcb096e..5d1d47b 100644 --- a/MareSynchronos/Services/DalamudUtilService.cs +++ b/MareSynchronos/Services/DalamudUtilService.cs @@ -199,7 +199,7 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber return await RunOnFrameworkThread(() => GetHashedAccIdFromPlayerPointer(GetPlayerPointer())).ConfigureAwait(false); } - private unsafe string GetHashedAccIdFromPlayerPointer(nint ptr) + private unsafe static string GetHashedAccIdFromPlayerPointer(nint ptr) { if (ptr == nint.Zero) return string.Empty; return ((BattleChara*)ptr)->Character.AccountId.ToString().GetHash256(); diff --git a/MareSynchronos/Services/PlayerPerformanceService.cs b/MareSynchronos/Services/PlayerPerformanceService.cs index a080fd2..340edba 100644 --- a/MareSynchronos/Services/PlayerPerformanceService.cs +++ b/MareSynchronos/Services/PlayerPerformanceService.cs @@ -2,64 +2,127 @@ using MareSynchronos.API.Data; using MareSynchronos.FileCache; using MareSynchronos.MareConfiguration; using MareSynchronos.PlayerData.Handlers; +using MareSynchronos.Services.Events; using MareSynchronos.Services.Mediator; using MareSynchronos.UI; using MareSynchronos.WebAPI.Files.Models; -using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; namespace MareSynchronos.Services; -public class PlayerPerformanceService : IHostedService +public class PlayerPerformanceService { + private readonly FileCacheManager _fileCacheManager; + private readonly XivDataAnalyzer _xivDataAnalyzer; private readonly ILogger _logger; private readonly MareMediator _mediator; private readonly PlayerPerformanceConfigService _playerPerformanceConfigService; - private readonly FileCacheManager _fileCacheManager; public PlayerPerformanceService(ILogger logger, MareMediator mediator, - PlayerPerformanceConfigService playerPerformanceConfigService, FileCacheManager fileCacheManager) + PlayerPerformanceConfigService playerPerformanceConfigService, FileCacheManager fileCacheManager, + XivDataAnalyzer xivDataAnalyzer) { _logger = logger; _mediator = mediator; _playerPerformanceConfigService = playerPerformanceConfigService; _fileCacheManager = fileCacheManager; + _xivDataAnalyzer = xivDataAnalyzer; } - public Task StartAsync(CancellationToken cancellationToken) - { - return Task.CompletedTask; - } - - public Task StopAsync(CancellationToken cancellationToken) - { - return Task.CompletedTask; - } - - public bool TryCalculateVRAMUsage(PairHandler pairHandler, CharacterData charaData, List toDownloadFiles, out long vramUsage) + public async Task CheckTriangleUsageThresholds(PairHandler pairHandler, CharacterData charaData) { var config = _playerPerformanceConfigService.Current; - var pair = pairHandler.OnlineUser; + var pair = pairHandler.Pair; - vramUsage = 0; + long triUsage = 0; if (!charaData.FileReplacements.TryGetValue(API.Data.Enum.ObjectKind.Player, out List? playerReplacements)) + { + pair.LastAppliedDataTris = 0; return true; + } - var moddedTextureHashes = playerReplacements.Where(p => string.IsNullOrEmpty(p.FileSwapPath) && p.GamePaths.Any(g => g.EndsWith("tex", StringComparison.OrdinalIgnoreCase))) + 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 moddedTextureHashes.ToList()) + foreach (var hash in moddedModelHashes) + { + triUsage += await _xivDataAnalyzer.GetTrianglesByHash(hash).ConfigureAwait(false); + } + + pair.LastAppliedDataTris = triUsage; + + _logger.LogDebug("Calculated VRAM usage for {p}", pairHandler); + + // no warning of any kind on ignored pairs + if (config.UIDsToIgnore + .Exists(uid => string.Equals(uid, pair.UserData.Alias, StringComparison.Ordinal) || string.Equals(uid, pair.UserData.UID, StringComparison.Ordinal))) + return true; + + bool isPrefPerm = pair.UserPair.OwnPermissions.HasFlag(API.Data.Enum.UserPermissions.Sticky); + + // now check auto pause + if (CheckForThreshold(config.AutoPausePlayersExceedingThresholds, config.TrisAutoPauseThresholdThousands * 1000, + triUsage, config.AutoPausePlayersWithPreferredPermissionsExceedingThresholds, isPrefPerm)) + { + _mediator.Publish(new NotificationMessage($"{pair.PlayerName} ({pair.UserData.AliasOrUID}) automatically paused", + $"Player {pair.PlayerName} exceeded your configured triangle auto pause threshold (" + + $"{triUsage}/{config.TrisAutoPauseThresholdThousands * 1000} triangles)" + + $" and has been automatically paused.", + 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)"))); + + _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) + { + var config = _playerPerformanceConfigService.Current; + 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.Total; - // todo: use TotalRaw after updating API + fileSize = download.TotalRaw; } else { @@ -78,6 +141,8 @@ public class PlayerPerformanceService : IHostedService vramUsage += fileSize; } + pair.LastAppliedApproximateVRAMBytes = vramUsage; + _logger.LogDebug("Calculated VRAM usage for {p}", pairHandler); // no warning of any kind on ignored pairs @@ -99,6 +164,9 @@ public class PlayerPerformanceService : IHostedService _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)"))); + return false; } @@ -110,7 +178,9 @@ public class PlayerPerformanceService : IHostedService $"Player {pair.PlayerName} exceeds your configured VRAM warning threshold (" + $"{UiSharedService.ByteToString(vramUsage, true)}/{config.VRAMSizeWarningThresholdMiB}MiB).", MareConfiguration.Models.NotificationType.Warning)); - return true; + + _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/UI/SettingsUi.cs b/MareSynchronos/UI/SettingsUi.cs index 8da380c..2dd3743 100644 --- a/MareSynchronos/UI/SettingsUi.cs +++ b/MareSynchronos/UI/SettingsUi.cs @@ -181,7 +181,7 @@ public class SettingsUi : WindowMediatorSubscriberBase } ImGui.SameLine(); ImGui.SetNextItemWidth(100); - _uiShared.DrawCombo("###speed", new[] { DownloadSpeeds.Bps, DownloadSpeeds.KBps, DownloadSpeeds.MBps }, + _uiShared.DrawCombo("###speed", [DownloadSpeeds.Bps, DownloadSpeeds.KBps, DownloadSpeeds.MBps], (s) => s switch { DownloadSpeeds.Bps => "Byte/s", diff --git a/MareSynchronos/WebAPI/Files/Models/DownloadFileTransfer.cs b/MareSynchronos/WebAPI/Files/Models/DownloadFileTransfer.cs index 0665b49..5080a1a 100644 --- a/MareSynchronos/WebAPI/Files/Models/DownloadFileTransfer.cs +++ b/MareSynchronos/WebAPI/Files/Models/DownloadFileTransfer.cs @@ -18,5 +18,7 @@ public class DownloadFileTransfer : FileTransfer } get => Dto.Size; } + + public long TotalRaw => Dto.RawSize; private DownloadFileDto Dto => (DownloadFileDto)TransferDto; } \ No newline at end of file