diff --git a/LightlessSync/PlayerData/Pairs/PairHandlerAdapter.cs b/LightlessSync/PlayerData/Pairs/PairHandlerAdapter.cs index fd1db53..b392e62 100644 --- a/LightlessSync/PlayerData/Pairs/PairHandlerAdapter.cs +++ b/LightlessSync/PlayerData/Pairs/PairHandlerAdapter.cs @@ -2826,7 +2826,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa HandleVisibilityLoss(logChange: false); } - private static bool TryResolveDescriptorHash(ActorObjectService.ActorDescriptor descriptor, out string hashedCid) + private bool TryResolveDescriptorHash(ActorObjectService.ActorDescriptor descriptor, out string hashedCid) { hashedCid = descriptor.HashedContentId ?? string.Empty; if (!string.IsNullOrEmpty(hashedCid)) @@ -2835,8 +2835,8 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa if (descriptor.ObjectKind != DalamudObjectKind.Player || descriptor.Address == nint.Zero) return false; - hashedCid = DalamudUtilService.GetHashedCIDFromPlayerPointer(descriptor.Address); - return !string.IsNullOrEmpty(hashedCid); + return _dalamudUtil.TryGetHashedCIDFromAddress(descriptor.Address, out hashedCid) + && !string.IsNullOrEmpty(hashedCid); } private void UpdateLastKnownActor(ActorObjectService.ActorDescriptor descriptor) diff --git a/LightlessSync/Services/ActorTracking/ActorObjectService.cs b/LightlessSync/Services/ActorTracking/ActorObjectService.cs index e443496..a00839e 100644 --- a/LightlessSync/Services/ActorTracking/ActorObjectService.cs +++ b/LightlessSync/Services/ActorTracking/ActorObjectService.cs @@ -93,6 +93,7 @@ public sealed class ActorObjectService : IHostedService, IDisposable, IMediatorS } RefreshTrackedActors(force: true); }); + _mediator.Subscribe(this, _ => ClearTrackingState()); } private bool IsZoning => _condition[ConditionFlag.BetweenAreas] || _condition[ConditionFlag.BetweenAreas51]; @@ -342,18 +343,8 @@ public sealed class ActorObjectService : IHostedService, IDisposable, IMediatorS public Task StopAsync(CancellationToken cancellationToken) { DisposeHooks(); - _activePlayers.Clear(); - _gposePlayers.Clear(); - _actorsByHash.Clear(); - _actorsByName.Clear(); - _pendingHashResolutions.Clear(); + ClearTrackingState(); _mediator.UnsubscribeAll(this); - lock (_playerRelatedHandlerLock) - { - _playerRelatedHandlers.Clear(); - } - Volatile.Write(ref _snapshot, ActorSnapshot.Empty); - Volatile.Write(ref _gposeSnapshot, GposeSnapshot.Empty); return Task.CompletedTask; } @@ -1077,6 +1068,22 @@ public sealed class ActorObjectService : IHostedService, IDisposable, IMediatorS } } + private void ClearTrackingState() + { + _activePlayers.Clear(); + _gposePlayers.Clear(); + _actorsByHash.Clear(); + _actorsByName.Clear(); + _pendingHashResolutions.Clear(); + lock (_playerRelatedHandlerLock) + { + _playerRelatedHandlers.Clear(); + } + Volatile.Write(ref _snapshot, ActorSnapshot.Empty); + Volatile.Write(ref _gposeSnapshot, GposeSnapshot.Empty); + _nextRefreshAllowed = DateTime.MinValue; + } + public void Dispose() { DisposeHooks(); diff --git a/LightlessSync/Services/DalamudUtilService.cs b/LightlessSync/Services/DalamudUtilService.cs index 5da96bb..672f1b5 100644 --- a/LightlessSync/Services/DalamudUtilService.cs +++ b/LightlessSync/Services/DalamudUtilService.cs @@ -22,10 +22,8 @@ using LightlessSync.Utils; using Lumina.Excel.Sheets; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using System.Diagnostics; using System.Numerics; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Text; using BattleNpcSubKind = FFXIVClientStructs.FFXIV.Client.Game.Object.BattleNpcSubKind; using DalamudObjectKind = Dalamud.Game.ClientState.Objects.Enums.ObjectKind; @@ -229,6 +227,28 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber _ = RunOnFrameworkThread(ReleaseFocusUnsafe); } + public void TargetPlayerByAddress(nint address) + { + if (address == nint.Zero) return; + if (_clientState.IsPvP) return; + + _ = RunOnFrameworkThread(() => + { + var gameObject = CreateGameObject(address); + if (gameObject is null) return; + + var useFocusTarget = _configService.Current.UseFocusTarget; + if (useFocusTarget) + { + _targetManager.FocusTarget = gameObject; + } + else + { + _targetManager.Target = gameObject; + } + }); + } + private void FocusPairUnsafe(nint address, PairUniqueIdentifier pairIdent) { var target = CreateGameObject(address); @@ -634,6 +654,37 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber return true; } + public bool TryGetHashedCIDFromAddress(nint address, out string hashedCid) + { + hashedCid = string.Empty; + if (address == nint.Zero) + return false; + + if (_framework.IsInFrameworkUpdateThread) + { + return TryGetHashedCIDFromAddressInternal(address, out hashedCid); + } + + var result = _framework.RunOnFrameworkThread(() => + { + var success = TryGetHashedCIDFromAddressInternal(address, out var resolved); + return (success, resolved); + }).GetAwaiter().GetResult(); + + hashedCid = result.resolved; + return result.success; + } + + private bool TryGetHashedCIDFromAddressInternal(nint address, out string hashedCid) + { + hashedCid = string.Empty; + var player = _objectTable.CreateObjectReference(address) as IPlayerCharacter; + if (player == null || player.Address != address) + return false; + + return TryGetHashedCID(player, out hashedCid); + } + public unsafe static string GetHashedCIDFromPlayerPointer(nint ptr) { return ((BattleChara*)ptr)->Character.ContentId.ToString().GetHash256(); @@ -845,43 +896,33 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber return Task.CompletedTask; } - public async Task WaitWhileCharacterIsDrawing( - ILogger logger, - GameObjectHandler handler, - Guid redrawId, - int timeOut = 5000, - CancellationToken? ct = null) + public async Task WaitWhileCharacterIsDrawing(ILogger logger, GameObjectHandler handler, Guid redrawId, int timeOut = 5000, CancellationToken? ct = null) { if (!_clientState.IsLoggedIn) return; - var token = ct ?? CancellationToken.None; + if (ct == null) + ct = CancellationToken.None; const int tick = 250; - const int initialSettle = 50; - - var sw = Stopwatch.StartNew(); - + int curWaitTime = 0; try { logger.LogTrace("[{redrawId}] Starting wait for {handler} to draw", redrawId, handler); + await Task.Delay(tick, ct.Value).ConfigureAwait(true); + curWaitTime += tick; - await Task.Delay(initialSettle, token).ConfigureAwait(false); - - while (!token.IsCancellationRequested - && sw.ElapsedMilliseconds < timeOut - && await handler.IsBeingDrawnRunOnFrameworkAsync().ConfigureAwait(false)) + while ((!ct.Value.IsCancellationRequested) + && curWaitTime < timeOut + && await handler.IsBeingDrawnRunOnFrameworkAsync().ConfigureAwait(false)) // 0b100000000000 is "still rendering" or something { logger.LogTrace("[{redrawId}] Waiting for {handler} to finish drawing", redrawId, handler); - await Task.Delay(tick, token).ConfigureAwait(false); + curWaitTime += tick; + await Task.Delay(tick, ct.Value).ConfigureAwait(true); } - logger.LogTrace("[{redrawId}] Finished drawing after {ms}ms", redrawId, sw.ElapsedMilliseconds); + logger.LogTrace("[{redrawId}] Finished drawing after {curWaitTime}ms", redrawId, curWaitTime); } - catch (OperationCanceledException) - { - // ignore - } - catch (Exception ex) + catch (AccessViolationException ex) { logger.LogWarning(ex, "Error accessing {handler}, object does not exist anymore?", handler); } @@ -931,109 +972,37 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber return WorldData.Value.TryGetValue(worldId, out var worldName) ? worldName : null; } - public void TargetPlayerByAddress(nint address) - { - if (address == nint.Zero) return; - if (_clientState.IsPvP) return; - - _ = RunOnFrameworkThread(() => - { - var gameObject = CreateGameObject(address); - if (gameObject is null) return; - - var useFocusTarget = _configService.Current.UseFocusTarget; - if (useFocusTarget) - { - _targetManager.FocusTarget = gameObject; - } - else - { - _targetManager.Target = gameObject; - } - }); - } - - [DllImport("kernel32.dll", SetLastError = true)] - private static extern bool IsBadReadPtr(IntPtr ptr, UIntPtr size); - - private static bool IsValidPointer(nint ptr, int size = 8) - { - if (ptr == nint.Zero) - return false; - - try - { - if (!Util.IsWine()) - { - return !IsBadReadPtr(ptr, (UIntPtr)size); - } - return ptr != nint.Zero && (ptr % IntPtr.Size) == 0; - } - catch - { - return false; - } - } - private unsafe void CheckCharacterForDrawing(nint address, string characterName) { - if (address == nint.Zero) - return; - - if (!IsValidPointer(address)) - { - _logger.LogDebug("Invalid pointer for character {name} at {addr}", characterName, address.ToString("X")); - return; - } - var gameObj = (GameObject*)address; - - if (gameObj == null) - return; - - if (!_objectTable.Any(o => o?.Address == address)) - { - _logger.LogDebug("Character {name} at {addr} no longer in object table", characterName, address.ToString("X")); - return; - } - - if (gameObj->ObjectKind == 0) - return; - var drawObj = gameObj->DrawObject; bool isDrawing = false; bool isDrawingChanged = false; - - if ((nint)drawObj != IntPtr.Zero && IsValidPointer((nint)drawObj)) + if ((nint)drawObj != IntPtr.Zero) { isDrawing = gameObj->RenderFlags == (VisibilityFlags)0b100000000000; - if (!isDrawing) { - var charBase = (CharacterBase*)drawObj; - if (charBase != null && IsValidPointer((nint)charBase)) + isDrawing = ((CharacterBase*)drawObj)->HasModelInSlotLoaded != 0; + if (!isDrawing) { - isDrawing = charBase->HasModelInSlotLoaded != 0; - if (!isDrawing) + isDrawing = ((CharacterBase*)drawObj)->HasModelFilesInSlotLoaded != 0; + if (isDrawing && !string.Equals(_lastGlobalBlockPlayer, characterName, StringComparison.Ordinal) + && !string.Equals(_lastGlobalBlockReason, "HasModelFilesInSlotLoaded", StringComparison.Ordinal)) { - isDrawing = charBase->HasModelFilesInSlotLoaded != 0; - if (isDrawing && !string.Equals(_lastGlobalBlockPlayer, characterName, StringComparison.Ordinal) - && !string.Equals(_lastGlobalBlockReason, "HasModelFilesInSlotLoaded", StringComparison.Ordinal)) - { - _lastGlobalBlockPlayer = characterName; - _lastGlobalBlockReason = "HasModelFilesInSlotLoaded"; - isDrawingChanged = true; - } + _lastGlobalBlockPlayer = characterName; + _lastGlobalBlockReason = "HasModelFilesInSlotLoaded"; + isDrawingChanged = true; } - else + } + else + { + if (!string.Equals(_lastGlobalBlockPlayer, characterName, StringComparison.Ordinal) + && !string.Equals(_lastGlobalBlockReason, "HasModelInSlotLoaded", StringComparison.Ordinal)) { - if (!string.Equals(_lastGlobalBlockPlayer, characterName, StringComparison.Ordinal) - && !string.Equals(_lastGlobalBlockReason, "HasModelInSlotLoaded", StringComparison.Ordinal)) - { - _lastGlobalBlockPlayer = characterName; - _lastGlobalBlockReason = "HasModelInSlotLoaded"; - isDrawingChanged = true; - } + _lastGlobalBlockPlayer = characterName; + _lastGlobalBlockReason = "HasModelInSlotLoaded"; + isDrawingChanged = true; } } } @@ -1064,12 +1033,8 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber private unsafe void FrameworkOnUpdateInternal() { - if (!_clientState.IsLoggedIn || _objectTable.LocalPlayer == null) - { - return; - } - - if ((_objectTable.LocalPlayer?.IsDead ?? false) && _condition[ConditionFlag.BoundByDuty]) + var localPlayer = _objectTable.LocalPlayer; + if ((localPlayer?.IsDead ?? false) && _condition[ConditionFlag.BoundByDuty]) { return; } @@ -1079,6 +1044,44 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber _performanceCollector.LogPerformance(this, $"FrameworkOnUpdateInternal+{(isNormalFrameworkUpdate ? "Regular" : "Delayed")}", () => { IsAnythingDrawing = false; + + if (!isNormalFrameworkUpdate) + { + if (localPlayer != null && !IsLoggedIn) + { + _logger.LogDebug("Logged in"); + IsLoggedIn = true; + _lastZone = _clientState.TerritoryType; + _lastWorldId = (ushort)localPlayer.CurrentWorld.RowId; + _cid = RebuildCID(); + Mediator.Publish(new DalamudLoginMessage()); + } + else if (localPlayer == null && IsLoggedIn) + { + _logger.LogDebug("Logged out"); + IsLoggedIn = false; + _lastWorldId = 0; + Mediator.Publish(new DalamudLogoutMessage()); + } + + if (_gameConfig != null + && _gameConfig.TryGet(Dalamud.Game.Config.SystemConfigOption.LodType_DX11, out bool lodEnabled)) + { + IsLodEnabled = lodEnabled; + } + + if (IsInCombat || IsPerforming || IsInInstance) + Mediator.Publish(new FrameworkUpdateMessage()); + + Mediator.Publish(new DelayedFrameworkUpdateMessage()); + + _delayedFrameworkUpdateCheck = DateTime.UtcNow; + } + + if (!_clientState.IsLoggedIn || localPlayer == null) + { + return; + } _performanceCollector.LogPerformance(this, $"TrackedActorsToState", () => { @@ -1087,40 +1090,46 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber _actorObjectService.RefreshTrackedActors(); } - var playerDescriptors = _actorObjectService.PlayerDescriptors; - var descriptorCount = playerDescriptors.Count; - - for (var i = 0; i < descriptorCount; i++) + if (_clientState.IsLoggedIn && localPlayer != null) { - if (i >= playerDescriptors.Count) - break; - - var actor = playerDescriptors[i]; - - var playerAddress = actor.Address; - if (playerAddress == nint.Zero || !IsValidPointer(playerAddress)) - continue; - - if (actor.ObjectIndex >= 200) - continue; - - if (_blockedCharacterHandler.IsCharacterBlocked(playerAddress, actor.ObjectIndex, out bool firstTime) && firstTime) + var playerDescriptors = _actorObjectService.PlayerDescriptors; + for (var i = 0; i < playerDescriptors.Count; i++) { - _logger.LogTrace("Skipping character {addr}, blocked/muted", playerAddress.ToString("X")); - continue; - } + var actor = playerDescriptors[i]; - if (!IsAnythingDrawing) - { - if (!_objectTable.Any(o => o?.Address == playerAddress)) + var playerAddress = actor.Address; + if (playerAddress == nint.Zero) + continue; + + if (actor.ObjectIndex >= 200) + continue; + + var obj = _objectTable[actor.ObjectIndex]; + if (obj is not IPlayerCharacter player || player.Address != playerAddress) + continue; + + if (_blockedCharacterHandler.IsCharacterBlocked(playerAddress, actor.ObjectIndex, out bool firstTime) && firstTime) { + _logger.LogTrace("Skipping character {addr}, blocked/muted", playerAddress.ToString("X")); continue; } - CheckCharacterForDrawing(playerAddress, actor.Name); + if (!IsAnythingDrawing) + { + var charaName = player.Name.TextValue; + if (string.IsNullOrEmpty(charaName)) + { + charaName = actor.Name; + } - if (IsAnythingDrawing) + CheckCharacterForDrawing(playerAddress, charaName); + if (IsAnythingDrawing) + break; + } + else + { break; + } } } }); @@ -1246,7 +1255,6 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber } - var localPlayer = _objectTable.LocalPlayer; if (localPlayer != null) { _classJobId = localPlayer.ClassJob.RowId; @@ -1268,39 +1276,6 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber Mediator.Publish(new FrameworkUpdateMessage()); Mediator.Publish(new PriorityFrameworkUpdateMessage()); - - if (isNormalFrameworkUpdate) - return; - - if (localPlayer != null && !IsLoggedIn) - { - _logger.LogDebug("Logged in"); - IsLoggedIn = true; - _lastZone = _clientState.TerritoryType; - _lastWorldId = (ushort)localPlayer.CurrentWorld.RowId; - _cid = RebuildCID(); - Mediator.Publish(new DalamudLoginMessage()); - } - else if (localPlayer == null && IsLoggedIn) - { - _logger.LogDebug("Logged out"); - IsLoggedIn = false; - _lastWorldId = 0; - Mediator.Publish(new DalamudLogoutMessage()); - } - - if (_gameConfig != null - && _gameConfig.TryGet(Dalamud.Game.Config.SystemConfigOption.LodType_DX11, out bool lodEnabled)) - { - IsLodEnabled = lodEnabled; - } - - if (IsInCombat || IsPerforming || IsInInstance) - Mediator.Publish(new FrameworkUpdateMessage()); - - Mediator.Publish(new DelayedFrameworkUpdateMessage()); - - _delayedFrameworkUpdateCheck = DateTime.UtcNow; }); } @@ -1330,4 +1305,4 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber onExit(); } } -} \ No newline at end of file +}