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