From de2cd97dbec35ef94b1d2d3c46eda61a72d59d45 Mon Sep 17 00:00:00 2001 From: rootdarkarchon Date: Sat, 29 Apr 2023 17:48:05 +0200 Subject: [PATCH] rework character disposal --- .../PlayerData/Factories/PlayerDataFactory.cs | 24 +-- .../PlayerData/Pairs/CachedPlayer.cs | 144 ++++++++++++------ .../PlayerData/Pairs/OnlinePlayerManager.cs | 17 +-- MareSynchronos/PlayerData/Pairs/Pair.cs | 99 ++++-------- .../PlayerData/Pairs/PairManager.cs | 36 +---- MareSynchronos/Plugin.cs | 2 +- MareSynchronos/Services/DalamudUtilService.cs | 16 +- .../Services/Mediator/MareMediator.cs | 39 +++-- .../Services/Mediator/MessageBase.cs | 5 + MareSynchronos/Services/Mediator/Messages.cs | 23 +-- .../WebAPI/SignalR/ApiController.cs | 1 - 11 files changed, 188 insertions(+), 218 deletions(-) diff --git a/MareSynchronos/PlayerData/Factories/PlayerDataFactory.cs b/MareSynchronos/PlayerData/Factories/PlayerDataFactory.cs index 01c33f0..a47a268 100644 --- a/MareSynchronos/PlayerData/Factories/PlayerDataFactory.cs +++ b/MareSynchronos/PlayerData/Factories/PlayerDataFactory.cs @@ -55,7 +55,7 @@ public class PlayerDataFactory pointerIsZero = playerRelatedObject.Address == IntPtr.Zero; try { - pointerIsZero = await CheckForNullDrawObject(playerRelatedObject.Address); + pointerIsZero = await CheckForNullDrawObject(playerRelatedObject.Address).ConfigureAwait(false); } catch { @@ -101,16 +101,6 @@ public class PlayerDataFactory previousData.GlamourerString = previousGlamourerData; } - private async Task CheckForNullDrawObject(IntPtr playerPointer) - { - return await _dalamudUtil.RunOnFrameworkThread(() => CheckForNullDrawObjectUnsafe(playerPointer)); - } - - private unsafe bool CheckForNullDrawObjectUnsafe(IntPtr playerPointer) - { - return ((Character*)playerPointer)->GameObject.DrawObject == null; - } - private unsafe void AddPlayerSpecificReplacements(Human* human, HashSet forwardResolve, HashSet reverseResolve) { var weaponObject = (Weapon*)((Object*)human)->ChildObject; @@ -297,6 +287,16 @@ public class PlayerDataFactory return (forwardResolve, reverseResolve); } + private async Task CheckForNullDrawObject(IntPtr playerPointer) + { + return await _dalamudUtil.RunOnFrameworkThread(() => CheckForNullDrawObjectUnsafe(playerPointer)).ConfigureAwait(false); + } + + private unsafe bool CheckForNullDrawObjectUnsafe(IntPtr playerPointer) + { + return ((Character*)playerPointer)->GameObject.DrawObject == null; + } + private async Task CreateCharacterData(CharacterData previousData, GameObjectHandler playerRelatedObject, CancellationToken token) { var objectKind = playerRelatedObject.ObjectKind; @@ -326,7 +326,7 @@ public class PlayerDataFactory Stopwatch st = Stopwatch.StartNew(); // gather static replacements from render model - var (forwardResolve, reverseResolve) = await _dalamudUtil.RunOnFrameworkThread(() => BuildDataFromModel(objectKind, charaPointer, token)); + var (forwardResolve, reverseResolve) = await _dalamudUtil.RunOnFrameworkThread(() => BuildDataFromModel(objectKind, charaPointer, token)).ConfigureAwait(false); Dictionary> resolvedPaths = await GetFileReplacementsFromPaths(forwardResolve, reverseResolve).ConfigureAwait(false); previousData.FileReplacements[objectKind] = new HashSet(resolvedPaths.Select(c => new FileReplacement(c.Value, c.Key, _fileCacheManager)), FileReplacementComparer.Instance) diff --git a/MareSynchronos/PlayerData/Pairs/CachedPlayer.cs b/MareSynchronos/PlayerData/Pairs/CachedPlayer.cs index baa10ae..0629d00 100644 --- a/MareSynchronos/PlayerData/Pairs/CachedPlayer.cs +++ b/MareSynchronos/PlayerData/Pairs/CachedPlayer.cs @@ -5,6 +5,7 @@ using MareSynchronos.API.Data; using MareSynchronos.API.Dto.User; using MareSynchronos.FileCache; using MareSynchronos.Interop; +using MareSynchronos.MareConfiguration; using MareSynchronos.PlayerData.Handlers; using MareSynchronos.Services; using MareSynchronos.Services.Mediator; @@ -26,20 +27,21 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase private readonly Func, bool, GameObjectHandler> _gameObjectHandlerFactory; private readonly IpcManager _ipcManager; private readonly IHostApplicationLifetime _lifetime; + private readonly OptionalPluginWarning _pluginWarnings; private CancellationTokenSource? _applicationCancellationTokenSource = new(); private Guid _applicationId; private Task? _applicationTask; private CharacterData _cachedData = new(); private GameObjectHandler? _charaHandler; private CancellationTokenSource? _downloadCancellationTokenSource = new(); + private int _framesSinceNotVisible = 0; private string _lastGlamourerData = string.Empty; private string _originalGlamourerData = string.Empty; - private CancellationTokenSource _redrawCts = new(); public CachedPlayer(ILogger logger, OnlineUserIdentDto onlineUser, - Func, bool, GameObjectHandler> gameObjectHandlerFactory, - IpcManager ipcManager, FileDownloadManager transferManager, + Func, bool, GameObjectHandler> gameObjectHandlerFactory, + IpcManager ipcManager, FileDownloadManager transferManager, MareConfigService mareConfigService, DalamudUtilService dalamudUtil, IHostApplicationLifetime lifetime, FileCacheManager fileDbManager, MareMediator mediator) : base(logger, mediator) { OnlineUser = onlineUser; @@ -49,6 +51,14 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase _dalamudUtil = dalamudUtil; _lifetime = lifetime; _fileDbManager = fileDbManager; + Mediator.Subscribe(this, (_) => FrameworkUpdate()); + _pluginWarnings ??= new() + { + ShownCustomizePlusWarning = mareConfigService.Current.DisableOptionalPluginWarnings, + ShownHeelsWarning = mareConfigService.Current.DisableOptionalPluginWarnings, + ShownPalettePlusWarning = mareConfigService.Current.DisableOptionalPluginWarnings, + ShownHonorificWarning = mareConfigService.Current.DisableOptionalPluginWarnings, + }; } private enum PlayerChanges @@ -56,28 +66,34 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase Heels = 1, Customize = 2, Palette = 3, - Mods = 4, - Honorific = 5, + Honorific = 4, + Mods = 5, } + public bool IsVisible { get; private set; } + public OnlineUserIdentDto OnlineUser { get; private set; } public IntPtr PlayerCharacter => _charaHandler?.Address ?? IntPtr.Zero; - public unsafe uint PlayerCharacterId => (_charaHandler?.Address ?? IntPtr.Zero) == IntPtr.Zero ? uint.MaxValue - : ((GameObject*)_charaHandler.Address)->ObjectID; - + : ((GameObject*)_charaHandler!.Address)->ObjectID; public string? PlayerName { get; private set; } public string PlayerNameHash => OnlineUser.Ident; - private OnlineUserIdentDto OnlineUser { get; set; } + public uint? PlayerWorld { get; private set; } - public void ApplyCharacterData(CharacterData characterData, OptionalPluginWarning warning, bool forced = false) + public void ApplyCharacterData(CharacterData characterData, bool forced = false) { + if (_charaHandler == null) + { + _cachedData = characterData; + return; + } + SetUploading(false); Logger.LogDebug("Received data for {player}", this); + Logger.LogDebug("Hash for data is {newHash}, current cache hash is {oldHash}", characterData.DataHash.Value, _cachedData.DataHash.Value); Logger.LogDebug("Checking for files to download for player {name}", this); - Logger.LogDebug("Hash for data is {newHash}, current cache hash is {oldHash}", characterData.DataHash.Value, _cachedData.DataHash.Value); if (!_ipcManager.CheckPenumbraApi()) return; if (!_ipcManager.CheckGlamourerApi()) return; @@ -94,7 +110,7 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase if (charaDataToUpdate.TryGetValue(ObjectKind.Player, out var playerChanges)) { - NotifyForMissingPlugins(playerChanges, warning); + NotifyForMissingPlugins(playerChanges); } Logger.LogDebug("Downloading and applying character for {name}", this); @@ -116,28 +132,6 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase return true; } - public async Task Initialize(string name) - { - PlayerName = name; - _charaHandler = _gameObjectHandlerFactory(ObjectKind.Player, () => _dalamudUtil.GetPlayerCharacterFromObjectTableByName(PlayerName), false); - - _originalGlamourerData = await _ipcManager.GlamourerGetCharacterCustomization(PlayerCharacter).ConfigureAwait(false); - _lastGlamourerData = _originalGlamourerData; - Mediator.Subscribe(this, IpcManagerOnPenumbraRedrawEvent); - Mediator.Subscribe(this, async (msg) => - { - if (msg.GameObjectHandler == _charaHandler && (_applicationTask?.IsCompleted ?? true)) - { - Logger.LogTrace("Saving new Glamourer Data for {this}", this); - _lastGlamourerData = await _ipcManager.GlamourerGetCharacterCustomization(PlayerCharacter).ConfigureAwait(false); - } - }); - - _downloadManager.Initialize(); - - Logger.LogDebug("Initializing Player {obj}", this); - } - public override string ToString() { return OnlineUser == null @@ -163,6 +157,7 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase SetUploading(false); _downloadManager.Dispose(); var name = PlayerName; + var world = PlayerWorld; Logger.LogDebug("Disposing {name} ({user})", name, OnlineUser); try { @@ -185,7 +180,7 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase { try { - RevertCustomizationData(item.Key, name, applicationId).GetAwaiter().GetResult(); + RevertCustomizationData(item.Key, name, world ?? 0, applicationId).GetAwaiter().GetResult(); } catch (InvalidOperationException ex) { @@ -414,7 +409,7 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase Logger.LogDebug("Downloading missing files for player {name}, {kind}", PlayerName, updatedData); if (toDownloadReplacements.Any()) { - await _downloadManager.DownloadFiles(_charaHandler, toDownloadReplacements, downloadToken).ConfigureAwait(false); + await _downloadManager.DownloadFiles(_charaHandler!, toDownloadReplacements, downloadToken).ConfigureAwait(false); _downloadManager.CancelDownload(); } @@ -486,6 +481,61 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase }, downloadToken); } + private void FrameworkUpdate() + { + if (string.IsNullOrEmpty(PlayerName)) + { + var pc = _dalamudUtil.FindPlayerByNameHash(OnlineUser.Ident); + if (pc == null) return; + Logger.LogDebug("One-Time Initializing {this}", this); + Initialize(pc.Name.ToString(), pc.HomeWorld.Id); + Logger.LogDebug("One-Time Initialized {this}", this); + } + + if (_charaHandler?.Address != IntPtr.Zero && !IsVisible) + { + IsVisible = true; + Mediator.Publish(new CachedPlayerVisibleMessage(this)); + Logger.LogTrace("{this} visibility changed, now: {visi}", this, IsVisible); + _framesSinceNotVisible = 0; + _lastGlamourerData = _ipcManager.GlamourerGetCharacterCustomization(PlayerCharacter).GetAwaiter().GetResult(); + if (_cachedData != null) + { + Task.Run(() => ApplyCharacterData(_cachedData, true)); + } + } + else if (_charaHandler?.Address == IntPtr.Zero && IsVisible) + { + _framesSinceNotVisible++; + if (_framesSinceNotVisible > 30) + { + IsVisible = false; + Logger.LogTrace("{this} visibility changed, now: {visi}", this, IsVisible); + } + } + } + + private void Initialize(string name, uint worldid) + { + PlayerName = name; + PlayerWorld = worldid; + _charaHandler = _gameObjectHandlerFactory(ObjectKind.Player, () => _dalamudUtil.GetPlayerCharacterFromObjectTableByName(PlayerName, worldid), false); + + _originalGlamourerData = _ipcManager.GlamourerGetCharacterCustomization(PlayerCharacter).ConfigureAwait(false).GetAwaiter().GetResult(); + _lastGlamourerData = _originalGlamourerData; + Mediator.Subscribe(this, IpcManagerOnPenumbraRedrawEvent); + Mediator.Subscribe(this, async (msg) => + { + if (msg.GameObjectHandler == _charaHandler && (_applicationTask?.IsCompleted ?? true)) + { + Logger.LogTrace("Saving new Glamourer Data for {this}", this); + _lastGlamourerData = await _ipcManager.GlamourerGetCharacterCustomization(PlayerCharacter).ConfigureAwait(false); + } + }); + + _downloadManager.Initialize(); + } + private void IpcManagerOnPenumbraRedrawEvent(PenumbraRedrawMessage msg) { var player = _dalamudUtil.GetCharacterFromObjectTableByIndex(msg.ObjTblIdx); @@ -505,30 +555,30 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase }, token); } - private void NotifyForMissingPlugins(HashSet changes, OptionalPluginWarning warning) + private void NotifyForMissingPlugins(HashSet changes) { List missingPluginsForData = new(); - if (changes.Contains(PlayerChanges.Heels) && !warning.ShownHeelsWarning && !_ipcManager.CheckHeelsApi()) + if (changes.Contains(PlayerChanges.Heels) && !_pluginWarnings.ShownHeelsWarning && !_ipcManager.CheckHeelsApi()) { missingPluginsForData.Add("Heels"); - warning.ShownHeelsWarning = true; + _pluginWarnings.ShownHeelsWarning = true; } - if (changes.Contains(PlayerChanges.Customize) && !warning.ShownCustomizePlusWarning && !_ipcManager.CheckCustomizePlusApi()) + if (changes.Contains(PlayerChanges.Customize) && !_pluginWarnings.ShownCustomizePlusWarning && !_ipcManager.CheckCustomizePlusApi()) { missingPluginsForData.Add("Customize+"); - warning.ShownCustomizePlusWarning = true; + _pluginWarnings.ShownCustomizePlusWarning = true; } - if (changes.Contains(PlayerChanges.Palette) && !warning.ShownPalettePlusWarning && !_ipcManager.CheckPalettePlusApi()) + if (changes.Contains(PlayerChanges.Palette) && !_pluginWarnings.ShownPalettePlusWarning && !_ipcManager.CheckPalettePlusApi()) { missingPluginsForData.Add("Palette+"); - warning.ShownPalettePlusWarning = true; + _pluginWarnings.ShownPalettePlusWarning = true; } - if (changes.Contains(PlayerChanges.Honorific) && !warning.ShownHonorificWarning && !_ipcManager.CheckHonorificApi()) + if (changes.Contains(PlayerChanges.Honorific) && !_pluginWarnings.ShownHonorificWarning && !_ipcManager.CheckHonorificApi()) { missingPluginsForData.Add("Honorific"); - warning.ShownHonorificWarning = true; + _pluginWarnings.ShownHonorificWarning = true; } if (missingPluginsForData.Any()) @@ -539,9 +589,9 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase } } - private async Task RevertCustomizationData(ObjectKind objectKind, string name, Guid applicationId) + private async Task RevertCustomizationData(ObjectKind objectKind, string name, uint world, Guid applicationId) { - nint address = _dalamudUtil.GetPlayerCharacterFromObjectTableByName(name); + nint address = _dalamudUtil.GetPlayerCharacterFromObjectTableByName(name, world); if (address == IntPtr.Zero) return; var cancelToken = new CancellationTokenSource(); diff --git a/MareSynchronos/PlayerData/Pairs/OnlinePlayerManager.cs b/MareSynchronos/PlayerData/Pairs/OnlinePlayerManager.cs index d815c5c..81e9ea8 100644 --- a/MareSynchronos/PlayerData/Pairs/OnlinePlayerManager.cs +++ b/MareSynchronos/PlayerData/Pairs/OnlinePlayerManager.cs @@ -13,6 +13,7 @@ public class OnlinePlayerManager : DisposableMediatorSubscriberBase private readonly ApiController _apiController; private readonly DalamudUtilService _dalamudUtil; private readonly FileUploadManager _fileTransferManager; + private readonly HashSet _newVisiblePlayers = new(); private readonly PairManager _pairManager; private CharacterData? _lastSentData; @@ -39,22 +40,18 @@ public class OnlinePlayerManager : DisposableMediatorSubscriberBase Logger.LogDebug("Not sending data for {hash}", newData.DataHash.Value); } }); + Mediator.Subscribe(this, (msg) => _newVisiblePlayers.Add(msg.Player)); } private void FrameworkOnUpdate() { if (!_dalamudUtil.IsPlayerPresent || !_apiController.IsConnected) return; - var playerCharacters = _dalamudUtil.GetPlayerCharacters(); - var chars = _pairManager.FindAllPairs(playerCharacters); - var newVisiblePlayers = (from pChar in chars.Where(p => p.Pair.InitializePair(p.Character.Name.ToString())) - select pChar.Pair.UserData).ToList(); - - if (newVisiblePlayers.Any()) - { - Logger.LogTrace("Has new visible players, pushing character data"); - PushCharacterData(newVisiblePlayers); - } + if (!_newVisiblePlayers.Any()) return; + var newVisiblePlayers = _newVisiblePlayers.ToList(); + _newVisiblePlayers.Clear(); + Logger.LogTrace("Has new visible players, pushing character data"); + PushCharacterData(newVisiblePlayers.Select(c => c.OnlineUser.User).ToList()); } private void PlayerManagerOnPlayerHasChanged() diff --git a/MareSynchronos/PlayerData/Pairs/Pair.cs b/MareSynchronos/PlayerData/Pairs/Pair.cs index e2d0aca..89d8f82 100644 --- a/MareSynchronos/PlayerData/Pairs/Pair.cs +++ b/MareSynchronos/PlayerData/Pairs/Pair.cs @@ -1,11 +1,9 @@ using Dalamud.ContextMenu; -using Dalamud.Utility; using MareSynchronos.API.Data; using MareSynchronos.API.Data.Comparer; using MareSynchronos.API.Data.Extensions; using MareSynchronos.API.Dto.Group; using MareSynchronos.API.Dto.User; -using MareSynchronos.MareConfiguration; using MareSynchronos.Services.Mediator; using MareSynchronos.Services.ServerConfiguration; using MareSynchronos.Utils; @@ -16,20 +14,17 @@ namespace MareSynchronos.PlayerData.Pairs; public class Pair { private readonly Func _cachedPlayerFactory; - private readonly MareConfigService _configService; private readonly ILogger _logger; private readonly MareMediator _mediator; private readonly ServerConfigurationManager _serverConfigurationManager; private OnlineUserIdentDto? _onlineUserIdentDto = null; - private OptionalPluginWarning? _pluginWarnings; public Pair(ILogger logger, Func cachedPlayerFactory, - MareMediator mediator, MareConfigService configService, ServerConfigurationManager serverConfigurationManager) + MareMediator mediator, ServerConfigurationManager serverConfigurationManager) { _logger = logger; _cachedPlayerFactory = cachedPlayerFactory; _mediator = mediator; - _configService = configService; _serverConfigurationManager = serverConfigurationManager; } @@ -41,7 +36,7 @@ public class Pair 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()); - public bool IsVisible => CachedPlayer?.PlayerName != null; + public bool IsVisible => CachedPlayer?.IsVisible ?? false; public CharacterData? LastReceivedCharacterData { get; set; } public string? PlayerName => CachedPlayer?.PlayerName ?? string.Empty; @@ -79,8 +74,6 @@ public class Pair { if (CachedPlayer == null) throw new InvalidOperationException("CachedPlayer not initialized"); - if (string.Equals(LastReceivedCharacterData?.DataHash.Value, data.CharaData.DataHash.Value, StringComparison.Ordinal)) return; - LastReceivedCharacterData = data.CharaData; ApplyLastReceivedData(); @@ -91,71 +84,10 @@ public class Pair if (CachedPlayer == null) return; if (LastReceivedCharacterData == null) return; - _pluginWarnings ??= new() - { - ShownCustomizePlusWarning = _configService.Current.DisableOptionalPluginWarnings, - ShownHeelsWarning = _configService.Current.DisableOptionalPluginWarnings, - ShownPalettePlusWarning = _configService.Current.DisableOptionalPluginWarnings, - ShownHonorificWarning = _configService.Current.DisableOptionalPluginWarnings, - }; - - CachedPlayer.ApplyCharacterData(RemoveNotSyncedFiles(LastReceivedCharacterData.DeepClone())!, _pluginWarnings, forced); + CachedPlayer.ApplyCharacterData(RemoveNotSyncedFiles(LastReceivedCharacterData.DeepClone())!, forced); } - public string? GetNote() - { - return _serverConfigurationManager.GetNoteForUid(UserData.UID); - } - - public string GetPlayerNameHash() - { - try - { - return CachedPlayer?.PlayerNameHash ?? string.Empty; - } - catch (Exception ex) - { - _logger.LogWarning(ex, "Error accessing PlayerNameHash, recreating CachedPlayer"); - RecreateCachedPlayer(); - } - - return string.Empty; - } - - public bool HasAnyConnection() - { - return UserPair != null || GroupPair.Any(); - } - - public bool InitializePair(string name) - { - if (!PlayerName.IsNullOrEmpty()) return false; - - if (CachedPlayer == null) throw new InvalidOperationException("CachedPlayer not initialized"); - _pluginWarnings ??= new() - { - ShownCustomizePlusWarning = _configService.Current.DisableOptionalPluginWarnings, - ShownHeelsWarning = _configService.Current.DisableOptionalPluginWarnings, - ShownPalettePlusWarning = _configService.Current.DisableOptionalPluginWarnings, - ShownHonorificWarning = _configService.Current.DisableOptionalPluginWarnings, - }; - - CachedPlayer.Initialize(name).Wait(); - - ApplyLastReceivedData(); - - return true; - } - - public void MarkOffline() - { - _onlineUserIdentDto = null; - LastReceivedCharacterData = null; - CachedPlayer?.Dispose(); - CachedPlayer = null; - } - - public void RecreateCachedPlayer(OnlineUserIdentDto? dto = null) + public void CreateCachedPlayer(OnlineUserIdentDto? dto = null) { if (dto == null && _onlineUserIdentDto == null) { @@ -172,6 +104,29 @@ public class Pair CachedPlayer = _cachedPlayerFactory(_onlineUserIdentDto!); } + public string? GetNote() + { + return _serverConfigurationManager.GetNoteForUid(UserData.UID); + } + + public string GetPlayerNameHash() + { + return CachedPlayer?.PlayerNameHash ?? string.Empty; + } + + public bool HasAnyConnection() + { + return UserPair != null || GroupPair.Any(); + } + + public void MarkOffline() + { + _onlineUserIdentDto = null; + LastReceivedCharacterData = null; + CachedPlayer?.Dispose(); + CachedPlayer = null; + } + public void SetNote(string note) { _serverConfigurationManager.SetNoteForUid(UserData.UID, note); diff --git a/MareSynchronos/PlayerData/Pairs/PairManager.cs b/MareSynchronos/PlayerData/Pairs/PairManager.cs index c5bb439..e30da4a 100644 --- a/MareSynchronos/PlayerData/Pairs/PairManager.cs +++ b/MareSynchronos/PlayerData/Pairs/PairManager.cs @@ -1,7 +1,6 @@ using Dalamud.ContextMenu; using Dalamud.Game.ClientState.Objects.SubKinds; using Dalamud.Interface.Internal.Notifications; -using Dalamud.Utility; using MareSynchronos.API.Data; using MareSynchronos.API.Data.Comparer; using MareSynchronos.API.Data.Extensions; @@ -33,7 +32,6 @@ public sealed class PairManager : DisposableMediatorSubscriberBase _pairFactory = pairFactory; _configurationService = configurationService; _dalamudContextMenu = dalamudContextMenu; - Mediator.Subscribe(this, (_) => DalamudUtilOnZoneSwitched()); Mediator.Subscribe(this, (_) => DalamudUtilOnDelayedFrameworkUpdate()); Mediator.Subscribe(this, (_) => ClearPairs()); Mediator.Subscribe(this, (_) => ReapplyPairData()); @@ -105,7 +103,7 @@ public sealed class PairManager : DisposableMediatorSubscriberBase public List GetOnlineUserPairs() => _allClientPairs.Where(p => !string.IsNullOrEmpty(p.Value.GetPlayerNameHash())).Select(p => p.Value).ToList(); - public List GetVisibleUsers() => _allClientPairs.Where(p => p.Value.HasCachedPlayer).Select(p => p.Key).ToList(); + public List GetVisibleUsers() => _allClientPairs.Where(p => p.Value.IsVisible).Select(p => p.Key).ToList(); public void MarkPairOffline(UserData user) { @@ -139,7 +137,7 @@ public sealed class PairManager : DisposableMediatorSubscriberBase Mediator.Publish(new NotificationMessage("User online", msg, NotificationType.Info, 5000)); } - pair.RecreateCachedPlayer(dto); + pair.CreateCachedPlayer(dto); RecreateLazy(); } @@ -147,15 +145,7 @@ public sealed class PairManager : DisposableMediatorSubscriberBase { if (!_allClientPairs.ContainsKey(dto.User)) throw new InvalidOperationException("No user found for " + dto.User); - var pair = _allClientPairs[dto.User]; - if (!pair.PlayerName.IsNullOrEmpty()) - { - pair.ApplyData(dto); - } - else - { - _allClientPairs[dto.User].LastReceivedCharacterData = dto.CharaData; - } + _allClientPairs[dto.User].ApplyData(dto); } public void RemoveGroup(GroupData data) @@ -360,32 +350,16 @@ public sealed class PairManager : DisposableMediatorSubscriberBase if (string.IsNullOrEmpty(hash)) continue; _indexedPairs[hash] = pair; } - - foreach (Pair pair in _allClientPairs.Select(p => p.Value).Where(p => p.HasCachedPlayer).ToList()) - { - if (!pair.CachedPlayerExists) - { - pair.RecreateCachedPlayer(); - } - } - } - - private void DalamudUtilOnZoneSwitched() - { - DisposePairs(recreate: true); } private Lazy> DirectPairsLazy() => new(() => _allClientPairs.Select(k => k.Value).Where(k => k.UserPair != null).ToList()); - private void DisposePairs(bool recreate = false) + private void DisposePairs() { Logger.LogDebug("Disposing all Pairs"); Parallel.ForEach(_allClientPairs, item => { - if (recreate) - item.Value.RecreateCachedPlayer(); - else - item.Value.MarkOffline(); + item.Value.MarkOffline(); }); RecreateLazy(); diff --git a/MareSynchronos/Plugin.cs b/MareSynchronos/Plugin.cs index b712224..cc71b30 100644 --- a/MareSynchronos/Plugin.cs +++ b/MareSynchronos/Plugin.cs @@ -98,6 +98,7 @@ public sealed class Plugin : IDalamudPlugin s.GetRequiredService, bool, GameObjectHandler>>(), s.GetRequiredService(), s.GetRequiredService>().Invoke(), + s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService(), @@ -107,7 +108,6 @@ public sealed class Plugin : IDalamudPlugin => new Pair(s.GetRequiredService>(), s.GetRequiredService>(), s.GetRequiredService(), - s.GetRequiredService(), s.GetRequiredService()))); collection.AddSingleton(s => new Func(() diff --git a/MareSynchronos/Services/DalamudUtilService.cs b/MareSynchronos/Services/DalamudUtilService.cs index 4cbdd05..fb27421 100644 --- a/MareSynchronos/Services/DalamudUtilService.cs +++ b/MareSynchronos/Services/DalamudUtilService.cs @@ -11,6 +11,7 @@ using MareSynchronos.Services.Mediator; using MareSynchronos.Utils; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using System.Globalization; using System.Numerics; using System.Runtime.CompilerServices; using GameObject = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject; @@ -112,9 +113,9 @@ public class DalamudUtilService : IHostedService return await RunOnFrameworkThread(() => GetPetInternal(playerPointer)).ConfigureAwait(false); } - public IntPtr GetPlayerCharacterFromObjectTableByName(string characterName) + public IntPtr GetPlayerCharacterFromObjectTableByName(string characterName, uint worldid) { - if (_playerCharas.TryGetValue(characterName, out var pchar)) return pchar; + if (_playerCharas.TryGetValue(characterName + worldid.ToString(CultureInfo.InvariantCulture), out var pchar)) return pchar; return IntPtr.Zero; } @@ -133,8 +134,6 @@ public class DalamudUtilService : IHostedService { if (!_framework.IsInFrameworkUpdateThread) { - //_logger.LogTrace("Running Action on framework thread (FrameworkContext: {ctx}): {member} in {file}:{line}", _framework.IsInFrameworkUpdateThread, callerMember, callerFilePath, lineNumber); - await _framework.RunOnFrameworkThread(act).ContinueWith((_) => Task.CompletedTask).ConfigureAwait(false); while (_framework.IsInFrameworkUpdateThread) // yield the thread again, should technically never be triggered { @@ -151,8 +150,6 @@ public class DalamudUtilService : IHostedService { if (!_framework.IsInFrameworkUpdateThread) { - //_logger.LogTrace("Running Func on framework thread (FrameworkContext: {ctx}): {member} in {file}:{line}", _framework.IsInFrameworkUpdateThread, callerMember, callerFilePath, lineNumber); - var result = await _framework.RunOnFrameworkThread(func).ContinueWith((task) => task.Result).ConfigureAwait(false); while (_framework.IsInFrameworkUpdateThread) // yield the thread again, should technically never be triggered { @@ -239,6 +236,11 @@ public class DalamudUtilService : IHostedService return _gameGui.WorldToScreen(obj.Position, out var screenPos) ? screenPos : Vector2.Zero; } + internal PlayerCharacter? FindPlayerByNameHash(string ident) + { + return _objectTable.OfType().FirstOrDefault(p => p.GetHash256().Equals(ident, StringComparison.Ordinal)); + } + private void FrameworkOnUpdate(Framework framework) { _performanceCollector.LogPerformance(this, "FrameworkOnUpdate", FrameworkOnUpdateInternal); @@ -248,7 +250,7 @@ public class DalamudUtilService : IHostedService { if (_clientState.LocalPlayer?.IsDead ?? false) return; - _playerCharas = _objectTable.OfType().ToDictionary(p => p.Name.ToString(), p => p.Address, StringComparer.Ordinal); + _playerCharas = _objectTable.OfType().ToDictionary(p => p.Name.ToString() + p.HomeWorld.Id.ToString(), p => p.Address, StringComparer.Ordinal); if (GposeTarget != null && !IsInGpose) { diff --git a/MareSynchronos/Services/Mediator/MareMediator.cs b/MareSynchronos/Services/Mediator/MareMediator.cs index b6e7843..7ec0baa 100644 --- a/MareSynchronos/Services/Mediator/MareMediator.cs +++ b/MareSynchronos/Services/Mediator/MareMediator.cs @@ -135,33 +135,32 @@ public sealed class MareMediator : IHostedService { subscribersCopy = subscribers?.Where(s => s.Subscriber != null).ToHashSet() ?? new HashSet(); } - _performanceCollector.LogPerformance(this, $"Execute>{message.GetType().Name}", () => - { - foreach (SubscriberAction subscriber in subscribersCopy) - { - try - { - typeof(MareMediator) - .GetMethod(nameof(ExecuteSubscriber), System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)? - .MakeGenericMethod(message.GetType()) - .Invoke(this, new object[] { subscriber, message }); - } - catch (Exception ex) - { - if (_lastErrorTime.TryGetValue(subscriber, out var lastErrorTime) && lastErrorTime.Add(TimeSpan.FromSeconds(10)) > DateTime.UtcNow) - continue; - _logger.LogCritical(ex, "Error executing {type} for subscriber {subscriber}", message.GetType().Name, subscriber.Subscriber.GetType().Name); - _lastErrorTime[subscriber] = DateTime.UtcNow; - } + foreach (SubscriberAction subscriber in subscribersCopy) + { + try + { + typeof(MareMediator) + .GetMethod(nameof(ExecuteSubscriber), System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)? + .MakeGenericMethod(message.GetType()) + .Invoke(this, new object[] { subscriber, message }); } - }); + catch (Exception ex) + { + if (_lastErrorTime.TryGetValue(subscriber, out var lastErrorTime) && lastErrorTime.Add(TimeSpan.FromSeconds(10)) > DateTime.UtcNow) + continue; + + _logger.LogCritical(ex, "Error executing {type} for subscriber {subscriber}", message.GetType().Name, subscriber.Subscriber.GetType().Name); + _lastErrorTime[subscriber] = DateTime.UtcNow; + } + } } } private void ExecuteSubscriber(SubscriberAction subscriber, T message) where T : MessageBase { - _performanceCollector.LogPerformance(this, $"Publish>{message.GetType().Name}+{subscriber.Subscriber.GetType().Name}", () => ((Action)subscriber.Action).Invoke(message)); + var isSameThread = message.KeepThreadContext ? "$" : string.Empty; + _performanceCollector.LogPerformance(this, $"{isSameThread}Execute>{message.GetType().Name}+{subscriber.Subscriber.GetType().Name}", () => ((Action)subscriber.Action).Invoke(message)); } private sealed class SubscriberAction diff --git a/MareSynchronos/Services/Mediator/MessageBase.cs b/MareSynchronos/Services/Mediator/MessageBase.cs index 012e36a..cd1c3d5 100644 --- a/MareSynchronos/Services/Mediator/MessageBase.cs +++ b/MareSynchronos/Services/Mediator/MessageBase.cs @@ -3,4 +3,9 @@ public abstract record MessageBase { public virtual bool KeepThreadContext => false; +} + +public record SameThreadMessage : MessageBase +{ + public override bool KeepThreadContext => true; } \ No newline at end of file diff --git a/MareSynchronos/Services/Mediator/Messages.cs b/MareSynchronos/Services/Mediator/Messages.cs index dccfcc3..c30da00 100644 --- a/MareSynchronos/Services/Mediator/Messages.cs +++ b/MareSynchronos/Services/Mediator/Messages.cs @@ -15,36 +15,24 @@ public record SwitchToMainUiMessage : MessageBase; public record OpenSettingsUiMessage : MessageBase; public record DalamudLoginMessage : MessageBase; public record DalamudLogoutMessage : MessageBase; -public record FrameworkUpdateMessage : MessageBase -{ - public override bool KeepThreadContext => true; -} +public record FrameworkUpdateMessage : SameThreadMessage; public record ClassJobChangedMessage(uint? ClassJob) : MessageBase; -public record DelayedFrameworkUpdateMessage : MessageBase -{ - public override bool KeepThreadContext => true; -} +public record DelayedFrameworkUpdateMessage : SameThreadMessage; public record ZoneSwitchStartMessage : MessageBase; public record ZoneSwitchEndMessage : MessageBase; public record CutsceneStartMessage : MessageBase; public record GposeStartMessage : MessageBase; public record GposeEndMessage : MessageBase; public record CutsceneEndMessage : MessageBase; -public record CutsceneFrameworkUpdateMessage : MessageBase -{ - public override bool KeepThreadContext => true; -} +public record CutsceneFrameworkUpdateMessage : SameThreadMessage; public record ConnectedMessage(ConnectionDto Connection) : MessageBase; -public record DisconnectedMessage : MessageBase; +public record DisconnectedMessage : SameThreadMessage; public record PenumbraModSettingChangedMessage : MessageBase; public record PenumbraInitializedMessage : MessageBase; public record PenumbraDisposedMessage : MessageBase; public record PenumbraRedrawMessage(IntPtr Address, int ObjTblIdx, bool WasRequested) : MessageBase; public record HeelsOffsetMessage : MessageBase; -public record PenumbraResourceLoadMessage(IntPtr GameObject, string GamePath, string FilePath) : MessageBase -{ - public override bool KeepThreadContext => true; -} +public record PenumbraResourceLoadMessage(IntPtr GameObject, string GamePath, string FilePath) : SameThreadMessage; public record CustomizePlusMessage : MessageBase; public record PalettePlusMessage(Character Character) : MessageBase; public record HonorificMessage(string NewHonorificTitle) : MessageBase; @@ -76,5 +64,6 @@ public record ProfilePopoutToggle(Pair? Pair) : MessageBase; public record CompactUiChange(Vector2 Size, Vector2 Position) : MessageBase; public record ProfileOpenStandaloneMessage(Pair Pair) : MessageBase; public record RemoveWindowMessage(WindowMediatorSubscriberBase Window) : MessageBase; +public record CachedPlayerVisibleMessage(CachedPlayer Player) : MessageBase; #pragma warning restore MA0048 // File name must match type name \ No newline at end of file diff --git a/MareSynchronos/WebAPI/SignalR/ApiController.cs b/MareSynchronos/WebAPI/SignalR/ApiController.cs index ddccf3d..f340bb6 100644 --- a/MareSynchronos/WebAPI/SignalR/ApiController.cs +++ b/MareSynchronos/WebAPI/SignalR/ApiController.cs @@ -329,7 +329,6 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM { _healthCheckTokenSource?.Cancel(); Mediator.Publish(new DisconnectedMessage()); - _pairManager.ClearPairs(); ServerState = ServerState.Offline; if (arg != null) {