diff --git a/MareSynchronos/PlayerData/Factories/PlayerDataFactory.cs b/MareSynchronos/PlayerData/Factories/PlayerDataFactory.cs index a188f1f..8c66e7b 100644 --- a/MareSynchronos/PlayerData/Factories/PlayerDataFactory.cs +++ b/MareSynchronos/PlayerData/Factories/PlayerDataFactory.cs @@ -136,7 +136,7 @@ public class PlayerDataFactory totalWaitTime -= 50; } - Stopwatch st = Stopwatch.StartNew(); + DateTime start = DateTime.UtcNow; // penumbra call, it's currently broken IReadOnlyDictionary? resolvedPaths; @@ -225,8 +225,7 @@ public class PlayerDataFactory } } - st.Stop(); - _logger.LogInformation("Building character data for {obj} took {time}ms", objectKind, TimeSpan.FromTicks(st.ElapsedTicks).TotalMilliseconds); + _logger.LogInformation("Building character data for {obj} took {time}ms", objectKind, TimeSpan.FromTicks(DateTime.UtcNow.Ticks - start.Ticks).TotalMilliseconds); return previousData; } diff --git a/MareSynchronos/PlayerData/Handlers/PairHandler.cs b/MareSynchronos/PlayerData/Handlers/PairHandler.cs index 84b3387..13a2f6b 100644 --- a/MareSynchronos/PlayerData/Handlers/PairHandler.cs +++ b/MareSynchronos/PlayerData/Handlers/PairHandler.cs @@ -459,7 +459,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase if (string.IsNullOrEmpty(PlayerName)) { var pc = _dalamudUtil.FindPlayerByNameHash(OnlineUser.Ident); - if (pc == default((string, nint))) return; + if (pc.ObjectId == 0) return; Logger.LogDebug("One-Time Initializing {this}", this); Initialize(pc.Name); Logger.LogDebug("One-Time Initialized {this}", this); diff --git a/MareSynchronos/Services/DalamudUtilService.cs b/MareSynchronos/Services/DalamudUtilService.cs index c4273ee..560f6de 100644 --- a/MareSynchronos/Services/DalamudUtilService.cs +++ b/MareSynchronos/Services/DalamudUtilService.cs @@ -1,7 +1,5 @@ using Dalamud.Game.ClientState.Conditions; using Dalamud.Game.ClientState.Objects; -using Dalamud.Game.ClientState.Objects.Enums; -using Dalamud.Game.ClientState.Objects.SubKinds; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Game.Control; @@ -14,16 +12,24 @@ using Microsoft.Extensions.Logging; using System.Numerics; using System.Runtime.CompilerServices; using GameObject = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject; +using DalamudGameObject = Dalamud.Game.ClientState.Objects.Types.GameObject; namespace MareSynchronos.Services; public class DalamudUtilService : IHostedService, IMediatorSubscriber { + internal struct PlayerCharacter + { + public uint ObjectId; + public string Name; + public uint HomeWorldId; + public nint Address; + }; + private struct PlayerInfo { - public String Name; - public uint WorldId; - public String Hash; + public PlayerCharacter Character; + public string Hash; }; private readonly List _classJobIdsIgnoredForPets = [30]; @@ -40,7 +46,8 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber private string _lastGlobalBlockPlayer = string.Empty; private string _lastGlobalBlockReason = string.Empty; private ushort _lastZone = 0; - private Dictionary _playerCharas = new(StringComparer.Ordinal); + private readonly Dictionary _playerCharas = new(StringComparer.Ordinal); + private readonly List _notUpdatedCharas = []; private bool _sentBetweenAreas = false; private static readonly Dictionary _playerInfoCache = new(); @@ -70,7 +77,7 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber await RunOnFrameworkThread(() => { var addr = GetPlayerCharacterFromCachedTableByIdent(ident); - var pc = GetPlayerCharacter(); + var pc = _clientState.LocalPlayer!; var gobj = CreateGameObject(addr); // Any further than roughly 55y is out of range for targetting if (gobj != null && Vector3.Distance(pc.Position, gobj.Position) < 55.0f) @@ -179,12 +186,6 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber return await RunOnFrameworkThread(() => GetPet(playerPointer)).ConfigureAwait(false); } - public PlayerCharacter GetPlayerCharacter() - { - EnsureIsOnFramework(); - return _clientState.LocalPlayer!; - } - public IntPtr GetPlayerCharacterFromCachedTableByIdent(string characterName) { if (_playerCharas.TryGetValue(characterName, out var pchar)) return pchar.Address; @@ -372,24 +373,28 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber return _gameGui.WorldToScreen(obj.Position, out var screenPos) ? screenPos : Vector2.Zero; } - internal (string Name, nint Address) FindPlayerByNameHash(string ident) + internal PlayerCharacter FindPlayerByNameHash(string ident) { _playerCharas.TryGetValue(ident, out var result); return result; } - private PlayerInfo GetPlayerInfo(PlayerCharacter p) + private unsafe PlayerInfo GetPlayerInfo(DalamudGameObject chara) { - uint id = p.ObjectId; + uint id = chara.ObjectId; if (!_playerInfoCache.TryGetValue(id, out var info)) { - info.Name = p.Name.ToString(); - info.WorldId = p.HomeWorld.Id; - info.Hash = Crypto.GetHash256(info.Name + info.WorldId.ToString()); + info.Character.ObjectId = id; + info.Character.Name = chara.Name.ToString(); + info.Character.HomeWorldId = ((BattleChara*)chara.Address)->Character.HomeWorld; + info.Character.Address = chara.Address; + info.Hash = Crypto.GetHash256(info.Character.Name + info.Character.HomeWorldId.ToString()); _playerInfoCache[id] = info; } + info.Character.Address = chara.Address; + return info; } @@ -399,7 +404,6 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber { var gameObj = (GameObject*)p.Address; var drawObj = gameObj->DrawObject; - var playerInfo = GetPlayerInfo(p); bool isDrawing = false; bool isDrawingChanged = false; if ((nint)drawObj != IntPtr.Zero) @@ -411,20 +415,20 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber if (!isDrawing) { isDrawing = ((CharacterBase*)drawObj)->HasModelFilesInSlotLoaded != 0; - if (isDrawing && !string.Equals(_lastGlobalBlockPlayer, playerInfo.Name, StringComparison.Ordinal) + if (isDrawing && !string.Equals(_lastGlobalBlockPlayer, p.Name, StringComparison.Ordinal) && !string.Equals(_lastGlobalBlockReason, "HasModelFilesInSlotLoaded", StringComparison.Ordinal)) { - _lastGlobalBlockPlayer = playerInfo.Name; + _lastGlobalBlockPlayer = p.Name; _lastGlobalBlockReason = "HasModelFilesInSlotLoaded"; isDrawingChanged = true; } } else { - if (!string.Equals(_lastGlobalBlockPlayer, playerInfo.Name, StringComparison.Ordinal) + if (!string.Equals(_lastGlobalBlockPlayer, p.Name, StringComparison.Ordinal) && !string.Equals(_lastGlobalBlockReason, "HasModelInSlotLoaded", StringComparison.Ordinal)) { - _lastGlobalBlockPlayer = playerInfo.Name; + _lastGlobalBlockPlayer = p.Name; _lastGlobalBlockReason = "HasModelInSlotLoaded"; isDrawingChanged = true; } @@ -432,10 +436,10 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber } else { - if (!string.Equals(_lastGlobalBlockPlayer, playerInfo.Name, StringComparison.Ordinal) + if (!string.Equals(_lastGlobalBlockPlayer, p.Name, StringComparison.Ordinal) && !string.Equals(_lastGlobalBlockReason, "RenderFlags", StringComparison.Ordinal)) { - _lastGlobalBlockPlayer = playerInfo.Name; + _lastGlobalBlockPlayer = p.Name; _lastGlobalBlockReason = "RenderFlags"; isDrawingChanged = true; } @@ -444,7 +448,7 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber if (isDrawingChanged) { - _logger.LogTrace("Global draw block: START => {name} ({reason})", playerInfo.Name, _lastGlobalBlockReason); + _logger.LogTrace("Global draw block: START => {name} ({reason})", p.Name, _lastGlobalBlockReason); } IsAnythingDrawing |= isDrawing; @@ -464,19 +468,32 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber } IsAnythingDrawing = false; - - var playerCharaList = new Dictionary(StringComparer.Ordinal); _performanceCollector.LogPerformance(this, "ObjTableToCharas", - () => { - foreach (var p in _objectTable.OfType().Where(o => o.ObjectIndex < 200)) + () => + { + _notUpdatedCharas.AddRange(_playerCharas.Keys); + + foreach (var chara in _objectTable) { - CheckCharacterForDrawing(p); - var info = GetPlayerInfo(p); - playerCharaList.Add(info.Hash, (info.Name, p.Address)); + if (chara.ObjectIndex % 2 != 0 || chara.ObjectIndex >= 200) continue; + + string charaName = chara.Name.ToString(); + uint homeWorldId = ((BattleChara*)chara.Address)->Character.HomeWorld; + + var info = GetPlayerInfo(chara); + if (!IsAnythingDrawing) + CheckCharacterForDrawing(info.Character); + _notUpdatedCharas.Remove(info.Hash); + _playerCharas[info.Hash] = info.Character; } - } - ); - _playerCharas = playerCharaList; + + foreach (var notUpdatedChara in _notUpdatedCharas) + { + _playerCharas.Remove(notUpdatedChara); + } + + _notUpdatedCharas.Clear(); + }); if (!IsAnythingDrawing && !string.IsNullOrEmpty(_lastGlobalBlockPlayer)) { diff --git a/MareSynchronos/Services/PerformanceCollectorService.cs b/MareSynchronos/Services/PerformanceCollectorService.cs index 5dc209d..aab74a4 100644 --- a/MareSynchronos/Services/PerformanceCollectorService.cs +++ b/MareSynchronos/Services/PerformanceCollectorService.cs @@ -3,7 +3,6 @@ using MareSynchronos.Utils; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using System.Collections.Concurrent; -using System.Diagnostics; using System.Globalization; using System.Text; @@ -14,7 +13,7 @@ public sealed class PerformanceCollectorService : IHostedService private const string _counterSplit = "=>"; private readonly ILogger _logger; private readonly MareConfigService _mareConfigService; - private readonly ConcurrentDictionary>> _performanceCounters = new(StringComparer.Ordinal); + public ConcurrentDictionary>> PerformanceCounters { get; } = new(StringComparer.Ordinal); private readonly CancellationTokenSource _periodicLogPruneTask = new(); public PerformanceCollectorService(ILogger logger, MareConfigService mareConfigService) @@ -23,49 +22,57 @@ public sealed class PerformanceCollectorService : IHostedService _mareConfigService = mareConfigService; } - public T LogPerformance(object sender, string counterName, Func func) + public T LogPerformance(object sender, string counterName, Func func, int maxEntries = 10000) { if (!_mareConfigService.Current.LogPerformance) return func.Invoke(); counterName = sender.GetType().Name + _counterSplit + counterName; - if (!_performanceCounters.TryGetValue(counterName, out var list)) + if (!PerformanceCounters.TryGetValue(counterName, out var list)) { - list = _performanceCounters[counterName] = new(10000); + list = PerformanceCounters[counterName] = new(maxEntries); } - Stopwatch st = Stopwatch.StartNew(); + var dt = DateTime.UtcNow.Ticks; try { return func.Invoke(); } finally { - st.Stop(); - list.Add(new(TimeOnly.FromDateTime(DateTime.Now), st.ElapsedTicks)); + var elapsed = DateTime.UtcNow.Ticks - dt; +#if DEBUG + if (TimeSpan.FromTicks(elapsed) > TimeSpan.FromMilliseconds(10)) + _logger.LogWarning(">10ms spike on {counterName}: {time}", counterName, TimeSpan.FromTicks(elapsed)); +#endif + list.Add(new(TimeOnly.FromDateTime(DateTime.Now), elapsed)); } } - public void LogPerformance(object sender, string counterName, Action act) + public void LogPerformance(object sender, string counterName, Action act, int maxEntries = 10000) { if (!_mareConfigService.Current.LogPerformance) { act.Invoke(); return; } counterName = sender.GetType().Name + _counterSplit + counterName; - if (!_performanceCounters.TryGetValue(counterName, out var list)) + if (!PerformanceCounters.TryGetValue(counterName, out var list)) { - list = _performanceCounters[counterName] = new(10000); + list = PerformanceCounters[counterName] = new(maxEntries); } - Stopwatch st = Stopwatch.StartNew(); + var dt = DateTime.UtcNow.Ticks; try { act.Invoke(); } finally { - st.Stop(); - list.Add(new(TimeOnly.FromDateTime(DateTime.Now), st.ElapsedTicks)); + var elapsed = DateTime.UtcNow.Ticks - dt; +#if DEBUG + if (TimeSpan.FromTicks(elapsed) > TimeSpan.FromMilliseconds(10)) + _logger.LogWarning(">10ms spike on {counterName}: {time}", counterName, TimeSpan.FromTicks(elapsed)); +#endif + list.Add(new(TimeOnly.FromDateTime(DateTime.Now), elapsed)); } } @@ -97,7 +104,7 @@ public sealed class PerformanceCollectorService : IHostedService { sb.AppendLine("Performance metrics over total lifetime of each counter"); } - var data = _performanceCounters.ToList(); + var data = PerformanceCounters.ToList(); var longestCounterName = data.OrderByDescending(d => d.Key.Length).First().Key.Length + 2; sb.Append("-Last".PadRight(15, '-')); sb.Append('|'); @@ -169,12 +176,12 @@ public sealed class PerformanceCollectorService : IHostedService { await Task.Delay(TimeSpan.FromMinutes(10), _periodicLogPruneTask.Token).ConfigureAwait(false); - foreach (var entries in _performanceCounters.ToList()) + foreach (var entries in PerformanceCounters.ToList()) { try { var last = entries.Value.ToList().Last(); - if (last.Item1.AddMinutes(10) < TimeOnly.FromDateTime(DateTime.Now) && !_performanceCounters.TryRemove(entries.Key, out _)) + if (last.Item1.AddMinutes(10) < TimeOnly.FromDateTime(DateTime.Now) && !PerformanceCounters.TryRemove(entries.Key, out _)) { _logger.LogDebug("Could not remove performance counter {counter}", entries.Key); } diff --git a/MareSynchronos/Utils/Crypto.cs b/MareSynchronos/Utils/Crypto.cs index 45f7482..a954c02 100644 --- a/MareSynchronos/Utils/Crypto.cs +++ b/MareSynchronos/Utils/Crypto.cs @@ -1,16 +1,16 @@ -using Dalamud.Game.ClientState.Objects.SubKinds; - -using System.Security.Cryptography; +using System.Security.Cryptography; using System.Text; namespace MareSynchronos.Utils; public static class Crypto { +#pragma warning disable SYSLIB0021 // Type or member is obsolete + private static readonly Dictionary _hashListSHA1 = new(StringComparer.Ordinal); private static readonly Dictionary _hashListSHA256 = new(StringComparer.Ordinal); - -#pragma warning disable SYSLIB0021 // Type or member is obsolete + private static readonly SHA256CryptoServiceProvider _sha256CryptoProvider = new(); + private static readonly SHA1CryptoServiceProvider _sha1CryptoProvider = new(); public static string GetFileHash(this string filePath) { @@ -28,21 +28,12 @@ public static class Crypto return GetOrComputeHashSHA256(stringToHash); } - public static string GetHash256(this PlayerCharacter character) - { - var charName = character.Name + character.HomeWorld.Id.ToString(); - return GetOrComputeHashSHA256(charName); - } - private static string GetOrComputeHashSHA1(string stringToCompute) { if (_hashListSHA1.TryGetValue(stringToCompute, out var hash)) return hash; - using SHA1CryptoServiceProvider cryptoProvider = new(); - var computedHash = BitConverter.ToString(cryptoProvider.ComputeHash(Encoding.UTF8.GetBytes(stringToCompute))).Replace("-", "", StringComparison.Ordinal); - _hashListSHA1[stringToCompute] = computedHash; - return computedHash; + return _hashListSHA1[stringToCompute] = BitConverter.ToString(_sha1CryptoProvider.ComputeHash(Encoding.UTF8.GetBytes(stringToCompute))).Replace("-", "", StringComparison.Ordinal); } private static string GetOrComputeHashSHA256(string stringToCompute) @@ -50,10 +41,7 @@ public static class Crypto if (_hashListSHA256.TryGetValue(stringToCompute, out var hash)) return hash; - using SHA256CryptoServiceProvider cryptoProvider = new(); - var computedHash = BitConverter.ToString(cryptoProvider.ComputeHash(Encoding.UTF8.GetBytes(stringToCompute))).Replace("-", "", StringComparison.Ordinal); - _hashListSHA256[stringToCompute] = computedHash; - return computedHash; + return _hashListSHA256[stringToCompute] = BitConverter.ToString(_sha256CryptoProvider.ComputeHash(Encoding.UTF8.GetBytes(stringToCompute))).Replace("-", "", StringComparison.Ordinal); } #pragma warning restore SYSLIB0021 // Type or member is obsolete