implement triangle check

This commit is contained in:
Stanley Dimant
2024-09-09 15:39:41 +02:00
parent 6567c4deba
commit 81bb5885e9
13 changed files with 161 additions and 110 deletions

Submodule MareAPI updated: 4a0ee24688...7cc3ec43e2

View File

@@ -102,10 +102,13 @@ dotnet_diagnostic.MA0075.severity = silent
dotnet_diagnostic.S3358.severity = suggestion dotnet_diagnostic.S3358.severity = suggestion
# S6678: Use PascalCase for named placeholders # 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 # 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. # S6667: Logging in a catch clause should pass the caught exception as a parameter.
dotnet_diagnostic.S6667.severity = suggestion dotnet_diagnostic.S6667.severity = suggestion
# IDE0290: Use primary constructor
csharp_style_prefer_primary_constructors = false

View File

@@ -59,6 +59,7 @@ public sealed class IpcCallerGlamourer : DisposableMediatorSubscriberBase, IIpcC
base.Dispose(disposing); base.Dispose(disposing);
_redrawManager.Cancel(); _redrawManager.Cancel();
_glamourerStateChanged?.Dispose();
} }
public bool APIAvailable { get; private set; } 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) 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; if (!APIAvailable || string.IsNullOrEmpty(customization) || _dalamudUtil.IsZoning) return;

View File

@@ -32,23 +32,23 @@
<PackageReference Include="DalamudPackager" Version="2.1.13" /> <PackageReference Include="DalamudPackager" Version="2.1.13" />
<PackageReference Include="Downloader" Version="3.1.2" /> <PackageReference Include="Downloader" Version="3.1.2" />
<PackageReference Include="lz4net" Version="1.0.15.93" /> <PackageReference Include="lz4net" Version="1.0.15.93" />
<PackageReference Include="Meziantou.Analyzer" Version="2.0.160"> <PackageReference Include="Meziantou.Analyzer" Version="2.0.163">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="8.0.7" /> <PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="8.0.8" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="8.0.7" /> <PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="8.0.8" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
<PackageReference Include="Glamourer.Api" Version="2.1.0" /> <PackageReference Include="Glamourer.Api" Version="2.2.0" />
<PackageReference Include="NReco.Logging.File" Version="1.2.1" /> <PackageReference Include="NReco.Logging.File" Version="1.2.1" />
<PackageReference Include="Penumbra.Api" Version="5.2.0" /> <PackageReference Include="Penumbra.Api" Version="5.3.0" />
<PackageReference Include="Penumbra.String" Version="1.0.4" /> <PackageReference Include="Penumbra.String" Version="1.0.4" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.4" /> <PackageReference Include="SixLabors.ImageSharp" Version="3.1.5" />
<PackageReference Include="SonarAnalyzer.CSharp" Version="9.29.0.95321"> <PackageReference Include="SonarAnalyzer.CSharp" Version="9.32.0.97167">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.0.0" /> <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.0.2" />
</ItemGroup> </ItemGroup>
<PropertyGroup> <PropertyGroup>

View File

