From fae8941dcefbc3d18a53e3b870a5f4fa4c46a749 Mon Sep 17 00:00:00 2001 From: Loporrit <141286461+loporrit@users.noreply.github.com> Date: Wed, 14 May 2025 12:01:29 +0000 Subject: [PATCH] Add VisibilityService to improve tick performance with many online pairs --- .../Factories/PairHandlerFactory.cs | 6 ++- .../PlayerData/Handlers/PairHandler.cs | 21 +++++--- MareSynchronos/Plugin.cs | 1 + MareSynchronos/Services/DalamudUtilService.cs | 4 +- MareSynchronos/Services/Mediator/Messages.cs | 1 + MareSynchronos/Services/VisibilityService.cs | 51 +++++++++++++++++++ 6 files changed, 73 insertions(+), 11 deletions(-) create mode 100644 MareSynchronos/Services/VisibilityService.cs diff --git a/MareSynchronos/PlayerData/Factories/PairHandlerFactory.cs b/MareSynchronos/PlayerData/Factories/PairHandlerFactory.cs index d41efc3..0fadae8 100644 --- a/MareSynchronos/PlayerData/Factories/PairHandlerFactory.cs +++ b/MareSynchronos/PlayerData/Factories/PairHandlerFactory.cs @@ -26,13 +26,14 @@ public class PairHandlerFactory private readonly ServerConfigurationManager _serverConfigManager; private readonly PluginWarningNotificationService _pluginWarningNotificationManager; private readonly PairAnalyzerFactory _pairAnalyzerFactory; + private readonly VisibilityService _visibilityService; public PairHandlerFactory(ILoggerFactory loggerFactory, GameObjectHandlerFactory gameObjectHandlerFactory, IpcManager ipcManager, FileDownloadManagerFactory fileDownloadManagerFactory, DalamudUtilService dalamudUtilService, PluginWarningNotificationService pluginWarningNotificationManager, IHostApplicationLifetime hostApplicationLifetime, FileCacheManager fileCacheManager, MareMediator mareMediator, PlayerPerformanceService playerPerformanceService, ServerConfigurationManager serverConfigManager, PairAnalyzerFactory pairAnalyzerFactory, - MareConfigService configService) + MareConfigService configService, VisibilityService visibilityService) { _loggerFactory = loggerFactory; _gameObjectHandlerFactory = gameObjectHandlerFactory; @@ -47,12 +48,13 @@ public class PairHandlerFactory _serverConfigManager = serverConfigManager; _pairAnalyzerFactory = pairAnalyzerFactory; _configService = configService; + _visibilityService = visibilityService; } public PairHandler Create(Pair pair) { return new PairHandler(_loggerFactory.CreateLogger(), pair, _pairAnalyzerFactory.Create(pair), _gameObjectHandlerFactory, _ipcManager, _fileDownloadManagerFactory.Create(), _pluginWarningNotificationManager, _dalamudUtilService, _hostApplicationLifetime, - _fileCacheManager, _mareMediator, _playerPerformanceService, _serverConfigManager, _configService); + _fileCacheManager, _mareMediator, _playerPerformanceService, _serverConfigManager, _configService, _visibilityService); } } \ No newline at end of file diff --git a/MareSynchronos/PlayerData/Handlers/PairHandler.cs b/MareSynchronos/PlayerData/Handlers/PairHandler.cs index d4a43da..28673a6 100644 --- a/MareSynchronos/PlayerData/Handlers/PairHandler.cs +++ b/MareSynchronos/PlayerData/Handlers/PairHandler.cs @@ -33,6 +33,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase private readonly PlayerPerformanceService _playerPerformanceService; private readonly ServerConfigurationManager _serverConfigManager; private readonly PluginWarningNotificationService _pluginWarningNotificationManager; + private readonly VisibilityService _visibilityService; private CancellationTokenSource? _applicationCancellationTokenSource = new(); private Guid _applicationId; private Task? _applicationTask; @@ -54,7 +55,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase FileCacheManager fileDbManager, MareMediator mediator, PlayerPerformanceService playerPerformanceService, ServerConfigurationManager serverConfigManager, - MareConfigService configService) : base(logger, mediator) + MareConfigService configService, VisibilityService visibilityService) : base(logger, mediator) { Pair = pair; PairAnalyzer = pairAnalyzer; @@ -68,8 +69,12 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase _playerPerformanceService = playerPerformanceService; _serverConfigManager = serverConfigManager; _configService = configService; + _visibilityService = visibilityService; + + _visibilityService.StartTracking(Pair.Ident); + + Mediator.SubscribeKeyed(this, Pair.Ident, (msg) => UpdateVisibility(msg.IsVisible)); - Mediator.Subscribe(this, (_) => FrameworkUpdate()); Mediator.Subscribe(this, (_) => { _downloadCancellationTokenSource?.CancelDispose(); @@ -137,7 +142,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase } public long LastAppliedDataBytes { get; private set; } - public Pair Pair { get; private set; } + public Pair Pair { get; private init; } public PairAnalyzer PairAnalyzer { get; private init; } public nint PlayerCharacter => _charaHandler?.Address ?? nint.Zero; public unsafe uint PlayerCharacterId => (_charaHandler?.Address ?? nint.Zero) == nint.Zero @@ -255,6 +260,8 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase if (!disposing) return; + _visibilityService.StopTracking(Pair.Ident); + SetUploading(isUploading: false); var name = PlayerName; Logger.LogDebug("Disposing {name} ({user})", name, Pair); @@ -634,7 +641,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase } } - private void FrameworkUpdate() + private void UpdateVisibility(bool nowVisible) { if (string.IsNullOrEmpty(PlayerName)) { @@ -647,7 +654,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase $"Initializing User For Character {pc.Name}"))); } - if (_charaHandler?.Address != nint.Zero && !IsVisible) + if (!IsVisible && nowVisible) { IsVisible = true; Mediator.Publish(new PairHandlerVisibleMessage(this)); @@ -666,10 +673,10 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase Logger.LogTrace("{this} visibility changed, now: {visi}, no cached data exists", this, IsVisible); } } - else if (_charaHandler?.Address == nint.Zero && IsVisible) + else if (IsVisible && !nowVisible) { IsVisible = false; - _charaHandler.Invalidate(); + _charaHandler?.Invalidate(); _downloadCancellationTokenSource?.CancelDispose(); _downloadCancellationTokenSource = null; Logger.LogTrace("{this} visibility changed, now: {visi}", this, IsVisible); diff --git a/MareSynchronos/Plugin.cs b/MareSynchronos/Plugin.cs index babe30a..424379d 100644 --- a/MareSynchronos/Plugin.cs +++ b/MareSynchronos/Plugin.cs @@ -114,6 +114,7 @@ public sealed class Plugin : IDalamudPlugin collection.AddSingleton(); collection.AddSingleton(); collection.AddSingleton(); + collection.AddSingleton(); collection.AddSingleton(); collection.AddSingleton(); collection.AddSingleton(); diff --git a/MareSynchronos/Services/DalamudUtilService.cs b/MareSynchronos/Services/DalamudUtilService.cs index 0b7ee22..d1e0c8f 100644 --- a/MareSynchronos/Services/DalamudUtilService.cs +++ b/MareSynchronos/Services/DalamudUtilService.cs @@ -19,7 +19,7 @@ namespace MareSynchronos.Services; public class DalamudUtilService : IHostedService, IMediatorSubscriber { - internal struct PlayerCharacter + public struct PlayerCharacter { public uint ObjectId; public string Name; @@ -390,7 +390,7 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber return _gameGui.WorldToScreen(obj.Position, out var screenPos) ? screenPos : Vector2.Zero; } - internal PlayerCharacter FindPlayerByNameHash(string ident) + public PlayerCharacter FindPlayerByNameHash(string ident) { _playerCharas.TryGetValue(ident, out var result); return result; diff --git a/MareSynchronos/Services/Mediator/Messages.cs b/MareSynchronos/Services/Mediator/Messages.cs index 2d77c3a..a134fc9 100644 --- a/MareSynchronos/Services/Mediator/Messages.cs +++ b/MareSynchronos/Services/Mediator/Messages.cs @@ -72,6 +72,7 @@ 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 PlayerVisibilityMessage(string Ident, bool IsVisible) : KeyedMessage(Ident, SameThread: true); public record PairHandlerVisibleMessage(PairHandler Player) : MessageBase; public record OpenReportPopupMessage(Pair PairToReport) : MessageBase; public record OpenBanUserPopupMessage(Pair PairToBan, GroupFullInfoDto GroupFullInfoDto) : MessageBase; diff --git a/MareSynchronos/Services/VisibilityService.cs b/MareSynchronos/Services/VisibilityService.cs new file mode 100644 index 0000000..524eee1 --- /dev/null +++ b/MareSynchronos/Services/VisibilityService.cs @@ -0,0 +1,51 @@ +using MareSynchronos.Services.Mediator; +using Microsoft.Extensions.Logging; +using System.Collections.Concurrent; + +namespace MareSynchronos.Services; + +// Detect when players of interest are visible +public class VisibilityService : DisposableMediatorSubscriberBase +{ + private readonly DalamudUtilService _dalamudUtil; + private ConcurrentDictionary _trackedPlayerVisibility = new(); + private ConcurrentQueue _removedPlayerMessageQueue = new(); + + public VisibilityService(ILogger logger, MareMediator mediator, DalamudUtilService dalamudUtil) + : base(logger, mediator) + { + _dalamudUtil = dalamudUtil; + Mediator.Subscribe(this, (_) => FrameworkUpdate()); + } + + public void StartTracking(string ident) + { + _trackedPlayerVisibility.TryAdd(ident, false); + } + + public void StopTracking(string ident) + { + // No PairVisibilityMessage is emitted if the player was visible when removed + _trackedPlayerVisibility.TryRemove(ident, out _); + } + + private void FrameworkUpdate() + { + foreach (var player in _trackedPlayerVisibility) + { + string ident = player.Key; + var findResult = _dalamudUtil.FindPlayerByNameHash(ident); + + if (!player.Value && findResult.ObjectId != 0) + { + if (_trackedPlayerVisibility.TryUpdate(ident, newValue: true, comparisonValue: false)) + Mediator.Publish(new(ident, IsVisible: true)); + } + else if (player.Value && findResult.ObjectId == 0) + { + if (_trackedPlayerVisibility.TryUpdate(ident, newValue: false, comparisonValue: true)) + Mediator.Publish(new(ident, IsVisible: false)); + } + } + } +} \ No newline at end of file