From 3448601b6fc25975a9925f8152c1f51fe1840e49 Mon Sep 17 00:00:00 2001 From: rootdarkarchon Date: Wed, 3 May 2023 21:59:44 +0200 Subject: [PATCH] various fixes and improvements for drawing checks and data sending --- .../FileCache/TransientResourceManager.cs | 4 +- MareSynchronos/Interop/IpcManager.cs | 193 +++++++++--------- MareSynchronos/MareSynchronos.csproj | 2 +- .../PlayerData/Factories/PlayerDataFactory.cs | 4 +- .../PlayerData/Handlers/GameObjectHandler.cs | 35 +++- .../PlayerData/Pairs/CachedPlayer.cs | 23 ++- .../PlayerData/Pairs/OnlinePlayerManager.cs | 1 + .../Services/CacheCreationService.cs | 15 +- MareSynchronos/Services/DalamudUtilService.cs | 80 ++++++-- .../WebAPI/SignalR/ApiController.cs | 32 ++- 10 files changed, 242 insertions(+), 147 deletions(-) diff --git a/MareSynchronos/FileCache/TransientResourceManager.cs b/MareSynchronos/FileCache/TransientResourceManager.cs index ffb6987..5df0873 100644 --- a/MareSynchronos/FileCache/TransientResourceManager.cs +++ b/MareSynchronos/FileCache/TransientResourceManager.cs @@ -185,9 +185,9 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase Task.Run(() => { Logger.LogDebug("Penumbra Mod Settings changed, verifying SemiTransientResources"); - foreach (var item in SemiTransientResources) + foreach (var item in _playerRelatedPointers) { - Mediator.Publish(new TransientResourceChangedMessage(_dalamudUtil.GetPlayerPointer())); + Mediator.Publish(new TransientResourceChangedMessage(item.Address)); } }); } diff --git a/MareSynchronos/Interop/IpcManager.cs b/MareSynchronos/Interop/IpcManager.cs index ffbea7b..e6682bf 100644 --- a/MareSynchronos/Interop/IpcManager.cs +++ b/MareSynchronos/Interop/IpcManager.cs @@ -170,24 +170,30 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase public async Task CustomizePlusRevertAsync(IntPtr character) { if (!CheckCustomizePlusApi()) return; - var gameObj = await _dalamudUtil.CreateGameObjectAsync(character).ConfigureAwait(false); - if (gameObj is Character c) + await _dalamudUtil.RunOnFrameworkThread(() => { - Logger.LogTrace("CustomizePlus reverting for {chara}", c.Address.ToString("X")); - await _dalamudUtil.RunOnFrameworkThread(() => _customizePlusRevert!.InvokeAction(c)).ConfigureAwait(false); - } + var gameObj = _dalamudUtil.CreateGameObject(character); + if (gameObj is Character c) + { + Logger.LogTrace("CustomizePlus reverting for {chara}", c.Address.ToString("X")); + _customizePlusRevert!.InvokeAction(c); + } + }).ConfigureAwait(false); } public async Task CustomizePlusSetBodyScaleAsync(IntPtr character, string scale) { if (!CheckCustomizePlusApi() || string.IsNullOrEmpty(scale)) return; - var gameObj = await _dalamudUtil.CreateGameObjectAsync(character).ConfigureAwait(false); - if (gameObj is Character c) + await _dalamudUtil.RunOnFrameworkThread(() => { - string decodedScale = Encoding.UTF8.GetString(Convert.FromBase64String(scale)); - Logger.LogTrace("CustomizePlus applying for {chara}", c.Address.ToString("X")); - await _dalamudUtil.RunOnFrameworkThread(() => _customizePlusSetBodyScaleToCharacter!.InvokeAction(decodedScale, c)).ConfigureAwait(false); - } + var gameObj = _dalamudUtil.CreateGameObject(character); + if (gameObj is Character c) + { + string decodedScale = Encoding.UTF8.GetString(Convert.FromBase64String(scale)); + Logger.LogTrace("CustomizePlus applying for {chara}", c.Address.ToString("X")); + _customizePlusSetBodyScaleToCharacter!.InvokeAction(decodedScale, c); + } + }).ConfigureAwait(false); } public async Task GetCustomizePlusScaleAsync() @@ -198,10 +204,10 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase return Convert.ToBase64String(Encoding.UTF8.GetBytes(scale)); } - public float GetHeelsOffset() + public async Task GetHeelsOffsetAsync() { if (!CheckHeelsApi()) return 0.0f; - return _heelsGetOffset.InvokeFunc(); + return await _dalamudUtil.RunOnFrameworkThread(_heelsGetOffset.InvokeFunc).ConfigureAwait(false); } public async Task GlamourerApplyAllAsync(ILogger logger, GameObjectHandler handler, string? customization, Guid applicationId, CancellationToken token, bool fireAndForget = false) @@ -210,15 +216,12 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase try { await _redrawSemaphore.WaitAsync(token).ConfigureAwait(false); - var gameObj = await _dalamudUtil.CreateGameObjectAsync(handler.Address).ConfigureAwait(false); - if (gameObj is Character c) + + await PenumbraRedrawInternalAsync(logger, handler, applicationId, (chara) => { - await PenumbraRedrawAsync(logger, handler, applicationId, () => - { - logger.LogDebug("[{appid}] Calling on IPC: GlamourerApplyAll", applicationId); - _glamourerApplyAll!.InvokeAction(customization, c); - }).ConfigureAwait(false); - } + logger.LogDebug("[{appid}] Calling on IPC: GlamourerApplyAll", applicationId); + _glamourerApplyAll!.InvokeAction(customization, chara); + }).ConfigureAwait(false); } finally { @@ -232,20 +235,16 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase try { await _redrawSemaphore.WaitAsync(token).ConfigureAwait(false); - var gameObj = await _dalamudUtil.CreateGameObjectAsync(handler.Address).ConfigureAwait(false); - if (gameObj is Character c) + await PenumbraRedrawInternalAsync(logger, handler, applicationid, (chara) => { - await PenumbraRedrawAsync(logger, handler, applicationid, () => - { - logger.LogDebug("[{appid}] Calling on IPC: GlamourerApplyOnlyCustomization", applicationid); - _glamourerApplyOnlyCustomization!.InvokeAction(customization, c); - }).ConfigureAwait(false); - await PenumbraRedrawAsync(logger, handler, applicationid, () => - { - logger.LogDebug("[{appid}] Calling on IPC: GlamourerApplyOnlyEquipment", applicationid); - _glamourerApplyOnlyEquipment!.InvokeAction(equipment, c); - }).ConfigureAwait(false); - } + logger.LogDebug("[{appid}] Calling on IPC: GlamourerApplyOnlyCustomization", applicationid); + _glamourerApplyOnlyCustomization!.InvokeAction(customization, chara); + }).ConfigureAwait(false); + await PenumbraRedrawInternalAsync(logger, handler, applicationid, (chara) => + { + logger.LogDebug("[{appid}] Calling on IPC: GlamourerApplyOnlyEquipment", applicationid); + _glamourerApplyOnlyEquipment!.InvokeAction(equipment, chara); + }).ConfigureAwait(false); } finally { @@ -258,17 +257,20 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase if (!CheckGlamourerApi()) return string.Empty; try { - var gameObj = await _dalamudUtil.CreateGameObjectAsync(character).ConfigureAwait(false); - if (gameObj is Character c) + return await _dalamudUtil.RunOnFrameworkThread(() => { - var glamourerString = await _dalamudUtil.RunOnFrameworkThread(() => _glamourerGetAllCustomization!.InvokeFunc(c)).ConfigureAwait(false); - byte[] bytes = Convert.FromBase64String(glamourerString); - // ignore transparency - bytes[88] = 128; - bytes[89] = 63; - return Convert.ToBase64String(bytes); - } - return string.Empty; + var gameObj = _dalamudUtil.CreateGameObject(character); + if (gameObj is Character c) + { + var glamourerString = _glamourerGetAllCustomization!.InvokeFunc(c); + byte[] bytes = Convert.FromBase64String(glamourerString); + // ignore transparency + bytes[88] = 128; + bytes[89] = 63; + return Convert.ToBase64String(bytes); + } + return string.Empty; + }).ConfigureAwait(false); } catch { @@ -279,34 +281,43 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase public async Task HeelsRestoreOffsetForPlayerAsync(IntPtr character) { if (!CheckHeelsApi()) return; - var gameObj = await _dalamudUtil.CreateGameObjectAsync(character).ConfigureAwait(false); - if (gameObj != null) + await _dalamudUtil.RunOnFrameworkThread(() => { - Logger.LogTrace("Restoring Heels data to {chara}", character.ToString("X")); - await _dalamudUtil.RunOnFrameworkThread(() => _heelsUnregisterPlayer.InvokeAction(gameObj)).ConfigureAwait(false); - } + var gameObj = _dalamudUtil.CreateGameObject(character); + if (gameObj != null) + { + Logger.LogTrace("Restoring Heels data to {chara}", character.ToString("X")); + _heelsUnregisterPlayer.InvokeAction(gameObj); + } + }).ConfigureAwait(false); } public async Task HeelsSetOffsetForPlayerAsync(IntPtr character, float offset) { if (!CheckHeelsApi()) return; - var gameObj = await _dalamudUtil.CreateGameObjectAsync(character).ConfigureAwait(false); - if (gameObj != null) + await _dalamudUtil.RunOnFrameworkThread(() => { - Logger.LogTrace("Applying Heels data to {chara}", character.ToString("X")); - await _dalamudUtil.RunOnFrameworkThread(() => _heelsRegisterPlayer.InvokeAction(gameObj, offset)).ConfigureAwait(false); - } + var gameObj = _dalamudUtil.CreateGameObject(character); + if (gameObj != null) + { + Logger.LogTrace("Applying Heels data to {chara}", character.ToString("X")); + _heelsRegisterPlayer.InvokeAction(gameObj, offset); + } + }).ConfigureAwait(false); } public async Task HonorificClearTitleAsync(nint character) { if (!CheckHonorificApi()) return; - var gameObj = await _dalamudUtil.CreateGameObjectAsync(character).ConfigureAwait(false); - if (gameObj is PlayerCharacter c) + await _dalamudUtil.RunOnFrameworkThread(() => { - Logger.LogTrace("Honorific removing for {addr}", c.Address.ToString("X")); - await _dalamudUtil.RunOnFrameworkThread(() => _honorificClearCharacterTitle!.InvokeAction(c)).ConfigureAwait(false); - } + var gameObj = _dalamudUtil.CreateGameObject(character); + if (gameObj is PlayerCharacter c) + { + Logger.LogTrace("Honorific removing for {addr}", c.Address.ToString("X")); + _honorificClearCharacterTitle!.InvokeAction(c); + } + }).ConfigureAwait(false); } public string HonorificGetTitle() @@ -320,10 +331,10 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase { if (!CheckHonorificApi()) return; Logger.LogTrace("Applying Honorific data to {chara}", character.ToString("X")); - var gameObj = await _dalamudUtil.CreateGameObjectAsync(character).ConfigureAwait(false); - if (gameObj is PlayerCharacter pc) + await _dalamudUtil.RunOnFrameworkThread(() => { - await _dalamudUtil.RunOnFrameworkThread(() => + var gameObj = _dalamudUtil.CreateGameObject(character); + if (gameObj is PlayerCharacter pc) { if (string.IsNullOrEmpty(honorificData)) { @@ -333,8 +344,8 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase { _honorificSetCharacterTitle!.InvokeAction(pc, honorificData[1..], honorificData[0] == '1'); } - }).ConfigureAwait(false); - } + } + }).ConfigureAwait(false); } public async Task PalettePlusBuildPaletteAsync() @@ -348,22 +359,25 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase public async Task PalettePlusRemovePaletteAsync(IntPtr character) { if (!CheckPalettePlusApi()) return; - var gameObj = await _dalamudUtil.CreateGameObjectAsync(character).ConfigureAwait(false); - if (gameObj is Character c) + await _dalamudUtil.RunOnFrameworkThread(() => { - Logger.LogTrace("PalettePlus removing for {addr}", c.Address.ToString("X")); - await _dalamudUtil.RunOnFrameworkThread(() => _palettePlusRemoveCharaPalette!.InvokeAction(c)).ConfigureAwait(false); - } + var gameObj = _dalamudUtil.CreateGameObject(character); + if (gameObj is Character c) + { + Logger.LogTrace("PalettePlus removing for {addr}", c.Address.ToString("X")); + _palettePlusRemoveCharaPalette!.InvokeAction(c); + } + }).ConfigureAwait(false); } public async Task PalettePlusSetPaletteAsync(IntPtr character, string palette) { if (!CheckPalettePlusApi()) return; string decodedPalette = Encoding.UTF8.GetString(Convert.FromBase64String(palette)); - var gameObj = await _dalamudUtil.CreateGameObjectAsync(character).ConfigureAwait(false); - if (gameObj is Character c) + await _dalamudUtil.RunOnFrameworkThread(() => { - await _dalamudUtil.RunOnFrameworkThread(() => + var gameObj = _dalamudUtil.CreateGameObject(character); + if (gameObj is Character c) { if (string.IsNullOrEmpty(decodedPalette)) { @@ -375,8 +389,8 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase Logger.LogTrace("PalettePlus applying for {addr}", c.Address.ToString("X")); _palettePlusSetCharaPalette!.InvokeAction(c, decodedPalette); } - }).ConfigureAwait(false); - } + } + }).ConfigureAwait(false); } public string PenumbraGetMetaManipulations() @@ -385,21 +399,17 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase return _penumbraGetMetaManipulations.Invoke(); } - public async Task PenumbraRedrawAsync(ILogger logger, GameObjectHandler handler, Guid applicationId, CancellationToken token, bool fireAndForget = false) + public async Task PenumbraRedrawAsync(ILogger logger, GameObjectHandler handler, Guid applicationId, CancellationToken token) { if (!CheckPenumbraApi() || _dalamudUtil.IsZoning) return; try { await _redrawSemaphore.WaitAsync(token).ConfigureAwait(false); - var gameObj = await _dalamudUtil.CreateGameObjectAsync(handler.Address).ConfigureAwait(false); - if (gameObj is Character c) + await PenumbraRedrawInternalAsync(logger, handler, applicationId, (chara) => { - await PenumbraRedrawAsync(logger, handler, applicationId, () => - { - logger.LogDebug("[{appid}] Calling on IPC: PenumbraRedraw", applicationId); - _penumbraRedrawObject!.Invoke(c, RedrawType.Redraw); - }).ConfigureAwait(false); - } + logger.LogDebug("[{appid}] Calling on IPC: PenumbraRedraw", applicationId); + _penumbraRedrawObject!.Invoke(chara, RedrawType.Redraw); + }).ConfigureAwait(false); } finally { @@ -442,6 +452,7 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase logger.LogTrace("[{applicationId}] Change: {from} => {to}", applicationId, mod.Key, mod.Value); } + logger.LogTrace("[{applicationId}] Manip: {data}", applicationId, manipulationData); var ret2 = _penumbraAddTemporaryMod.Invoke("MareChara", collName, modPaths, manipulationData, 0); logger.LogTrace("[{applicationId}] Setting temp mods for {collName}, Success: {ret2}", applicationId, collName, ret2); }).ConfigureAwait(false); @@ -650,29 +661,27 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase _penumbraRedraw!.Invoke("self", RedrawType.Redraw); } - private async Task PenumbraRedrawAsync(ILogger logger, GameObjectHandler obj, Guid applicationId, Action action) + private async Task PenumbraRedrawInternalAsync(ILogger logger, GameObjectHandler handler, Guid applicationId, Action action) { - Mediator.Publish(new PenumbraStartRedrawMessage(obj.Address)); + Mediator.Publish(new PenumbraStartRedrawMessage(handler.Address)); - _penumbraRedrawRequests[obj.Address] = true; + _penumbraRedrawRequests[handler.Address] = true; try { CancellationTokenSource cancelToken = new CancellationTokenSource(); cancelToken.CancelAfter(TimeSpan.FromSeconds(15)); - await obj.ActOnFrameworkAfterEnsureNoDrawAsync(action, cancelToken.Token); - - await Task.Delay(TimeSpan.FromSeconds(1), _disposalCts.Token).ConfigureAwait(false); + await handler.ActOnFrameworkAfterEnsureNoDrawAsync(action, cancelToken.Token).ConfigureAwait(false); if (!_disposalCts.Token.IsCancellationRequested) - await _dalamudUtil.WaitWhileCharacterIsDrawing(logger, obj, applicationId, 30000, _disposalCts.Token).ConfigureAwait(false); + await _dalamudUtil.WaitWhileCharacterIsDrawing(logger, handler, applicationId, 30000, _disposalCts.Token).ConfigureAwait(false); } finally { - _penumbraRedrawRequests[obj.Address] = false; + _penumbraRedrawRequests[handler.Address] = false; } - Mediator.Publish(new PenumbraEndRedrawMessage(obj.Address)); + Mediator.Publish(new PenumbraEndRedrawMessage(handler.Address)); } private void PeriodicApiStateCheck() diff --git a/MareSynchronos/MareSynchronos.csproj b/MareSynchronos/MareSynchronos.csproj index 85d0b03..136a151 100644 --- a/MareSynchronos/MareSynchronos.csproj +++ b/MareSynchronos/MareSynchronos.csproj @@ -3,7 +3,7 @@ - 0.8.32 + 0.8.33 https://github.com/Penumbra-Sync/client diff --git a/MareSynchronos/PlayerData/Factories/PlayerDataFactory.cs b/MareSynchronos/PlayerData/Factories/PlayerDataFactory.cs index 35104d1..670cfed 100644 --- a/MareSynchronos/PlayerData/Factories/PlayerDataFactory.cs +++ b/MareSynchronos/PlayerData/Factories/PlayerDataFactory.cs @@ -375,7 +375,7 @@ public class PlayerDataFactory // gather up data from ipc previousData.ManipulationString = _ipcManager.PenumbraGetMetaManipulations(); - previousData.HeelsOffset = _ipcManager.GetHeelsOffset(); + Task getHeelsOffset = _ipcManager.GetHeelsOffsetAsync(); Task getGlamourerData = _ipcManager.GlamourerGetCharacterCustomizationAsync(playerRelatedObject.Address); Task getCustomizeData = _ipcManager.GetCustomizePlusScaleAsync(); Task getPalettePlusData = _ipcManager.PalettePlusBuildPaletteAsync(); @@ -387,6 +387,8 @@ public class PlayerDataFactory _logger.LogDebug("Palette is now: {data}", previousData.PalettePlusPalette); previousData.HonorificData = _ipcManager.HonorificGetTitle(); _logger.LogDebug("Honorific is now: {data}", previousData.HonorificData); + previousData.HeelsOffset = await getHeelsOffset.ConfigureAwait(false); + _logger.LogDebug("Heels is now: {heels}", previousData.HeelsOffset); st.Stop(); _logger.LogInformation("Building character data for {obj} took {time}ms", objectKind, TimeSpan.FromTicks(st.ElapsedTicks).TotalMilliseconds); diff --git a/MareSynchronos/PlayerData/Handlers/GameObjectHandler.cs b/MareSynchronos/PlayerData/Handlers/GameObjectHandler.cs index 2392e09..5c96652 100644 --- a/MareSynchronos/PlayerData/Handlers/GameObjectHandler.cs +++ b/MareSynchronos/PlayerData/Handlers/GameObjectHandler.cs @@ -1,6 +1,4 @@ -using FFXIVClientStructs.FFXIV.Client.Game.Character; -using FFXIVClientStructs.FFXIV.Client.Game.Object; -using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; +using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using MareSynchronos.Services; using MareSynchronos.Services.Mediator; using Microsoft.Extensions.Logging; @@ -61,6 +59,7 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase Mediator.Subscribe(this, (_) => { _haltProcessing = false; + ZoneSwitchEnd(); }); Mediator.Subscribe(this, (msg) => { @@ -93,12 +92,16 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase private IntPtr DrawObjectAddress { get; set; } private byte[] EquipSlotData { get; set; } = new byte[40]; - public async Task ActOnFrameworkAfterEnsureNoDrawAsync(Action act, CancellationToken token) + public async Task ActOnFrameworkAfterEnsureNoDrawAsync(Action act, CancellationToken token) { while (await _dalamudUtil.RunOnFrameworkThread(() => { if (IsBeingDrawn()) return true; - act(); + var gameObj = _dalamudUtil.CreateGameObject(Address); + if (gameObj is Dalamud.Game.ClientState.Objects.Types.Character chara) + { + act.Invoke(chara); + } return false; }).ConfigureAwait(false)) { @@ -117,6 +120,12 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase return _dalamudUtil.CreateGameObject(Address); } + public void Invalidate() + { + Address = IntPtr.Zero; + DrawObjectAddress = IntPtr.Zero; + } + public async Task IsBeingDrawnRunOnFrameworkAsync() { return await _dalamudUtil.RunOnFrameworkThread(IsBeingDrawn).ConfigureAwait(false); @@ -146,7 +155,7 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase Address = _getAddress(); if (Address != IntPtr.Zero) { - var drawObjAddr = (IntPtr)((GameObject*)Address)->DrawObject; + var drawObjAddr = (IntPtr)((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)Address)->DrawObject; DrawObjectAddress = drawObjAddr; } else @@ -165,7 +174,7 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase _clearCts?.Cancel(); _clearCts = null; } - var chara = (Character*)Address; + var chara = (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)Address; var name = new ByteString(chara->GameObject.Name).ToString(); bool nameChange = !string.Equals(name, Name, StringComparison.Ordinal); Name = name; @@ -259,11 +268,17 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase private unsafe IntPtr GetDrawObj(nint curPtr) { Logger.LogTrace("[{this}] IsBeingDrawnRunOnFramework, Getting new DrawObject", this); - return (IntPtr)((GameObject*)curPtr)->DrawObject; + return (IntPtr)((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)curPtr)->DrawObject; } private bool IsBeingDrawn() { + if (_dalamudUtil.IsAnythingDrawing) + { + Logger.LogTrace("[{this}] IsBeingDrawnRunOnFramework, Global draw block", this); + return true; + } + var curPtr = _getAddress(); Logger.LogTrace("[{this}] IsBeingDrawnRunOnFramework, CurPtr: {ptr}", this, curPtr.ToString("X")); @@ -289,7 +304,7 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase var drawObjZero = drawObj == IntPtr.Zero; Logger.LogTrace("[{this}] IsBeingDrawnRunOnFramework, Condition IsDrawObjZero: {cond}", this, drawObjZero); if (drawObjZero) return true; - var renderFlags = (((GameObject*)curPtr)->RenderFlags) != 0x0; + var renderFlags = (((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)curPtr)->RenderFlags) != 0x0; Logger.LogTrace("[{this}] IsBeingDrawnRunOnFramework, Condition RenderFlags: {cond}", this, renderFlags); if (renderFlags) return true; var modelInSlotLoaded = (((CharacterBase*)drawObj)->HasModelInSlotLoaded != 0); @@ -303,7 +318,7 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase } return drawObj == IntPtr.Zero - || ((GameObject*)curPtr)->RenderFlags != 0x0; + || ((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)curPtr)->RenderFlags != 0x0; } private void ZoneSwitchEnd() diff --git a/MareSynchronos/PlayerData/Pairs/CachedPlayer.cs b/MareSynchronos/PlayerData/Pairs/CachedPlayer.cs index bc69d14..9616977 100644 --- a/MareSynchronos/PlayerData/Pairs/CachedPlayer.cs +++ b/MareSynchronos/PlayerData/Pairs/CachedPlayer.cs @@ -14,10 +14,8 @@ using MareSynchronos.Utils; using MareSynchronos.WebAPI.Files; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using System; using System.Collections.Concurrent; using System.Diagnostics; -using static System.Net.Mime.MediaTypeNames; using ObjectKind = MareSynchronos.API.Data.Enum.ObjectKind; namespace MareSynchronos.PlayerData.Pairs; @@ -55,6 +53,12 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase _lifetime = lifetime; _fileDbManager = fileDbManager; Mediator.Subscribe(this, (_) => FrameworkUpdate()); + Mediator.Subscribe(this, (_) => + { + _charaHandler?.Invalidate(); + IsVisible = false; + } + ); _pluginWarnings ??= new() { ShownCustomizePlusWarning = mareConfigService.Current.DisableOptionalPluginWarnings, @@ -445,16 +449,14 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase if (updateModdedPaths && (moddedPaths.Any() || !string.IsNullOrEmpty(charaData.ManipulationData))) { - await _charaHandler!.ActOnFrameworkAfterEnsureNoDrawAsync(() => _ipcManager + await _charaHandler!.ActOnFrameworkAfterEnsureNoDrawAsync((_) => _ipcManager .PenumbraRemoveTemporaryCollectionAsync(Logger, _applicationId, PlayerName!) .ConfigureAwait(true).GetAwaiter().GetResult(), token).ConfigureAwait(false); token.ThrowIfCancellationRequested(); - await _charaHandler!.ActOnFrameworkAfterEnsureNoDrawAsync(() => + await _charaHandler!.ActOnFrameworkAfterEnsureNoDrawAsync((chara) => { - var gameObj = _charaHandler!.GetGameObject(); - if (gameObj == null) return; - var objTableIndex = gameObj.ObjectTableIndex(); + var objTableIndex = chara.ObjectTableIndex(); _ipcManager.PenumbraSetTemporaryModsAsync(Logger, _applicationId, PlayerName!, objTableIndex, moddedPaths, charaData.ManipulationData) .ConfigureAwait(true).GetAwaiter().GetResult(); }, token).ConfigureAwait(false); @@ -510,6 +512,7 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase _framesSinceNotVisible++; if (_framesSinceNotVisible > 30) { + _framesSinceNotVisible = 30; IsVisible = false; Logger.LogTrace("{this} visibility changed, now: {visi}", this, IsVisible); } @@ -627,7 +630,7 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase if (minionOrMount != IntPtr.Zero) { using GameObjectHandler tempHandler = await _gameObjectHandlerFactory.Create(ObjectKind.MinionOrMount, () => minionOrMount, false).ConfigureAwait(false); - await _ipcManager.PenumbraRedrawAsync(Logger, tempHandler, applicationId, cancelToken.Token, fireAndForget: false).ConfigureAwait(false); + await _ipcManager.PenumbraRedrawAsync(Logger, tempHandler, applicationId, cancelToken.Token).ConfigureAwait(false); } } else if (objectKind == ObjectKind.Pet) @@ -636,7 +639,7 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase if (pet != IntPtr.Zero) { using GameObjectHandler tempHandler = await _gameObjectHandlerFactory.Create(ObjectKind.Pet, () => pet, false).ConfigureAwait(false); - await _ipcManager.PenumbraRedrawAsync(Logger, tempHandler, applicationId, cancelToken.Token, fireAndForget: false).ConfigureAwait(false); + await _ipcManager.PenumbraRedrawAsync(Logger, tempHandler, applicationId, cancelToken.Token).ConfigureAwait(false); } } else if (objectKind == ObjectKind.Companion) @@ -645,7 +648,7 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase if (companion != IntPtr.Zero) { using GameObjectHandler tempHandler = await _gameObjectHandlerFactory.Create(ObjectKind.Pet, () => companion, false).ConfigureAwait(false); - await _ipcManager.PenumbraRedrawAsync(Logger, tempHandler, applicationId, cancelToken.Token, fireAndForget: false).ConfigureAwait(false); + await _ipcManager.PenumbraRedrawAsync(Logger, tempHandler, applicationId, cancelToken.Token).ConfigureAwait(false); } } } diff --git a/MareSynchronos/PlayerData/Pairs/OnlinePlayerManager.cs b/MareSynchronos/PlayerData/Pairs/OnlinePlayerManager.cs index 95888b5..f65e718 100644 --- a/MareSynchronos/PlayerData/Pairs/OnlinePlayerManager.cs +++ b/MareSynchronos/PlayerData/Pairs/OnlinePlayerManager.cs @@ -41,6 +41,7 @@ public class OnlinePlayerManager : DisposableMediatorSubscriberBase } }); Mediator.Subscribe(this, (msg) => _newVisiblePlayers.Add(msg.Player)); + Mediator.Subscribe(this, (_) => PushCharacterData(_pairManager.GetVisibleUsers())); } private void FrameworkOnUpdate() diff --git a/MareSynchronos/PlayerData/Services/CacheCreationService.cs b/MareSynchronos/PlayerData/Services/CacheCreationService.cs index 9a5904d..45cf8ba 100644 --- a/MareSynchronos/PlayerData/Services/CacheCreationService.cs +++ b/MareSynchronos/PlayerData/Services/CacheCreationService.cs @@ -18,6 +18,7 @@ public sealed class CacheCreationService : DisposableMediatorSubscriberBase private readonly Dictionary _playerRelatedObjects = new(); private Task? _cacheCreationTask; private CancellationTokenSource _honorificCts = new(); + private bool _isZoning = false; private CancellationTokenSource _palettePlusCts = new(); public CacheCreationService(ILogger logger, MareMediator mediator, GameObjectHandlerFactory gameObjectHandlerFactory, @@ -27,7 +28,7 @@ public sealed class CacheCreationService : DisposableMediatorSubscriberBase Mediator.Subscribe(this, (msg) => { - Logger.LogDebug("Received CreateCacheForObject for {handler}, updating player", msg.ObjectToCreateFor); + Logger.LogDebug("Received CreateCacheForObject for {handler}, updating", msg.ObjectToCreateFor); _cacheCreateLock.Wait(); _cachesToCreate[msg.ObjectToCreateFor.ObjectKind] = msg.ObjectToCreateFor; _cacheCreateLock.Release(); @@ -37,25 +38,32 @@ public sealed class CacheCreationService : DisposableMediatorSubscriberBase { Task.Run(() => { + Logger.LogTrace("Clearing cache for {obj}", msg.ObjectToCreateFor); _playerData.FileReplacements.Remove(msg.ObjectToCreateFor.ObjectKind); _playerData.GlamourerString.Remove(msg.ObjectToCreateFor.ObjectKind); Mediator.Publish(new CharacterDataCreatedMessage(_playerData.ToAPI())); }); }); + Mediator.Subscribe(this, (msg) => _isZoning = true); + Mediator.Subscribe(this, (msg) => _isZoning = false); + Mediator.Subscribe(this, (msg) => ProcessCacheCreation()); Mediator.Subscribe(this, async (_) => { + if (_isZoning) return; Logger.LogDebug("Received CustomizePlus change, updating player"); await AddPlayerCacheToCreate().ConfigureAwait(false); }); Mediator.Subscribe(this, async (_) => { + if (_isZoning) return; Logger.LogDebug("Received Heels Offset change, updating player"); await AddPlayerCacheToCreate().ConfigureAwait(false); }); Mediator.Subscribe(this, (msg) => { + if (_isZoning) return; if (msg.Character.Address == _playerRelatedObjects[ObjectKind.Player].Address) { Logger.LogDebug("Received PalettePlus change, updating player"); @@ -64,6 +72,7 @@ public sealed class CacheCreationService : DisposableMediatorSubscriberBase }); Mediator.Subscribe(this, (msg) => { + if (_isZoning) return; if (!string.Equals(msg.NewHonorificTitle, _playerData.HonorificData, StringComparison.Ordinal)) { Logger.LogDebug("Received Honorific change, updating player"); @@ -76,7 +85,7 @@ public sealed class CacheCreationService : DisposableMediatorSubscriberBase await AddPlayerCacheToCreate().ConfigureAwait(false); }); - _playerRelatedObjects[ObjectKind.Player] = gameObjectHandlerFactory.Create(ObjectKind.Player, () => dalamudUtil.GetPlayerPointer(), true) + _playerRelatedObjects[ObjectKind.Player] = gameObjectHandlerFactory.Create(ObjectKind.Player, dalamudUtil.GetPlayerPointer, true) .GetAwaiter().GetResult(); _playerRelatedObjects[ObjectKind.MinionOrMount] = gameObjectHandlerFactory.Create(ObjectKind.MinionOrMount, () => dalamudUtil.GetMinionOrMount(), true) .GetAwaiter().GetResult(); @@ -131,6 +140,8 @@ public sealed class CacheCreationService : DisposableMediatorSubscriberBase private void ProcessCacheCreation() { + if (_isZoning) return; + if (_cachesToCreate.Any() && (_cacheCreationTask?.IsCompleted ?? true)) { _cacheCreateLock.Wait(); diff --git a/MareSynchronos/Services/DalamudUtilService.cs b/MareSynchronos/Services/DalamudUtilService.cs index ac28bd8..685d7fa 100644 --- a/MareSynchronos/Services/DalamudUtilService.cs +++ b/MareSynchronos/Services/DalamudUtilService.cs @@ -6,12 +6,14 @@ using Dalamud.Game.ClientState.Objects.SubKinds; using Dalamud.Game.Gui; using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Game.Control; +using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using MareSynchronos.PlayerData.Handlers; using MareSynchronos.Services.Mediator; using MareSynchronos.Utils; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using System.Numerics; +using System.Runtime.CompilerServices; using GameObject = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject; namespace MareSynchronos.Services; @@ -53,6 +55,7 @@ public class DalamudUtilService : IHostedService public unsafe GameObject* GposeTarget => TargetSystem.Instance()->GPoseTarget; public unsafe Dalamud.Game.ClientState.Objects.Types.GameObject? GposeTargetGameObject => GposeTarget == null ? null : _objectTable[GposeTarget->ObjectIndex]; + public bool IsAnythingDrawing { get; private set; } = false; public bool IsInCutscene { get; private set; } = false; public bool IsInGpose { get; private set; } = false; public bool IsLoggedIn { get; private set; } @@ -171,6 +174,11 @@ public class DalamudUtilService : IHostedService return _clientState.LocalPlayer?.Address ?? IntPtr.Zero; } + public async Task GetPlayerPointerAsync() + { + return await RunOnFrameworkThread(GetPlayerPointer).ConfigureAwait(false); + } + public uint GetWorldId() { EnsureIsOnFramework(); @@ -198,35 +206,43 @@ public class DalamudUtilService : IHostedService return await RunOnFrameworkThread(() => IsObjectPresent(obj)).ConfigureAwait(false); } - public async Task RunOnFrameworkThread(Action act) + public async Task RunOnFrameworkThread(Action act, [CallerMemberName] string callerMember = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = 0) { - if (!_framework.IsInFrameworkUpdateThread) + var fileName = Path.GetFileNameWithoutExtension(callerFilePath); + await _performanceCollector.LogPerformance(this, "RunOnFramework:Act/" + fileName + ">" + callerMember + ":" + callerLineNumber, async () => { - await _framework.RunOnFrameworkThread(act).ContinueWith((_) => Task.CompletedTask).ConfigureAwait(false); - while (_framework.IsInFrameworkUpdateThread) // yield the thread again, should technically never be triggered + if (!_framework.IsInFrameworkUpdateThread) { - _logger.LogTrace("Still on framework"); - await Task.Delay(1).ConfigureAwait(false); + await _framework.RunOnFrameworkThread(act).ContinueWith((_) => Task.CompletedTask).ConfigureAwait(false); + while (_framework.IsInFrameworkUpdateThread) // yield the thread again, should technically never be triggered + { + _logger.LogTrace("Still on framework"); + await Task.Delay(1).ConfigureAwait(false); + } } - } - else - act(); + else + act(); + }).ConfigureAwait(false); } - public async Task RunOnFrameworkThread(Func func) + public async Task RunOnFrameworkThread(Func func, [CallerMemberName] string callerMember = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = 0) { - if (!_framework.IsInFrameworkUpdateThread) + var fileName = Path.GetFileNameWithoutExtension(callerFilePath); + return await _performanceCollector.LogPerformance(this, "RunOnFramework:Func<" + typeof(T) + ">/" + fileName + ">" + callerMember + ":" + callerLineNumber, async () => { - var result = await _framework.RunOnFrameworkThread(func).ContinueWith((task) => task.Result).ConfigureAwait(false); - while (_framework.IsInFrameworkUpdateThread) // yield the thread again, should technically never be triggered + if (!_framework.IsInFrameworkUpdateThread) { - _logger.LogTrace("Still on framework"); - await Task.Delay(1).ConfigureAwait(false); + var result = await _framework.RunOnFrameworkThread(func).ContinueWith((task) => task.Result).ConfigureAwait(false); + while (_framework.IsInFrameworkUpdateThread) // yield the thread again, should technically never be triggered + { + _logger.LogTrace("Still on framework"); + await Task.Delay(1).ConfigureAwait(false); + } + return result; } - return result; - } - return func.Invoke(); + return func.Invoke(); + }).ConfigureAwait(false); } public Task StartAsync(CancellationToken cancellationToken) @@ -318,9 +334,35 @@ public class DalamudUtilService : IHostedService { if (_clientState.LocalPlayer?.IsDead ?? false) return; + IsAnythingDrawing = false; _playerCharas = _performanceCollector.LogPerformance(this, "ObjTableToCharas", () => _objectTable.OfType().Where(o => o.ObjectIndex < 240) - .ToDictionary(p => p.GetHash256(), p => (p.Name.ToString(), p.Address), StringComparer.Ordinal)); + .ToDictionary(p => p.GetHash256(), p => + { + if (!IsAnythingDrawing) + { + var gameObj = (GameObject*)p.Address; + bool isDrawing = gameObj->RenderFlags == 0b100000000000; + if (!isDrawing) + { + var drawObj = gameObj->DrawObject; + if ((nint)drawObj != IntPtr.Zero) + { + isDrawing = ((CharacterBase*)drawObj)->HasModelInSlotLoaded != 0; + if (!isDrawing) + { + isDrawing = ((CharacterBase*)drawObj)->HasModelFilesInSlotLoaded != 0; + } + } + } + + if (isDrawing) + { + IsAnythingDrawing = true; + } + } + return (p.Name.ToString(), p.Address); + }, StringComparer.Ordinal)); if (GposeTarget != null && !IsInGpose) { diff --git a/MareSynchronos/WebAPI/SignalR/ApiController.cs b/MareSynchronos/WebAPI/SignalR/ApiController.cs index fa738d2..633021f 100644 --- a/MareSynchronos/WebAPI/SignalR/ApiController.cs +++ b/MareSynchronos/WebAPI/SignalR/ApiController.cs @@ -168,7 +168,8 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM await _mareHub.StartAsync(token).ConfigureAwait(false); - await InitializeData().ConfigureAwait(false); + InitializeApiHooks(); + await LoadIninitialPairs().ConfigureAwait(false); _connectionDto = await GetConnectionDto().ConfigureAwait(false); @@ -198,6 +199,8 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM $"Please keep your Mare Synchronos client up-to-date.", Dalamud.Interface.Internal.Notifications.NotificationType.Error)); } + + await LoadOnlinePairs().ConfigureAwait(false); } catch (HttpRequestException ex) { @@ -283,7 +286,7 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM ServerState = ServerState.Offline; } - private async Task InitializeData() + private void InitializeApiHooks() { if (_mareHub == null) return; @@ -311,6 +314,16 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM OnGroupSendFullInfo((dto) => Client_GroupSendFullInfo(dto)); OnGroupSendInfo((dto) => Client_GroupSendInfo(dto)); + _healthCheckTokenSource?.Cancel(); + _healthCheckTokenSource?.Dispose(); + _healthCheckTokenSource = new CancellationTokenSource(); + _ = ClientHealthCheck(_healthCheckTokenSource.Token); + + _initialized = true; + } + + private async Task LoadIninitialPairs() + { foreach (var userPair in await UserGetPairedClients().ConfigureAwait(false)) { Logger.LogDebug("Individual Pair: {userPair}", userPair); @@ -330,18 +343,15 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM _pairManager.AddGroupPair(user); } } + } + private async Task LoadOnlinePairs() + { foreach (var entry in await UserGetOnlinePairs().ConfigureAwait(false)) { + Logger.LogDebug("Pair online: {pair}", entry); _pairManager.MarkPairOnline(entry, sendNotif: false); } - - _healthCheckTokenSource?.Cancel(); - _healthCheckTokenSource?.Dispose(); - _healthCheckTokenSource = new CancellationTokenSource(); - _ = ClientHealthCheck(_healthCheckTokenSource.Token); - - _initialized = true; } private void MareHubOnClosed(Exception? arg) @@ -364,13 +374,15 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM ServerState = ServerState.Connecting; try { - await InitializeData().ConfigureAwait(false); + InitializeApiHooks(); + await LoadIninitialPairs().ConfigureAwait(false); _connectionDto = await GetConnectionDto().ConfigureAwait(false); if (_connectionDto.ServerVersion != IMareHub.ApiVersion) { await StopConnection(ServerState.VersionMisMatch).ConfigureAwait(false); return; } + await LoadOnlinePairs().ConfigureAwait(false); ServerState = ServerState.Connected; } catch (Exception ex)