@@ -20,14 +20,13 @@ public class PairHandlerFactory
private readonly IpcManager _ipcManager; private readonly IpcManager _ipcManager;
private readonly ILoggerFactory _loggerFactory; private readonly ILoggerFactory _loggerFactory;
private readonly MareMediator _mareMediator; private readonly MareMediator _mareMediator;
private readonly XivDataAnalyzer _xivDataAnalyzer;
private readonly PlayerPerformanceService _playerPerformanceService; private readonly PlayerPerformanceService _playerPerformanceService;
private readonly PluginWarningNotificationService _pluginWarningNotificationManager; private readonly PluginWarningNotificationService _pluginWarningNotificationManager;
public PairHandlerFactory(ILoggerFactory loggerFactory, GameObjectHandlerFactory gameObjectHandlerFactory, IpcManager ipcManager, public PairHandlerFactory(ILoggerFactory loggerFactory, GameObjectHandlerFactory gameObjectHandlerFactory, IpcManager ipcManager,
FileDownloadManagerFactory fileDownloadManagerFactory, DalamudUtilService dalamudUtilService, FileDownloadManagerFactory fileDownloadManagerFactory, DalamudUtilService dalamudUtilService,
PluginWarningNotificationService pluginWarningNotificationManager, IHostApplicationLifetime hostApplicationLifetime, PluginWarningNotificationService pluginWarningNotificationManager, IHostApplicationLifetime hostApplicationLifetime,
FileCacheManager fileCacheManager, MareMediator mareMediator, XivDataAnalyzer modelAnalyzer, PlayerPerformanceService playerPerformanceService) FileCacheManager fileCacheManager, MareMediator mareMediator, PlayerPerformanceService playerPerformanceService)
{ {
_loggerFactory = loggerFactory; _loggerFactory = loggerFactory;
_gameObjectHandlerFactory = gameObjectHandlerFactory; _gameObjectHandlerFactory = gameObjectHandlerFactory;
@@ -38,7 +37,6 @@ public class PairHandlerFactory
_hostApplicationLifetime = hostApplicationLifetime; _hostApplicationLifetime = hostApplicationLifetime;
_fileCacheManager = fileCacheManager; _fileCacheManager = fileCacheManager;
_mareMediator = mareMediator; _mareMediator = mareMediator;
_xivDataAnalyzer = modelAnalyzer;
_playerPerformanceService = playerPerformanceService; _playerPerformanceService = playerPerformanceService;
} }
@@ -46,6 +44,6 @@ public class PairHandlerFactory
{ {
return new PairHandler(_loggerFactory.CreateLogger<PairHandler>(), pair, _gameObjectHandlerFactory, return new PairHandler(_loggerFactory.CreateLogger<PairHandler>(), pair, _gameObjectHandlerFactory,
_ipcManager, _fileDownloadManagerFactory.Create(), _pluginWarningNotificationManager, _dalamudUtilService, _hostApplicationLifetime, _ipcManager, _fileDownloadManagerFactory.Create(), _pluginWarningNotificationManager, _dalamudUtilService, _hostApplicationLifetime,
_fileCacheManager, _mareMediator, _xivDataAnalyzer, _playerPerformanceService); _fileCacheManager, _mareMediator, _playerPerformanceService);
} }
} }

View File

