diff --git a/MareSynchronos/Interop/IpcManager.cs b/MareSynchronos/Interop/IpcManager.cs index ef81ad7..54d49b2 100644 --- a/MareSynchronos/Interop/IpcManager.cs +++ b/MareSynchronos/Interop/IpcManager.cs @@ -660,7 +660,15 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase { if (!fireAndForget) { - await _dalamudUtil.RunOnFrameworkThread(action).ConfigureAwait(false); + while (!await _dalamudUtil.RunOnFrameworkThread(() => + { + if (obj.IsBeingDrawn()) return false; + action(); + return true; + }).ConfigureAwait(false)) + { + await Task.Delay(250).ConfigureAwait(false); + } await Task.Delay(TimeSpan.FromSeconds(1), _disposalCts.Token).ConfigureAwait(false); diff --git a/MareSynchronos/PlayerData/Handlers/GameObjectHandler.cs b/MareSynchronos/PlayerData/Handlers/GameObjectHandler.cs index 5e7172b..3b4cb72 100644 --- a/MareSynchronos/PlayerData/Handlers/GameObjectHandler.cs +++ b/MareSynchronos/PlayerData/Handlers/GameObjectHandler.cs @@ -84,8 +84,6 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase public IntPtr Address { get; set; } public unsafe Character* Character => (Character*)Address; - public Lazy GameObjectLazy { get; private set; } - public string Name { get; private set; } public ObjectKind ObjectKind { get; } @@ -101,22 +99,33 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase return await _dalamudUtil.RunOnFrameworkThread(_getAddress.Invoke).ConfigureAwait(true); } + public async Task GetGameObject() + { + return await _dalamudUtil.CreateGameObject(Address).ConfigureAwait(true); + } + + public bool IsBeingDrawn() + { + var curPtr = _getAddress(); + Logger.LogTrace("[{this}] IsBeingDrawnRunOnFramework, CurPtr: {ptr}", this, curPtr.ToString("X")); + + if (curPtr == IntPtr.Zero) + { + Logger.LogTrace("[{this}] IsBeingDrawnRunOnFramework, CurPtr is ZERO, returning", this); + + Address = IntPtr.Zero; + DrawObjectAddress = IntPtr.Zero; + return false; + } + + var drawObj = GetDrawObj(curPtr); + Logger.LogTrace("[{this}] IsBeingDrawnRunOnFramework, DrawObjPtr: {ptr}", this, drawObj.ToString("X")); + return IsBeingDrawn(drawObj, curPtr); + } + public async Task IsBeingDrawnRunOnFramework() { - return await _dalamudUtil.RunOnFrameworkThread(() => - { - var curPtr = _getAddress.Invoke(); - - if (curPtr == IntPtr.Zero) - { - Address = IntPtr.Zero; - DrawObjectAddress = IntPtr.Zero; - return false; - } - - var drawObj = GetDrawObj(curPtr); - return IsBeingDrawn(drawObj, curPtr); - }).ConfigureAwait(false); + return await _dalamudUtil.RunOnFrameworkThread(IsBeingDrawn).ConfigureAwait(false); } public override string ToString() @@ -137,23 +146,24 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase { if (_haltProcessing) return; - var curPtr = CurrentAddress().GetAwaiter().GetResult(); - bool drawObjDiff = false; - try + var prevAddr = Address; + var prevDrawObj = DrawObjectAddress; + + Address = _getAddress(); + if (Address != IntPtr.Zero) { - if (curPtr != IntPtr.Zero) - { - var drawObjAddr = (IntPtr)((GameObject*)curPtr)->DrawObject; - drawObjDiff = drawObjAddr != DrawObjectAddress; - DrawObjectAddress = drawObjAddr; - } + var drawObjAddr = (IntPtr)((GameObject*)Address)->DrawObject; + DrawObjectAddress = drawObjAddr; } - catch (Exception ex) + else { - Logger.LogError(ex, "Error during checking for draw object for {name}", this); + DrawObjectAddress = IntPtr.Zero; } - if (curPtr != IntPtr.Zero && DrawObjectAddress != IntPtr.Zero) + bool drawObjDiff = DrawObjectAddress != prevDrawObj; + bool addrDiff = Address != prevAddr; + + if (Address != IntPtr.Zero && DrawObjectAddress != IntPtr.Zero) { if (_clearCts != null) { @@ -161,13 +171,7 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase _clearCts?.Cancel(); _clearCts = null; } - bool addrDiff = Address != curPtr; - Address = curPtr; - if (addrDiff) - { - GameObjectLazy = new(() => _dalamudUtil.CreateGameObject(curPtr).GetAwaiter().GetResult()); - } - var chara = (Character*)curPtr; + var chara = (Character*)Address; var name = new ByteString(chara->GameObject.Name).ToString(); bool nameChange = !string.Equals(name, Name, StringComparison.Ordinal); Name = name; @@ -181,19 +185,15 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase var customizeDiff = CompareAndUpdateCustomizeData(chara->CustomizeData); - if ((addrDiff || equipDiff || customizeDiff || drawObjDiff || nameChange) && _isOwnedObject) + if ((addrDiff || drawObjDiff || equipDiff || customizeDiff || nameChange) && _isOwnedObject) { - Logger.LogTrace("[{this}] Changed", this); - - Logger.LogDebug("[{this}] Sending CreateCacheObjectMessage", this); + Logger.LogDebug("[{this}] Changed, Sending CreateCacheObjectMessage", this); Mediator.Publish(new CreateCacheForObjectMessage(this)); } } - else if (Address != IntPtr.Zero || DrawObjectAddress != IntPtr.Zero) + else if (addrDiff || drawObjDiff) { - Address = IntPtr.Zero; - DrawObjectAddress = IntPtr.Zero; - Logger.LogTrace("[{this}] Changed -> Null", this); + Logger.LogTrace("[{this}] Changed", this); if (_isOwnedObject && ObjectKind != ObjectKind.Player) { _clearCts?.Cancel(); @@ -264,18 +264,29 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase private unsafe IntPtr GetDrawObj(nint curPtr) { + Logger.LogTrace("[{this}] IsBeingDrawnRunOnFramework, Getting new DrawObject", this); return (IntPtr)((GameObject*)curPtr)->DrawObject; } private unsafe bool IsBeingDrawn(IntPtr drawObj, IntPtr curPtr) { - Logger.LogTrace("IsBeingDrawn for {kind} ptr {curPtr} : {drawObj}", ObjectKind, curPtr.ToString("X"), drawObj.ToString("X")); + Logger.LogTrace("[{this}] IsBeingDrawnRunOnFramework, Checking IsBeingDrawn for Ptr {curPtr} : DrawObj {drawObj}", this, curPtr.ToString("X"), drawObj.ToString("X")); if (ObjectKind == ObjectKind.Player) { - return drawObj == IntPtr.Zero - || (((CharacterBase*)drawObj)->HasModelInSlotLoaded != 0) - || (((CharacterBase*)drawObj)->HasModelFilesInSlotLoaded != 0) - || (((GameObject*)curPtr)->RenderFlags & 0b100000000000) == 0b100000000000; + var drawObjZero = drawObj == IntPtr.Zero; + Logger.LogTrace("[{this}] IsBeingDrawnRunOnFramework, Condition IsDrawObjZero: {cond}", this, drawObjZero); + if (drawObjZero) return true; + var renderFlags = (((GameObject*)curPtr)->RenderFlags) != 0x0; + Logger.LogTrace("[{this}] IsBeingDrawnRunOnFramework, Condition RenderFlags: {cond}", this, renderFlags); + if (renderFlags) return true; + var modelInSlotLoaded = (((CharacterBase*)drawObj)->HasModelInSlotLoaded != 0); + Logger.LogTrace("[{this}] IsBeingDrawnRunOnFramework, Condition ModelInSlotLoaded: {cond}", this, modelInSlotLoaded); + if (modelInSlotLoaded) return true; + var modelFilesInSlotLoaded = (((CharacterBase*)drawObj)->HasModelFilesInSlotLoaded != 0); + Logger.LogTrace("[{this}] IsBeingDrawnRunOnFramework, Condition ModelFilesInSlotLoaded: {cond}", this, modelFilesInSlotLoaded); + if (modelFilesInSlotLoaded) return true; + Logger.LogTrace("[{this}] IsBeingDrawnRunOnFramework, Is not being drawn", this); + return false; } return drawObj == IntPtr.Zero diff --git a/MareSynchronos/PlayerData/Pairs/CachedPlayer.cs b/MareSynchronos/PlayerData/Pairs/CachedPlayer.cs index 145d234..c6ca751 100644 --- a/MareSynchronos/PlayerData/Pairs/CachedPlayer.cs +++ b/MareSynchronos/PlayerData/Pairs/CachedPlayer.cs @@ -115,7 +115,7 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase Logger.LogDebug("Downloading and applying character for {name}", this); - DownloadAndApplyCharacter(characterData, charaDataToUpdate); + DownloadAndApplyCharacter(characterData.DeepClone(), charaDataToUpdate); _cachedData = characterData; } @@ -203,10 +203,12 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase private async Task ApplyBaseData(Guid applicationId, Dictionary moddedPaths, string manipulationData, CancellationToken token) { - await _ipcManager.PenumbraRemoveTemporaryCollection(Logger, applicationId, PlayerName!).ConfigureAwait(false); + await _ipcManager.PenumbraRemoveTemporaryCollection(Logger, applicationId, PlayerName!).ConfigureAwait(true); token.ThrowIfCancellationRequested(); - var objTableIndex = await _dalamudUtil.RunOnFrameworkThread(() => _charaHandler!.GameObjectLazy!.Value.ObjectTableIndex()).ConfigureAwait(false); - await _ipcManager.PenumbraSetTemporaryMods(Logger, applicationId, PlayerName!, objTableIndex, moddedPaths, manipulationData).ConfigureAwait(false); + var gameObj = await _charaHandler!.GetGameObject().ConfigureAwait(true); + if (gameObj == null) return; + var objTableIndex = await _dalamudUtil.RunOnFrameworkThread(() => gameObj.ObjectTableIndex()).ConfigureAwait(true); + await _ipcManager.PenumbraSetTemporaryMods(Logger, applicationId, PlayerName!, objTableIndex, moddedPaths, manipulationData).ConfigureAwait(true); token.ThrowIfCancellationRequested(); } @@ -443,16 +445,26 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase try { _applicationId = Guid.NewGuid(); - Logger.LogDebug("[{applicationId}] Starting application task", _applicationId); + Logger.LogDebug("[{applicationId}] Starting application task for {this}", _applicationId, this); Logger.LogDebug("[{applicationId}] Waiting for initial draw for for {handler}", _applicationId, _charaHandler); await _dalamudUtil.WaitWhileCharacterIsDrawing(Logger, _charaHandler!, _applicationId, 30000, token).ConfigureAwait(false); token.ThrowIfCancellationRequested(); + var applyBaseData = new Action(() => ApplyBaseData(_applicationId, moddedPaths, charaData.ManipulationData, token).ConfigureAwait(true).GetAwaiter().GetResult()); + if (updateModdedPaths && (moddedPaths.Any() || !string.IsNullOrEmpty(charaData.ManipulationData))) { - await ApplyBaseData(_applicationId, moddedPaths, charaData.ManipulationData, token).ConfigureAwait(false); + while (!await _dalamudUtil.RunOnFrameworkThread(() => + { + if (_charaHandler!.IsBeingDrawn()) return false; + applyBaseData(); + return true; + }).ConfigureAwait(false)) + { + await Task.Delay(250, token).ConfigureAwait(false); + } } token.ThrowIfCancellationRequested(); diff --git a/MareSynchronos/Services/DalamudUtilService.cs b/MareSynchronos/Services/DalamudUtilService.cs index b98a523..02fa5d4 100644 --- a/MareSynchronos/Services/DalamudUtilService.cs +++ b/MareSynchronos/Services/DalamudUtilService.cs @@ -176,7 +176,7 @@ public class DalamudUtilService : IHostedService { while ((!ct?.IsCancellationRequested ?? true) && curWaitTime < timeOut - && await handler.IsBeingDrawnRunOnFramework().ConfigureAwait(true)) // 0b100000000000 is "still rendering" or something + && await handler.IsBeingDrawnRunOnFramework().ConfigureAwait(false)) // 0b100000000000 is "still rendering" or something { logger.LogTrace("[{redrawId}] Waiting for {handler} to finish drawing", redrawId, handler); curWaitTime += tick; diff --git a/MareSynchronos/Services/Mediator/Messages.cs b/MareSynchronos/Services/Mediator/Messages.cs index d8535af..1b2a59c 100644 --- a/MareSynchronos/Services/Mediator/Messages.cs +++ b/MareSynchronos/Services/Mediator/Messages.cs @@ -36,7 +36,7 @@ public record PenumbraResourceLoadMessage(IntPtr GameObject, string GamePath, st public record CustomizePlusMessage : MessageBase; public record PalettePlusMessage(Character Character) : MessageBase; public record HonorificMessage(string NewHonorificTitle) : MessageBase; -public record PlayerChangedMessage(API.Data.CharacterData Data) : MessageBase; +public record PlayerChangedMessage(CharacterData Data) : MessageBase; public record CharacterChangedMessage(GameObjectHandler GameObjectHandler) : MessageBase; public record TransientResourceChangedMessage(IntPtr Address) : MessageBase; public record AddWatchedGameObjectHandler(GameObjectHandler Handler) : MessageBase; @@ -47,7 +47,7 @@ public record NotificationMessage (string Title, string Message, NotificationType Type, uint TimeShownOnScreen = 3000) : MessageBase; public record CreateCacheForObjectMessage(GameObjectHandler ObjectToCreateFor) : MessageBase; public record ClearCacheForObjectMessage(GameObjectHandler ObjectToCreateFor) : MessageBase; -public record CharacterDataCreatedMessage(API.Data.CharacterData CharacterData) : MessageBase; +public record CharacterDataCreatedMessage(CharacterData CharacterData) : SameThreadMessage; public record PenumbraStartRedrawMessage(IntPtr Address) : MessageBase; public record PenumbraEndRedrawMessage(IntPtr Address) : MessageBase; public record HubReconnectingMessage(Exception? Exception) : MessageBase; diff --git a/MareSynchronos/Services/PerformanceCollectorService.cs b/MareSynchronos/Services/PerformanceCollectorService.cs index 4f08b26..0afaef1 100644 --- a/MareSynchronos/Services/PerformanceCollectorService.cs +++ b/MareSynchronos/Services/PerformanceCollectorService.cs @@ -181,7 +181,7 @@ public sealed class PerformanceCollectorService : IHostedService } catch (Exception e) { - _logger.LogDebug(e, "Error removing performance counter {counter}", entries.Key); + _logger.LogWarning(e, "Error removing performance counter {counter}", entries.Key); } } } diff --git a/MareSynchronos/UI/DownloadUi.cs b/MareSynchronos/UI/DownloadUi.cs index 7807226..e6928c9 100644 --- a/MareSynchronos/UI/DownloadUi.cs +++ b/MareSynchronos/UI/DownloadUi.cs @@ -139,7 +139,7 @@ public class DownloadUi : WindowMediatorSubscriberBase foreach (var transfer in _currentDownloads.ToList()) { - var screenPos = _dalamudUtilService.WorldToScreen(transfer.Key.GameObjectLazy.Value); + var screenPos = _dalamudUtilService.WorldToScreen(transfer.Key.GetGameObject().ConfigureAwait(true).GetAwaiter().GetResult()); if (screenPos == Vector2.Zero) continue; var totalBytes = transfer.Value.Sum(c => c.Value.TotalBytes); @@ -182,7 +182,7 @@ public class DownloadUi : WindowMediatorSubscriberBase { foreach (var player in _uploadingPlayers.Select(p => p.Key).ToList()) { - var screenPos = _dalamudUtilService.WorldToScreen(player.GameObjectLazy.Value); + var screenPos = _dalamudUtilService.WorldToScreen(player.GetGameObject().GetAwaiter().GetResult()); if (screenPos == Vector2.Zero) continue; try