From 6685139caf7caad5fc107df193bf53d32771ae39 Mon Sep 17 00:00:00 2001 From: rootdarkarchon Date: Mon, 12 Feb 2024 17:02:49 +0100 Subject: [PATCH] minor performance improvements --- .../PlayerData/Handlers/GameObjectHandler.cs | 5 +- MareSynchronos/Services/DalamudUtilService.cs | 328 +++++++++--------- .../Services/Mediator/MareMediator.cs | 35 +- MareSynchronos/Utils/Crypto.cs | 18 +- 4 files changed, 200 insertions(+), 186 deletions(-) diff --git a/MareSynchronos/PlayerData/Handlers/GameObjectHandler.cs b/MareSynchronos/PlayerData/Handlers/GameObjectHandler.cs index ee2e71f..9514e08 100644 --- a/MareSynchronos/PlayerData/Handlers/GameObjectHandler.cs +++ b/MareSynchronos/PlayerData/Handlers/GameObjectHandler.cs @@ -1,4 +1,5 @@ -using FFXIVClientStructs.FFXIV.Client.Game.Character; +using Dalamud.Memory; +using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using MareSynchronos.Services; using MareSynchronos.Services.Mediator; @@ -209,7 +210,7 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase _clearCts = null; } var chara = (Character*)Address; - var name = new ByteString(chara->GameObject.Name).ToString(); + MemoryHelper.ReadStringNullTerminated((nint)chara->GameObject.Name, out var name); bool nameChange = !string.Equals(name, Name, StringComparison.Ordinal); if (nameChange) { diff --git a/MareSynchronos/Services/DalamudUtilService.cs b/MareSynchronos/Services/DalamudUtilService.cs index 13aee2b..c63331b 100644 --- a/MareSynchronos/Services/DalamudUtilService.cs +++ b/MareSynchronos/Services/DalamudUtilService.cs @@ -1,6 +1,7 @@ using Dalamud.Game.ClientState.Conditions; using Dalamud.Game.ClientState.Objects; using Dalamud.Game.ClientState.Objects.SubKinds; +using Dalamud.Memory; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Game.Control; @@ -363,60 +364,57 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber private unsafe void CheckCharacterForDrawing(nint address, string characterName) { - if (!IsAnythingDrawing) + var gameObj = (GameObject*)address; + var drawObj = gameObj->DrawObject; + var playerName = characterName; + bool isDrawing = false; + bool isDrawingChanged = false; + if ((nint)drawObj != IntPtr.Zero) { - var gameObj = (GameObject*)address; - var drawObj = gameObj->DrawObject; - var playerName = characterName; - bool isDrawing = false; - bool isDrawingChanged = false; - if ((nint)drawObj != IntPtr.Zero) + isDrawing = gameObj->RenderFlags == 0b100000000000; + if (!isDrawing) { - isDrawing = gameObj->RenderFlags == 0b100000000000; + isDrawing = ((CharacterBase*)drawObj)->HasModelInSlotLoaded != 0; if (!isDrawing) { - isDrawing = ((CharacterBase*)drawObj)->HasModelInSlotLoaded != 0; - if (!isDrawing) + isDrawing = ((CharacterBase*)drawObj)->HasModelFilesInSlotLoaded != 0; + if (isDrawing && !string.Equals(_lastGlobalBlockPlayer, playerName, StringComparison.Ordinal) + && !string.Equals(_lastGlobalBlockReason, "HasModelFilesInSlotLoaded", StringComparison.Ordinal)) { - isDrawing = ((CharacterBase*)drawObj)->HasModelFilesInSlotLoaded != 0; - if (isDrawing && !string.Equals(_lastGlobalBlockPlayer, playerName, StringComparison.Ordinal) - && !string.Equals(_lastGlobalBlockReason, "HasModelFilesInSlotLoaded", StringComparison.Ordinal)) - { - _lastGlobalBlockPlayer = playerName; - _lastGlobalBlockReason = "HasModelFilesInSlotLoaded"; - isDrawingChanged = true; - } - } - else - { - if (!string.Equals(_lastGlobalBlockPlayer, playerName, StringComparison.Ordinal) - && !string.Equals(_lastGlobalBlockReason, "HasModelInSlotLoaded", StringComparison.Ordinal)) - { - _lastGlobalBlockPlayer = playerName; - _lastGlobalBlockReason = "HasModelInSlotLoaded"; - isDrawingChanged = true; - } + _lastGlobalBlockPlayer = playerName; + _lastGlobalBlockReason = "HasModelFilesInSlotLoaded"; + isDrawingChanged = true; } } else { if (!string.Equals(_lastGlobalBlockPlayer, playerName, StringComparison.Ordinal) - && !string.Equals(_lastGlobalBlockReason, "RenderFlags", StringComparison.Ordinal)) + && !string.Equals(_lastGlobalBlockReason, "HasModelInSlotLoaded", StringComparison.Ordinal)) { _lastGlobalBlockPlayer = playerName; - _lastGlobalBlockReason = "RenderFlags"; + _lastGlobalBlockReason = "HasModelInSlotLoaded"; isDrawingChanged = true; } } } - - if (isDrawingChanged) + else { - _logger.LogTrace("Global draw block: START => {name} ({reason})", playerName, _lastGlobalBlockReason); + if (!string.Equals(_lastGlobalBlockPlayer, playerName, StringComparison.Ordinal) + && !string.Equals(_lastGlobalBlockReason, "RenderFlags", StringComparison.Ordinal)) + { + _lastGlobalBlockPlayer = playerName; + _lastGlobalBlockReason = "RenderFlags"; + isDrawingChanged = true; + } } - - IsAnythingDrawing |= isDrawing; } + + if (isDrawingChanged) + { + _logger.LogTrace("Global draw block: START => {name} ({reason})", playerName, _lastGlobalBlockReason); + } + + IsAnythingDrawing |= isDrawing; } private void FrameworkOnUpdate(IFramework framework) @@ -431,140 +429,154 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber return; } - IsAnythingDrawing = false; - _performanceCollector.LogPerformance(this, "ObjTableToCharas", - () => + bool isNormalFrameworkUpdate = DateTime.Now < _delayedFrameworkUpdateCheck.AddSeconds(1); + + _performanceCollector.LogPerformance(this, "FrameworkOnUpdateInternal+" + (isNormalFrameworkUpdate ? "Regular" : "Delayed"), () => + { + IsAnythingDrawing = false; + _performanceCollector.LogPerformance(this, "ObjTableToCharas", + () => + { + _notUpdatedCharas.AddRange(_playerCharas.Keys); + + for (int i = 0; i < 200; i += 2) + { + var chara = _objectTable[i]; + if (chara == null || chara.ObjectKind != Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Player) + continue; + + MemoryHelper.ReadStringNullTerminated((nint)((GameObject*)chara.Address)->Name, out string charaName); + string homeWorldId = ((BattleChara*)chara.Address)->Character.HomeWorld.ToString(); + + var hash = (charaName + homeWorldId).GetHash256(); + if (!IsAnythingDrawing) + CheckCharacterForDrawing(chara.Address, charaName); + _notUpdatedCharas.Remove(hash); + _playerCharas[hash] = (charaName, chara.Address); + } + + foreach (var notUpdatedChara in _notUpdatedCharas) + { + _playerCharas.Remove(notUpdatedChara); + } + + _notUpdatedCharas.Clear(); + }); + + if (!IsAnythingDrawing && !string.IsNullOrEmpty(_lastGlobalBlockPlayer)) { - _notUpdatedCharas.AddRange(_playerCharas.Keys); - - foreach (var chara in _objectTable) - { - if (chara.ObjectIndex % 2 != 0 || chara.ObjectIndex >= 200) continue; - - string charaName = chara.Name.ToString(); - string homeWorldId = ((BattleChara*)chara.Address)->Character.HomeWorld.ToString(); - - var hash = (charaName + homeWorldId).GetHash256(); - if (!IsAnythingDrawing) - CheckCharacterForDrawing(chara.Address, charaName); - _notUpdatedCharas.Remove(hash); - _playerCharas[hash] = (charaName, chara.Address); - } - - foreach (var notUpdatedChara in _notUpdatedCharas) - { - _playerCharas.Remove(notUpdatedChara); - } - - _notUpdatedCharas.Clear(); - }); - - if (!IsAnythingDrawing && !string.IsNullOrEmpty(_lastGlobalBlockPlayer)) - { - _logger.LogTrace("Global draw block: END => {name}", _lastGlobalBlockPlayer); - _lastGlobalBlockPlayer = string.Empty; - _lastGlobalBlockReason = string.Empty; - } - - if (GposeTarget != null && !IsInGpose) - { - _logger.LogDebug("Gpose start"); - IsInGpose = true; - Mediator.Publish(new GposeStartMessage()); - } - else if (GposeTarget == null && IsInGpose) - { - _logger.LogDebug("Gpose end"); - IsInGpose = false; - Mediator.Publish(new GposeEndMessage()); - } - - if ((_condition[ConditionFlag.Performing] || _condition[ConditionFlag.InCombat]) && !IsInCombatOrPerforming) - { - _logger.LogDebug("Combat/Performance start"); - IsInCombatOrPerforming = true; - Mediator.Publish(new CombatOrPerformanceStartMessage()); - Mediator.Publish(new HaltScanMessage(nameof(IsInCombatOrPerforming))); - } - else if ((!_condition[ConditionFlag.Performing] && !_condition[ConditionFlag.InCombat]) && IsInCombatOrPerforming) - { - _logger.LogDebug("Combat/Performance end"); - IsInCombatOrPerforming = false; - Mediator.Publish(new CombatOrPerformanceEndMessage()); - Mediator.Publish(new ResumeScanMessage(nameof(IsInCombatOrPerforming))); - } - - if (_condition[ConditionFlag.WatchingCutscene] && !IsInCutscene) - { - _logger.LogDebug("Cutscene start"); - IsInCutscene = true; - Mediator.Publish(new CutsceneStartMessage()); - Mediator.Publish(new HaltScanMessage(nameof(IsInCutscene))); - } - else if (!_condition[ConditionFlag.WatchingCutscene] && IsInCutscene) - { - _logger.LogDebug("Cutscene end"); - IsInCutscene = false; - Mediator.Publish(new CutsceneEndMessage()); - Mediator.Publish(new ResumeScanMessage(nameof(IsInCutscene))); - } - - if (IsInCutscene) { Mediator.Publish(new CutsceneFrameworkUpdateMessage()); return; } - - if (_condition[ConditionFlag.BetweenAreas] || _condition[ConditionFlag.BetweenAreas51]) - { - var zone = _clientState.TerritoryType; - if (_lastZone != zone) - { - _lastZone = zone; - if (!_sentBetweenAreas) - { - _logger.LogDebug("Zone switch/Gpose start"); - _sentBetweenAreas = true; - Mediator.Publish(new ZoneSwitchStartMessage()); - Mediator.Publish(new HaltScanMessage(nameof(ConditionFlag.BetweenAreas))); - } + _logger.LogTrace("Global draw block: END => {name}", _lastGlobalBlockPlayer); + _lastGlobalBlockPlayer = string.Empty; + _lastGlobalBlockReason = string.Empty; } - return; - } + if (GposeTarget != null && !IsInGpose) + { + _logger.LogDebug("Gpose start"); + IsInGpose = true; + Mediator.Publish(new GposeStartMessage()); + } + else if (GposeTarget == null && IsInGpose) + { + _logger.LogDebug("Gpose end"); + IsInGpose = false; + Mediator.Publish(new GposeEndMessage()); + } - if (_sentBetweenAreas) - { - _logger.LogDebug("Zone switch/Gpose end"); - _sentBetweenAreas = false; - Mediator.Publish(new ZoneSwitchEndMessage()); - Mediator.Publish(new ResumeScanMessage(nameof(ConditionFlag.BetweenAreas))); - } + if ((_condition[ConditionFlag.Performing] || _condition[ConditionFlag.InCombat]) && !IsInCombatOrPerforming) + { + _logger.LogDebug("Combat/Performance start"); + IsInCombatOrPerforming = true; + Mediator.Publish(new CombatOrPerformanceStartMessage()); + Mediator.Publish(new HaltScanMessage(nameof(IsInCombatOrPerforming))); + } + else if ((!_condition[ConditionFlag.Performing] && !_condition[ConditionFlag.InCombat]) && IsInCombatOrPerforming) + { + _logger.LogDebug("Combat/Performance end"); + IsInCombatOrPerforming = false; + Mediator.Publish(new CombatOrPerformanceEndMessage()); + Mediator.Publish(new ResumeScanMessage(nameof(IsInCombatOrPerforming))); + } - if (!IsInCombatOrPerforming) - Mediator.Publish(new FrameworkUpdateMessage()); + if (_condition[ConditionFlag.WatchingCutscene] && !IsInCutscene) + { + _logger.LogDebug("Cutscene start"); + IsInCutscene = true; + Mediator.Publish(new CutsceneStartMessage()); + Mediator.Publish(new HaltScanMessage(nameof(IsInCutscene))); + } + else if (!_condition[ConditionFlag.WatchingCutscene] && IsInCutscene) + { + _logger.LogDebug("Cutscene end"); + IsInCutscene = false; + Mediator.Publish(new CutsceneEndMessage()); + Mediator.Publish(new ResumeScanMessage(nameof(IsInCutscene))); + } - Mediator.Publish(new PriorityFrameworkUpdateMessage()); + if (IsInCutscene) + { + Mediator.Publish(new CutsceneFrameworkUpdateMessage()); + return; + } - if (DateTime.Now < _delayedFrameworkUpdateCheck.AddSeconds(1)) return; + if (_condition[ConditionFlag.BetweenAreas] || _condition[ConditionFlag.BetweenAreas51]) + { + var zone = _clientState.TerritoryType; + if (_lastZone != zone) + { + _lastZone = zone; + if (!_sentBetweenAreas) + { + _logger.LogDebug("Zone switch/Gpose start"); + _sentBetweenAreas = true; + Mediator.Publish(new ZoneSwitchStartMessage()); + Mediator.Publish(new HaltScanMessage(nameof(ConditionFlag.BetweenAreas))); + } + } - var localPlayer = _clientState.LocalPlayer; + return; + } - if (localPlayer != null && !IsLoggedIn) - { - _logger.LogDebug("Logged in"); - IsLoggedIn = true; - _lastZone = _clientState.TerritoryType; - Mediator.Publish(new DalamudLoginMessage()); - } - else if (localPlayer == null && IsLoggedIn) - { - _logger.LogDebug("Logged out"); - IsLoggedIn = false; - Mediator.Publish(new DalamudLogoutMessage()); - } + if (_sentBetweenAreas) + { + _logger.LogDebug("Zone switch/Gpose end"); + _sentBetweenAreas = false; + Mediator.Publish(new ZoneSwitchEndMessage()); + Mediator.Publish(new ResumeScanMessage(nameof(ConditionFlag.BetweenAreas))); + } - if (IsInCombatOrPerforming) - Mediator.Publish(new FrameworkUpdateMessage()); + if (!IsInCombatOrPerforming) + _performanceCollector.LogPerformance(this, "FrameworkOnUpdateInternal>MediatorFrameworkUpdate", + () => Mediator.Publish(new FrameworkUpdateMessage())); - Mediator.Publish(new DelayedFrameworkUpdateMessage()); + Mediator.Publish(new PriorityFrameworkUpdateMessage()); - _delayedFrameworkUpdateCheck = DateTime.Now; + if (isNormalFrameworkUpdate) + return; + + var localPlayer = _clientState.LocalPlayer; + + if (localPlayer != null && !IsLoggedIn) + { + _logger.LogDebug("Logged in"); + IsLoggedIn = true; + _lastZone = _clientState.TerritoryType; + Mediator.Publish(new DalamudLoginMessage()); + } + else if (localPlayer == null && IsLoggedIn) + { + _logger.LogDebug("Logged out"); + IsLoggedIn = false; + Mediator.Publish(new DalamudLogoutMessage()); + } + + if (IsInCombatOrPerforming) + _performanceCollector.LogPerformance(this, "FrameworkOnUpdateInternal>MediatorFrameworkUpdate", + () => Mediator.Publish(new FrameworkUpdateMessage())); + + Mediator.Publish(new DelayedFrameworkUpdateMessage()); + + _delayedFrameworkUpdateCheck = DateTime.Now; + }); } } \ No newline at end of file diff --git a/MareSynchronos/Services/Mediator/MareMediator.cs b/MareSynchronos/Services/Mediator/MareMediator.cs index 5489aa5..b5a654b 100644 --- a/MareSynchronos/Services/Mediator/MareMediator.cs +++ b/MareSynchronos/Services/Mediator/MareMediator.cs @@ -130,40 +130,43 @@ public sealed class MareMediator : IHostedService { if (!_subscriberDict.TryGetValue(message.GetType(), out HashSet? subscribers) || subscribers == null || !subscribers.Any()) return; - HashSet subscribersCopy = []; + List subscribersCopy = []; lock (_addRemoveLock) { - subscribersCopy = subscribers?.Where(s => s.Subscriber != null).ToHashSet() ?? []; + subscribersCopy = subscribers?.Where(s => s.Subscriber != null).ToList() ?? []; } - foreach (SubscriberAction subscriber in subscribersCopy) +#pragma warning disable S3011 // Reflection should not be used to increase accessibility of classes, methods, or fields + GetType() + .GetMethod(nameof(ExecuteReflected), System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)? + .MakeGenericMethod(message.GetType())? + .Invoke(this, [subscribersCopy, message]); +#pragma warning restore S3011 // Reflection should not be used to increase accessibility of classes, methods, or fields + } + + private void ExecuteReflected(List subscribers, T message) where T : MessageBase + { + var msgTypeName = message.GetType().Name; + foreach (SubscriberAction subscriber in subscribers) { try { -#pragma warning disable S3011 // Reflection should not be used to increase accessibility of classes, methods, or fields - typeof(MareMediator) - .GetMethod(nameof(ExecuteSubscriber), System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)? - .MakeGenericMethod(message.GetType()) - .Invoke(this, new object[] { subscriber, message }); -#pragma warning restore S3011 // Reflection should not be used to increase accessibility of classes, methods, or fields + var isSameThread = message.KeepThreadContext ? "$" : string.Empty; + _performanceCollector.LogPerformance(this, $"{isSameThread}Execute>{msgTypeName}+{subscriber.Subscriber.GetType().Name}>{subscriber.Subscriber}", + () => ((Action)subscriber.Action).Invoke(message)); } catch (Exception ex) { if (_lastErrorTime.TryGetValue(subscriber, out var lastErrorTime) && lastErrorTime.Add(TimeSpan.FromSeconds(10)) > DateTime.UtcNow) continue; - _logger.LogError(ex.InnerException ?? ex, "Error executing {type} for subscriber {subscriber}", message.GetType().Name, subscriber.Subscriber.GetType().Name); + _logger.LogError(ex.InnerException ?? ex, "Error executing {type} for subscriber {subscriber}", + message.GetType().Name, subscriber.Subscriber.GetType().Name); _lastErrorTime[subscriber] = DateTime.UtcNow; } } } - private void ExecuteSubscriber(SubscriberAction subscriber, T message) where T : MessageBase - { - var isSameThread = message.KeepThreadContext ? "$" : string.Empty; - _performanceCollector.LogPerformance(this, $"{isSameThread}Execute>{message.GetType().Name}+{subscriber.Subscriber.GetType().Name}>{subscriber.Subscriber}", () => ((Action)subscriber.Action).Invoke(message)); - } - private sealed class SubscriberAction { public SubscriberAction(IMediatorSubscriber subscriber, object action) diff --git a/MareSynchronos/Utils/Crypto.cs b/MareSynchronos/Utils/Crypto.cs index b8890bc..2aa7bd4 100644 --- a/MareSynchronos/Utils/Crypto.cs +++ b/MareSynchronos/Utils/Crypto.cs @@ -30,10 +30,13 @@ public static class Crypto return GetOrComputeHashSHA256(stringToHash); } - public static string GetHash256(this PlayerCharacter character) + private static string GetOrComputeHashSHA256(string stringToCompute) { - var charName = character.Name + character.HomeWorld.Id.ToString(); - return GetOrComputeHashSHA256(charName); + if (_hashListSHA256.TryGetValue(stringToCompute, out var hash)) + return hash; + + return _hashListSHA256[stringToCompute] = + BitConverter.ToString(_sha256CryptoProvider.ComputeHash(Encoding.UTF8.GetBytes(stringToCompute))).Replace("-", "", StringComparison.Ordinal); } private static string GetOrComputeHashSHA1(string stringToCompute) @@ -41,16 +44,11 @@ public static class Crypto if (_hashListSHA1.TryGetValue(stringToCompute, out var hash)) return hash; - return _hashListSHA1[stringToCompute] = BitConverter.ToString(_sha1CryptoProvider.ComputeHash(Encoding.UTF8.GetBytes(stringToCompute))).Replace("-", "", StringComparison.Ordinal); + return _hashListSHA1[stringToCompute] = + BitConverter.ToString(_sha1CryptoProvider.ComputeHash(Encoding.UTF8.GetBytes(stringToCompute))).Replace("-", "", StringComparison.Ordinal); } - private static string GetOrComputeHashSHA256(string stringToCompute) - { - if (_hashListSHA256.TryGetValue(stringToCompute, out var hash)) - return hash; - return _hashListSHA256[stringToCompute] = BitConverter.ToString(_sha256CryptoProvider.ComputeHash(Encoding.UTF8.GetBytes(stringToCompute))).Replace("-", "", StringComparison.Ordinal); - } #pragma warning restore SYSLIB0021 // Type or member is obsolete } \ No newline at end of file