@@ -24,27 +24,23 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
private readonly DalamudUtilService _dalamudUtil; private readonly DalamudUtilService _dalamudUtil;
private readonly FileDownloadManager _downloadManager; private readonly FileDownloadManager _downloadManager;
private readonly FileCacheManager _fileDbManager; private readonly FileCacheManager _fileDbManager;
private readonly XivDataAnalyzer _xivDataAnalyzer;
private readonly PlayerPerformanceService _playerPerformanceService;
private readonly GameObjectHandlerFactory _gameObjectHandlerFactory; private readonly GameObjectHandlerFactory _gameObjectHandlerFactory;
private readonly IpcManager _ipcManager; private readonly IpcManager _ipcManager;
private readonly IHostApplicationLifetime _lifetime; private readonly IHostApplicationLifetime _lifetime;
private readonly PlayerPerformanceService _playerPerformanceService;
private readonly PluginWarningNotificationService _pluginWarningNotificationManager; private readonly PluginWarningNotificationService _pluginWarningNotificationManager;
private CancellationTokenSource? _applicationCancellationTokenSource = new(); private CancellationTokenSource? _applicationCancellationTokenSource = new();
private Guid _applicationId; private Guid _applicationId;
private Task? _applicationTask; private Task? _applicationTask;
private CharacterData? _cachedData = null; private CharacterData? _cachedData = null;
private GameObjectHandler? _charaHandler; private GameObjectHandler? _charaHandler;
private readonly Dictionary<ObjectKind, Guid?> _customizeIds = [];
private CombatData? _dataReceivedInDowntime;
private CancellationTokenSource? _downloadCancellationTokenSource = new(); private CancellationTokenSource? _downloadCancellationTokenSource = new();
private bool _forceApplyMods = false; private bool _forceApplyMods = false;
private bool _isVisible; private bool _isVisible;
private Guid _penumbraCollection; private Guid _penumbraCollection;
private Dictionary<ObjectKind, Guid?> _customizeIds = [];
private bool _redrawOnNextApplication = false; 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<PairHandler> logger, Pair pair, public PairHandler(ILogger<PairHandler> logger, Pair pair,
GameObjectHandlerFactory gameObjectHandlerFactory, GameObjectHandlerFactory gameObjectHandlerFactory,
@@ -52,9 +48,9 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
PluginWarningNotificationService pluginWarningNotificationManager, PluginWarningNotificationService pluginWarningNotificationManager,
DalamudUtilService dalamudUtil, IHostApplicationLifetime lifetime, DalamudUtilService dalamudUtil, IHostApplicationLifetime lifetime,
FileCacheManager fileDbManager, MareMediator mediator, FileCacheManager fileDbManager, MareMediator mediator,
XivDataAnalyzer modelAnalyzer, PlayerPerformanceService playerPerformanceService) : base(logger, mediator) PlayerPerformanceService playerPerformanceService) : base(logger, mediator)
{ {
OnlineUser = pair; Pair = pair;
_gameObjectHandlerFactory = gameObjectHandlerFactory; _gameObjectHandlerFactory = gameObjectHandlerFactory;
_ipcManager = ipcManager; _ipcManager = ipcManager;
_downloadManager = transferManager; _downloadManager = transferManager;
@@ -62,9 +58,8 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
_dalamudUtil = dalamudUtil; _dalamudUtil = dalamudUtil;
_lifetime = lifetime; _lifetime = lifetime;
_fileDbManager = fileDbManager; _fileDbManager = fileDbManager;
_xivDataAnalyzer = modelAnalyzer;
_playerPerformanceService = playerPerformanceService; _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<FrameworkUpdateMessage>(this, (_) => FrameworkUpdate()); Mediator.Subscribe<FrameworkUpdateMessage>(this, (_) => FrameworkUpdate());
Mediator.Subscribe<ZoneSwitchStartMessage>(this, (_) => Mediator.Subscribe<ZoneSwitchStartMessage>(this, (_) =>
@@ -75,7 +70,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
}); });
Mediator.Subscribe<PenumbraInitializedMessage>(this, (_) => Mediator.Subscribe<PenumbraInitializedMessage>(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) if (!IsVisible && _charaHandler != null)
{ {
PlayerName = string.Empty; PlayerName = string.Empty;
@@ -107,7 +102,6 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
}); });
LastAppliedDataBytes = -1; LastAppliedDataBytes = -1;
LastAppliedDataTris = -1;
} }
public bool IsVisible public bool IsVisible
@@ -119,25 +113,27 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
{ {
_isVisible = value; _isVisible = value;
string text = "User Visibility Changed, now: " + (_isVisible ? "Is Visible" : "Is not Visible"); 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))); EventSeverity.Informational, text)));
Mediator.Publish(new RefreshUiMessage()); 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 nint PlayerCharacter => _charaHandler?.Address ?? nint.Zero;
public unsafe uint PlayerCharacterId => (_charaHandler?.Address ?? nint.Zero) == nint.Zero public unsafe uint PlayerCharacterId => (_charaHandler?.Address ?? nint.Zero) == nint.Zero
? uint.MaxValue ? uint.MaxValue
: ((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)_charaHandler!.Address)->EntityId; : ((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)_charaHandler!.Address)->EntityId;
public string? PlayerName { get; private set; } 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) public void ApplyCharacterData(Guid applicationBase, CharacterData characterData, bool forceApplyCustomization = false)
{ {
if (_dalamudUtil.IsInCombatOrPerforming) 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"))); "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); Logger.LogDebug("[BASE-{appBase}] Received data but player is in combat or performing", applicationBase);
_dataReceivedInDowntime = new(applicationBase, characterData, forceApplyCustomization); _dataReceivedInDowntime = new(applicationBase, characterData, forceApplyCustomization);
@@ -147,7 +143,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
if (_charaHandler == null || (PlayerCharacter == IntPtr.Zero)) 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"))); "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}", Logger.LogDebug("[BASE-{appBase}] Received data but player was in invalid state, charaHandlerIsNull: {charaIsNull}, playerPointerIsNull: {ptrIsNull}",
applicationBase, _charaHandler == null, PlayerCharacter == IntPtr.Zero); 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) 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"))); "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); Logger.LogInformation("[BASE-{appbase}] Application of data for {player} while in cutscene/gpose or Penumbra/Glamourer unavailable, returning", applicationBase, this);
return; 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"))); "Applying Character Data")));
_forceApplyMods |= forceApplyCustomization; _forceApplyMods |= forceApplyCustomization;
@@ -195,7 +191,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
if (charaDataToUpdate.TryGetValue(ObjectKind.Player, out var playerChanges)) 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); 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() public override string ToString()
{ {
return OnlineUser == null return Pair == null
? base.ToString() ?? string.Empty ? 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) internal void SetUploading(bool isUploading = true)
@@ -226,7 +222,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
SetUploading(isUploading: false); SetUploading(isUploading: false);
_downloadManager.Dispose(); _downloadManager.Dispose();
var name = PlayerName; var name = PlayerName;
Logger.LogDebug("Disposing {name} ({user})", name, OnlineUser); Logger.LogDebug("Disposing {name} ({user})", name, Pair);
try try
{ {
Guid applicationId = Guid.NewGuid(); Guid applicationId = Guid.NewGuid();
@@ -239,19 +235,19 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
if (!string.IsNullOrEmpty(name)) 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 (_lifetime.ApplicationStopping.IsCancellationRequested) return;
if (_dalamudUtil is { IsZoning: false, IsInCutscene: false } && !string.IsNullOrEmpty(name)) if (_dalamudUtil is { IsZoning: false, IsInCutscene: false } && !string.IsNullOrEmpty(name))
{ {
Logger.LogTrace("[{applicationId}] Restoring state for {name} ({OnlineUser})", 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, OnlineUser.UserPair); Logger.LogDebug("[{applicationId}] Removing Temp Collection for {name} ({user})", applicationId, name, Pair.UserPair);
_ipcManager.Penumbra.RemoveTemporaryCollectionAsync(Logger, applicationId, _penumbraCollection).GetAwaiter().GetResult(); _ipcManager.Penumbra.RemoveTemporaryCollectionAsync(Logger, applicationId, _penumbraCollection).GetAwaiter().GetResult();
if (!IsVisible) 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(); _ipcManager.Glamourer.RevertByNameAsync(Logger, name, applicationId).GetAwaiter().GetResult();
} }
else else
@@ -382,33 +378,28 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
_ = Task.Run(async () => _ = Task.Run(async () =>
{ {
Dictionary<(string GamePath, string? Hash), string> moddedPaths = new(); Dictionary<(string GamePath, string? Hash), string> moddedPaths = [];
if (updateModdedPaths) if (updateModdedPaths)
{ {
int attempts = 0; int attempts = 0;
List<FileReplacementData> toDownloadReplacements = TryCalculateModdedDictionary(applicationBase, charaData, out moddedPaths, downloadToken); List<FileReplacementData> toDownloadReplacements = TryCalculateModdedDictionary(applicationBase, charaData, out moddedPaths, downloadToken);
LastAppliedApproximateVRAMBytes = -1;
while (toDownloadReplacements.Count > 0 && attempts++ <= 10 && !downloadToken.IsCancellationRequested) while (toDownloadReplacements.Count > 0 && attempts++ <= 10 && !downloadToken.IsCancellationRequested)
{ {
_downloadManager.CancelDownload(); _downloadManager.CancelDownload();
Logger.LogDebug("[BASE-{appBase}] Downloading missing files for player {name}, {kind}", applicationBase, PlayerName, updatedData); 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"))); $"Starting download for {toDownloadReplacements.Count} files")));
var toDownloadFiles = await _downloadManager.InitiateDownloadList(_charaHandler!, toDownloadReplacements, downloadToken).ConfigureAwait(false); 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(); _downloadManager.CancelDownload();
return; return;
} }
LastAppliedApproximateVRAMBytes = vramDuringDl;
await _downloadManager.DownloadFiles(_charaHandler!, toDownloadReplacements, downloadToken).ConfigureAwait(false); await _downloadManager.DownloadFiles(_charaHandler!, toDownloadReplacements, downloadToken).ConfigureAwait(false);
_downloadManager.CancelDownload(); _downloadManager.CancelDownload();
@@ -429,10 +420,13 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
await Task.Delay(TimeSpan.FromSeconds(2)).ConfigureAwait(false); await Task.Delay(TimeSpan.FromSeconds(2)).ConfigureAwait(false);
} }
if (LastAppliedApproximateVRAMBytes == -1 if (attempts == 0 && !_playerPerformanceService.CheckVRAMUsageThresholds(this, charaData, []))
&& !_playerPerformanceService.TryCalculateVRAMUsage(this, charaData, [], out long vramUsage)) {
return;
}
if (!await _playerPerformanceService.CheckTriangleUsageThresholds(this, charaData).ConfigureAwait(false))
{ {
LastAppliedApproximateVRAMBytes = vramUsage;
return; return;
} }
} }
@@ -470,22 +464,11 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
await _ipcManager.Penumbra.SetTemporaryModsAsync(Logger, _applicationId, _penumbraCollection, await _ipcManager.Penumbra.SetTemporaryModsAsync(Logger, _applicationId, _penumbraCollection,
moddedPaths.ToDictionary(k => k.Key.GamePath, k => k.Value, StringComparer.Ordinal)).ConfigureAwait(false); moddedPaths.ToDictionary(k => k.Key.GamePath, k => k.Value, StringComparer.Ordinal)).ConfigureAwait(false);
LastAppliedDataBytes = -1; LastAppliedDataBytes = -1;
LastAppliedDataTris = -1;
foreach (var path in moddedPaths.Values.Distinct(StringComparer.OrdinalIgnoreCase).Select(v => new FileInfo(v)).Where(p => p.Exists)) foreach (var path in moddedPaths.Values.Distinct(StringComparer.OrdinalIgnoreCase).Select(v => new FileInfo(v)).Where(p => p.Exists))
{ {
if (LastAppliedDataBytes == -1) LastAppliedDataBytes = 0; if (LastAppliedDataBytes == -1) LastAppliedDataBytes = 0;
if (LastAppliedApproximateVRAMBytes == -1) LastAppliedApproximateVRAMBytes = 0;
LastAppliedDataBytes += path.Length; 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)) if (string.IsNullOrEmpty(PlayerName))
{ {
var pc = _dalamudUtil.FindPlayerByNameHash(OnlineUser.Ident); var pc = _dalamudUtil.FindPlayerByNameHash(Pair.Ident);
if (pc == default((string, nint))) return; if (pc == default((string, nint))) return;
Logger.LogDebug("One-Time Initializing {this}", this); Logger.LogDebug("One-Time Initializing {this}", this);
Initialize(pc.Name); Initialize(pc.Name);
Logger.LogDebug("One-Time Initialized {this}", this); 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}"))); $"Initializing User For Character {pc.Name}")));
} }
@@ -569,7 +552,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
private void Initialize(string name) private void Initialize(string name)
{ {
PlayerName = 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<HonorificReadyMessage>(this, async (_) => Mediator.Subscribe<HonorificReadyMessage>(this, async (_) =>
{ {
@@ -583,10 +566,10 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
private async Task RevertCustomizationDataAsync(ObjectKind objectKind, string name, Guid applicationId, CancellationToken cancelToken) 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; 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)) 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); using GameObjectHandler tempHandler = await _gameObjectHandlerFactory.Create(ObjectKind.Player, () => address, isWatched: false).ConfigureAwait(false);
tempHandler.CompareNameAndThrow(name); 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); await _ipcManager.Glamourer.RevertAsync(Logger, name, tempHandler, applicationId, cancelToken).ConfigureAwait(false);
tempHandler.CompareNameAndThrow(name); 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); await _ipcManager.Heels.RestoreOffsetForPlayerAsync(address).ConfigureAwait(false);
tempHandler.CompareNameAndThrow(name); 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); await _ipcManager.CustomizePlus.RevertByIdAsync(customizeId).ConfigureAwait(false);
tempHandler.CompareNameAndThrow(name); 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); 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); await _ipcManager.Moodles.RevertStatusAsync(address).ConfigureAwait(false);
} }
else if (objectKind == ObjectKind.MinionOrMount) else if (objectKind == ObjectKind.MinionOrMount)
@@ -650,7 +633,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
{ {
Stopwatch st = Stopwatch.StartNew(); Stopwatch st = Stopwatch.StartNew();
ConcurrentBag<FileReplacementData> missingFiles = []; ConcurrentBag<FileReplacementData> missingFiles = [];
moddedDictionary = new Dictionary<(string GamePath, string? Hash), string>(); moddedDictionary = [];
ConcurrentDictionary<(string GamePath, string? Hash), string> outputDict = new(); ConcurrentDictionary<(string GamePath, string? Hash), string> outputDict = new();
bool hasMigrationChanges = false; bool hasMigrationChanges = false;

View File

@@ -53,7 +53,7 @@ public class OnlinePlayerManager : DisposableMediatorSubscriberBase
var newVisiblePlayers = _newVisiblePlayers.ToList(); var newVisiblePlayers = _newVisiblePlayers.ToList();
_newVisiblePlayers.Clear(); _newVisiblePlayers.Clear();
Logger.LogTrace("Has new visible players, pushing character data"); 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() private void PlayerManagerOnPlayerHasChanged()

View File

@@ -20,7 +20,7 @@ public class Pair
private readonly ILogger<Pair> _logger; private readonly ILogger<Pair> _logger;
private readonly MareMediator _mediator; private readonly MareMediator _mediator;
private readonly ServerConfigurationManager _serverConfigurationManager; private readonly ServerConfigurationManager _serverConfigurationManager;
private CancellationTokenSource _applicationCts = new CancellationTokenSource(); private CancellationTokenSource _applicationCts = new();
private OnlineUserIdentDto? _onlineUserIdentDto = null; private OnlineUserIdentDto? _onlineUserIdentDto = null;
public Pair(ILogger<Pair> logger, UserFullPairDto userPair, PairHandlerFactory cachedPlayerFactory, public Pair(ILogger<Pair> logger, UserFullPairDto userPair, PairHandlerFactory cachedPlayerFactory,
@@ -45,8 +45,8 @@ public class Pair
public CharacterData? LastReceivedCharacterData { get; set; } public CharacterData? LastReceivedCharacterData { get; set; }
public string? PlayerName => CachedPlayer?.PlayerName ?? string.Empty; public string? PlayerName => CachedPlayer?.PlayerName ?? string.Empty;
public long LastAppliedDataBytes => CachedPlayer?.LastAppliedDataBytes ?? -1; public long LastAppliedDataBytes => CachedPlayer?.LastAppliedDataBytes ?? -1;
public long LastAppliedDataTris => CachedPlayer?.LastAppliedDataTris ?? -1; public long LastAppliedDataTris { get; set; } = -1;
public long LastAppliedApproximateVRAMBytes => CachedPlayer?.LastAppliedApproximateVRAMBytes ?? -1; public long LastAppliedApproximateVRAMBytes { get; set; } = -1;
public string Ident => _onlineUserIdentDto?.Ident ?? string.Empty; public string Ident => _onlineUserIdentDto?.Ident ?? string.Empty;
public UserData UserData => UserPair.User; public UserData UserData => UserPair.User;

View File

@@ -199,7 +199,6 @@ public sealed class Plugin : IDalamudPlugin
collection.AddHostedService(p => p.GetRequiredService<DalamudUtilService>()); collection.AddHostedService(p => p.GetRequiredService<DalamudUtilService>());
collection.AddHostedService(p => p.GetRequiredService<PerformanceCollectorService>()); collection.AddHostedService(p => p.GetRequiredService<PerformanceCollectorService>());
collection.AddHostedService(p => p.GetRequiredService<DtrEntry>()); collection.AddHostedService(p => p.GetRequiredService<DtrEntry>());
collection.AddHostedService(p => p.GetRequiredService<PlayerPerformanceService>());
collection.AddHostedService(p => p.GetRequiredService<EventAggregator>()); collection.AddHostedService(p => p.GetRequiredService<EventAggregator>());
collection.AddHostedService(p => p.GetRequiredService<IpcProvider>()); collection.AddHostedService(p => p.GetRequiredService<IpcProvider>());
collection.AddHostedService(p => p.GetRequiredService<MarePlugin>()); collection.AddHostedService(p => p.GetRequiredService<MarePlugin>());

View File

@@ -199,7 +199,7 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
return await RunOnFrameworkThread(() => GetHashedAccIdFromPlayerPointer(GetPlayerPointer())).ConfigureAwait(false); 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; if (ptr == nint.Zero) return string.Empty;
return ((BattleChara*)ptr)->Character.AccountId.ToString().GetHash256(); return ((BattleChara*)ptr)->Character.AccountId.ToString().GetHash256();

View File

@@ -2,64 +2,127 @@ using MareSynchronos.API.Data;
using MareSynchronos.FileCache; using MareSynchronos.FileCache;
using MareSynchronos.MareConfiguration; using MareSynchronos.MareConfiguration;
using MareSynchronos.PlayerData.Handlers; using MareSynchronos.PlayerData.Handlers;
using MareSynchronos.Services.Events;
using MareSynchronos.Services.Mediator; using MareSynchronos.Services.Mediator;
using MareSynchronos.UI; using MareSynchronos.UI;
using MareSynchronos.WebAPI.Files.Models; using MareSynchronos.WebAPI.Files.Models;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace MareSynchronos.Services; namespace MareSynchronos.Services;
public class PlayerPerformanceService : IHostedService public class PlayerPerformanceService
{ {
private readonly FileCacheManager _fileCacheManager;
private readonly XivDataAnalyzer _xivDataAnalyzer;
private readonly ILogger<PlayerPerformanceService> _logger; private readonly ILogger<PlayerPerformanceService> _logger;
private readonly MareMediator _mediator; private readonly MareMediator _mediator;
private readonly PlayerPerformanceConfigService _playerPerformanceConfigService; private readonly PlayerPerformanceConfigService _playerPerformanceConfigService;
private readonly FileCacheManager _fileCacheManager;
public PlayerPerformanceService(ILogger<PlayerPerformanceService> logger, MareMediator mediator, public PlayerPerformanceService(ILogger<PlayerPerformanceService> logger, MareMediator mediator,
PlayerPerformanceConfigService playerPerformanceConfigService, FileCacheManager fileCacheManager) PlayerPerformanceConfigService playerPerformanceConfigService, FileCacheManager fileCacheManager,
XivDataAnalyzer xivDataAnalyzer)
{ {
_logger = logger; _logger = logger;
_mediator = mediator; _mediator = mediator;
_playerPerformanceConfigService = playerPerformanceConfigService; _playerPerformanceConfigService = playerPerformanceConfigService;
_fileCacheManager = fileCacheManager; _fileCacheManager = fileCacheManager;
_xivDataAnalyzer = xivDataAnalyzer;
} }
public Task StartAsync(CancellationToken cancellationToken) public async Task<bool> CheckTriangleUsageThresholds(PairHandler pairHandler, CharacterData charaData)
{
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
public bool TryCalculateVRAMUsage(PairHandler pairHandler, CharacterData charaData, List<DownloadFileTransfer> toDownloadFiles, out long vramUsage)
{ {
var config = _playerPerformanceConfigService.Current; 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<FileReplacementData>? playerReplacements)) if (!charaData.FileReplacements.TryGetValue(API.Data.Enum.ObjectKind.Player, out List<FileReplacementData>? playerReplacements))
{
pair.LastAppliedDataTris = 0;
return true; 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) .Select(p => p.Hash)
.Distinct(StringComparer.OrdinalIgnoreCase) .Distinct(StringComparer.OrdinalIgnoreCase)
.ToList(); .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<DownloadFileTransfer> toDownloadFiles)
{
var config = _playerPerformanceConfigService.Current;
var pair = pairHandler.Pair;
long vramUsage = 0;
if (!charaData.FileReplacements.TryGetValue(API.Data.Enum.ObjectKind.Player, out List<FileReplacementData>? 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; long fileSize = 0;
var download = toDownloadFiles.Find(f => string.Equals(hash, f.Hash, StringComparison.OrdinalIgnoreCase)); var download = toDownloadFiles.Find(f => string.Equals(hash, f.Hash, StringComparison.OrdinalIgnoreCase));
if (download != null) if (download != null)
{ {
fileSize = download.Total; fileSize = download.TotalRaw;
// todo: use TotalRaw after updating API
} }
else else
{ {
@@ -78,6 +141,8 @@ public class PlayerPerformanceService : IHostedService
vramUsage += fileSize; vramUsage += fileSize;
} }
pair.LastAppliedApproximateVRAMBytes = vramUsage;
_logger.LogDebug("Calculated VRAM usage for {p}", pairHandler); _logger.LogDebug("Calculated VRAM usage for {p}", pairHandler);
// no warning of any kind on ignored pairs // 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 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; return false;
} }
@@ -110,7 +178,9 @@ public class PlayerPerformanceService : IHostedService
$"Player {pair.PlayerName} exceeds your configured VRAM warning threshold (" + $"Player {pair.PlayerName} exceeds your configured VRAM warning threshold (" +
$"{UiSharedService.ByteToString(vramUsage, true)}/{config.VRAMSizeWarningThresholdMiB}MiB).", $"{UiSharedService.ByteToString(vramUsage, true)}/{config.VRAMSizeWarningThresholdMiB}MiB).",
MareConfiguration.Models.NotificationType.Warning)); 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; return true;

View File

@@ -181,7 +181,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
} }
ImGui.SameLine(); ImGui.SameLine();
ImGui.SetNextItemWidth(100); 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 (s) => s switch
{ {
DownloadSpeeds.Bps => "Byte/s", DownloadSpeeds.Bps => "Byte/s",

View File

@@ -18,5 +18,7 @@ public class DownloadFileTransfer : FileTransfer
} }
get => Dto.Size; get => Dto.Size;
} }
public long TotalRaw => Dto.RawSize;
private DownloadFileDto Dto => (DownloadFileDto)TransferDto; private DownloadFileDto Dto => (DownloadFileDto)TransferDto;
} }