From 0cf12d57ef6baa0394ec19376f287cbd2d904927 Mon Sep 17 00:00:00 2001 From: rootdarkarchon Date: Fri, 17 Feb 2023 00:38:42 +0100 Subject: [PATCH] make application of character data blocking --- MareSynchronos/Export/MareCharaFileManager.cs | 3 +- .../Managers/CacheCreationService.cs | 2 +- MareSynchronos/Managers/CachedPlayer.cs | 152 ++++++++++-------- MareSynchronos/Managers/IpcManager.cs | 102 ++++++------ .../Managers/TransientResourceManager.cs | 1 + MareSynchronos/MareSynchronos.csproj | 2 +- MareSynchronos/Mediator/Messages.cs | 3 +- MareSynchronos/Models/GameObjectHandler.cs | 18 ++- MareSynchronos/Utils/DalamudUtil.cs | 11 ++ 9 files changed, 178 insertions(+), 116 deletions(-) diff --git a/MareSynchronos/Export/MareCharaFileManager.cs b/MareSynchronos/Export/MareCharaFileManager.cs index 2294614..11593b0 100644 --- a/MareSynchronos/Export/MareCharaFileManager.cs +++ b/MareSynchronos/Export/MareCharaFileManager.cs @@ -100,6 +100,7 @@ public class MareCharaFileManager var unwrapped = File.OpenRead(LoadedCharaFile.FilePath); await using (unwrapped.ConfigureAwait(false)) { + CancellationTokenSource disposeCts = new(); using var lz4Stream = new LZ4Stream(unwrapped, LZ4StreamMode.Decompress, LZ4StreamFlags.HighCompression); using var reader = new BinaryReader(lz4Stream); LoadedCharaFile.AdvanceReaderToData(reader); @@ -118,7 +119,7 @@ public class MareCharaFileManager _ipcManager.PenumbraSetTemporaryMods(charaTarget.Name.TextValue, extractedFiles.Union(fileSwaps).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal), LoadedCharaFile.CharaFileData.ManipulationData); - _ipcManager.GlamourerApplyAll(LoadedCharaFile.CharaFileData.GlamourerData, charaTarget.Address); + await _ipcManager.GlamourerApplyAll(LoadedCharaFile.CharaFileData.GlamourerData, charaTarget.Address, disposeCts.Token).ConfigureAwait(false); _dalamudUtil.WaitWhileGposeCharacterIsDrawing(charaTarget.Address, 30000); _ipcManager.PenumbraRemoveTemporaryCollection(charaTarget.Name.TextValue); _ipcManager.ToggleGposeQueueMode(on: false); diff --git a/MareSynchronos/Managers/CacheCreationService.cs b/MareSynchronos/Managers/CacheCreationService.cs index 380808e..4f9df7e 100644 --- a/MareSynchronos/Managers/CacheCreationService.cs +++ b/MareSynchronos/Managers/CacheCreationService.cs @@ -91,7 +91,7 @@ public class CacheCreationService : MediatorSubscriberBase, IDisposable private void UpdatePointers() { - Mediator.Publish(new PlayerRelatedObjectPointerUpdateMessage(_playerRelatedObjects.Select(f => f.CurrentAddress).ToArray())); + Mediator.Publish(new PlayerRelatedObjectPointerUpdateMessage(_playerRelatedObjects.Select(f => f.Address).ToArray())); } private void ProcessCacheCreation() diff --git a/MareSynchronos/Managers/CachedPlayer.cs b/MareSynchronos/Managers/CachedPlayer.cs index 115374d..9bfb37b 100644 --- a/MareSynchronos/Managers/CachedPlayer.cs +++ b/MareSynchronos/Managers/CachedPlayer.cs @@ -34,9 +34,10 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable } public OnlineUserIdentDto OnlineUser { get; set; } - public IntPtr PlayerCharacter => _currentOtherChara?.CurrentAddress ?? IntPtr.Zero; + public IntPtr PlayerCharacter => _currentOtherChara?.Address ?? IntPtr.Zero; public string? PlayerName { get; private set; } public string PlayerNameHash => OnlineUser.Ident; + public void ApplyCharacterData(API.Data.CharacterData characterData, OptionalPluginWarning warning, bool forced = false) { Logger.Debug("Received data for " + this); @@ -56,14 +57,25 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable if (string.Equals(characterData.DataHash.Value, _cachedData.DataHash.Value, StringComparison.Ordinal) && !forced) return; - bool updateModdedPaths = false; - List charaDataToUpdate = new(); + CheckUpdatedData(characterData, out bool updateModdedPaths, out List charaDataToUpdate); + + NotifyForMissingPlugins(characterData, warning); + + _cachedData = characterData; + + DownloadAndApplyCharacter(characterData, charaDataToUpdate, updateModdedPaths); + } + + private void CheckUpdatedData(API.Data.CharacterData newData, out bool updateModdedPaths, out List charaDataToUpdate) + { + updateModdedPaths = false; + charaDataToUpdate = new(); foreach (var objectKind in Enum.GetValues()) { _cachedData.FileReplacements.TryGetValue(objectKind, out var existingFileReplacements); - characterData.FileReplacements.TryGetValue(objectKind, out var newFileReplacements); + newData.FileReplacements.TryGetValue(objectKind, out var newFileReplacements); _cachedData.GlamourerData.TryGetValue(objectKind, out var existingGlamourerData); - characterData.GlamourerData.TryGetValue(objectKind, out var newGlamourerData); + newData.GlamourerData.TryGetValue(objectKind, out var newGlamourerData); bool hasNewButNotOldFileReplacements = newFileReplacements != null && existingFileReplacements == null; bool hasOldButNotNewFileReplacements = existingFileReplacements != null && newFileReplacements == null; @@ -84,7 +96,7 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable if (hasNewAndOldFileReplacements) { - bool listsAreEqual = Enumerable.SequenceEqual(_cachedData.FileReplacements[objectKind], characterData.FileReplacements[objectKind], FileReplacementDataComparer.Instance); + bool listsAreEqual = Enumerable.SequenceEqual(_cachedData.FileReplacements[objectKind], newData.FileReplacements[objectKind], FileReplacementDataComparer.Instance); if (!listsAreEqual) { Logger.Debug($"Updating {objectKind} (FileReplacements not equal)"); @@ -96,7 +108,7 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable if (hasNewAndOldGlamourerData) { - bool glamourerDataDifferent = !string.Equals(_cachedData.GlamourerData[objectKind], characterData.GlamourerData[objectKind], StringComparison.Ordinal); + bool glamourerDataDifferent = !string.Equals(_cachedData.GlamourerData[objectKind], newData.GlamourerData[objectKind], StringComparison.Ordinal); if (glamourerDataDifferent) { Logger.Debug($"Updating {objectKind} (Diff glamourer data)"); @@ -107,7 +119,7 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable if (objectKind == ObjectKind.Player) { - bool manipDataDifferent = !string.Equals(_cachedData.ManipulationData, characterData.ManipulationData, StringComparison.Ordinal); + bool manipDataDifferent = !string.Equals(_cachedData.ManipulationData, newData.ManipulationData, StringComparison.Ordinal); if (manipDataDifferent) { Logger.Debug($"Updating {objectKind} (Diff manip data)"); @@ -115,7 +127,7 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable continue; } - bool heelsOffsetDifferent = _cachedData.HeelsOffset != characterData.HeelsOffset; + bool heelsOffsetDifferent = _cachedData.HeelsOffset != newData.HeelsOffset; if (heelsOffsetDifferent) { Logger.Debug($"Updating {objectKind} (Diff heels data)"); @@ -123,7 +135,7 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable continue; } - bool customizeDataDifferent = !string.Equals(_cachedData.CustomizePlusData, characterData.CustomizePlusData, StringComparison.Ordinal); + bool customizeDataDifferent = !string.Equals(_cachedData.CustomizePlusData, newData.CustomizePlusData, StringComparison.Ordinal); if (customizeDataDifferent) { Logger.Debug($"Updating {objectKind} (Diff customize data)"); @@ -131,7 +143,7 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable continue; } - bool palettePlusDataDifferent = !string.Equals(_cachedData.PalettePlusData, characterData.PalettePlusData, StringComparison.Ordinal); + bool palettePlusDataDifferent = !string.Equals(_cachedData.PalettePlusData, newData.PalettePlusData, StringComparison.Ordinal); if (palettePlusDataDifferent) { Logger.Debug($"Updating {objectKind} (Diff palette data)"); @@ -140,7 +152,10 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable } } } + } + private void NotifyForMissingPlugins(API.Data.CharacterData characterData, OptionalPluginWarning warning) + { List missingPluginsForData = new(); if (characterData.HeelsOffset != default) { @@ -174,15 +189,11 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable $"Received data for {PlayerName} that contained information for plugins you have not installed. Install {string.Join(", ", missingPluginsForData)} to experience their character fully.", NotificationType.Warning, 10000)); } - - _cachedData = characterData; - - DownloadAndApplyCharacter(charaDataToUpdate, updateModdedPaths); } public bool CheckExistence() { - if (PlayerName == null || _currentOtherChara == null + if (PlayerName == null || _currentOtherChara == null || !string.Equals(PlayerName, _currentOtherChara.Name, StringComparison.Ordinal) || _currentOtherChara.Address == IntPtr.Zero) { @@ -192,7 +203,7 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable return true; } - public override void Dispose() + public override async void Dispose() { if (string.IsNullOrEmpty(PlayerName)) return; // already disposed @@ -211,7 +222,7 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable { foreach (var item in _cachedData.FileReplacements) { - RevertCustomizationData(item.Key); + await RevertCustomizationData(item.Key).ConfigureAwait(false); } } _currentOtherChara = null; @@ -231,7 +242,7 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable public void Initialize(string name) { PlayerName = name; - _currentOtherChara = new GameObjectHandler(Mediator, ObjectKind.Player, () => _dalamudUtil.GetPlayerCharacterFromObjectTableByName(PlayerName)?.Address ?? IntPtr.Zero, false); + _currentOtherChara = new GameObjectHandler(Mediator, ObjectKind.Player, () => _dalamudUtil.GetPlayerCharacterFromObjectTableByName(PlayerName)?.Address ?? IntPtr.Zero, watchedObject: false); _originalGlamourerData = _ipcManager.GlamourerGetCharacterCustomization(PlayerCharacter); _lastGlamourerData = _originalGlamourerData; @@ -259,45 +270,46 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable _ipcManager.PenumbraSetTemporaryMods(PlayerName!, moddedPaths, _cachedData.ManipulationData); } - private unsafe void ApplyCustomizationData(ObjectKind objectKind, CancellationToken ct) + private async Task ApplyCustomizationData(ObjectKind objectKind) { if (PlayerCharacter == IntPtr.Zero) return; _cachedData.GlamourerData.TryGetValue(objectKind, out var glamourerData); + CancellationTokenSource applicationTokenSource = new(); + applicationTokenSource.CancelAfter(TimeSpan.FromSeconds(30)); + switch (objectKind) { case ObjectKind.Player: - _dalamudUtil.WaitWhileCharacterIsDrawing(PlayerName!, PlayerCharacter, 30000, ct); - ct.ThrowIfCancellationRequested(); + _dalamudUtil.WaitWhileCharacterIsDrawing(PlayerName!, PlayerCharacter, 30000); _ipcManager.HeelsSetOffsetForPlayer(_cachedData.HeelsOffset, PlayerCharacter); _ipcManager.CustomizePlusSetBodyScale(PlayerCharacter, _cachedData.CustomizePlusData); _ipcManager.PalettePlusSetPalette(PlayerCharacter, _cachedData.PalettePlusData); Logger.Debug($"Request Redraw for {PlayerName}"); if (_ipcManager.CheckGlamourerApi() && !string.IsNullOrEmpty(glamourerData)) { - _ipcManager.GlamourerApplyAll(glamourerData, PlayerCharacter); + await _ipcManager.GlamourerApplyAll(glamourerData, PlayerCharacter, applicationTokenSource.Token).ConfigureAwait(false); } else { - _ipcManager.PenumbraRedraw(PlayerCharacter); + await _ipcManager.PenumbraRedraw(PlayerCharacter, applicationTokenSource.Token).ConfigureAwait(false); } break; case ObjectKind.MinionOrMount: { - var minionOrMount = ((Character*)PlayerCharacter)->CompanionObject; + var minionOrMount = _dalamudUtil.GetMinionOrMount(PlayerCharacter); if (minionOrMount != null) { Logger.Debug($"Request Redraw for {PlayerName} Minion/Mount"); - _dalamudUtil.WaitWhileCharacterIsDrawing(PlayerName! + " minion or mount", (IntPtr)minionOrMount, 30000, ct); - ct.ThrowIfCancellationRequested(); + _dalamudUtil.WaitWhileCharacterIsDrawing(PlayerName! + " minion or mount", (IntPtr)minionOrMount, 30000); if (_ipcManager.CheckGlamourerApi() && !string.IsNullOrEmpty(glamourerData)) { - _ipcManager.GlamourerApplyAll(glamourerData, (IntPtr)minionOrMount); + await _ipcManager.GlamourerApplyAll(glamourerData, (IntPtr)minionOrMount, applicationTokenSource.Token).ConfigureAwait(false); } else { - _ipcManager.PenumbraRedraw((IntPtr)minionOrMount); + await _ipcManager.PenumbraRedraw((IntPtr)minionOrMount, applicationTokenSource.Token).ConfigureAwait(false); } } @@ -324,11 +336,11 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable if (_ipcManager.CheckGlamourerApi() && !string.IsNullOrEmpty(glamourerData)) { - _ipcManager.GlamourerApplyAll(glamourerData, newPet); + await _ipcManager.GlamourerApplyAll(glamourerData, newPet, applicationTokenSource.Token).ConfigureAwait(false); } else { - _ipcManager.PenumbraRedraw(newPet); + await _ipcManager.PenumbraRedraw(newPet, applicationTokenSource.Token).ConfigureAwait(false); } } @@ -341,15 +353,14 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable if (companion != IntPtr.Zero) { Logger.Debug($"Request Redraw for {PlayerName} Companion"); - _dalamudUtil.WaitWhileCharacterIsDrawing(PlayerName! + " companion", companion, 30000, ct); - ct.ThrowIfCancellationRequested(); + _dalamudUtil.WaitWhileCharacterIsDrawing(PlayerName! + " companion", companion, 30000); if (_ipcManager.CheckGlamourerApi() && !string.IsNullOrEmpty(glamourerData)) { - _ipcManager.GlamourerApplyAll(glamourerData, companion); + await _ipcManager.GlamourerApplyAll(glamourerData, companion, applicationTokenSource.Token).ConfigureAwait(false); } else { - _ipcManager.PenumbraRedraw(companion); + await _ipcManager.PenumbraRedraw(companion, applicationTokenSource.Token).ConfigureAwait(false); } } @@ -358,7 +369,7 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable } } - private void DownloadAndApplyCharacter(List objectKind, bool updateModdedPaths) + private void DownloadAndApplyCharacter(API.Data.CharacterData charaData, List objectKind, bool updateModdedPaths) { if (!objectKind.Any()) { @@ -375,11 +386,12 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable { List toDownloadReplacements; + Dictionary moddedPaths = new(StringComparer.Ordinal); + if (updateModdedPaths) { - Dictionary moddedPaths; int attempts = 0; - while ((toDownloadReplacements = TryCalculateModdedDictionary(out moddedPaths)).Count > 0 && attempts++ <= 10) + while ((toDownloadReplacements = TryCalculateModdedDictionary(charaData, out moddedPaths)).Count > 0 && attempts++ <= 10) { downloadId = _apiController.GetDownloadId(); @@ -389,38 +401,56 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable await _apiController.DownloadFiles(downloadId, toDownloadReplacements, downloadToken).ConfigureAwait(false); _apiController.CancelDownload(downloadId); } + if (downloadToken.IsCancellationRequested) { Logger.Verbose("Detected cancellation"); return; } - if ((TryCalculateModdedDictionary(out moddedPaths)).All(c => _apiController.ForbiddenTransfers.Any(f => string.Equals(f.Hash, c.Hash, StringComparison.Ordinal)))) + if ((TryCalculateModdedDictionary(charaData, out moddedPaths)).All(c => _apiController.ForbiddenTransfers.Any(f => string.Equals(f.Hash, c.Hash, StringComparison.Ordinal)))) { break; } await Task.Delay(TimeSpan.FromSeconds(2)).ConfigureAwait(false); } - - ApplyBaseData(moddedPaths); } - foreach (var kind in objectKind) + while (!_applicationTask?.IsCompleted ?? false) { - ApplyCustomizationData(kind, downloadToken); + // block until current application is done + Logger.Debug("Waiting for current data application to finish"); + await Task.Delay(250).ConfigureAwait(false); + if (downloadToken.IsCancellationRequested) return; } + + _applicationTask = Task.Run(async () => + { + if (moddedPaths.Any()) + { + ApplyBaseData(moddedPaths); + } + + foreach (var kind in objectKind) + { + await ApplyCustomizationData(kind).ConfigureAwait(false); + } + }); + }, downloadToken).ContinueWith(task => { _downloadCancellationTokenSource = null; if (!task.IsCanceled) return; - Logger.Debug("Download Task was cancelled"); + Logger.Debug("Application was cancelled"); _apiController.CancelDownload(downloadId); }); } + private Task? _applicationTask; + private CancellationTokenSource _redrawCts = new CancellationTokenSource(); private void IpcManagerOnPenumbraRedrawEvent(PenumbraRedrawMessage msg) @@ -433,31 +463,27 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable _redrawCts.CancelAfter(TimeSpan.FromSeconds(30)); var token = _redrawCts.Token; - Task.Run(() => + Task.Run(async () => { _dalamudUtil.WaitWhileCharacterIsDrawing(PlayerName!, PlayerCharacter, ct: token); - if (!msg.WasRequested) - { - Logger.Debug("Unauthorized character change detected"); - ApplyCustomizationData(ObjectKind.Player, token); - } - else - { - Logger.Debug($"Penumbra Redraw done for {PlayerName}"); - } + Logger.Debug("Unauthorized character change detected"); + await ApplyCustomizationData(ObjectKind.Player).ConfigureAwait(false); }, token); } - private unsafe void RevertCustomizationData(ObjectKind objectKind) + private async Task RevertCustomizationData(ObjectKind objectKind) { if (PlayerCharacter == IntPtr.Zero) return; + var cancelToken = new CancellationTokenSource(); + cancelToken.CancelAfter(TimeSpan.FromSeconds(10)); + if (objectKind == ObjectKind.Player) { Logger.Debug($"Restoring Customization for {OnlineUser.User.AliasOrUID}/{PlayerName}: {_originalGlamourerData}"); - _ipcManager.GlamourerApplyOnlyCustomization(_originalGlamourerData, PlayerCharacter); + await _ipcManager.GlamourerApplyOnlyCustomization(_originalGlamourerData, PlayerCharacter, cancelToken.Token, fireAndForget: true).ConfigureAwait(false); Logger.Debug($"Restoring Equipment for {OnlineUser.User.AliasOrUID}/{PlayerName}: {_lastGlamourerData}"); - _ipcManager.GlamourerApplyOnlyEquipment(_lastGlamourerData, PlayerCharacter); + await _ipcManager.GlamourerApplyOnlyEquipment(_lastGlamourerData, PlayerCharacter, cancelToken.Token, fireAndForget: true).ConfigureAwait(false); Logger.Debug($"Restoring Heels for {OnlineUser.User.AliasOrUID}/{PlayerName}"); _ipcManager.HeelsRestoreOffsetForPlayer(PlayerCharacter); Logger.Debug($"Restoring C+ for {OnlineUser.User.AliasOrUID}/{PlayerName}"); @@ -466,10 +492,10 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable } else if (objectKind == ObjectKind.MinionOrMount) { - var minionOrMount = ((Character*)PlayerCharacter)->CompanionObject; + var minionOrMount = _dalamudUtil.GetMinionOrMount(PlayerCharacter); if (minionOrMount != null) { - _ipcManager.PenumbraRedraw((IntPtr)minionOrMount); + await _ipcManager.PenumbraRedraw(minionOrMount.Value, cancelToken.Token, fireAndForget: true).ConfigureAwait(false); } } else if (objectKind == ObjectKind.Pet) @@ -477,7 +503,7 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable var pet = _dalamudUtil.GetPet(PlayerCharacter); if (pet != IntPtr.Zero) { - _ipcManager.PenumbraRedraw(pet); + await _ipcManager.PenumbraRedraw(pet, cancelToken.Token, fireAndForget: true).ConfigureAwait(false); } } else if (objectKind == ObjectKind.Companion) @@ -485,18 +511,18 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable var companion = _dalamudUtil.GetCompanion(PlayerCharacter); if (companion != IntPtr.Zero) { - _ipcManager.PenumbraRedraw(companion); + await _ipcManager.PenumbraRedraw(companion, cancelToken.Token, fireAndForget: true).ConfigureAwait(false); } } } - private List TryCalculateModdedDictionary(out Dictionary moddedDictionary) + private List TryCalculateModdedDictionary(API.Data.CharacterData charaData, out Dictionary moddedDictionary) { List missingFiles = new(); moddedDictionary = new Dictionary(StringComparer.Ordinal); try { - foreach (var item in _cachedData.FileReplacements.SelectMany(k => k.Value.Where(v => string.IsNullOrEmpty(v.FileSwapPath))).ToList()) + foreach (var item in charaData.FileReplacements.SelectMany(k => k.Value.Where(v => string.IsNullOrEmpty(v.FileSwapPath))).ToList()) { foreach (var gamePath in item.GamePaths) { diff --git a/MareSynchronos/Managers/IpcManager.cs b/MareSynchronos/Managers/IpcManager.cs index c8d186e..2d66bb8 100644 --- a/MareSynchronos/Managers/IpcManager.cs +++ b/MareSynchronos/Managers/IpcManager.cs @@ -66,6 +66,7 @@ public class IpcManager : MediatorSubscriberBase, IDisposable private readonly ConcurrentQueue _gposeActionQueue = new(); private ConcurrentDictionary _penumbraRedrawRequests = new(); + private CancellationTokenSource _disposalCts = new(); private bool _penumbraAvailable = false; private bool _glamourerAvailable = false; @@ -298,6 +299,8 @@ public class IpcManager : MediatorSubscriberBase, IDisposable { base.Dispose(); + _disposalCts.Cancel(); + int totalSleepTime = 0; while (!ActionQueue.IsEmpty && totalSleepTime < 2000) { @@ -393,52 +396,60 @@ public class IpcManager : MediatorSubscriberBase, IDisposable }); } - public void GlamourerApplyAll(string? customization, IntPtr obj) + private async Task PenumbraRedrawAction(IntPtr obj, Action action, CancellationToken token, bool fireAndForget) { - if (!CheckGlamourerApi() || string.IsNullOrEmpty(customization)) return; - ActionQueue.Enqueue(() => - { - var gameObj = _dalamudUtil.CreateGameObject(obj); - if (gameObj is Character c) - { - _penumbraRedrawRequests[obj] = true; + Mediator.Publish(new PenumbraStartRedrawMessage(obj)); + _penumbraRedrawRequests[obj] = !fireAndForget; - Logger.Verbose("Glamourer applying for " + c.Address.ToString("X")); - _glamourerApplyAll!.InvokeAction(customization, c); + ActionQueue.Enqueue(action); + + if (!fireAndForget) + { + var disposeToken = _disposalCts.Token; + var combinedToken = CancellationTokenSource.CreateLinkedTokenSource(disposeToken, token).Token; + while (_penumbraRedrawRequests[obj] && !combinedToken.IsCancellationRequested) + { + Logger.Debug("Waiting for Penumbra Reply"); + await Task.Delay(100).ConfigureAwait(true); } - }); + + Logger.Debug("Received Penumbra reply, waiting for draw"); + + if (!combinedToken.IsCancellationRequested) + _dalamudUtil.WaitWhileCharacterIsDrawing(obj.ToString("X"), obj, 10000, combinedToken); + } + + Mediator.Publish(new PenumbraEndRedrawMessage(obj)); } - public void GlamourerApplyOnlyEquipment(string customization, IntPtr obj) + public async Task GlamourerApplyAll(string? customization, IntPtr obj, CancellationToken token, bool fireAndForget = false) { if (!CheckGlamourerApi() || string.IsNullOrEmpty(customization)) return; - ActionQueue.Enqueue(() => + var gameObj = _dalamudUtil.CreateGameObject(obj); + if (gameObj is Character c) { - var gameObj = _dalamudUtil.CreateGameObject(obj); - if (gameObj is Character c) - { - _penumbraRedrawRequests[obj] = true; - - Logger.Verbose("Glamourer apply only equipment to " + c.Address.ToString("X")); - _glamourerApplyOnlyEquipment!.InvokeAction(customization, c); - } - }); + await PenumbraRedrawAction(obj, () => _glamourerApplyAll!.InvokeAction(customization, c), token, fireAndForget).ConfigureAwait(false); + } } - public void GlamourerApplyOnlyCustomization(string customization, IntPtr obj) + public async Task GlamourerApplyOnlyEquipment(string customization, IntPtr obj, CancellationToken token, bool fireAndForget = false) { if (!CheckGlamourerApi() || string.IsNullOrEmpty(customization)) return; - ActionQueue.Enqueue(() => + var gameObj = _dalamudUtil.CreateGameObject(obj); + if (gameObj is Character c) { - var gameObj = _dalamudUtil.CreateGameObject(obj); - if (gameObj is Character c) - { - _penumbraRedrawRequests[obj] = true; + await PenumbraRedrawAction(obj, () => _glamourerApplyOnlyEquipment!.InvokeAction(customization, c), token, fireAndForget).ConfigureAwait(false); + } + } - Logger.Verbose("Glamourer apply only customization to " + c.Address.ToString("X")); - _glamourerApplyOnlyCustomization!.InvokeAction(customization, c); - } - }); + public async Task GlamourerApplyOnlyCustomization(string customization, IntPtr obj, CancellationToken token, bool fireAndForget = false) + { + if (!CheckGlamourerApi() || string.IsNullOrEmpty(customization)) return; + var gameObj = _dalamudUtil.CreateGameObject(obj); + if (gameObj is Character c) + { + await PenumbraRedrawAction(obj, () => _glamourerApplyOnlyCustomization!.InvokeAction(customization, c), token, fireAndForget).ConfigureAwait(false); + } } public string GlamourerGetCharacterCustomization(IntPtr character) @@ -484,19 +495,14 @@ public class IpcManager : MediatorSubscriberBase, IDisposable return _penumbraResolveModDir!.Invoke().ToLowerInvariant(); } - public void PenumbraRedraw(IntPtr obj) + public async Task PenumbraRedraw(IntPtr obj, CancellationToken token, bool fireAndForget = false) { if (!CheckPenumbraApi()) return; - ActionQueue.Enqueue(() => + var gameObj = _dalamudUtil.CreateGameObject(obj); + if (gameObj is Character c) { - var gameObj = _dalamudUtil.CreateGameObject(obj); - if (gameObj != null) - { - _penumbraRedrawRequests[obj] = true; - Logger.Verbose("Redrawing " + gameObj); - _penumbraRedrawObject!.Invoke(gameObj, RedrawType.Redraw); - } - }); + await PenumbraRedrawAction(obj, () => _penumbraRedrawObject!.Invoke(c, RedrawType.Redraw), token, fireAndForget).ConfigureAwait(false); + } } public void PenumbraRemoveTemporaryCollection(string characterName) @@ -565,13 +571,14 @@ public class IpcManager : MediatorSubscriberBase, IDisposable private void RedrawEvent(IntPtr objectAddress, int objectTableIndex) { bool wasRequested = false; - if (_penumbraRedrawRequests.TryGetValue(objectAddress, out var redrawRequest)) + if (_penumbraRedrawRequests.TryGetValue(objectAddress, out var redrawRequest) && redrawRequest) { - wasRequested = redrawRequest; _penumbraRedrawRequests[objectAddress] = false; } - - Mediator.Publish(new PenumbraRedrawMessage(objectAddress, objectTableIndex, wasRequested)); + else + { + Mediator.Publish(new PenumbraRedrawMessage(objectAddress, objectTableIndex, wasRequested)); + } } private void PenumbraInit() @@ -646,8 +653,11 @@ public class IpcManager : MediatorSubscriberBase, IDisposable private void PenumbraDispose() { + _disposalCts.Cancel(); + _disposalCts.Dispose(); Mediator.Publish(new PenumbraDisposedMessage()); ActionQueue.Clear(); + _disposalCts = new(); } internal bool RequestedRedraw(nint address) diff --git a/MareSynchronos/Managers/TransientResourceManager.cs b/MareSynchronos/Managers/TransientResourceManager.cs index 75b5498..5f254f6 100644 --- a/MareSynchronos/Managers/TransientResourceManager.cs +++ b/MareSynchronos/Managers/TransientResourceManager.cs @@ -4,6 +4,7 @@ using MareSynchronos.Mediator; using MareSynchronos.Models; using MareSynchronos.Utils; using System.Collections.Concurrent; +using System.Linq; namespace MareSynchronos.Managers; diff --git a/MareSynchronos/MareSynchronos.csproj b/MareSynchronos/MareSynchronos.csproj index 35d778d..37f158e 100644 --- a/MareSynchronos/MareSynchronos.csproj +++ b/MareSynchronos/MareSynchronos.csproj @@ -3,7 +3,7 @@ - 0.7.28 + 0.7.29 https://github.com/Penumbra-Sync/client diff --git a/MareSynchronos/Mediator/Messages.cs b/MareSynchronos/Mediator/Messages.cs index 0d9f125..c2de0be 100644 --- a/MareSynchronos/Mediator/Messages.cs +++ b/MareSynchronos/Mediator/Messages.cs @@ -42,5 +42,6 @@ public record NotificationMessage public record CreateCacheForObjectMessage(GameObjectHandler ObjectToCreateFor) : IMessage; public record ClearCacheForObjectMessage(GameObjectHandler ObjectToCreateFor) : IMessage; public record CharacterDataCreatedMessage(CharacterData CharacterData) : IMessage; - +public record PenumbraStartRedrawMessage(IntPtr Address) : IMessage; +public record PenumbraEndRedrawMessage(IntPtr Address) : IMessage; #pragma warning restore MA0048 // File name must match type name \ No newline at end of file diff --git a/MareSynchronos/Models/GameObjectHandler.cs b/MareSynchronos/Models/GameObjectHandler.cs index 606ea71..6135d8d 100644 --- a/MareSynchronos/Models/GameObjectHandler.cs +++ b/MareSynchronos/Models/GameObjectHandler.cs @@ -71,6 +71,20 @@ public class GameObjectHandler : MediatorSubscriberBase Mediator.Subscribe(this, (_) => ZoneSwitchStart()); Mediator.Subscribe(this, (_) => FrameworkUpdate()); }); + Mediator.Subscribe(this, (msg) => + { + if (((PenumbraStartRedrawMessage)msg).Address == Address) + { + Mediator.Unsubscribe(this); + } + }); + Mediator.Subscribe(this, (msg) => + { + if (((PenumbraEndRedrawMessage)msg).Address == Address) + { + Mediator.Subscribe(this, (_) => FrameworkUpdate()); + } + }); } private void FrameworkUpdate() @@ -120,7 +134,7 @@ public class GameObjectHandler : MediatorSubscriberBase public byte? VisorWeaponState { get; set; } private bool _doNotSendUpdate; - public unsafe bool CheckAndUpdateObject() + private unsafe bool CheckAndUpdateObject() { var curPtr = CurrentAddress; if (curPtr != IntPtr.Zero && (IntPtr)((Character*)curPtr)->GameObject.DrawObject != IntPtr.Zero) @@ -226,7 +240,6 @@ public class GameObjectHandler : MediatorSubscriberBase _doNotSendUpdate = true; } HatState = newHatState; - hasChanges = true; } newWeaponOrVisorState &= 0b1101; // ignore drawing weapon @@ -239,7 +252,6 @@ public class GameObjectHandler : MediatorSubscriberBase _doNotSendUpdate = true; } VisorWeaponState = newWeaponOrVisorState; - hasChanges = true; } return hasChanges; diff --git a/MareSynchronos/Utils/DalamudUtil.cs b/MareSynchronos/Utils/DalamudUtil.cs index 02079d6..54432a9 100644 --- a/MareSynchronos/Utils/DalamudUtil.cs +++ b/MareSynchronos/Utils/DalamudUtil.cs @@ -233,6 +233,17 @@ public class DalamudUtil : IDisposable return null; } + public unsafe IntPtr? GetMinionOrMount(IntPtr chara) + { + var minionOrMount = ((Character*)chara)->CompanionObject; + if (minionOrMount != null) + { + return (IntPtr)minionOrMount; + } + + return null; + } + public async Task RunOnFrameworkThread(Func func) { return await _framework.RunOnFrameworkThread(func).ConfigureAwait(false);