From eed44f090d3c2ac36d22986b70c728c17eae1582 Mon Sep 17 00:00:00 2001 From: rootdarkarchon Date: Wed, 15 Feb 2023 14:23:06 +0100 Subject: [PATCH] adjustments to cachedplayer handling --- .../FileCache/PeriodicFileScanner.cs | 24 +++ MareSynchronos/Managers/CachedPlayer.cs | 161 +++++++----------- MareSynchronos/Managers/IpcManager.cs | 82 +++++++-- .../Managers/OnlinePlayerManager.cs | 15 +- MareSynchronos/Managers/PairManager.cs | 4 +- .../Managers/TransientResourceManager.cs | 2 +- MareSynchronos/MareSynchronos.csproj | 2 +- MareSynchronos/Mediator/MareMediator.cs | 4 +- MareSynchronos/Mediator/Messages.cs | 3 +- MareSynchronos/Models/GameObjectHandler.cs | 129 +++++++++----- MareSynchronos/Models/Pair.cs | 10 +- .../Utils/FileReplacementDataComparer.cs | 45 +++++ 12 files changed, 303 insertions(+), 178 deletions(-) create mode 100644 MareSynchronos/Utils/FileReplacementDataComparer.cs diff --git a/MareSynchronos/FileCache/PeriodicFileScanner.cs b/MareSynchronos/FileCache/PeriodicFileScanner.cs index dae43c8..98f80da 100644 --- a/MareSynchronos/FileCache/PeriodicFileScanner.cs +++ b/MareSynchronos/FileCache/PeriodicFileScanner.cs @@ -224,6 +224,12 @@ public class PeriodicFileScanner : MediatorSubscriberBase, IDisposable Thread.Sleep(1); }, ct); + if (!_ipcManager.CheckPenumbraApi()) + { + Logger.Warn("Penumbra not available"); + return; + } + if (ct.IsCancellationRequested) return; } } @@ -238,6 +244,12 @@ public class PeriodicFileScanner : MediatorSubscriberBase, IDisposable Task.WaitAll(dbTasks); + if (!_ipcManager.CheckPenumbraApi()) + { + Logger.Warn("Penumbra not available"); + return; + } + if (entitiesToUpdate.Any() || entitiesToRemove.Any()) { foreach (var entity in entitiesToUpdate) @@ -255,6 +267,12 @@ public class PeriodicFileScanner : MediatorSubscriberBase, IDisposable Logger.Verbose("Scanner validated existing db files"); + if (!_ipcManager.CheckPenumbraApi()) + { + Logger.Warn("Penumbra not available"); + return; + } + if (ct.IsCancellationRequested) return; // scan new files @@ -279,6 +297,12 @@ public class PeriodicFileScanner : MediatorSubscriberBase, IDisposable Thread.Sleep(1); }, ct); + if (!_ipcManager.CheckPenumbraApi()) + { + Logger.Warn("Penumbra not available"); + return; + } + if (ct.IsCancellationRequested) return; } diff --git a/MareSynchronos/Managers/CachedPlayer.cs b/MareSynchronos/Managers/CachedPlayer.cs index 07d1a2f..bb008ff 100644 --- a/MareSynchronos/Managers/CachedPlayer.cs +++ b/MareSynchronos/Managers/CachedPlayer.cs @@ -21,14 +21,9 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable private API.Data.CharacterData _cachedData = new(); private GameObjectHandler? _currentOtherChara; private CancellationTokenSource? _downloadCancellationTokenSource = new(); - private bool _isVisible; - private string _lastGlamourerData = string.Empty; - private string _originalGlamourerData = string.Empty; - private Task? _penumbraRedrawEventTask; - public CachedPlayer(OnlineUserIdentDto onlineUser, IpcManager ipcManager, ApiController apiController, DalamudUtil dalamudUtil, FileCacheManager fileDbManager, MareMediator mediator) : base(mediator) { OnlineUser = onlineUser; @@ -38,26 +33,10 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable _fileDbManager = fileDbManager; } - public bool IsVisible - { - get => _isVisible; - set - { - WasVisible = _isVisible; - _isVisible = value; - } - } - public OnlineUserIdentDto OnlineUser { get; set; } - public IntPtr PlayerCharacter { get; set; } = IntPtr.Zero; + public IntPtr PlayerCharacter => _currentOtherChara?.CurrentAddress ?? IntPtr.Zero; public string? PlayerName { get; private set; } - public string PlayerNameHash => OnlineUser.Ident; - - public bool RequestedPenumbraRedraw { get; set; } - - public bool WasVisible { get; private set; } - public void ApplyCharacterData(API.Data.CharacterData characterData, OptionalPluginWarning warning, bool forced = false) { Logger.Debug("Received data for " + this); @@ -67,13 +46,11 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable if (!_ipcManager.CheckPenumbraApi()) { - Mediator.Publish(new NotificationMessage("Penumbra inactive", "Your Penumbra installation is not active or out of date. Update Penumbra and/or the Enable Mods setting in Penumbra to continue to use Mare.", NotificationType.Error)); return; } if (!_ipcManager.CheckGlamourerApi()) { - Mediator.Publish(new NotificationMessage("Glamourer inactive", "Your Glamourer installation is not active or out of date. Update Glamourer to continue to use Mare.", NotificationType.Error)); return; } @@ -90,14 +67,16 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable bool hasNewButNotOldFileReplacements = newFileReplacements != null && existingFileReplacements == null; bool hasOldButNotNewFileReplacements = existingFileReplacements != null && newFileReplacements == null; + bool hasNewButNotOldGlamourerData = newGlamourerData != null && existingGlamourerData == null; bool hasOldButNotNewGlamourerData = existingGlamourerData != null && newGlamourerData == null; + bool hasNewAndOldFileReplacements = newFileReplacements != null && existingFileReplacements != null; bool hasNewAndOldGlamourerData = newGlamourerData != null && existingGlamourerData != null; if (hasNewButNotOldFileReplacements || hasOldButNotNewFileReplacements || hasNewButNotOldGlamourerData || hasOldButNotNewGlamourerData) { - Logger.Debug("Updating " + objectKind); + Logger.Debug($"Updating {objectKind} (Some new data arrived: {hasNewButNotOldFileReplacements} {hasOldButNotNewFileReplacements} {hasNewButNotOldGlamourerData} {hasOldButNotNewGlamourerData})"); updateModdedPaths = true; charaDataToUpdate.Add(objectKind); continue; @@ -105,10 +84,10 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable if (hasNewAndOldFileReplacements) { - bool listsAreEqual = Enumerable.SequenceEqual(_cachedData.FileReplacements[objectKind], characterData.FileReplacements[objectKind]); + bool listsAreEqual = Enumerable.SequenceEqual(_cachedData.FileReplacements[objectKind], characterData.FileReplacements[objectKind], FileReplacementDataComparer.Instance); if (!listsAreEqual) { - Logger.Debug("Updating " + objectKind); + Logger.Debug($"Updating {objectKind} (FileReplacements not equal)"); updateModdedPaths = true; charaDataToUpdate.Add(objectKind); continue; @@ -120,7 +99,7 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable bool glamourerDataDifferent = !string.Equals(_cachedData.GlamourerData[objectKind], characterData.GlamourerData[objectKind], StringComparison.Ordinal); if (glamourerDataDifferent) { - Logger.Debug("Updating " + objectKind); + Logger.Debug($"Updating {objectKind} (Diff glamourer data)"); charaDataToUpdate.Add(objectKind); continue; } @@ -131,7 +110,7 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable bool manipDataDifferent = !string.Equals(_cachedData.ManipulationData, characterData.ManipulationData, StringComparison.Ordinal); if (manipDataDifferent) { - Logger.Debug("Updating " + objectKind); + Logger.Debug($"Updating {objectKind} (Diff manip data)"); charaDataToUpdate.Add(objectKind); continue; } @@ -139,7 +118,7 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable bool heelsOffsetDifferent = _cachedData.HeelsOffset != characterData.HeelsOffset; if (heelsOffsetDifferent) { - Logger.Debug("Updating " + objectKind); + Logger.Debug($"Updating {objectKind} (Diff heels data)"); charaDataToUpdate.Add(objectKind); continue; } @@ -147,7 +126,7 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable bool customizeDataDifferent = !string.Equals(_cachedData.CustomizePlusData, characterData.CustomizePlusData, StringComparison.Ordinal); if (customizeDataDifferent) { - Logger.Debug("Updating " + objectKind); + Logger.Debug($"Updating {objectKind} (Diff customize data)"); charaDataToUpdate.Add(objectKind); continue; } @@ -155,7 +134,7 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable bool palettePlusDataDifferent = !string.Equals(_cachedData.PalettePlusData, characterData.PalettePlusData, StringComparison.Ordinal); if (palettePlusDataDifferent) { - Logger.Debug("Updating " + objectKind); + Logger.Debug($"Updating {objectKind} (Diff palette data)"); charaDataToUpdate.Add(objectKind); continue; } @@ -203,33 +182,27 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable public bool CheckExistence() { - var curPlayerCharacter = _dalamudUtil.GetPlayerCharacterFromObjectTableByName(PlayerName!)?.Address ?? IntPtr.Zero; - if (PlayerCharacter == IntPtr.Zero || PlayerCharacter != curPlayerCharacter) + if (PlayerName == null || _currentOtherChara == null + || !string.Equals(PlayerName, _currentOtherChara.Name, StringComparison.Ordinal) + || _currentOtherChara.Address == IntPtr.Zero) { return false; } - if (_currentOtherChara?.CheckAndUpdateObject() ?? false) - { - OnPlayerChanged(); - } - - IsVisible = true; - return true; } public override void Dispose() { - if (string.IsNullOrEmpty(PlayerName)) return; + if (string.IsNullOrEmpty(PlayerName)) return; // already disposed base.Dispose(); Logger.Debug("Disposing " + PlayerName + " (" + OnlineUser + ")"); try { + Logger.Verbose($"Restoring state for {PlayerName} ({OnlineUser})"); _currentOtherChara?.Dispose(); - Logger.Verbose("Restoring state for " + PlayerName); _ipcManager.PenumbraRemoveTemporaryCollection(PlayerName); _downloadCancellationTokenSource?.Cancel(); _downloadCancellationTokenSource?.Dispose(); @@ -241,6 +214,7 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable RevertCustomizationData(item.Key); } } + _currentOtherChara = null; } catch (Exception ex) { @@ -248,26 +222,30 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable } finally { - Mediator.UnsubscribeAll(this); _cachedData = new(); - var tempPlayerName = PlayerName; - PlayerName = string.Empty; - PlayerCharacter = IntPtr.Zero; - IsVisible = false; - Logger.Debug("Disposing " + tempPlayerName + " complete"); + Logger.Debug("Disposing " + PlayerName + " complete"); + PlayerName = null; } } - public void Initialize(IntPtr character, string name) + public void Initialize(string name) { - IsVisible = true; PlayerName = name; - PlayerCharacter = character; - Logger.Debug("Initializing Player " + this); - - Mediator.Subscribe(this, (msg) => IpcManagerOnPenumbraRedrawEvent(((PenumbraRedrawMessage)msg))); - _originalGlamourerData = _ipcManager.GlamourerGetCharacterCustomization(PlayerCharacter); _currentOtherChara = new GameObjectHandler(Mediator, ObjectKind.Player, () => _dalamudUtil.GetPlayerCharacterFromObjectTableByName(PlayerName)?.Address ?? IntPtr.Zero, false); + + _originalGlamourerData = _ipcManager.GlamourerGetCharacterCustomization(PlayerCharacter); + _lastGlamourerData = _originalGlamourerData; + Mediator.Subscribe(this, (msg) => IpcManagerOnPenumbraRedrawEvent(((PenumbraRedrawMessage)msg))); + Mediator.Subscribe(this, (msg) => + { + var actualMsg = (CharacterChangedMessage)msg; + if (actualMsg.GameObjectHandler == _currentOtherChara && !_ipcManager.RequestedRedraw(_currentOtherChara.Address)) + { + _lastGlamourerData = _ipcManager.GlamourerGetCharacterCustomization(PlayerCharacter); + } + }); + + Logger.Debug("Initializing Player " + this); } public override string ToString() @@ -294,9 +272,7 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable _ipcManager.HeelsSetOffsetForPlayer(_cachedData.HeelsOffset, PlayerCharacter); _ipcManager.CustomizePlusSetBodyScale(PlayerCharacter, _cachedData.CustomizePlusData); _ipcManager.PalettePlusSetPalette(PlayerCharacter, _cachedData.PalettePlusData); - RequestedPenumbraRedraw = true; - Logger.Debug( - $"Request Redraw for {PlayerName}"); + Logger.Debug($"Request Redraw for {PlayerName}"); if (_ipcManager.CheckGlamourerApi() && !string.IsNullOrEmpty(glamourerData)) { _ipcManager.GlamourerApplyAll(glamourerData, PlayerCharacter); @@ -312,7 +288,7 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable var minionOrMount = ((Character*)PlayerCharacter)->CompanionObject; if (minionOrMount != null) { - Logger.Debug($"Request Redraw for Minion/Mount"); + Logger.Debug($"Request Redraw for {PlayerName} Minion/Mount"); _dalamudUtil.WaitWhileCharacterIsDrawing(PlayerName! + " minion or mount", (IntPtr)minionOrMount, 30000, ct); ct.ThrowIfCancellationRequested(); if (_ipcManager.CheckGlamourerApi() && !string.IsNullOrEmpty(glamourerData)) @@ -337,7 +313,7 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable var totalWait = 0; var newPet = IntPtr.Zero; const int maxWait = 3000; - Logger.Debug($"Request Redraw for Pet, waiting {maxWait}ms"); + Logger.Debug($"Request Redraw for {PlayerName} Pet"); do { @@ -364,7 +340,7 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable var companion = _dalamudUtil.GetCompanion(PlayerCharacter); if (companion != IntPtr.Zero) { - Logger.Debug("Request Redraw for Companion"); + Logger.Debug($"Request Redraw for {PlayerName} Companion"); _dalamudUtil.WaitWhileCharacterIsDrawing(PlayerName! + " companion", companion, 30000, ct); ct.ThrowIfCancellationRequested(); if (_ipcManager.CheckGlamourerApi() && !string.IsNullOrEmpty(glamourerData)) @@ -403,7 +379,6 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable { Dictionary moddedPaths; int attempts = 0; - //Logger.Verbose(JsonConvert.SerializeObject(_cachedData, Formatting.Indented)); while ((toDownloadReplacements = TryCalculateModdedDictionary(out moddedPaths)).Count > 0 && attempts++ <= 10) { downloadId = _apiController.GetDownloadId(); @@ -446,44 +421,31 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable }); } + private CancellationTokenSource _redrawCts = new CancellationTokenSource(); + private void IpcManagerOnPenumbraRedrawEvent(PenumbraRedrawMessage msg) { var player = _dalamudUtil.GetCharacterFromObjectTableByIndex(msg.ObjTblIdx); if (player == null || !string.Equals(player.Name.ToString(), PlayerName, StringComparison.OrdinalIgnoreCase)) return; - if (!_penumbraRedrawEventTask?.IsCompleted ?? false) return; + _redrawCts.Cancel(); + _redrawCts.Dispose(); + _redrawCts = new(); + _redrawCts.CancelAfter(TimeSpan.FromSeconds(30)); + var token = _redrawCts.Token; - _penumbraRedrawEventTask = Task.Run(() => + Task.Run(() => { - PlayerCharacter = msg.Address; - var cts = new CancellationTokenSource(); - cts.CancelAfter(TimeSpan.FromSeconds(10)); - _dalamudUtil.WaitWhileCharacterIsDrawing(PlayerName!, PlayerCharacter, 30000, cts.Token); - cts.Dispose(); - cts = new CancellationTokenSource(); - cts.CancelAfter(TimeSpan.FromSeconds(5)); - if (RequestedPenumbraRedraw == false) + _dalamudUtil.WaitWhileCharacterIsDrawing(PlayerName!, PlayerCharacter, ct: token); + if (!msg.WasRequested) { Logger.Debug("Unauthorized character change detected"); - ApplyCustomizationData(ObjectKind.Player, cts.Token); + ApplyCustomizationData(ObjectKind.Player, token); } else { - RequestedPenumbraRedraw = false; - Logger.Debug( - $"Penumbra Redraw done for {PlayerName}"); + Logger.Debug($"Penumbra Redraw done for {PlayerName}"); } - cts.Dispose(); - }); - } - - private void OnPlayerChanged() - { - Logger.Debug($"Player {PlayerName} changed, PenumbraRedraw is {RequestedPenumbraRedraw}"); - if (!RequestedPenumbraRedraw && PlayerCharacter != IntPtr.Zero) - { - Logger.Debug($"Saving new Glamourer data"); - _lastGlamourerData = _ipcManager.GlamourerGetCharacterCustomization(PlayerCharacter); - } + }, token); } private unsafe void RevertCustomizationData(ObjectKind objectKind) @@ -492,18 +454,15 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable if (objectKind == ObjectKind.Player) { - if (_ipcManager.CheckGlamourerApi()) - { - _ipcManager.GlamourerApplyOnlyCustomization(_originalGlamourerData, PlayerCharacter); - _ipcManager.GlamourerApplyOnlyEquipment(_lastGlamourerData, PlayerCharacter); - _ipcManager.HeelsRestoreOffsetForPlayer(PlayerCharacter); - _ipcManager.CustomizePlusRevert(PlayerCharacter); - _ipcManager.PalettePlusRemovePalette(PlayerCharacter); - } - else - { - _ipcManager.PenumbraRedraw(PlayerCharacter); - } + Logger.Debug($"Restoring Customization for {PlayerCharacter}: {_originalGlamourerData}"); + _ipcManager.GlamourerApplyOnlyCustomization(_originalGlamourerData, PlayerCharacter); + Logger.Debug($"Restoring Equipment for {PlayerCharacter}: {_lastGlamourerData}"); + _ipcManager.GlamourerApplyOnlyEquipment(_lastGlamourerData, PlayerCharacter); + Logger.Debug("Restoring Heels"); + _ipcManager.HeelsRestoreOffsetForPlayer(PlayerCharacter); + Logger.Debug("Restoring C+"); + _ipcManager.CustomizePlusRevert(PlayerCharacter); + _ipcManager.PalettePlusRemovePalette(PlayerCharacter); } else if (objectKind == ObjectKind.MinionOrMount) { diff --git a/MareSynchronos/Managers/IpcManager.cs b/MareSynchronos/Managers/IpcManager.cs index ae8ec69..cb64c18 100644 --- a/MareSynchronos/Managers/IpcManager.cs +++ b/MareSynchronos/Managers/IpcManager.cs @@ -8,6 +8,7 @@ using System.Text; using Penumbra.Api.Enums; using Penumbra.Api.Helpers; using MareSynchronos.Mediator; +using Dalamud.Interface.Internal.Notifications; namespace MareSynchronos.Managers; @@ -64,6 +65,8 @@ public class IpcManager : MediatorSubscriberBase, IDisposable private readonly ConcurrentQueue _normalQueue = new(); private readonly ConcurrentQueue _gposeActionQueue = new(); + private ConcurrentDictionary _penumbraRedrawRequests = new(); + private bool _penumbraAvailable = false; private bool _glamourerAvailable = false; private bool _customizePlusAvailable = false; @@ -139,17 +142,17 @@ public class IpcManager : MediatorSubscriberBase, IDisposable Mediator.Subscribe(this, (_) => HandleActionQueue()); Mediator.Subscribe(this, (_) => HandleGposeActionQueue()); Mediator.Subscribe(this, (_) => ClearActionQueue()); - Mediator.Subscribe(this, (_) => CheckPenumbraModPath()); + Mediator.Subscribe(this, (_) => PeriodicApiStateCheck()); } - private void CheckPenumbraModPath() + private void PeriodicApiStateCheck() { - PenumbraModDirectory = GetPenumbraModDirectory(); _glamourerAvailable = CheckGlamourerApiInternal(); _penumbraAvailable = CheckPenumbraApiInternal(); _heelsAvailable = CheckHeelsApiInternal(); _customizePlusAvailable = CheckCustomizePlusApiInternal(); _palettePlusAvailable = CheckPalettePlusApiInternal(); + PenumbraModDirectory = GetPenumbraModDirectory(); } private void HandleGposeActionQueue() @@ -198,29 +201,54 @@ public class IpcManager : MediatorSubscriberBase, IDisposable public bool CheckGlamourerApi() => _glamourerAvailable; + private bool _shownGlamourerUnavailable = false; + public bool CheckGlamourerApiInternal() { + bool apiAvailable = false; try { - return _glamourerApiVersion.InvokeFunc() >= 0; + apiAvailable = _glamourerApiVersion.InvokeFunc() >= 0; + _shownGlamourerUnavailable = _shownGlamourerUnavailable && !apiAvailable; + return apiAvailable; } catch { - return false; + return apiAvailable; + } + finally + { + if (!apiAvailable && !_shownGlamourerUnavailable) + { + _shownGlamourerUnavailable = true; + Mediator.Publish(new NotificationMessage("Glamourer inactive", "Your Glamourer installation is not active or out of date. Update Glamourer to continue to use Mare.", NotificationType.Error)); + } } } public bool CheckPenumbraApi() => _penumbraAvailable; + private bool _shownPenumbraUnavailable = false; public bool CheckPenumbraApiInternal() { + bool apiAvailable = false; try { - return _penumbraApiVersion.Invoke() is { Item1: 4, Item2: >= 19 } && _penumbraEnabled.Invoke(); + apiAvailable = _penumbraApiVersion.Invoke() is { Item1: 4, Item2: >= 19 } && _penumbraEnabled.Invoke(); + _shownPenumbraUnavailable = _shownPenumbraUnavailable && !apiAvailable; + return apiAvailable; } catch { - return false; + return apiAvailable; + } + finally + { + if (!apiAvailable && !_shownPenumbraUnavailable) + { + _shownPenumbraUnavailable = true; + Mediator.Publish(new NotificationMessage("Penumbra inactive", "Your Penumbra installation is not active or out of date. Update Penumbra and/or the Enable Mods setting in Penumbra to continue to use Mare.", NotificationType.Error)); + } } } @@ -373,34 +401,40 @@ public class IpcManager : MediatorSubscriberBase, IDisposable var gameObj = _dalamudUtil.CreateGameObject(obj); if (gameObj is Character c) { + _penumbraRedrawRequests[obj] = true; + Logger.Verbose("Glamourer applying for " + c.Address.ToString("X")); _glamourerApplyAll!.InvokeAction(customization, c); } }); } - public void GlamourerApplyOnlyEquipment(string customization, IntPtr character) + public void GlamourerApplyOnlyEquipment(string customization, IntPtr obj) { if (!CheckGlamourerApi() || string.IsNullOrEmpty(customization)) return; ActionQueue.Enqueue(() => { - var gameObj = _dalamudUtil.CreateGameObject(character); + 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); } }); } - public void GlamourerApplyOnlyCustomization(string customization, IntPtr character) + public void GlamourerApplyOnlyCustomization(string customization, IntPtr obj) { if (!CheckGlamourerApi() || string.IsNullOrEmpty(customization)) return; ActionQueue.Enqueue(() => { - var gameObj = _dalamudUtil.CreateGameObject(character); + var gameObj = _dalamudUtil.CreateGameObject(obj); if (gameObj is Character c) { + _penumbraRedrawRequests[obj] = true; + Logger.Verbose("Glamourer apply only customization to " + c.Address.ToString("X")); _glamourerApplyOnlyCustomization!.InvokeAction(customization, c); } @@ -458,18 +492,13 @@ public class IpcManager : MediatorSubscriberBase, IDisposable var gameObj = _dalamudUtil.CreateGameObject(obj); if (gameObj != null) { + _penumbraRedrawRequests[obj] = true; Logger.Verbose("Redrawing " + gameObj); _penumbraRedrawObject!.Invoke(gameObj, RedrawType.Redraw); } }); } - public void PenumbraRedraw(string actorName) - { - if (!CheckPenumbraApi()) return; - ActionQueue.Enqueue(() => _penumbraRedraw!.Invoke(actorName, RedrawType.Redraw)); - } - public void PenumbraRemoveTemporaryCollection(string characterName) { if (!CheckPenumbraApi()) return; @@ -535,7 +564,14 @@ public class IpcManager : MediatorSubscriberBase, IDisposable private void RedrawEvent(IntPtr objectAddress, int objectTableIndex) { - Mediator.Publish(new PenumbraRedrawMessage(objectAddress, objectTableIndex)); + bool wasRequested = false; + if (_penumbraRedrawRequests.TryGetValue(objectAddress, out var redrawRequest)) + { + wasRequested = redrawRequest; + _penumbraRedrawRequests[objectAddress] = false; + } + + Mediator.Publish(new PenumbraRedrawMessage(objectAddress, objectTableIndex, wasRequested)); } private void PenumbraInit() @@ -613,4 +649,14 @@ public class IpcManager : MediatorSubscriberBase, IDisposable Mediator.Publish(new PenumbraDisposedMessage()); ActionQueue.Clear(); } + + internal bool RequestedRedraw(nint address) + { + if (_penumbraRedrawRequests.TryGetValue(address, out var requested)) + { + return requested; + } + + return false; + } } diff --git a/MareSynchronos/Managers/OnlinePlayerManager.cs b/MareSynchronos/Managers/OnlinePlayerManager.cs index 728cc50..079265e 100644 --- a/MareSynchronos/Managers/OnlinePlayerManager.cs +++ b/MareSynchronos/Managers/OnlinePlayerManager.cs @@ -1,5 +1,4 @@ using MareSynchronos.API.Data; -using MareSynchronos.API.Dto.User; using MareSynchronos.FileCache; using MareSynchronos.Mediator; using MareSynchronos.Utils; @@ -70,22 +69,22 @@ public class OnlinePlayerManager : MediatorSubscriberBase, IDisposable if (!_dalamudUtil.IsPlayerPresent || !_apiController.IsConnected) return; var playerCharacters = _dalamudUtil.GetPlayerCharacters(); - var onlinePairs = _pairManager.OnlineUserPairs; + var newVisiblePlayers = new List(); foreach (var pChar in playerCharacters) { var pair = _pairManager.FindPair(pChar); if (pair == null) continue; - pair.InitializePair(pChar.Address, pChar.Name.ToString()); + if (pair.InitializePair(pChar.Name.ToString())) + { + newVisiblePlayers.Add(pair.UserData ?? pair.GroupPair.First().Value.User); + } } - var newlyVisiblePlayers = onlinePairs.Select(v => v.CachedPlayer) - .Where(p => p != null && p.PlayerCharacter != IntPtr.Zero && p.IsVisible && !p.WasVisible).Select(p => (UserDto)p!.OnlineUser) - .ToList(); - if (newlyVisiblePlayers.Any()) + if (newVisiblePlayers.Any()) { Logger.Verbose("Has new visible players, pushing character data"); - PushCharacterData(newlyVisiblePlayers.Select(c => c.User).ToList()); + PushCharacterData(newVisiblePlayers); } } diff --git a/MareSynchronos/Managers/PairManager.cs b/MareSynchronos/Managers/PairManager.cs index 95ff577..a626a22 100644 --- a/MareSynchronos/Managers/PairManager.cs +++ b/MareSynchronos/Managers/PairManager.cs @@ -63,7 +63,7 @@ public class PairManager : MediatorSubscriberBase, IDisposable } public List OnlineUserPairs => _allClientPairs.Where(p => !string.IsNullOrEmpty(p.Value.PlayerNameHash)).Select(p => p.Value).ToList(); - public List VisibleUsers => _allClientPairs.Where(p => p.Value.CachedPlayer != null && p.Value.CachedPlayer.IsVisible).Select(p => p.Key).ToList(); + public List VisibleUsers => _allClientPairs.Where(p => p.Value.CachedPlayer?.PlayerName != null).Select(p => p.Key).ToList(); public Pair? LastAddedUser { get; internal set; } @@ -273,7 +273,7 @@ public class PairManager : MediatorSubscriberBase, IDisposable private void DalamudUtilOnDelayedFrameworkUpdate() { - foreach (var player in _allClientPairs.Select(p => p.Value).Where(p => p.CachedPlayer != null && p.CachedPlayer.IsVisible).ToList()) + foreach (var player in _allClientPairs.Select(p => p.Value).Where(p => p.CachedPlayer?.PlayerName != null).ToList()) { if (!player.CachedPlayer!.CheckExistence()) { diff --git a/MareSynchronos/Managers/TransientResourceManager.cs b/MareSynchronos/Managers/TransientResourceManager.cs index ff70ed6..75b5498 100644 --- a/MareSynchronos/Managers/TransientResourceManager.cs +++ b/MareSynchronos/Managers/TransientResourceManager.cs @@ -134,7 +134,7 @@ public class TransientResourceManager : MediatorSubscriberBase, IDisposable } if (!PlayerRelatedPointers.Contains(gameObject)) { - Logger.Debug("Got resource " + gamePath + " for ptr " + gameObject.ToString("X")); + //Logger.Debug("Got resource " + gamePath + " for ptr " + gameObject.ToString("X")); return; } diff --git a/MareSynchronos/MareSynchronos.csproj b/MareSynchronos/MareSynchronos.csproj index fadeb0e..ba834f5 100644 --- a/MareSynchronos/MareSynchronos.csproj +++ b/MareSynchronos/MareSynchronos.csproj @@ -3,7 +3,7 @@ - 0.7.26 + 0.7.27 https://github.com/Penumbra-Sync/client diff --git a/MareSynchronos/Mediator/MareMediator.cs b/MareSynchronos/Mediator/MareMediator.cs index 900fecc..fedd5f9 100644 --- a/MareSynchronos/Mediator/MareMediator.cs +++ b/MareSynchronos/Mediator/MareMediator.cs @@ -48,7 +48,9 @@ public class MareMediator : IDisposable { foreach (var kvp in _subscriberDict.ToList()) { - kvp.Value.RemoveWhere(p => p.Subscriber == subscriber); + var unSubbed = kvp.Value.RemoveWhere(p => p.Subscriber == subscriber); + if (unSubbed > 0) + Logger.Verbose(subscriber + " unsubscribed from " + kvp.Key.Name); } } diff --git a/MareSynchronos/Mediator/Messages.cs b/MareSynchronos/Mediator/Messages.cs index 676a9fe..8c7ddbc 100644 --- a/MareSynchronos/Mediator/Messages.cs +++ b/MareSynchronos/Mediator/Messages.cs @@ -22,12 +22,13 @@ public record DisconnectedMessage : IMessage; public record PenumbraModSettingChangedMessage : IMessage; public record PenumbraInitializedMessage : IMessage; public record PenumbraDisposedMessage : IMessage; -public record PenumbraRedrawMessage(IntPtr Address, int ObjTblIdx) : IMessage; +public record PenumbraRedrawMessage(IntPtr Address, int ObjTblIdx, bool WasRequested) : IMessage; public record HeelsOffsetMessage(float Offset) : IMessage; public record PenumbraResourceLoadMessage(IntPtr GameObject, string GamePath, string FilePath) : IMessage; public record CustomizePlusMessage(string? Data) : IMessage; public record PalettePlusMessage(string? Data) : IMessage; public record PlayerChangedMessage(API.Data.CharacterData Data) : IMessage; +public record CharacterChangedMessage(GameObjectHandler GameObjectHandler) : IMessage; public record TransientResourceChangedMessage(IntPtr Address) : IMessage; public record PlayerRelatedObjectPointerUpdateMessage(IntPtr[] RelatedObjects) : IMessage; diff --git a/MareSynchronos/Models/GameObjectHandler.cs b/MareSynchronos/Models/GameObjectHandler.cs index 98da88a..8b2daa3 100644 --- a/MareSynchronos/Models/GameObjectHandler.cs +++ b/MareSynchronos/Models/GameObjectHandler.cs @@ -4,8 +4,6 @@ using MareSynchronos.Utils; using Penumbra.String; using MareSynchronos.API.Data.Enum; using MareSynchronos.Mediator; -using System; -using Microsoft.Extensions.Logging.Abstractions; namespace MareSynchronos.Models; @@ -17,11 +15,12 @@ public class GameObjectHandler : MediatorSubscriberBase public unsafe Character* Character => (Character*)Address; - private string _name; - + public string Name { get; private set; } public ObjectKind ObjectKind { get; } public IntPtr Address { get; set; } public IntPtr DrawObjectAddress { get; set; } + private Task? _delayedZoningTask; + private CancellationTokenSource _zoningCts = new(); public IntPtr CurrentAddress { @@ -36,31 +35,71 @@ public class GameObjectHandler : MediatorSubscriberBase } } - public GameObjectHandler(MareMediator mediator, ObjectKind objectKind, Func getAddress, bool watchedPlayer = true) : base(mediator) + public GameObjectHandler(MareMediator mediator, ObjectKind objectKind, Func getAddress, bool watchedObject = true) : base(mediator) { _mediator = mediator; ObjectKind = objectKind; - this._getAddress = getAddress; - _sendUpdates = watchedPlayer; - _name = string.Empty; + _getAddress = getAddress; + _sendUpdates = watchedObject; + Name = string.Empty; - if (watchedPlayer) + if (watchedObject) { Mediator.Subscribe(this, (msg) => { - var actualMsg = (TransientResourceChangedMessage)msg; - if (actualMsg.Address != Address) return; - Mediator.Publish(new CreateCacheForObjectMessage(this)); + if (_delayedZoningTask?.IsCompleted ?? true) + { + var actualMsg = (TransientResourceChangedMessage)msg; + if (actualMsg.Address != Address) return; + Mediator.Publish(new CreateCacheForObjectMessage(this)); + } }); - - Mediator.Subscribe(this, (_) => CheckAndUpdateObject()); } + + Mediator.Subscribe(this, (_) => + { + if (_delayedZoningTask?.IsCompleted ?? true) + { + CheckAndUpdateObject(); + } + }); + + Mediator.Subscribe(this, (_) => + { + if (!watchedObject) return; + + _clearCts?.Cancel(); + _clearCts?.Dispose(); + _clearCts = null; + _zoningCts.CancelAfter(2500); + }); + + Mediator.Subscribe(this, (_) => + { + if (!watchedObject) return; + + _zoningCts = new(); + Logger.Debug("Starting Delay After Zoning for " + ObjectKind + " " + Name); + _delayedZoningTask = Task.Run(async () => + { + try + { + await Task.Delay(TimeSpan.FromSeconds(120), _zoningCts.Token).ConfigureAwait(false); + } + catch { } + finally + { + Logger.Debug("Delay complete for " + ObjectKind); + _zoningCts.Dispose(); + } + }); + }); } public byte[] EquipSlotData { get; set; } = new byte[40]; public byte[] CustomizeData { get; set; } = new byte[26]; - private Task? _petClearTask; - private CancellationTokenSource? _petCts = new(); + private Task? _clearTask; + private CancellationTokenSource? _clearCts = new(); public byte? HatState { get; set; } public byte? VisorWeaponState { get; set; } private bool _doNotSendUpdate; @@ -70,22 +109,23 @@ public class GameObjectHandler : MediatorSubscriberBase var curPtr = CurrentAddress; if (curPtr != IntPtr.Zero && (IntPtr)((Character*)curPtr)->GameObject.DrawObject != IntPtr.Zero) { - if (ObjectKind == ObjectKind.Pet && _petCts != null) + if (_clearCts != null) { - Logger.Debug("Cancelling PetClearTask for " + ObjectKind); - _petCts?.Cancel(); - _petCts = null; + Logger.Debug("Cancelling Clear Task for " + ObjectKind + " " + Name); + _clearCts?.Cancel(); + _clearCts = null; } var chara = (Character*)curPtr; bool addr = Address == IntPtr.Zero || Address != curPtr; - bool equip = CompareAndUpdateByteData(chara->EquipSlotData, chara->CustomizeData); + bool equip = CompareAndUpdateEquipByteData(chara->EquipSlotData); + var customize = CompareAndUpdateCustomizeData(chara->CustomizeData); bool drawObj = (IntPtr)chara->GameObject.DrawObject != DrawObjectAddress; var name = new ByteString(chara->GameObject.Name).ToString(); - bool nameChange = (!string.Equals(name, _name, StringComparison.Ordinal)); - if (addr || equip || drawObj || nameChange) + bool nameChange = (!string.Equals(name, Name, StringComparison.Ordinal)); + if (addr || equip || customize || drawObj || nameChange) { - _name = name; - Logger.Verbose($"{ObjectKind} changed: {_name}, now: {curPtr:X}, {(IntPtr)chara->GameObject.DrawObject:X}"); + Name = name; + Logger.Verbose($"{ObjectKind} changed: {Name}, now: {curPtr:X}, {(IntPtr)chara->GameObject.DrawObject:X}"); Address = curPtr; DrawObjectAddress = (IntPtr)chara->GameObject.DrawObject; @@ -95,6 +135,11 @@ public class GameObjectHandler : MediatorSubscriberBase Mediator.Publish(new CreateCacheForObjectMessage(this)); } + if (equip) + { + Mediator.Publish(new CharacterChangedMessage(this)); + } + return true; } } @@ -102,37 +147,32 @@ public class GameObjectHandler : MediatorSubscriberBase { Address = IntPtr.Zero; DrawObjectAddress = IntPtr.Zero; - Logger.Verbose(ObjectKind + " Changed: " + _name + ", now: " + Address + ", " + DrawObjectAddress); - if (_sendUpdates && ObjectKind == ObjectKind.Pet) + Logger.Verbose(ObjectKind + " Changed, DrawObj Zero: " + Name + ", now: " + Address + ", " + DrawObjectAddress); + if (_sendUpdates && ObjectKind != ObjectKind.Player) { - _petCts?.Cancel(); - _petCts?.Dispose(); - _petCts = new(); - var token = _petCts.Token; - _petClearTask = Task.Run(() => PetClearTask(token), token); - } - else if (_sendUpdates) - { - Logger.Debug("Sending ClearCachedForObjectMessage for " + ObjectKind); - Mediator.Publish(new ClearCacheForObjectMessage(this)); + _clearCts?.Cancel(); + _clearCts?.Dispose(); + _clearCts = new(); + var token = _clearCts.Token; + _clearTask = Task.Run(() => ClearTask(token), token); } } return false; } - private async Task PetClearTask(CancellationToken token) + private async Task ClearTask(CancellationToken token) { - Logger.Debug("Running PetClearTask for " + ObjectKind); + Logger.Debug("Running Clear Task for " + ObjectKind); await Task.Delay(TimeSpan.FromSeconds(1), token).ConfigureAwait(false); Logger.Debug("Sending ClearCachedForObjectMessage for " + ObjectKind); Mediator.Publish(new ClearCacheForObjectMessage(this)); + _clearCts = null; } - private unsafe bool CompareAndUpdateByteData(byte* equipSlotData, byte* customizeData) + private unsafe bool CompareAndUpdateEquipByteData(byte* equipSlotData) { bool hasChanges = false; - _doNotSendUpdate = false; for (int i = 0; i < EquipSlotData.Length; i++) { var data = Marshal.ReadByte((IntPtr)equipSlotData, i); @@ -143,6 +183,13 @@ public class GameObjectHandler : MediatorSubscriberBase } } + return hasChanges; + } + private unsafe bool CompareAndUpdateCustomizeData(byte* customizeData) + { + bool hasChanges = false; + _doNotSendUpdate = false; + for (int i = 0; i < CustomizeData.Length; i++) { var data = Marshal.ReadByte((IntPtr)customizeData, i); diff --git a/MareSynchronos/Models/Pair.cs b/MareSynchronos/Models/Pair.cs index 0ba2fd6..a1dc886 100644 --- a/MareSynchronos/Models/Pair.cs +++ b/MareSynchronos/Models/Pair.cs @@ -30,7 +30,7 @@ public class Pair public string? PlayerName => CachedPlayer?.PlayerName ?? string.Empty; public UserData UserData => UserPair?.User ?? GroupPair.First().Value.User; public bool IsOnline => CachedPlayer != null; - public bool IsVisible => CachedPlayer != null && CachedPlayer.IsVisible; + public bool IsVisible => CachedPlayer?.PlayerName != null; public bool IsPaused => UserPair != null && UserPair.OtherPermissions.IsPaired() ? (UserPair.OtherPermissions.IsPaused() || UserPair.OwnPermissions.IsPaused()) : GroupPair.All(p => p.Key.GroupUserPermissions.IsPaused() || p.Value.GroupUserPermissions.IsPaused()); @@ -49,9 +49,9 @@ public class Pair return UserPair != null || GroupPair.Any(); } - public void InitializePair(nint address, string name) + public bool InitializePair(string name) { - if (!PlayerName.IsNullOrEmpty()) return; + if (!PlayerName.IsNullOrEmpty()) return false; if (CachedPlayer == null) throw new InvalidOperationException("CachedPlayer not initialized"); _pluginWarnings ??= new() @@ -61,9 +61,11 @@ public class Pair ShownPalettePlusWarning = _configService.Current.DisableOptionalPluginWarnings, }; - CachedPlayer.Initialize(address, name); + CachedPlayer.Initialize(name); ApplyLastReceivedData(); + + return true; } public void ApplyData(OnlineUserCharaDataDto data) diff --git a/MareSynchronos/Utils/FileReplacementDataComparer.cs b/MareSynchronos/Utils/FileReplacementDataComparer.cs new file mode 100644 index 0000000..500886a --- /dev/null +++ b/MareSynchronos/Utils/FileReplacementDataComparer.cs @@ -0,0 +1,45 @@ +using MareSynchronos.API.Data; + +namespace MareSynchronos.Utils; + +public class FileReplacementDataComparer : IEqualityComparer +{ + public static FileReplacementDataComparer Instance => _instance; + private static FileReplacementDataComparer _instance = new(); + private FileReplacementDataComparer() { } + public bool Equals(FileReplacementData? x, FileReplacementData? y) + { + if (x == null || y == null) return false; + return x.Hash.Equals(y.Hash) && CompareLists(x.GamePaths.ToHashSet(StringComparer.Ordinal), y.GamePaths.ToHashSet(StringComparer.Ordinal)) && string.Equals(x.FileSwapPath, y.FileSwapPath, StringComparison.Ordinal); + } + + public int GetHashCode(FileReplacementData obj) + { + return HashCode.Combine(obj.Hash.GetHashCode(StringComparison.OrdinalIgnoreCase), GetOrderIndependentHashCode(obj.GamePaths), StringComparer.Ordinal.GetHashCode(obj.FileSwapPath)); + } + + private static int GetOrderIndependentHashCode(IEnumerable source) + { + int hash = 0; + foreach (T element in source) + { + hash = unchecked(hash + + EqualityComparer.Default.GetHashCode(element)); + } + return hash; + } + + private bool CompareLists(HashSet list1, HashSet list2) + { + if (list1.Count != list2.Count) + return false; + + for (int i = 0; i < list1.Count; i++) + { + if (!string.Equals(list1.ElementAt(i), list2.ElementAt(i), StringComparison.OrdinalIgnoreCase)) + return false; + } + + return true; + } +} \ No newline at end of file