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

View File

@@ -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

View File

@@ -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;

View File

@@ -32,23 +32,23 @@
<PackageReference Include="DalamudPackager" Version="2.1.13" />
<PackageReference Include="Downloader" Version="3.1.2" />
<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>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="8.0.7" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="8.0.7" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="8.0.8" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="8.0.8" />
<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="Penumbra.Api" Version="5.2.0" />
<PackageReference Include="Penumbra.Api" Version="5.3.0" />
<PackageReference Include="Penumbra.String" Version="1.0.4" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.4" />
<PackageReference Include="SonarAnalyzer.CSharp" Version="9.29.0.95321">
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.5" />
<PackageReference Include="SonarAnalyzer.CSharp" Version="9.32.0.97167">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.0.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.0.2" />
</ItemGroup>
<PropertyGroup>

View File

@@ -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<PairHandler>(), pair, _gameObjectHandlerFactory,
_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 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<ObjectKind, Guid?> _customizeIds = [];
private CombatData? _dataReceivedInDowntime;
private CancellationTokenSource? _downloadCancellationTokenSource = new();
private bool _forceApplyMods = false;
private bool _isVisible;
private Guid _penumbraCollection;
private Dictionary<ObjectKind, Guid?> _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<PairHandler> 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<FrameworkUpdateMessage>(this, (_) => FrameworkUpdate());
Mediator.Subscribe<ZoneSwitchStartMessage>(this, (_) =>
@@ -75,7 +70,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
});
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)
{
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<FileReplacementData> 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<HonorificReadyMessage>(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<FileReplacementData> missingFiles = [];
moddedDictionary = new Dictionary<(string GamePath, string? Hash), string>();
moddedDictionary = [];
ConcurrentDictionary<(string GamePath, string? Hash), string> outputDict = new();
bool hasMigrationChanges = false;

View File

@@ -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()

View File

@@ -20,7 +20,7 @@ public class Pair
private readonly ILogger<Pair> _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<Pair> 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;

View File

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

View File

@@ -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();

View File

@@ -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<PlayerPerformanceService> _logger;
private readonly MareMediator _mediator;
private readonly PlayerPerformanceConfigService _playerPerformanceConfigService;
private readonly FileCacheManager _fileCacheManager;
public PlayerPerformanceService(ILogger<PlayerPerformanceService> 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<DownloadFileTransfer> toDownloadFiles, out long vramUsage)
public async Task<bool> 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<FileReplacementData>? 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<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;
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;

View File

@@ -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",

View File

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