From 92182ba0b1fdb8b78537a01768c25b0db801abfb Mon Sep 17 00:00:00 2001 From: Stanley Dimant Date: Wed, 4 Sep 2024 15:30:19 +0200 Subject: [PATCH] something something performance --- .../Configurations/PlayerPerformanceConfig.cs | 16 ++ .../PlayerPerformanceConfigService.cs | 11 ++ .../Factories/PairHandlerFactory.cs | 6 +- .../PlayerData/Handlers/PairHandler.cs | 47 +++-- MareSynchronos/PlayerData/Pairs/Pair.cs | 3 +- MareSynchronos/Plugin.cs | 3 + .../Services/PlayerPerformanceService.cs | 134 +++++++++++++++ .../ServerConfigurationManager.cs | 9 +- MareSynchronos/Services/XivDataAnalyzer.cs | 30 +++- MareSynchronos/UI/Components/DrawUserPair.cs | 8 +- MareSynchronos/UI/IntroUI.cs | 2 +- MareSynchronos/UI/SettingsUi.cs | 161 ++++++++++++++++++ .../WebAPI/Files/FileDownloadManager.cs | 13 +- 13 files changed, 412 insertions(+), 31 deletions(-) create mode 100644 MareSynchronos/MareConfiguration/Configurations/PlayerPerformanceConfig.cs create mode 100644 MareSynchronos/MareConfiguration/PlayerPerformanceConfigService.cs create mode 100644 MareSynchronos/Services/PlayerPerformanceService.cs diff --git a/MareSynchronos/MareConfiguration/Configurations/PlayerPerformanceConfig.cs b/MareSynchronos/MareConfiguration/Configurations/PlayerPerformanceConfig.cs new file mode 100644 index 0000000..6471f1b --- /dev/null +++ b/MareSynchronos/MareConfiguration/Configurations/PlayerPerformanceConfig.cs @@ -0,0 +1,16 @@ +namespace MareSynchronos.MareConfiguration.Configurations; + +public class PlayerPerformanceConfig : IMareConfiguration +{ + public int Version { get; set; } = 1; + public bool ShowPerformanceIndicator { get; set; } = true; + public bool WarnOnExceedingThresholds { get; set; } = true; + public bool WarnOnPreferredPermissionsExceedingThresholds { get; set; } = false; + public int VRAMSizeWarningThresholdMiB { get; set; } = 375; + public int TrisWarningThresholdThousands { get; set; } = 165; + public bool AutoPausePlayersExceedingThresholds { get; set; } = false; + public bool AutoPausePlayersWithPreferredPermissionsExceedingThresholds { get; set; } = false; + public int VRAMSizeAutoPauseThresholdMiB { get; set; } = 550; + public int TrisAutoPauseThresholdThousands { get; set; } = 250; + public List UIDsToIgnore { get; set; } = new(); +} \ No newline at end of file diff --git a/MareSynchronos/MareConfiguration/PlayerPerformanceConfigService.cs b/MareSynchronos/MareConfiguration/PlayerPerformanceConfigService.cs new file mode 100644 index 0000000..e07eca1 --- /dev/null +++ b/MareSynchronos/MareConfiguration/PlayerPerformanceConfigService.cs @@ -0,0 +1,11 @@ +using MareSynchronos.MareConfiguration.Configurations; + +namespace MareSynchronos.MareConfiguration; + +public class PlayerPerformanceConfigService : ConfigurationServiceBase +{ + public const string ConfigName = "playerperformance.json"; + public PlayerPerformanceConfigService(string configDir) : base(configDir) { } + + protected override string ConfigurationName => ConfigName; +} \ No newline at end of file diff --git a/MareSynchronos/PlayerData/Factories/PairHandlerFactory.cs b/MareSynchronos/PlayerData/Factories/PairHandlerFactory.cs index 5c2019e..d4476df 100644 --- a/MareSynchronos/PlayerData/Factories/PairHandlerFactory.cs +++ b/MareSynchronos/PlayerData/Factories/PairHandlerFactory.cs @@ -21,12 +21,13 @@ public class PairHandlerFactory 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) + FileCacheManager fileCacheManager, MareMediator mareMediator, XivDataAnalyzer modelAnalyzer, PlayerPerformanceService playerPerformanceService) { _loggerFactory = loggerFactory; _gameObjectHandlerFactory = gameObjectHandlerFactory; @@ -38,12 +39,13 @@ public class PairHandlerFactory _fileCacheManager = fileCacheManager; _mareMediator = mareMediator; _xivDataAnalyzer = modelAnalyzer; + _playerPerformanceService = playerPerformanceService; } public PairHandler Create(OnlineUserIdentDto onlineUserIdentDto) { return new PairHandler(_loggerFactory.CreateLogger(), onlineUserIdentDto, _gameObjectHandlerFactory, _ipcManager, _fileDownloadManagerFactory.Create(), _pluginWarningNotificationManager, _dalamudUtilService, _hostApplicationLifetime, - _fileCacheManager, _mareMediator, _xivDataAnalyzer); + _fileCacheManager, _mareMediator, _xivDataAnalyzer, _playerPerformanceService); } } \ No newline at end of file diff --git a/MareSynchronos/PlayerData/Handlers/PairHandler.cs b/MareSynchronos/PlayerData/Handlers/PairHandler.cs index f7aab7c..8f3592f 100644 --- a/MareSynchronos/PlayerData/Handlers/PairHandler.cs +++ b/MareSynchronos/PlayerData/Handlers/PairHandler.cs @@ -25,6 +25,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase 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; @@ -41,8 +42,9 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase private Dictionary _customizeIds = []; private bool _redrawOnNextApplication = false; private CombatData? _dataReceivedInDowntime; - public long LastAppliedDataSize { get; private set; } + public long LastAppliedDataBytes { get; private set; } public long LastAppliedDataTris { get; private set; } + public long LastAppliedApproximateVRAMBytes { get; private set; } public PairHandler(ILogger logger, OnlineUserIdentDto onlineUser, GameObjectHandlerFactory gameObjectHandlerFactory, @@ -50,7 +52,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase PluginWarningNotificationService pluginWarningNotificationManager, DalamudUtilService dalamudUtil, IHostApplicationLifetime lifetime, FileCacheManager fileDbManager, MareMediator mediator, - XivDataAnalyzer modelAnalyzer) : base(logger, mediator) + XivDataAnalyzer modelAnalyzer, PlayerPerformanceService playerPerformanceService) : base(logger, mediator) { OnlineUser = onlineUser; _gameObjectHandlerFactory = gameObjectHandlerFactory; @@ -61,6 +63,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase _lifetime = lifetime; _fileDbManager = fileDbManager; _xivDataAnalyzer = modelAnalyzer; + _playerPerformanceService = playerPerformanceService; _penumbraCollection = _ipcManager.Penumbra.CreateTemporaryCollectionAsync(logger, OnlineUser.User.UID).ConfigureAwait(false).GetAwaiter().GetResult(); Mediator.Subscribe(this, (_) => FrameworkUpdate()); @@ -103,7 +106,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase _applicationCancellationTokenSource = _applicationCancellationTokenSource?.CancelRecreate(); }); - LastAppliedDataSize = -1; + LastAppliedDataBytes = -1; LastAppliedDataTris = -1; } @@ -384,18 +387,29 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase 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); - if (toDownloadReplacements.Any()) + + Mediator.Publish(new EventMessage(new Event(PlayerName, OnlineUser.User, 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)) { - Mediator.Publish(new EventMessage(new Event(PlayerName, OnlineUser.User, nameof(PairHandler), EventSeverity.Informational, - $"Starting download for {toDownloadReplacements.Count} files"))); - await _downloadManager.DownloadFiles(_charaHandler!, toDownloadReplacements, downloadToken).ConfigureAwait(false); + LastAppliedApproximateVRAMBytes = vramDuringDl; _downloadManager.CancelDownload(); + return; } + LastAppliedApproximateVRAMBytes = vramDuringDl; + + await _downloadManager.DownloadFiles(_charaHandler!, toDownloadReplacements, downloadToken).ConfigureAwait(false); + _downloadManager.CancelDownload(); + if (downloadToken.IsCancellationRequested) { Logger.LogTrace("[BASE-{appBase}] Detected cancellation", applicationBase); @@ -412,6 +426,13 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase await Task.Delay(TimeSpan.FromSeconds(2)).ConfigureAwait(false); } + + if (LastAppliedApproximateVRAMBytes == -1 + && !_playerPerformanceService.TryCalculateVRAMUsage(this, charaData, [], out long vramUsage)) + { + LastAppliedApproximateVRAMBytes = vramUsage; + return; + } } downloadToken.ThrowIfCancellationRequested(); @@ -446,12 +467,18 @@ 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); - LastAppliedDataSize = -1; + LastAppliedDataBytes = -1; LastAppliedDataTris = -1; foreach (var path in moddedPaths.Values.Distinct(StringComparer.OrdinalIgnoreCase).Select(v => new FileInfo(v)).Where(p => p.Exists)) { - if (LastAppliedDataSize == -1) LastAppliedDataSize = 0; - LastAppliedDataSize += path.Length; + 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))) { diff --git a/MareSynchronos/PlayerData/Pairs/Pair.cs b/MareSynchronos/PlayerData/Pairs/Pair.cs index 27ae3bd..9e63c22 100644 --- a/MareSynchronos/PlayerData/Pairs/Pair.cs +++ b/MareSynchronos/PlayerData/Pairs/Pair.cs @@ -44,8 +44,9 @@ public class Pair public bool IsVisible => CachedPlayer?.IsVisible ?? false; public CharacterData? LastReceivedCharacterData { get; set; } public string? PlayerName => CachedPlayer?.PlayerName ?? string.Empty; - public long LastAppliedDataSize => CachedPlayer?.LastAppliedDataSize ?? -1; + public long LastAppliedDataBytes => CachedPlayer?.LastAppliedDataBytes ?? -1; public long LastAppliedDataTris => CachedPlayer?.LastAppliedDataTris ?? -1; + public long LastAppliedApproximateVRAMBytes => CachedPlayer?.LastAppliedApproximateVRAMBytes ?? -1; public UserData UserData => UserPair.User; diff --git a/MareSynchronos/Plugin.cs b/MareSynchronos/Plugin.cs index 9425470..bdab575 100644 --- a/MareSynchronos/Plugin.cs +++ b/MareSynchronos/Plugin.cs @@ -110,6 +110,7 @@ public sealed class Plugin : IDalamudPlugin collection.AddSingleton(); collection.AddSingleton(); collection.AddSingleton(); + collection.AddSingleton(); collection.AddSingleton((s) => new IpcProvider(s.GetRequiredService>(), pluginInterface, s.GetRequiredService(), s.GetRequiredService(), @@ -150,6 +151,7 @@ public sealed class Plugin : IDalamudPlugin collection.AddSingleton((s) => new ServerTagConfigService(pluginInterface.ConfigDirectory.FullName)); collection.AddSingleton((s) => new TransientConfigService(pluginInterface.ConfigDirectory.FullName)); collection.AddSingleton((s) => new XivDataStorageService(pluginInterface.ConfigDirectory.FullName)); + collection.AddSingleton((s) => new PlayerPerformanceConfigService(pluginInterface.ConfigDirectory.FullName)); collection.AddSingleton((s) => new ConfigurationMigrator(s.GetRequiredService>(), pluginInterface)); collection.AddSingleton(); @@ -197,6 +199,7 @@ 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/PlayerPerformanceService.cs b/MareSynchronos/Services/PlayerPerformanceService.cs new file mode 100644 index 0000000..2f00302 --- /dev/null +++ b/MareSynchronos/Services/PlayerPerformanceService.cs @@ -0,0 +1,134 @@ +using MareSynchronos.API.Data; +using MareSynchronos.API.Data.Extensions; +using MareSynchronos.FileCache; +using MareSynchronos.MareConfiguration; +using MareSynchronos.PlayerData.Handlers; +using MareSynchronos.PlayerData.Pairs; +using MareSynchronos.Services.Mediator; +using MareSynchronos.UI; +using MareSynchronos.Utils; +using MareSynchronos.WebAPI; +using MareSynchronos.WebAPI.Files.Models; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace MareSynchronos.Services; + +public class PlayerPerformanceService : IHostedService +{ + private readonly ILogger _logger; + private readonly MareMediator _mediator; + private readonly PlayerPerformanceConfigService _playerPerformanceConfigService; + private readonly FileCacheManager _fileCacheManager; + private readonly PairManager _pairManager; + private readonly ApiController _apiController; + + public PlayerPerformanceService(ILogger logger, MareMediator mediator, + PlayerPerformanceConfigService playerPerformanceConfigService, FileCacheManager fileCacheManager, + PairManager pairManager, ApiController apiController) + { + _logger = logger; + _mediator = mediator; + _playerPerformanceConfigService = playerPerformanceConfigService; + _fileCacheManager = fileCacheManager; + _pairManager = pairManager; + _apiController = apiController; + } + + 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) + { + var config = _playerPerformanceConfigService.Current; + var pair = _pairManager.GetOnlineUserPairs().First(p => string.Equals(p.UserData.UID, pairHandler.OnlineUser.User.UID, StringComparison.OrdinalIgnoreCase)); + + vramUsage = 0; + + if (!charaData.FileReplacements.TryGetValue(API.Data.Enum.ObjectKind.Player, out List? playerReplacements)) + 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.ToList()) + { + long fileSize = 0; + + var download = toDownloadFiles.Find(f => string.Equals(hash, f.Hash, StringComparison.OrdinalIgnoreCase)); + if (download != null) + { + fileSize = download.Total; + } + 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; + } + + _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.VRAMSizeAutoPauseThresholdMiB * 1024 * 1024, + vramUsage, config.AutoPausePlayersWithPreferredPermissionsExceedingThresholds, isPrefPerm)) + { + _mediator.Publish(new NotificationMessage($"{pair.PlayerName} ({pair.UserData.AliasOrUID}) automatically paused", + $"Player {pair.PlayerName} exceeded your configured VRAM auto pause threshold (" + + $"{UiSharedService.ByteToString(vramUsage, addSuffix: true)}/{config.VRAMSizeAutoPauseThresholdMiB}MiB)" + + $" and has been automatically paused.", + MareConfiguration.Models.NotificationType.Warning)); + + // pause + var perm = pair.UserPair.OwnPermissions.DeepClone(); + perm.SetPaused(paused: true); + _ = _apiController.SetBulkPermissions(new( + new(StringComparer.Ordinal) { { pair.UserData.UID, perm } }, + new(StringComparer.Ordinal))); + + 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)); + return true; + } + + return true; + } + + private static bool CheckForThreshold(bool thresholdEnabled, long threshold, long value, bool checkForPrefPerm, bool isPrefPerm) => + thresholdEnabled && threshold > 0 && threshold < value && ((checkForPrefPerm && isPrefPerm) || !isPrefPerm); +} \ No newline at end of file diff --git a/MareSynchronos/Services/ServerConfiguration/ServerConfigurationManager.cs b/MareSynchronos/Services/ServerConfiguration/ServerConfigurationManager.cs index 68c3d80..2c45413 100644 --- a/MareSynchronos/Services/ServerConfiguration/ServerConfigurationManager.cs +++ b/MareSynchronos/Services/ServerConfiguration/ServerConfigurationManager.cs @@ -169,7 +169,7 @@ public class ServerConfigurationManager Save(); } - internal void AddCurrentCharacterToServer(int serverSelectionIndex = -1, bool addLastSecretKey = false) + internal void AddCurrentCharacterToServer(int serverSelectionIndex = -1) { if (serverSelectionIndex == -1) serverSelectionIndex = CurrentServerIndex; var server = GetServerByIndex(serverSelectionIndex); @@ -181,7 +181,7 @@ public class ServerConfigurationManager { CharacterName = _dalamudUtil.GetPlayerNameAsync().GetAwaiter().GetResult(), WorldId = _dalamudUtil.GetHomeWorldIdAsync().GetAwaiter().GetResult(), - SecretKeyIdx = addLastSecretKey ? server.SecretKeys.Last().Key : -1, + SecretKeyIdx = server.SecretKeys.Last().Key, }); Save(); } @@ -189,7 +189,10 @@ public class ServerConfigurationManager internal void AddEmptyCharacterToServer(int serverSelectionIndex) { var server = GetServerByIndex(serverSelectionIndex); - server.Authentications.Add(new Authentication()); + server.Authentications.Add(new Authentication() + { + SecretKeyIdx = server.SecretKeys.Any() ? server.SecretKeys.First().Key : -1, + }); Save(); } diff --git a/MareSynchronos/Services/XivDataAnalyzer.cs b/MareSynchronos/Services/XivDataAnalyzer.cs index 1e09228..e5ceec7 100644 --- a/MareSynchronos/Services/XivDataAnalyzer.cs +++ b/MareSynchronos/Services/XivDataAnalyzer.cs @@ -177,7 +177,7 @@ public sealed class XivDataAnalyzer public Task GetTrianglesByHash(string hash) { - if (_configService.Current.TriangleDictionary.TryGetValue(hash, out var cachedTris)) + if (_configService.Current.TriangleDictionary.TryGetValue(hash, out var cachedTris) && cachedTris > 0) return Task.FromResult(cachedTris); var path = _fileCacheManager.GetFileCacheByHash(hash); @@ -192,13 +192,29 @@ public sealed class XivDataAnalyzer var file = _luminaGameData.GetFileFromDisk(filePath); if (file.FileHeader.LodCount <= 0) return Task.FromResult((long)0); - var meshIdx = file.Lods[0].MeshIndex; - var meshCnt = file.Lods[0].MeshCount; - var tris = file.Meshes.Skip(meshIdx).Take(meshCnt).Sum(p => p.IndexCount) / 3; + long tris = 0; + for (int i = 0; i < file.FileHeader.LodCount; i++) + { + try + { + var meshIdx = file.Lods[i].MeshIndex; + var meshCnt = file.Lods[i].MeshCount; + tris = file.Meshes.Skip(meshIdx).Take(meshCnt).Sum(p => p.IndexCount) / 3; + } + catch (Exception ex) + { + _logger.LogDebug(ex, "Could not load lod mesh {mesh} from {path}", i, filePath); + continue; + } - _logger.LogDebug("{filePath} => {tris} triangles", filePath, tris); - _configService.Current.TriangleDictionary[hash] = tris; - _configService.Save(); + if (tris > 0) + { + _logger.LogDebug("{filePath} => {tris} triangles", filePath, tris); + _configService.Current.TriangleDictionary[hash] = tris; + _configService.Save(); + break; + } + } return Task.FromResult(tris); } catch (Exception e) diff --git a/MareSynchronos/UI/Components/DrawUserPair.cs b/MareSynchronos/UI/Components/DrawUserPair.cs index 8723bf7..7917137 100644 --- a/MareSynchronos/UI/Components/DrawUserPair.cs +++ b/MareSynchronos/UI/Components/DrawUserPair.cs @@ -228,11 +228,15 @@ public class DrawUserPair userPairText += UiSharedService.TooltipSeparator + "You are directly Paired"; } - if (_pair.LastAppliedDataSize >= 0) + if (_pair.LastAppliedDataBytes >= 0) { userPairText += UiSharedService.TooltipSeparator; userPairText += ((!_pair.IsPaired) ? "(Last) " : string.Empty) + "Mods Info" + Environment.NewLine; - userPairText += "Files Size: " + UiSharedService.ByteToString(_pair.LastAppliedDataSize, true); + userPairText += "Files Size: " + UiSharedService.ByteToString(_pair.LastAppliedDataBytes, true); + if (_pair.LastAppliedApproximateVRAMBytes >= 0) + { + userPairText += Environment.NewLine + "Approximate max. VRAM Usage: " + UiSharedService.ByteToString(_pair.LastAppliedApproximateVRAMBytes, true); + } if (_pair.LastAppliedDataTris >= 0) { userPairText += Environment.NewLine + "Triangle Count (excl. Vanilla): " diff --git a/MareSynchronos/UI/IntroUI.cs b/MareSynchronos/UI/IntroUI.cs index d7a9eba..98b2ce6 100644 --- a/MareSynchronos/UI/IntroUI.cs +++ b/MareSynchronos/UI/IntroUI.cs @@ -244,7 +244,7 @@ public partial class IntroUi : WindowMediatorSubscriberBase FriendlyName = $"Secret Key added on Setup ({DateTime.Now:yyyy-MM-dd})", Key = _secretKey, }); - _serverConfigurationManager.AddCurrentCharacterToServer(addLastSecretKey: true); + _serverConfigurationManager.AddCurrentCharacterToServer(); } else { diff --git a/MareSynchronos/UI/SettingsUi.cs b/MareSynchronos/UI/SettingsUi.cs index b681955..73fcaf6 100644 --- a/MareSynchronos/UI/SettingsUi.cs +++ b/MareSynchronos/UI/SettingsUi.cs @@ -44,6 +44,7 @@ public class SettingsUi : WindowMediatorSubscriberBase private readonly PairManager _pairManager; private readonly PerformanceCollectorService _performanceCollector; private readonly ServerConfigurationManager _serverConfigurationManager; + private readonly PlayerPerformanceConfigService _playerPerformanceConfigService; private readonly UiSharedService _uiShared; private bool _deleteAccountPopupModalShown = false; private bool _deleteFilesPopupModalShown = false; @@ -64,6 +65,7 @@ public class SettingsUi : WindowMediatorSubscriberBase UiSharedService uiShared, MareConfigService configService, MareCharaFileManager mareCharaFileManager, PairManager pairManager, ServerConfigurationManager serverConfigurationManager, + PlayerPerformanceConfigService playerPerformanceConfigService, MareMediator mediator, PerformanceCollectorService performanceCollector, FileUploadManager fileTransferManager, FileTransferOrchestrator fileTransferOrchestrator, @@ -76,6 +78,7 @@ public class SettingsUi : WindowMediatorSubscriberBase _mareCharaFileManager = mareCharaFileManager; _pairManager = pairManager; _serverConfigurationManager = serverConfigurationManager; + _playerPerformanceConfigService = playerPerformanceConfigService; _performanceCollector = performanceCollector; _fileTransferManager = fileTransferManager; _fileTransferOrchestrator = fileTransferOrchestrator; @@ -118,6 +121,8 @@ public class SettingsUi : WindowMediatorSubscriberBase public override void OnClose() { _uiShared.EditTrackerPosition = false; + _uidToAddForIgnore = string.Empty; + base.OnClose(); } @@ -1316,6 +1321,156 @@ public class SettingsUi : WindowMediatorSubscriberBase } } + private void DrawPerformance() + { + _uiShared.BigText("Performance Settings"); + UiSharedService.TextWrapped("The configuration options here are to give you more informed warnings and automation when it comes to other performance-intensive synced players."); + ImGui.Dummy(new Vector2(10)); + ImGui.Separator(); + ImGui.Dummy(new Vector2(10)); + bool showPerformanceIndicator = _playerPerformanceConfigService.Current.ShowPerformanceIndicator; + if (ImGui.Checkbox("Show performance indicator", ref showPerformanceIndicator)) + { + _playerPerformanceConfigService.Current.ShowPerformanceIndicator = showPerformanceIndicator; + _playerPerformanceConfigService.Save(); + } + _uiShared.DrawHelpText("Will show a performance indicator when players exceed defined thresholds in the main UI." + Environment.NewLine + "Will use warning thresholds."); + bool warnOnExceedingThresholds = _playerPerformanceConfigService.Current.WarnOnExceedingThresholds; + if (ImGui.Checkbox("Warn on loading in players exceeding performance thresholds", ref warnOnExceedingThresholds)) + { + _playerPerformanceConfigService.Current.WarnOnExceedingThresholds = warnOnExceedingThresholds; + _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 var indent = ImRaii.PushIndent(); + var warnOnPref = _playerPerformanceConfigService.Current.WarnOnPreferredPermissionsExceedingThresholds; + if (ImGui.Checkbox("Warn also on players with preferred permissions", ref warnOnPref)) + { + _playerPerformanceConfigService.Current.WarnOnPreferredPermissionsExceedingThresholds = warnOnPref; + _playerPerformanceConfigService.Save(); + } + _uiShared.DrawHelpText("Mare will also print warnings for players where you enabled preferred permissions."); + } + using (ImRaii.Disabled(!showPerformanceIndicator && !warnOnExceedingThresholds)) + { + var vram = _playerPerformanceConfigService.Current.VRAMSizeWarningThresholdMiB; + var tris = _playerPerformanceConfigService.Current.TrisWarningThresholdThousands; + ImGui.SetNextItemWidth(100); + if (ImGui.InputInt("Warning VRAM threshold", ref vram)) + { + _playerPerformanceConfigService.Current.VRAMSizeWarningThresholdMiB = vram; + _playerPerformanceConfigService.Save(); + } + ImGui.SameLine(); + ImGui.Text("(MiB)"); + _uiShared.DrawHelpText("Limit in MiB of approximate VRAM usage to trigger warning or performance indicator on UI." + UiSharedService.TooltipSeparator + + "Default: 375 MiB"); + ImGui.SetNextItemWidth(100); + if (ImGui.InputInt("Warning Triangle threshold", ref tris)) + { + _playerPerformanceConfigService.Current.TrisWarningThresholdThousands = tris; + _playerPerformanceConfigService.Save(); + } + ImGui.SameLine(); + ImGui.Text("(thousand triangles)"); + _uiShared.DrawHelpText("Limit in approximate used triangles from mods to trigger warning or performance indicator on UI." + UiSharedService.TooltipSeparator + + "Default: 165 thousand"); + } + ImGui.Dummy(new Vector2(10)); + bool autoPause = _playerPerformanceConfigService.Current.AutoPausePlayersExceedingThresholds; + bool autoPauseEveryone = _playerPerformanceConfigService.Current.AutoPausePlayersWithPreferredPermissionsExceedingThresholds; + if (ImGui.Checkbox("Automatically pause players exceeding thresholds", ref autoPause)) + { + _playerPerformanceConfigService.Current.AutoPausePlayersExceedingThresholds = autoPause; + _playerPerformanceConfigService.Save(); + } + _uiShared.DrawHelpText("When enabled, it will automatically pause all players without preferred permissions that exceed the thresholds defined below." + Environment.NewLine + + "Will print a warning in chat when a player got paused automatically." + + UiSharedService.TooltipSeparator + "Warning: this will not automatically unpause those people again, you will have to do this manually."); + using (ImRaii.Disabled(!autoPause)) + { + using var indent = ImRaii.PushIndent(); + if (ImGui.Checkbox("Automatically pause also players with preferred permissions", ref autoPauseEveryone)) + { + _playerPerformanceConfigService.Current.AutoPausePlayersWithPreferredPermissionsExceedingThresholds = autoPauseEveryone; + _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"); + var vramAuto = _playerPerformanceConfigService.Current.VRAMSizeAutoPauseThresholdMiB; + var trisAuto = _playerPerformanceConfigService.Current.TrisAutoPauseThresholdThousands; + ImGui.SetNextItemWidth(100); + if (ImGui.InputInt("Auto Pause VRAM threshold", ref vramAuto)) + { + _playerPerformanceConfigService.Current.VRAMSizeAutoPauseThresholdMiB = vramAuto; + _playerPerformanceConfigService.Save(); + } + ImGui.SameLine(); + ImGui.Text("(MiB)"); + _uiShared.DrawHelpText("When a loading in player and their VRAM usage exceeds this amount, automatically pauses the synced player."); + ImGui.SetNextItemWidth(100); + if (ImGui.InputInt("Auto Pause Triangle threshold", ref trisAuto)) + { + _playerPerformanceConfigService.Current.TrisAutoPauseThresholdThousands = trisAuto; + _playerPerformanceConfigService.Save(); + } + ImGui.SameLine(); + ImGui.Text("(thousand triangles)"); + _uiShared.DrawHelpText("When a loading in player and their triangle count exceeds this amount, automatically pauses the synced player." + UiSharedService.TooltipSeparator + + "Default: 250 thousand"); + } + ImGui.Dummy(new Vector2(10)); + _uiShared.BigText("Whitelisted UIDs"); + UiSharedService.TextWrapped("The entries in the list below will be ignored for all warnings and auto pause operations."); + ImGui.Dummy(new Vector2(10)); + ImGui.SetNextItemWidth(200); + ImGui.InputText("##ignoreuid", ref _uidToAddForIgnore, 20); + ImGui.SameLine(); + using (ImRaii.Disabled(string.IsNullOrEmpty(_uidToAddForIgnore))) + { + if (_uiShared.IconTextButton(FontAwesomeIcon.Plus, "Add UID to whitelist")) + { + if (!_playerPerformanceConfigService.Current.UIDsToIgnore.Contains(_uidToAddForIgnore, StringComparer.Ordinal)) + { + _playerPerformanceConfigService.Current.UIDsToIgnore.Add(_uidToAddForIgnore); + } + _uidToAddForIgnore = string.Empty; + } + } + _uiShared.DrawHelpText("Hint: UIDs are case sensitive."); + var playerList = _playerPerformanceConfigService.Current.UIDsToIgnore; + ImGui.SetNextItemWidth(200); + using (var lb = ImRaii.ListBox("UID whitelist")) + { + if (lb) + { + for (int i = 0; i < playerList.Count; i++) + { + bool shouldBeSelected = _selectedEntry == i; + if (ImGui.Selectable(playerList[i] + "##" + i, shouldBeSelected)) + { + _selectedEntry = i; + } + } + } + } + using (ImRaii.Disabled(_selectedEntry == -1)) + { + if (_uiShared.IconTextButton(FontAwesomeIcon.Trash, "Delete selected UID")) + { + _playerPerformanceConfigService.Current.UIDsToIgnore.RemoveAt(_selectedEntry); + _selectedEntry = -1; + _playerPerformanceConfigService.Save(); + } + } + } + + private string _uidToAddForIgnore = string.Empty; + private int _selectedEntry = -1; + private void DrawSettingsContent() { if (_apiController.ServerState is ServerState.Connected) @@ -1349,6 +1504,12 @@ public class SettingsUi : WindowMediatorSubscriberBase ImGui.EndTabItem(); } + if (ImGui.BeginTabItem("Performance")) + { + DrawPerformance(); + ImGui.EndTabItem(); + } + if (ImGui.BeginTabItem("Export & Storage")) { DrawFileStorageSettings(); diff --git a/MareSynchronos/WebAPI/Files/FileDownloadManager.cs b/MareSynchronos/WebAPI/Files/FileDownloadManager.cs index 578f97c..5a8ade1 100644 --- a/MareSynchronos/WebAPI/Files/FileDownloadManager.cs +++ b/MareSynchronos/WebAPI/Files/FileDownloadManager.cs @@ -210,7 +210,7 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase } } - private async Task DownloadFilesInternal(GameObjectHandler gameObjectHandler, List fileReplacement, CancellationToken ct) + public async Task> InitiateDownloadList(GameObjectHandler gameObjectHandler, List fileReplacement, CancellationToken ct) { Logger.LogDebug("Download start: {id}", gameObjectHandler.Name); @@ -221,9 +221,6 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase Logger.LogDebug("Files with size 0 or less: {files}", string.Join(", ", downloadFileInfoFromService.Where(f => f.Size <= 0).Select(f => f.Hash))); - CurrentDownloads = downloadFileInfoFromService.Distinct().Select(d => new DownloadFileTransfer(d)) - .Where(d => d.CanBeTransferred).ToList(); - foreach (var dto in downloadFileInfoFromService.Where(c => c.IsForbidden)) { if (!_orchestrator.ForbiddenTransfers.Exists(f => string.Equals(f.Hash, dto.Hash, StringComparison.Ordinal))) @@ -232,7 +229,13 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase } } - var downloadGroups = CurrentDownloads.Where(f => f.CanBeTransferred).GroupBy(f => f.DownloadUri.Host + ":" + f.DownloadUri.Port, StringComparer.Ordinal); + return CurrentDownloads = downloadFileInfoFromService.Distinct().Select(d => new DownloadFileTransfer(d)) + .Where(d => d.CanBeTransferred).ToList(); + } + + private async Task DownloadFilesInternal(GameObjectHandler gameObjectHandler, List fileReplacement, CancellationToken ct) + { + var downloadGroups = CurrentDownloads.GroupBy(f => f.DownloadUri.Host + ":" + f.DownloadUri.Port, StringComparer.Ordinal); foreach (var downloadGroup in downloadGroups) {