diff --git a/LightlessSync/PlayerData/Handlers/GameObjectHandler.cs b/LightlessSync/PlayerData/Handlers/GameObjectHandler.cs index 2f085ec..da04bda 100644 --- a/LightlessSync/PlayerData/Handlers/GameObjectHandler.cs +++ b/LightlessSync/PlayerData/Handlers/GameObjectHandler.cs @@ -1,10 +1,10 @@ -using Dalamud.Game.ClientState.Objects.Types; +using Dalamud.Game.ClientState.Objects.Enums; +using Dalamud.Game.ClientState.Objects.Types; +using Dalamud.Plugin.Services; using LightlessSync.Services; using LightlessSync.Services.Mediator; using Microsoft.Extensions.Logging; using ObjectKind = LightlessSync.API.Data.Enum.ObjectKind; -using Dalamud.Game.ClientState.Objects.Enums; -using Dalamud.Plugin.Services; namespace LightlessSync.PlayerData.Handlers; @@ -15,30 +15,51 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP private readonly Func _getAddress; private readonly bool _isOwnedObject; private readonly PerformanceCollectorService _performanceCollector; - private readonly object _frameworkUpdateGate = new(); + private readonly Lock _frameworkUpdateGate = new(); private bool _frameworkUpdateSubscribed; private byte _classJob = 0; private Task? _delayedZoningTask; private bool _haltProcessing = false; private CancellationTokenSource _zoningCts = new(); - public GameObjectHandler(ILogger logger, PerformanceCollectorService performanceCollector, - LightlessMediator mediator, DalamudUtilService dalamudUtil, ObjectKind objectKind, Func getAddress, IObjectTable objectTable, bool ownedObject = true) : base(logger, mediator) + /// + /// Constructor for GameObjectHandler + /// + /// Logger + /// Performance Collector + /// Lightless Mediator + /// Dalamud Utilties Service + /// Object kind of Object + /// Get Adress + /// Object table of Dalamud + /// Object is owned by user + public GameObjectHandler( + ILogger logger, + PerformanceCollectorService performanceCollector, + LightlessMediator mediator, + DalamudUtilService dalamudUtil, + ObjectKind objectKind, + Func getAddress, + IObjectTable objectTable, + bool ownedObject = true) : base(logger, mediator) { _performanceCollector = performanceCollector; ObjectKind = objectKind; _dalamudUtil = dalamudUtil; + _objectTable = objectTable; + _getAddress = () => { _dalamudUtil.EnsureIsOnFramework(); return getAddress.Invoke(); }; + _isOwnedObject = ownedObject; Name = string.Empty; if (ownedObject) { - Mediator.Subscribe(this, (msg) => + Mediator.Subscribe(this, msg => { if (_delayedZoningTask?.IsCompleted ?? true) { @@ -48,44 +69,36 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP }); } - if (_isOwnedObject) - { - EnableFrameworkUpdates(); - } + EnableFrameworkUpdates(); - Mediator.Subscribe(this, (_) => ZoneSwitchEnd()); - Mediator.Subscribe(this, (_) => ZoneSwitchStart()); + Mediator.Subscribe(this, _ => ZoneSwitchEnd()); + Mediator.Subscribe(this, _ => ZoneSwitchStart()); - Mediator.Subscribe(this, (_) => - { - _haltProcessing = true; - }); - Mediator.Subscribe(this, (_) => + Mediator.Subscribe(this, _ => _haltProcessing = true); + Mediator.Subscribe(this, _ => { _haltProcessing = false; ZoneSwitchEnd(); }); - Mediator.Subscribe(this, (msg) => + + Mediator.Subscribe(this, msg => { - if (msg.Address == Address) - { - _haltProcessing = true; - } + if (msg.Address == Address) _haltProcessing = true; }); - Mediator.Subscribe(this, (msg) => + Mediator.Subscribe(this, msg => { - if (msg.Address == Address) - { - _haltProcessing = false; - } + if (msg.Address == Address) _haltProcessing = false; }); Mediator.Publish(new GameObjectHandlerCreatedMessage(this, _isOwnedObject)); - _objectTable = objectTable; - _dalamudUtil.RunOnFrameworkThread(CheckAndUpdateObject).GetAwaiter().GetResult(); + _dalamudUtil.EnsureIsOnFramework(); + CheckAndUpdateObject(allowPublish: true); } + /// + /// Draw Condition Enum + /// public enum DrawCondition { None, @@ -96,6 +109,7 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP ModelFilesInSlotLoaded } + // Properties public IntPtr Address { get; private set; } public DrawCondition CurrentDrawCondition { get; set; } = DrawCondition.None; public byte Gender { get; private set; } @@ -106,10 +120,13 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP public byte TribeId { get; private set; } private byte[] CustomizeData { get; set; } = new byte[26]; private IntPtr DrawObjectAddress { get; set; } - private byte[] EquipSlotData { get; set; } = new byte[40]; - private ushort[] MainHandData { get; set; } = new ushort[3]; - private ushort[] OffHandData { get; set; } = new ushort[3]; + /// + /// Act on framework thread after ensuring no draw condition + /// + /// Action of Character + /// Cancellation Token + /// Task Completion public async Task ActOnFrameworkAfterEnsureNoDrawAsync(Action act, CancellationToken token) { while (await _dalamudUtil.RunOnFrameworkThread(() => @@ -128,6 +145,11 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP } } + /// + /// Compare Name And Throw if not equal + /// + /// Name that will be compared to Object Handler. + /// Not equal if thrown public void CompareNameAndThrow(string name) { if (!string.Equals(Name, name, StringComparison.OrdinalIgnoreCase)) @@ -140,11 +162,18 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP } } - public IGameObject? GetGameObject() + /// + /// Gets the game object from the address + /// + /// Gane object + public IGameObject? GetGameObject() { return _dalamudUtil.CreateGameObject(Address); } + /// + /// Invalidate the object handler + /// public void Invalidate() { Address = IntPtr.Zero; @@ -153,26 +182,43 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP _haltProcessing = false; } + /// + /// Refresh the object handler state + /// public void Refresh() { _dalamudUtil.RunOnFrameworkThread(CheckAndUpdateObject).GetAwaiter().GetResult(); } + /// + /// Is Being Drawn Run On Framework Asyncronously + /// + /// Object is being run in framework public async Task IsBeingDrawnRunOnFrameworkAsync() { return await _dalamudUtil.RunOnFrameworkThread(IsBeingDrawn).ConfigureAwait(false); } + /// + /// Override ToString method for GameObjectHandler + /// + /// String public override string ToString() { var owned = _isOwnedObject ? "Self" : "Other"; return $"{owned}/{ObjectKind}:{Name} ({Address:X},{DrawObjectAddress:X})"; } + /// + /// Try Get Object By Address from Object Table + /// + /// Object address + /// Game Object of adress private IGameObject? TryGetObjectByAddress(nint address) { if (address == nint.Zero) return null; + // Search object table foreach (var obj in _objectTable) { if (obj is null) continue; @@ -182,8 +228,15 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP return null; } + /// + /// Checks and updates the object state + /// private void CheckAndUpdateObject() => CheckAndUpdateObject(allowPublish: true); + /// + /// Checks and updates the object state with option to allow publish + /// + /// Allows to publish the object private void CheckAndUpdateObject(bool allowPublish) { var prevAddr = Address; @@ -197,6 +250,7 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP if (Address != nint.Zero) { + // Try get object obj = TryGetObjectByAddress(Address); if (obj is not null) @@ -205,6 +259,7 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP DrawObjectAddress = Address; + // Name update nameString = obj.Name.TextValue ?? string.Empty; if (!string.IsNullOrEmpty(nameString) && !string.Equals(nameString, Name, StringComparison.Ordinal)) Name = nameString; @@ -223,13 +278,16 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP EntityId = uint.MaxValue; } + // Update draw condition CurrentDrawCondition = IsBeingDrawnSafe(obj, chara); if (_haltProcessing || !allowPublish) return; + // Determine differences bool drawObjDiff = DrawObjectAddress != prevDrawObj; bool addrDiff = Address != prevAddr; + // Name change check bool nameChange = false; if (nameString is not null) { @@ -237,10 +295,11 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP if (nameChange) Name = nameString; } + // Customize data change check bool customizeDiff = false; - if (chara is not null) { + // Class job change check var classJob = chara.ClassJob.RowId; if (classJob != _classJob) { @@ -249,8 +308,10 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP Mediator.Publish(new ClassJobChangedMessage(this)); } + // Customize data comparison customizeDiff = CompareAndUpdateCustomizeData(chara.Customize); + // Census update publish if (_isOwnedObject && ObjectKind == ObjectKind.Player && chara.Customize.Length > (int)CustomizeIndex.Tribe) { var gender = chara.Customize[(int)CustomizeIndex.Gender]; @@ -287,21 +348,35 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP } } + /// + /// Is object being drawn safe check + /// + /// Object thats being checked + /// Character of the object + /// Draw Condition of character private DrawCondition IsBeingDrawnSafe(IGameObject? obj, ICharacter? chara) { + // Object zero check if (Address == nint.Zero) return DrawCondition.ObjectZero; if (obj is null) return DrawCondition.DrawObjectZero; + // Draw Object check if (chara is not null && (chara.Customize is null || chara.Customize.Length == 0)) return DrawCondition.DrawObjectZero; return DrawCondition.None; } + /// + /// Compare and update customize data of character + /// + /// Customize+ data of object + /// Successfully applied or not private bool CompareAndUpdateCustomizeData(ReadOnlySpan customizeData) { bool hasChanges = false; + // Resize if needed var len = Math.Min(customizeData.Length, CustomizeData.Length); for (int i = 0; i < len; i++) { @@ -316,6 +391,9 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP return hasChanges; } + /// + /// Framework update method + /// private void FrameworkUpdate() { try @@ -329,6 +407,10 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP } } + /// + /// Is object being drawn check + /// + /// Is being drawn private bool IsBeingDrawn() { EnsureLatestObjectState(); @@ -343,6 +425,9 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP return CurrentDrawCondition != DrawCondition.None; } + /// + /// Ensures the latest object state + /// private void EnsureLatestObjectState() { if (_haltProcessing || !_frameworkUpdateSubscribed) @@ -351,6 +436,9 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP } } + /// + /// Enables framework updates for the object handler + /// private void EnableFrameworkUpdates() { lock (_frameworkUpdateGate) @@ -365,6 +453,9 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP } } + /// + /// Zone switch end handling + /// private void ZoneSwitchEnd() { if (!_isOwnedObject) return; @@ -375,7 +466,7 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP } catch (ObjectDisposedException) { - // ignore + // ignore canelled after disposed } catch (Exception ex) { @@ -383,6 +474,9 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP } } + /// + /// Zone switch start handling + /// private void ZoneSwitchStart() { if (!_isOwnedObject) return; diff --git a/LightlessSync/PlayerData/Handlers/OwnedObjectHandler.cs b/LightlessSync/PlayerData/Handlers/OwnedObjectHandler.cs index 1f1572d..afbcfdb 100644 --- a/LightlessSync/PlayerData/Handlers/OwnedObjectHandler.cs +++ b/LightlessSync/PlayerData/Handlers/OwnedObjectHandler.cs @@ -1,14 +1,13 @@ -using Dalamud.Game.ClientState.Objects.Types; +using Dalamud.Game.ClientState.Objects.Enums; +using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Plugin.Services; using LightlessSync.API.Data; -using LightlessSync.API.Data.Enum; using LightlessSync.Interop.Ipc; using LightlessSync.PlayerData.Factories; using LightlessSync.PlayerData.Pairs; using LightlessSync.Services; using LightlessSync.Services.ActorTracking; using Microsoft.Extensions.Logging; -using Dalamud.Game.ClientState.Objects.Enums; using DalamudObjectKind = Dalamud.Game.ClientState.Objects.Enums.ObjectKind; using ObjectKind = LightlessSync.API.Data.Enum.ObjectKind; diff --git a/LightlessSync/Services/DalamudUtilService.cs b/LightlessSync/Services/DalamudUtilService.cs index 6f0869d..737db27 100644 --- a/LightlessSync/Services/DalamudUtilService.cs +++ b/LightlessSync/Services/DalamudUtilService.cs @@ -816,9 +816,12 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber { _logger.LogInformation("Starting DalamudUtilService"); _framework.Update += FrameworkOnUpdate; - if (IsLoggedIn) + _clientState.Login += OnClientLogin; + _clientState.Logout += OnClientLogout; + + if (_clientState.IsLoggedIn) { - _classJobId = _objectTable.LocalPlayer!.ClassJob.RowId; + OnClientLogin(); } _logger.LogInformation("Started DalamudUtilService"); @@ -831,6 +834,9 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber Mediator.UnsubscribeAll(this); _framework.Update -= FrameworkOnUpdate; + _clientState.Login -= OnClientLogin; + _clientState.Logout -= OnClientLogout; + if (_FocusPairIdent.HasValue) { if (_framework.IsInFrameworkUpdateThread) @@ -845,6 +851,41 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber return Task.CompletedTask; } + private void OnClientLogin() + { + if (IsLoggedIn) + return; + _ = RunOnFrameworkThread(() => + { + if (IsLoggedIn) + return; + var localPlayer = _objectTable.LocalPlayer; + IsLoggedIn = true; + _lastZone = _clientState.TerritoryType; + if (localPlayer != null) + { + _lastWorldId = (ushort)localPlayer.CurrentWorld.RowId; + _classJobId = localPlayer.ClassJob.RowId; + } + _cid = RebuildCID(); + Mediator.Publish(new DalamudLoginMessage()); + }); + } + + private void OnClientLogout(int type, int code) + { + if (!IsLoggedIn) + return; + _ = RunOnFrameworkThread(() => + { + if (!IsLoggedIn) + return; + IsLoggedIn = false; + _lastWorldId = 0; + Mediator.Publish(new DalamudLogoutMessage()); + }); + } + public async Task WaitWhileCharacterIsDrawing( ILogger logger, GameObjectHandler handler, @@ -1272,23 +1313,6 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber 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)) {